mjpg-streamer/plugins/output_viewer/output_viewer.c

435 lines
14 KiB
C

/*******************************************************************************
# #
# MJPG-streamer allows to stream JPG frames from an input-plugin #
# to several output plugins #
# #
# Copyright (C) 2008 Tom Stöveken #
# #
# This program is free software; you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program; if not, write to the Free Software #
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #
# #
*******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <getopt.h>
#include <pthread.h>
#include <syslog.h>
#include <SDL/SDL.h>
#include <jpeglib.h>
#include "../../utils.h"
#include "../../mjpg_streamer.h"
#define OUTPUT_PLUGIN_NAME "VIEWER output plugin"
static pthread_t worker;
static globals *pglobal;
static unsigned char *frame = NULL;
static int input_number = 0;
/******************************************************************************
Description.: print a help message
Input Value.: -
Return Value: -
******************************************************************************/
void help(void)
{
fprintf(stderr, " ---------------------------------------------------------------\n" \
" Help for output plugin..: "OUTPUT_PLUGIN_NAME"\n" \
" ---------------------------------------------------------------\n");
}
/******************************************************************************
Description.: clean up allocated resources
Input Value.: unused argument
Return Value: -
******************************************************************************/
void worker_cleanup(void *arg)
{
static unsigned char first_run = 1;
if(!first_run) {
DBG("already cleaned up resources\n");
return;
}
first_run = 0;
OPRINT("cleaning up resources allocated by worker thread\n");
free(frame);
SDL_Quit();
}
typedef struct {
struct jpeg_source_mgr pub;
Uint8 *jpegdata;
int jpegsize;
} my_source_mgr;
static void init_source(j_decompress_ptr cinfo)
{
return;
}
static int fill_input_buffer(j_decompress_ptr cinfo)
{
my_source_mgr * src = (my_source_mgr *) cinfo->src;
src->pub.next_input_byte = src->jpegdata;
src->pub.bytes_in_buffer = src->jpegsize;
return TRUE;
}
static void skip_input_data(j_decompress_ptr cinfo, long num_bytes)
{
my_source_mgr * src = (my_source_mgr *) cinfo->src;
if(num_bytes > 0) {
src->pub.next_input_byte += (size_t) num_bytes;
src->pub.bytes_in_buffer -= (size_t) num_bytes;
}
}
static void term_source(j_decompress_ptr cinfo)
{
return;
}
static void jpeg_init_src(j_decompress_ptr cinfo, Uint8 *jpegdata, int jpegsize)
{
my_source_mgr *src;
if(cinfo->src == NULL) { /* first time for this JPEG object? */
cinfo->src = (struct jpeg_source_mgr *)(*cinfo->mem->alloc_small)((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(my_source_mgr));
src = (my_source_mgr *) cinfo->src;
}
src = (my_source_mgr *) cinfo->src;
src->pub.init_source = init_source;
src->pub.fill_input_buffer = fill_input_buffer;
src->pub.skip_input_data = skip_input_data;
src->pub.resync_to_restart = jpeg_resync_to_restart;
src->pub.term_source = term_source;
src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
src->pub.next_input_byte = NULL; /* until buffer loaded */
src->jpegdata = jpegdata;
src->jpegsize = jpegsize;
}
static void my_error_exit(j_common_ptr cinfo)
{
DBG("JPEG data contains an error\n");
}
static void my_error_output_message(j_common_ptr cinfo)
{
DBG("JPEG data contains an error\n");
}
typedef struct {
int height;
int width;
unsigned char *buffer;
int buffersize;
} decompressed_image;
int decompress_jpeg(unsigned char *jpeg, int jpegsize, decompressed_image *image)
{
struct jpeg_decompress_struct cinfo;
JSAMPROW rowptr[1];
struct jpeg_error_mgr jerr;
/* create an error handler that does not terminate MJPEG-streamer */
cinfo.err = jpeg_std_error(&jerr);
jerr.error_exit = my_error_exit;
jerr.output_message = my_error_output_message;
/* create the decompressor structures */
jpeg_create_decompress(&cinfo);
/* initalize the structures of decompressor */
jpeg_init_src(&cinfo, jpeg, jpegsize);
/* read the JPEG header data */
if(jpeg_read_header(&cinfo, TRUE) < 0) {
jpeg_destroy_decompress(&cinfo);
DBG("could not read the header\n");
return 1;
}
/*
* I just expect RGB colored JPEGs, so the num_components must be three
*/
if(cinfo.num_components != 3) {
jpeg_destroy_decompress(&cinfo);
DBG("unsupported number of components (~colorspace)\n");
return 1;
}
/* just use RGB output and adjust decompression parameters */
cinfo.out_color_space = JCS_RGB;
cinfo.quantize_colors = FALSE;
/* to scale the decompressed image, the fraction could be changed here */
cinfo.scale_num = 1;
cinfo.scale_denom = 1;
cinfo.dct_method = JDCT_FASTEST;
cinfo.do_fancy_upsampling = FALSE;
jpeg_calc_output_dimensions(&cinfo);
/* store the image information */
image->width = cinfo.output_width;
image->height = cinfo.output_height;
/*
* just allocate a new buffer if not already allocated
* pay a lot attention, that the calling function has to ensure, that the buffer
* must be large enough
*/
if(image->buffer == NULL) {
image->buffersize = image->width * image->height * cinfo.num_components;
/* the calling function has to ensure that this buffer will become freed after use! */
image->buffer = malloc(image->buffersize);
if(image->buffer == NULL) {
jpeg_destroy_decompress(&cinfo);
DBG("allocating memory failed\n");
return 1;
}
}
/* start to decompress */
if(jpeg_start_decompress(&cinfo) < 0) {
jpeg_destroy_decompress(&cinfo);
DBG("could not start decompression\n");
return 1;
}
while(cinfo.output_scanline < cinfo.output_height) {
rowptr[0] = (JSAMPROW)(Uint8 *)image->buffer + cinfo.output_scanline * image->width * cinfo.num_components;
if(jpeg_read_scanlines(&cinfo, rowptr, (JDIMENSION) 1) < 0) {
jpeg_destroy_decompress(&cinfo);
DBG("could not decompress this line\n");
return 1;
}
}
if(jpeg_finish_decompress(&cinfo) < 0) {
jpeg_destroy_decompress(&cinfo);
DBG("could not finish compression\n");
return 1;
}
/* all is done */
jpeg_destroy_decompress(&cinfo);
return 0;
}
/******************************************************************************
Description.: this is the main worker thread
it loops forever, grabs a fresh frame, decompressed the JPEG
and displays the decoded data using SDL
Input Value.:
Return Value:
******************************************************************************/
void *worker_thread(void *arg)
{
int frame_size = 0, firstrun = 1;
SDL_Surface *screen = NULL, *image = NULL;
decompressed_image rgbimage;
/* initialze the buffer for the decompressed image */
rgbimage.buffersize = 0;
rgbimage.buffer = NULL;
/* initialze the SDL video subsystem */
if(SDL_Init(SDL_INIT_VIDEO) < 0) {
fprintf(stderr, "Couldn't initialize SDL: %s\n", SDL_GetError());
exit(EXIT_FAILURE);
}
/* just allocate a large buffer for the JPEGs */
if((frame = malloc(4096 * 1024)) == NULL) {
OPRINT("not enough memory for worker thread\n");
exit(EXIT_FAILURE);
}
/* set cleanup handler to cleanup allocated resources */
pthread_cleanup_push(worker_cleanup, NULL);
while(!pglobal->stop) {
DBG("waiting for fresh frame\n");
pthread_mutex_lock(&pglobal->in[input_number].db);
pthread_cond_wait(&pglobal->in[input_number].db_update, &pglobal->in[input_number].db);
/* read buffer */
frame_size = pglobal->in[input_number].size;
memcpy(frame, pglobal->in[input_number].buf, frame_size);
pthread_mutex_unlock(&pglobal->in[input_number].db);
/* decompress the JPEG and store results in memory */
if(decompress_jpeg(frame, frame_size, &rgbimage)) {
DBG("could not properly decompress JPEG data\n");
continue;
}
if(firstrun) {
/* create the primary surface (the visible window) */
screen = SDL_SetVideoMode(rgbimage.width, rgbimage.height, 0, SDL_ANYFORMAT | SDL_HWSURFACE);
SDL_WM_SetCaption("MJPG-Streamer Viewer", NULL);
/* create a SDL surface to display the data */
image = SDL_AllocSurface(SDL_SWSURFACE, rgbimage.width, rgbimage.height, 24,
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
0x0000FF, 0x00FF00, 0xFF0000,
#else
0xFF0000, 0x00FF00, 0x0000FF,
#endif
0);
/* copy the decoded data across */
memcpy(image->pixels, rgbimage.buffer, rgbimage.width * rgbimage.height * 3);
free(rgbimage.buffer);
/* now, that we know the dimensions, we can directly copy to the right surface */
rgbimage.buffer = image->pixels;
rgbimage.buffersize = rgbimage.width * rgbimage.height * 3;
firstrun = 0;
}
/* copy the image to the primary surface */
SDL_BlitSurface(image, NULL, screen, NULL);
/* redraw the whole surface */
SDL_Flip(screen);
}
pthread_cleanup_pop(1);
/* get rid of the image */
SDL_FreeSurface(image);
return NULL;
}
/*** plugin interface functions ***/
/******************************************************************************
Description.: this function is called first, in order to initialise
this plugin and pass a parameter string
Input Value.: parameters
Return Value: 0 if everything is ok, non-zero otherwise
******************************************************************************/
int output_init(output_parameter *param)
{
int i;
param->argv[0] = OUTPUT_PLUGIN_NAME;
/* show all parameters for DBG purposes */
for(i = 0; i < param->argc; i++) {
DBG("argv[%d]=%s\n", i, param->argv[i]);
}
reset_getopt();
while(1) {
int option_index = 0, c = 0;
static struct option long_options[] = {
{"h", no_argument, 0, 0
},
{"help", no_argument, 0, 0},
{"i", required_argument, 0, 0},
{"input", required_argument, 0, 0},
{0, 0, 0, 0}
};
c = getopt_long_only(param->argc, param->argv, "", long_options, &option_index);
/* no more options to parse */
if(c == -1) break;
/* unrecognized option */
if(c == '?') {
help();
return 1;
}
switch(option_index) {
/* h, help */
case 0:
case 1:
DBG("case 0,1\n");
help();
return 1;
break;
/* i, input */
case 2:
case 3:
DBG("case 2,3\n");
input_number = atoi(optarg);
break;
}
}
pglobal = param->global;
if(!(input_number < pglobal->incnt)) {
OPRINT("ERROR: the %d input_plugin number is too much only %d plugins loaded\n", input_number, pglobal->incnt);
return 1;
}
OPRINT("input plugin.....: %d: %s\n", input_number, pglobal->in[input_number].plugin);
return 0;
}
/******************************************************************************
Description.: calling this function stops the worker thread
Input Value.: -
Return Value: always 0
******************************************************************************/
int output_stop(int id)
{
DBG("will cancel worker thread\n");
pthread_cancel(worker);
return 0;
}
/******************************************************************************
Description.: calling this function creates and starts the worker thread
Input Value.: -
Return Value: always 0
******************************************************************************/
int output_run(int id)
{
DBG("launching worker thread\n");
pthread_create(&worker, 0, worker_thread, NULL);
pthread_detach(worker);
return 0;
}
int output_cmd()
{
}