mirror of
https://github.com/Motion-Project/motion.git
synced 2026-02-01 10:31:34 -05:00
493 lines
14 KiB
C
493 lines
14 KiB
C
/*
|
|
* netcam_jpeg.c
|
|
*
|
|
* Module for handling JPEG decompression for network cameras.
|
|
*
|
|
* This code was inspired by the original module written by
|
|
* Jeroen Vreeken and enhanced by several Motion project
|
|
* contributors, particularly Angel Carpintero and
|
|
* Christopher Price.
|
|
*
|
|
* Copyright 2005, William M. Brack
|
|
* This program is published under the GNU Public license
|
|
*/
|
|
|
|
#include "motion.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <jpeglib.h>
|
|
#include <jerror.h>
|
|
|
|
#include "rotate.h"
|
|
|
|
/*
|
|
* netcam_source_mgr is a locally-defined structure to contain elements
|
|
* which are not present in the standard libjpeg (the element 'pub' is a
|
|
* pointer to the standard information)
|
|
*/
|
|
typedef struct {
|
|
struct jpeg_source_mgr pub;
|
|
char *data;
|
|
int length;
|
|
JOCTET *buffer;
|
|
boolean start_of_file;
|
|
} netcam_source_mgr;
|
|
|
|
typedef netcam_source_mgr *netcam_src_ptr;
|
|
|
|
|
|
/*
|
|
* Here we declare function prototypes for all the routines involved with
|
|
* overriding libjpeg functions. The reason these are required is that,
|
|
* although the standard library handles input and output with stdio,
|
|
* we are working with "remote" data (from the camera or from a file).
|
|
*/
|
|
static void netcam_init_source(j_decompress_ptr);
|
|
static boolean netcam_fill_input_buffer(j_decompress_ptr);
|
|
static void netcam_skip_input_data(j_decompress_ptr, long);
|
|
static void netcam_term_source(j_decompress_ptr);
|
|
static void netcam_memory_src(j_decompress_ptr, char *, int);
|
|
static void netcam_error_exit(j_common_ptr);
|
|
|
|
static void netcam_init_source(j_decompress_ptr cinfo)
|
|
{
|
|
/*
|
|
* Get our "private" structure from the libjpeg structure
|
|
*/
|
|
netcam_src_ptr src = (netcam_src_ptr) cinfo->src;
|
|
/*
|
|
* Set the 'start_of_file' flag in our private structure
|
|
* (used by my_fill_input_buffer)
|
|
*/
|
|
src->start_of_file = TRUE;
|
|
}
|
|
|
|
static boolean netcam_fill_input_buffer(j_decompress_ptr cinfo)
|
|
{
|
|
netcam_src_ptr src = (netcam_src_ptr) cinfo->src;
|
|
size_t nbytes;
|
|
|
|
/*
|
|
* start_of_file is set any time netcam_init_source has been called
|
|
* since the last entry to this routine. This would be the normal
|
|
* path when a new image is to be processed. It is assumed that
|
|
* this routine will only be called once for the entire image.
|
|
* If an unexpected call (with start_of_file FALSE) occurs, the
|
|
* routine will insert a "fake" "end of image" marker and return
|
|
* to the library to process whatever data remains from the original
|
|
* image (the one with errors).
|
|
*
|
|
* I'm not yet clear on what the result (from the application's
|
|
* point of view) will be from this logic. If the application
|
|
* expects that a completely new image will be started, this will
|
|
* give trouble.
|
|
*/
|
|
if (src->start_of_file) {
|
|
nbytes = src->length;
|
|
src->buffer = (JOCTET *) src->data;
|
|
} else {
|
|
/* Insert a fake EOI marker - as per jpeglib recommendation */
|
|
if (debug_level)
|
|
motion_log(LOG_INFO, 0, "**fake EOI inserted**");
|
|
src->buffer[0] = (JOCTET) 0xFF;
|
|
src->buffer[1] = (JOCTET) JPEG_EOI; /* 0xD9 */
|
|
nbytes = 2;
|
|
}
|
|
|
|
src->pub.next_input_byte = src->buffer;
|
|
src->pub.bytes_in_buffer = nbytes;
|
|
src->start_of_file = FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void netcam_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
|
|
{
|
|
netcam_src_ptr src = (netcam_src_ptr) cinfo->src;
|
|
|
|
if (num_bytes > 0) {
|
|
while (num_bytes > (long) src->pub.bytes_in_buffer) {
|
|
num_bytes -= (long) src->pub.bytes_in_buffer;
|
|
(void) netcam_fill_input_buffer (cinfo);
|
|
}
|
|
src->pub.next_input_byte += (size_t) num_bytes;
|
|
src->pub.bytes_in_buffer -= (size_t) num_bytes;
|
|
}
|
|
}
|
|
|
|
static void netcam_term_source(j_decompress_ptr cinfo ATTRIBUTE_UNUSED)
|
|
{
|
|
}
|
|
|
|
/**
|
|
* netcam_memory_src
|
|
*
|
|
* Routine to setup for fetching data from a netcam buffer, used by the
|
|
* JPEG library decompression routine.
|
|
*
|
|
* Parameters:
|
|
* cinfo pointer to the jpeg decompression object
|
|
* data pointer to the image data received from a netcam
|
|
* length total size of the image
|
|
*
|
|
* Returns: Nothing
|
|
*
|
|
*/
|
|
static void netcam_memory_src(j_decompress_ptr cinfo, char *data, int length)
|
|
{
|
|
netcam_src_ptr src;
|
|
|
|
if (cinfo->src == NULL) {
|
|
cinfo->src = (struct jpeg_source_mgr *)
|
|
(*cinfo->mem->alloc_small)
|
|
((j_common_ptr) cinfo, JPOOL_PERMANENT,
|
|
sizeof (netcam_source_mgr));
|
|
}
|
|
|
|
src = (netcam_src_ptr)cinfo->src;
|
|
src->data = data;
|
|
src->length = length;
|
|
src->pub.init_source = netcam_init_source;
|
|
src->pub.fill_input_buffer = netcam_fill_input_buffer;
|
|
src->pub.skip_input_data = netcam_skip_input_data;
|
|
src->pub.resync_to_restart = jpeg_resync_to_restart;
|
|
src->pub.term_source = netcam_term_source;
|
|
src->pub.bytes_in_buffer = 0;
|
|
src->pub.next_input_byte = NULL;
|
|
}
|
|
|
|
/**
|
|
* netcam_error_exit
|
|
*
|
|
* Routine to override the libjpeg error exit routine so
|
|
* that we can just throw away the bad frame and continue
|
|
* with more data from the netcam.
|
|
*
|
|
* Parameters
|
|
*
|
|
* cinfo pointer to the decompression control structure
|
|
*
|
|
* Returns: does an (ugly) longjmp to get back to netcam_jpeg
|
|
* code
|
|
*
|
|
*/
|
|
static void netcam_error_exit(j_common_ptr cinfo)
|
|
{
|
|
/* fetch our pre-stored pointer to the netcam context */
|
|
netcam_context_ptr netcam = cinfo->client_data;
|
|
/* output the message associated with the error */
|
|
(*cinfo->err->output_message)(cinfo);
|
|
/* set flag to show the decompression had errors */
|
|
netcam->jpeg_error |= 1;
|
|
/* need to "cleanup" the aborted decompression */
|
|
jpeg_destroy (cinfo);
|
|
/* jump back to wherever we started */
|
|
longjmp(netcam->setjmp_buffer, 1);
|
|
}
|
|
|
|
/**
|
|
* netcam_output_message
|
|
*
|
|
* Routine to override the libjpeg error message output routine.
|
|
* We do this so that we can output our module and thread i.d.,
|
|
* as well as put the message to the motion log.
|
|
*
|
|
* Parameters
|
|
*
|
|
* cinfo pointer to the decompression control structure
|
|
*
|
|
* Returns Nothing
|
|
*
|
|
*/
|
|
static void netcam_output_message(j_common_ptr cinfo)
|
|
{
|
|
char buffer[JMSG_LENGTH_MAX];
|
|
|
|
/* fetch our pre-stored pointer to the netcam context */
|
|
netcam_context_ptr netcam = cinfo->client_data;
|
|
|
|
/*
|
|
* While experimenting with a "appro" netcam it was discovered
|
|
* that the jpeg data produced by the camera caused warning
|
|
* messages from libjpeg (JWRN_EXTRANEOUS_DATA). The following
|
|
* code is to assure that specific warning is ignored.
|
|
*
|
|
* NOTE: It's likely that we will discover other error message
|
|
* codes which we want to ignore. In that case, we should have
|
|
* some sort of table-lookup to decide which messages we really
|
|
* care about.
|
|
*/
|
|
if (cinfo->err->msg_code != JWRN_EXTRANEOUS_DATA)
|
|
netcam->jpeg_error |= 2; /* Set flag to show problem */
|
|
/*
|
|
* We only display and log errors when debug_level
|
|
* is non-zero. The reasoning here is that these kinds
|
|
* of errors are only produced when the input data is
|
|
* wrong, and that indicates a network problem rather
|
|
* than a problem with the content.
|
|
*/
|
|
if (debug_level) {
|
|
/*
|
|
* Format the message according to library standards.
|
|
* Write it out to the motion log.
|
|
*/
|
|
(*cinfo->err->format_message)(cinfo, buffer);
|
|
motion_log(LOG_ERR, 0, buffer);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* netcam_init_jpeg
|
|
*
|
|
* Initialises the JPEG library prior to doing a
|
|
* decompression.
|
|
*
|
|
* Parameters:
|
|
* netcam pointer to netcam_context
|
|
* cinfo pointer to JPEG decompression context
|
|
*
|
|
* Returns: Error code
|
|
*/
|
|
static int netcam_init_jpeg(netcam_context_ptr netcam, j_decompress_ptr cinfo)
|
|
{
|
|
netcam_buff_ptr buff;
|
|
|
|
/*
|
|
* First we check whether a new image has arrived. If not, we
|
|
* setup to wait for 1/2 a frame time. This will (hopefully)
|
|
* help in synchronizing the camera frames with the motion main
|
|
* loop.
|
|
*/
|
|
pthread_mutex_lock(&netcam->mutex);
|
|
|
|
if (netcam->imgcnt_last == netcam->imgcnt) { /* need to wait */
|
|
struct timespec waittime;
|
|
struct timeval curtime;
|
|
int retcode;
|
|
|
|
/*
|
|
* We calculate the delay time (representing the desired frame
|
|
* rate). This delay time is in *nanoseconds*.
|
|
* We will wait 0.5 seconds which gives a practical minimum
|
|
* framerate of 2 which is desired for the motion_loop to
|
|
* function.
|
|
*/
|
|
gettimeofday(&curtime, NULL);
|
|
curtime.tv_usec += 500000;
|
|
|
|
if (curtime.tv_usec > 1000000) {
|
|
curtime.tv_usec -= 1000000;
|
|
curtime.tv_sec++;
|
|
}
|
|
|
|
waittime.tv_sec = curtime.tv_sec;
|
|
waittime.tv_nsec = 1000L * curtime.tv_usec;
|
|
|
|
do {
|
|
retcode = pthread_cond_timedwait(&netcam->pic_ready,
|
|
&netcam->mutex, &waittime);
|
|
} while (retcode == EINTR);
|
|
|
|
if (retcode) { /* we assume a non-zero reply is ETIMEOUT */
|
|
pthread_mutex_unlock(&netcam->mutex);
|
|
|
|
if (debug_level > 3)
|
|
motion_log(-1,0,"no new pic, no signal rcvd");
|
|
|
|
return NETCAM_GENERAL_ERROR | NETCAM_NOTHING_NEW_ERROR;
|
|
}
|
|
|
|
if (debug_level > 3)
|
|
motion_log(-1, 0, "***new pic delay successful***");
|
|
}
|
|
|
|
netcam->imgcnt_last = netcam->imgcnt;
|
|
|
|
/* set latest buffer as "current" */
|
|
buff = netcam->latest;
|
|
netcam->latest = netcam->jpegbuf;
|
|
netcam->jpegbuf = buff;
|
|
pthread_mutex_unlock(&netcam->mutex);
|
|
|
|
/* clear any error flag from previous work */
|
|
netcam->jpeg_error = 0;
|
|
|
|
buff = netcam->jpegbuf;
|
|
/* prepare for the decompression */
|
|
/* Initialize the JPEG decompression object */
|
|
jpeg_create_decompress(cinfo);
|
|
|
|
/* Set up own error exit routine */
|
|
cinfo->err = jpeg_std_error(&netcam->jerr);
|
|
cinfo->client_data = netcam;
|
|
netcam->jerr.error_exit = netcam_error_exit;
|
|
netcam->jerr.output_message = netcam_output_message;
|
|
|
|
/* Specify the data source as our own routine */
|
|
netcam_memory_src(cinfo, buff->ptr, buff->used);
|
|
|
|
/* Read file parameters (rejecting tables-only) */
|
|
jpeg_read_header(cinfo, TRUE);
|
|
|
|
/* Override the desired colour space */
|
|
cinfo->out_color_space = JCS_YCbCr;
|
|
|
|
/* Start the decompressor */
|
|
jpeg_start_decompress(cinfo);
|
|
return netcam->jpeg_error;
|
|
}
|
|
|
|
static int netcam_image_conv(netcam_context_ptr netcam,
|
|
struct jpeg_decompress_struct *cinfo,
|
|
unsigned char *image)
|
|
{
|
|
JSAMPARRAY line; /* array of decomp data lines */
|
|
unsigned char *wline; /* will point to line[0] */
|
|
/* Working variables */
|
|
int linesize, i;
|
|
unsigned char *upic, *vpic;
|
|
unsigned char *pic = image;
|
|
unsigned char y; /* switch for decoding YUV data */
|
|
unsigned int width, height;
|
|
|
|
width = cinfo->output_width;
|
|
height = cinfo->output_height;
|
|
if (width && ((width != netcam->width) || (height != netcam->height))) {
|
|
motion_log(LOG_ERR, 0,
|
|
"JPEG image size %dx%d, JPEG was %dx%d",
|
|
netcam->width, netcam->height, width, height);
|
|
jpeg_destroy_decompress (cinfo);
|
|
netcam->jpeg_error |= 4;
|
|
return netcam->jpeg_error;
|
|
}
|
|
/* Set the output pointers (these come from YUV411P definition */
|
|
upic = pic + width * height;
|
|
vpic = upic + (width * height) / 4;
|
|
|
|
|
|
/* YCbCr format will give us one byte each for YUV */
|
|
linesize = cinfo->output_width * 3;
|
|
|
|
/* Allocate space for one line */
|
|
line = (cinfo->mem->alloc_sarray)((j_common_ptr) cinfo, JPOOL_IMAGE,
|
|
cinfo->output_width * cinfo->output_components, 1);
|
|
|
|
wline = line[0];
|
|
y = 0;
|
|
while (cinfo->output_scanline < height) {
|
|
jpeg_read_scanlines (cinfo, line, 1);
|
|
for (i = 0; i < linesize; i += 3) {
|
|
pic[i / 3] = wline[i];
|
|
if (i & 1) {
|
|
upic[(i / 3) / 2] = wline[i + 1];
|
|
vpic[(i / 3) / 2] = wline[i + 2];
|
|
}
|
|
}
|
|
pic += linesize / 3;
|
|
if (y++ & 1) {
|
|
upic += width / 2;
|
|
vpic += width / 2;
|
|
}
|
|
}
|
|
|
|
jpeg_finish_decompress (cinfo);
|
|
jpeg_destroy_decompress (cinfo);
|
|
if (netcam->cnt->rotate_data.degrees > 0) {
|
|
/* rotate as specified */
|
|
rotate_map(netcam->cnt, image);
|
|
}
|
|
return netcam->jpeg_error;
|
|
}
|
|
|
|
/**
|
|
* netcam_proc_jpeg
|
|
*
|
|
* Routine to decode an image received from a netcam into a YUV420P buffer
|
|
* suitable for processing by motion.
|
|
*
|
|
* Parameters:
|
|
* netcam pointer to the netcam_context structure
|
|
* image Pointer to a buffer for the returned image
|
|
*
|
|
* Returns:
|
|
*
|
|
* 0 Success
|
|
* non-zero error code from other routines
|
|
* (e.g. netcam_init_jpeg or netcam_image_conv)
|
|
* or just NETCAM_GENERAL_ERROR
|
|
*/
|
|
int netcam_proc_jpeg(netcam_context_ptr netcam, unsigned char *image)
|
|
{
|
|
struct jpeg_decompress_struct cinfo; /* decompression control struct */
|
|
int retval = 0; /* value returned to caller */
|
|
int ret; /* working var */
|
|
|
|
/*
|
|
* This routine is only called from the main thread.
|
|
* We need to "protect" the "latest" image while we
|
|
* decompress it. netcam_init_jpeg uses
|
|
* netcam->mutex to do this;
|
|
*/
|
|
if (debug_level > 5) {
|
|
motion_log(LOG_INFO, 0, "processing jpeg image - content length "
|
|
"%d", netcam->latest->content_length);
|
|
}
|
|
|
|
ret = netcam_init_jpeg(netcam, &cinfo);
|
|
|
|
if (ret != 0)
|
|
return ret;
|
|
|
|
/* Do a sanity check on dimensions
|
|
* If dimensions have changed we throw an
|
|
* error message that will cause
|
|
* restart of Motion
|
|
*/
|
|
if (netcam->width) { /* 0 means not yet init'ed */
|
|
if ((cinfo.output_width != netcam->width) ||
|
|
(cinfo.output_height != netcam->height)) {
|
|
motion_log(LOG_ERR, 0,
|
|
"Camera width/height mismatch "
|
|
"with JPEG image - expected %dx%d, JPEG %dx%d",
|
|
netcam->width, netcam->height,
|
|
cinfo.output_width, cinfo.output_height);
|
|
retval = NETCAM_RESTART_ERROR;
|
|
return retval;
|
|
}
|
|
}
|
|
|
|
/* do the conversion */
|
|
ret = netcam_image_conv(netcam, &cinfo, image);
|
|
|
|
if (ret != 0)
|
|
retval |= NETCAM_JPEG_CONV_ERROR;
|
|
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* netcam_get_dimensions
|
|
*
|
|
* This function gets the height and width of the JPEG image
|
|
* located in the supplied netcam_image_buffer
|
|
*
|
|
* Parameters
|
|
*
|
|
* netcam pointer to the netcam context
|
|
*
|
|
* Returns: Nothing, but fills in width and height into context
|
|
*
|
|
*/
|
|
void netcam_get_dimensions(netcam_context_ptr netcam)
|
|
{
|
|
struct jpeg_decompress_struct cinfo; /* decompression control struct */
|
|
|
|
netcam_init_jpeg(netcam, &cinfo);
|
|
netcam->width = cinfo.output_width;
|
|
netcam->height = cinfo.output_height;
|
|
|
|
jpeg_destroy_decompress(&cinfo);
|
|
}
|