Index: netcam.h =================================================================== --- netcam.h (revisión: 351) +++ netcam.h (copia de trabajo) @@ -94,14 +94,26 @@ size_t used; /* bytes already used */ struct timeval image_time; /* time this image was received */ } netcam_buff; + typedef netcam_buff *netcam_buff_ptr; + +/* + * struct file_context contains data to use with file URI + * + */ typedef struct file_context { char *path; /* the path within the URL */ int control_file_desc; /* file descriptor for the control socket */ time_t last_st_mtime; /* time this image was modified */ } tfile_context; + + +#define NCS_UNSUPPORTED 0 /* streaming is not supported */ +#define NCS_MULTIPART 1 /* Streaming is done via multipart/x */ +#define NCS_BLOCK 2 /* Streaming is done via MJPG-blocks. */ + /* * struct netcam_context contains all the structures and other data * for an individual netcam. @@ -202,7 +214,7 @@ struct netcam_caps { /* netcam capabilities: */ - unsigned char streaming; /* 1 - supported */ + unsigned char streaming; /* See the NCS_* defines */ unsigned char content_length; /* 0 - unsupported */ } caps; @@ -249,9 +261,33 @@ } netcam_context; +#define MJPG_MH_MAGIC "MJPG" +#define MJPG_MH_MAGIC_SIZE 4 + /* + * MJPG Chunk header for mjpg streaming. + * Little-endian data is read from the network. + */ +typedef struct { + char mh_magic[MJPG_MH_MAGIC_SIZE]; /* must contain the string MJP + not null-terminated. */ + unsigned int mh_framesize; /* Total size of the current + frame in bytes (~45kb on WVC200) */ + unsigned short mh_framewidth; /* Frame width in pixels */ + unsigned short mh_frameheight; /* Frame height in pixels */ + unsigned int mh_frameoffset; /* Offset of this chunk relative + to the beginning of frame. */ + unsigned short mh_chunksize; /* The size of the chunk data + following this header. */ + char mh_reserved[30]; /* Unknown data, seems to be + constant between all headers */ +} mjpg_header; + + +/* * Declare prototypes for our external entry points */ + /* Within netcam_jpeg.c */ int netcam_proc_jpeg (struct netcam_context *, unsigned char *); void netcam_get_dimensions (struct netcam_context *); Index: netcam.c =================================================================== --- netcam.c (revisión: 351) +++ netcam.c (copia de trabajo) @@ -41,16 +41,16 @@ #include #include -#include /* For parsing of the URL */ +#include /* For parsing of the URL */ #include #include "netcam_ftp.h" -#define CONNECT_TIMEOUT 10 /* timeout on remote connection attempt */ -#define READ_TIMEOUT 5 /* default timeout on recv requests */ -#define POLLING_TIMEOUT READ_TIMEOUT /* file polling timeout [s] */ -#define POLLING_TIME 500*1000*1000 /* file polling time quantum [ns] (500ms) */ -#define MAX_HEADER_RETRIES 5 /* Max tries to find a header record */ +#define CONNECT_TIMEOUT 10 /* timeout on remote connection attempt */ +#define READ_TIMEOUT 5 /* default timeout on recv requests */ +#define POLLING_TIMEOUT READ_TIMEOUT /* file polling timeout [s] */ +#define POLLING_TIME 500*1000*1000 /* file polling time quantum [ns] (500ms) */ +#define MAX_HEADER_RETRIES 5 /* Max tries to find a header record */ #define MINVAL(x, y) ((x) < (y) ? (x) : (y)) /* @@ -155,7 +155,7 @@ { char *s; int i; - const char *re = "(http|ftp)://(((.*):(.*))@)?" + const char *re = "(http|ftp|mjpg)://(((.*):(.*))@)?" "([^/:]|[-.a-z0-9]+)(:([0-9]+))?($|(/[^:]*))"; regex_t pattbuf; regmatch_t matches[10]; @@ -387,6 +387,7 @@ * 0 Content-type not recognized * 1 image/jpeg * 2 multipart/x-mixed-replace or multipart/mixed + * 3 application/octet-stream (used by WVC200 linksys IP cameras) * */ static int netcam_check_content_type(char *header) @@ -402,6 +403,8 @@ } else if (!strcmp(content_type, "multipart/x-mixed-replace") || !strcmp(content_type, "multipart/mixed")) { ret = 2; + } else if (!strcmp(content_type, "application/octet-stream")) { + ret = 3; } else ret = 0; @@ -446,7 +449,7 @@ * If this is a "streaming" camera, the stream header must be * preceded by a "boundary" string */ - if (netcam->caps.streaming) { + if (netcam->caps.streaming == NCS_MULTIPART) { while (1) { retval = header_get(netcam, &header, HG_NONE); @@ -496,7 +499,7 @@ if ((retval = (int) netcam_check_content_length(header)) > 0) { netcam->caps.content_length = 1; /* set flag */ - netcam->receiving->content_length = (int) retval; + netcam->receiving->content_length = retval; } free(header); @@ -544,7 +547,7 @@ /* Send the initial command to the camera */ if (send(netcam->sock, netcam->connect_request, - strlen(netcam->connect_request), 0) < 0) { + strlen(netcam->connect_request), 0) < 0) { motion_log(LOG_ERR, 1, "Error sending 'connect' request"); return -1; } @@ -624,14 +627,14 @@ else motion_log(LOG_DEBUG, 0, "Non-streaming camera (keep-alive not set)"); } - netcam->caps.streaming = 0; + netcam->caps.streaming = NCS_UNSUPPORTED; break; case 2: /* streaming */ if (SETUP) motion_log(LOG_DEBUG, 0, "Streaming camera"); - netcam->caps.streaming = 1; + netcam->caps.streaming = NCS_MULTIPART; if ((boundary = strstr(header, "boundary="))) { /* @@ -658,6 +661,12 @@ } break; + case 3: /* MJPG-Block style streaming */ + if (SETUP) + motion_log(LOG_DEBUG, 0, "Streaming camera using MJPG-blocks"); + + break; + default:{ /* error */ motion_log(LOG_ERR, 0, "Unrecognized content type"); free(header); @@ -687,7 +696,7 @@ } free(header); - if (!netcam->caps.streaming && netcam->connect_keepalive) { + if (netcam->caps.streaming == NCS_UNSUPPORTED && netcam->connect_keepalive) { /* * If we are a non-streaming (ie. Jpeg) netcam and keepalive is configured @@ -695,31 +704,31 @@ if (aliveflag) { if (closeflag) { - /* - * If not a streaming cam, and keepalive is set, and the flag shows we - * did not see a Keep-Alive field returned from netcam and a Close field. - * Not quite sure what the correct course of action is here. In for testing. - */ + /* + * If not a streaming cam, and keepalive is set, and the flag shows we + * did not see a Keep-Alive field returned from netcam and a Close field. + * Not quite sure what the correct course of action is here. In for testing. + */ motion_log(LOG_INFO, 0, "Info: Both 'Connection: Keep-Alive' and 'Connection: close' " "header received. Motion continues unchanged."); } else { - /* aliveflag && !closeflag - * - * If not a streaming cam, and keepalive is set, and the flag shows we - * just got a Keep-Alive field returned from netcam and no Close field. - * No action, as this is the normal case. In debug we print a notification. - */ + /* aliveflag && !closeflag + * + * If not a streaming cam, and keepalive is set, and the flag shows we + * just got a Keep-Alive field returned from netcam and no Close field. + * No action, as this is the normal case. In debug we print a notification. + */ if (debug_level > CAMERA_INFO) motion_log(LOG_INFO, 0, "Info: Received a Keep-Alive field in this set of headers."); } } else { /* !aliveflag */ if (!closeflag) { - /* - * If not a streaming cam, and keepalive is set, and the flag shows we - * did not see a Keep-Alive field returned from netcam nor a Close field. - * Not quite sure what the correct course of action is here. In for testing. - */ + /* + * If not a streaming cam, and keepalive is set, and the flag shows we + * did not see a Keep-Alive field returned from netcam nor a Close field. + * Not quite sure what the correct course of action is here. In for testing. + */ motion_log(LOG_INFO, 0, "Info: No 'Connection: Keep-Alive' nor 'Connection: close' " "header received. Motion continues unchanged."); }else{ @@ -740,17 +749,17 @@ * To tell between the sitation where a camera has been in Keep-Alive mode and * is now finishing (and will want to be re-started in Keep-Alive) and the other * case when a cam does not support it, we have a flag which says if the netcam - * has returned a Keep-Alive flag during this connection. If that's set, we - * set ourselves up to re-connect with Keep-Alive after the socket is closed. - * If it's not set, then we will not try again to use Keep-Alive. - */ + * has returned a Keep-Alive flag during this connection. If that's set, we + * set ourselves up to re-connect with Keep-Alive after the socket is closed. + * If it's not set, then we will not try again to use Keep-Alive. + */ if (!netcam->keepalive_thisconn) { - netcam->connect_keepalive = FALSE; /* No further attempts at keep-alive */ + netcam->connect_keepalive = FALSE; /* No further attempts at keep-alive */ motion_log(LOG_INFO, 0, "Removed netcam Keep-Alive flag because 'Connection: close' " "header received. Netcam does not support Keep-Alive. Motion " "continues in non-Keep-Alive."); } else { - netcam->keepalive_timeup = TRUE; /* We will close and re-open keep-alive */ + netcam->keepalive_timeup = TRUE; /* We will close and re-open keep-alive */ motion_log(LOG_INFO, 0, "Keep-Alive has reached end of valid period. Motion will close " "netcam, then resume Keep-Alive with a new socket."); } @@ -774,7 +783,6 @@ */ static void netcam_disconnect(netcam_context_ptr netcam) { - if (netcam->sock > 0) { if (close(netcam->sock) < 0) motion_log(LOG_ERR, 1, "netcam_disconnect"); @@ -826,47 +834,48 @@ } if (debug_level > CAMERA_INFO ) motion_log(LOG_DEBUG, 0, "netcam_connect with no keepalive, new socket created fd %d", netcam->sock); - } else { /* We are in keepalive mode, check for invalid socket */ - if (netcam->sock == -1) { - /* Must be first time, or closed, create a new socket */ - if ((netcam->sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) { - motion_log(LOG_ERR, 1, "netcam_connect with keepalive set, invalid socket." - "This could be the first time. Creating a new one failed."); - return -1; - } - if (debug_level > CAMERA_INFO ) - motion_log(LOG_DEBUG, 0, "netcam_connect with keepalive set, invalid socket." - "This could be first time, created a new one with fd %d", netcam->sock); - /* Record that this connection has not yet received a Keep-Alive header */ - netcam->keepalive_thisconn = FALSE; + } else if (netcam->sock == -1) { /* We are in keepalive mode, check for invalid socket */ + /* Must be first time, or closed, create a new socket */ + if ((netcam->sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) { + motion_log(LOG_ERR, 1, "netcam_connect with keepalive set, invalid socket." + "This could be the first time. Creating a new one failed."); + return -1; + } + if (debug_level > CAMERA_INFO ) + motion_log(LOG_DEBUG, 0, "netcam_connect with keepalive set, invalid socket." + "This could be first time, created a new one with fd %d", netcam->sock); - /* Check the socket status for the keepalive option */ - if (getsockopt(netcam->sock, SOL_SOCKET, SO_KEEPALIVE, &optval, &optlen) < 0) { - motion_log(LOG_ERR, 1, "netcam_connect : getsockopt()"); - return -1; - } - if (debug_level > CAMERA_INFO) { - if (optval == 1) - motion_log(LOG_DEBUG, 0, "netcam_connect: SO_KEEPALIVE is ON"); - else - motion_log(LOG_DEBUG, 0, "netcam_connect: SO_KEEPALIVE is OFF"); - } - - /* Set the option active */ - optval = 1; - optlen = sizeof(optval); - if(setsockopt(netcam->sock, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) < 0) { - motion_log(LOG_ERR, 1, "netcam_connect : setsockopt()"); - return -1; - } - if (debug_level > CAMERA_INFO ) - motion_log(LOG_DEBUG, 0, "netcam_connect: SO_KEEPALIVE set on socket."); - } else if (debug_level > CAMERA_INFO ) { - motion_log(LOG_DEBUG, 0, "netcam_connect re-using socket %d since keepalive is set.", netcam->sock); + /* Record that this connection has not yet received a Keep-Alive header */ + netcam->keepalive_thisconn = FALSE; + + /* Check the socket status for the keepalive option */ + if (getsockopt(netcam->sock, SOL_SOCKET, SO_KEEPALIVE, &optval, &optlen) < 0) { + motion_log(LOG_ERR, 1, "netcam_connect : getsockopt()"); + return -1; } + if (debug_level > CAMERA_INFO) { + if (optval == 1) + motion_log(LOG_DEBUG, 0, "netcam_connect: SO_KEEPALIVE is ON"); + else + motion_log(LOG_DEBUG, 0, "netcam_connect: SO_KEEPALIVE is OFF"); + } + + /* Set the option active */ + optval = 1; + optlen = sizeof(optval); + if(setsockopt(netcam->sock, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) < 0) { + motion_log(LOG_ERR, 1, "netcam_connect : setsockopt()"); + return -1; + } + if (debug_level > CAMERA_INFO ) + motion_log(LOG_DEBUG, 0, "netcam_connect: SO_KEEPALIVE set on socket."); + + } else if (debug_level > CAMERA_INFO ) { + motion_log(LOG_DEBUG, 0, "netcam_connect re-using socket %d since keepalive is set.", netcam->sock); } + /* lookup the hostname given in the netcam URL */ if ((ret = getaddrinfo(netcam->connect_host, NULL, NULL, &res)) != 0) { if (!err_flag) @@ -983,16 +992,20 @@ */ static void netcam_check_buffsize(netcam_buff_ptr buff, size_t numbytes) { + int newsize; + if ((buff->size - buff->used) >= numbytes) return; + newsize = buff->size + ((numbytes / NETCAM_BUFFSIZE) + (numbytes % NETCAM_BUFFSIZE ? 1 : 0)) * NETCAM_BUFFSIZE; + if (debug_level > CAMERA_INFO) motion_log(-1, 0, "expanding buffer from %d to %d bytes", - (int) buff->size, (int) buff->size + NETCAM_BUFFSIZE); + (int) buff->size, (int) newsize); - buff->ptr = myrealloc(buff->ptr, buff->size + NETCAM_BUFFSIZE, + buff->ptr = myrealloc(buff->ptr, newsize, "netcam_check_buf_size"); - buff->size += NETCAM_BUFFSIZE; + buff->size = newsize; } /** @@ -1281,7 +1294,7 @@ pthread_mutex_unlock(&netcam->mutex); - if (!netcam->caps.streaming) { + if (netcam->caps.streaming == NCS_UNSUPPORTED) { if (!netcam->connect_keepalive) { if (debug_level > CAMERA_INFO ) motion_log(LOG_DEBUG, 0, "netcam_read_html_jpeg disconnecting netcam since keep-alive not set." ); @@ -1294,7 +1307,247 @@ return 0; } +static int netcam_http_request(netcam_context_ptr netcam) +{ + int ix; + + /* + * Our basic initialisation has been completed. Now we will attempt + * to connect with the camera so that we can then get a "header" + * in order to find out what kind of camera we are dealing with, + * as well as what are the picture dimensions. Note that for + * this initial connection, any failure will cause an error + * return from netcam_start (unlike later possible attempts at + * re-connecting, if the network connection is later interrupted). + */ + for (ix = 0; ix < MAX_HEADER_RETRIES; ix++) { + /* + * netcam_connect does an automatic netcam_close, so it's + * safe to include it as part of this loop + * (Not always true now Keep-Alive is implemented) + */ + if (debug_level > CAMERA_INFO) + motion_log(-1, 0, "netcam_http_request: about to try to connect, time #%d", ix ); + if (netcam_connect(netcam, 0) != 0) { + motion_log(LOG_ERR, 0, "Failed to open camera - check your config and that netcamera is online"); + + /* Fatal error on startup */ + ix = MAX_HEADER_RETRIES; + break;; + } + + if (netcam_read_first_header(netcam) >= 0) + break; + + motion_log(LOG_ERR, 0, "Error reading first header - re-trying"); + } + + if (ix == MAX_HEADER_RETRIES) { + motion_log(LOG_ERR, 0, "Failed to read first camera header - giving up for now"); + return -1; + } + + return 0; +} + +static int netcam_mjpg_buffer_refill(netcam_context_ptr netcam) +{ + int retval; + + if (netcam->response->buffer_left > 0) + return netcam->response->buffer_left; + while (1) { + retval = rbuf_read_bufferful(netcam); + if (retval < 0) { + motion_log(-1, 0, "netcam_mjpg_buffer_refill: Read error, trying to reconnect.."); + /* We may have lost the connexion */ + if (netcam_http_request(netcam) < 0) { + motion_log(-1, 0, "netcam_mjpg_buffer_refill: lost the cam."); + return -1; /* We REALLY lost the cam... bail out for now. */ + } + } + if (retval > 0) + break; + } + + netcam->response->buffer_left = retval; + netcam->response->buffer_pos = netcam->response->buffer; + + /* motion_log(LOG_DEBUG, 0, "Refilled buffer with [%d] bytes from the network.", retval); */ + + return retval; +} + + + /** + * netcam_read_mjpg_jpeg + * + * This routine reads from a netcam using a MJPG-chunk based + * protocol, used by Linksys WVC200 for example. + * This implementation has been made by reverse-engineering + * the protocol, so it may contain bugs and should be considered as + * experimental. + * + * Protocol explanation + * + * The stream consists of JPG pictures, spanned across multiple + * MJPG chunks (in general 3 chunks, altough that's not guaranteed). + * + * Each data chunk can range from 1 to 65535 bytes + a header, altough + * i have not seen anything bigger than 20000 bytes + a header. + * + * One MJPG chunk is constituted by a header plus the chunk data. + * The chunk header is of fixed size, and the following data size + * and position in the frame is specified in the chunk header. + * + * From what i have seen on WVC200 cameras, the stream always begins + * on JPG frame boundary, so you don't have to worry about beginning + * in the middle of a frame. + * + * See netcam.h for the mjpg_header structure and more details. + */ +static int netcam_read_mjpg_jpeg(netcam_context_ptr netcam) +{ + netcam_buff_ptr buffer; + netcam_buff *xchg; + struct timeval curtime; + mjpg_header mh; + size_t read_bytes; + int retval; + + /* + * Initialisation - set our local pointers to the context + * information + */ + buffer = netcam->receiving; + /* Assure the target buffer is empty */ + buffer->used = 0; + + if (netcam_mjpg_buffer_refill(netcam) < 0) + return -1; + + /* Loop until we have a complete JPG. */ + while (1) { + read_bytes = 0; + while (read_bytes < sizeof(mh)) { + + /* Transfer what we have in buffer in the header structure */ + retval = rbuf_flush(netcam, ((char *)&mh) + read_bytes, sizeof(mh) - read_bytes); + + read_bytes += retval; + if (debug_level > CAMERA_INFO) + motion_log(-1, 0, "Read [%d/%d] header bytes.", read_bytes, sizeof(mh)); + + + /* If we don't have received a full header, refill our buffer. */ + if (read_bytes < sizeof(mh)) { + if (netcam_mjpg_buffer_refill(netcam) < 0) + return -1; + } + } + + /* Now check the validity of our header. */ + if (strncmp(mh.mh_magic, MJPG_MH_MAGIC, MJPG_MH_MAGIC_SIZE)) { + if (debug_level > CAMERA_INFO) + motion_log(-1, 0, "Invalid header received, reconnecting"); + + + /* + * We shall reconnect to restart the stream, and get a chance + * to resync. + */ + if (netcam_http_request(netcam) < 0) + return -1; /* We lost the cam... bail out. */ + /* Even there, we need to resync. */ + buffer->used = 0; + continue ; + } + + /* Make room for the chunk. */ + netcam_check_buffsize(buffer, buffer->used + (int) mh.mh_chunksize); + + read_bytes = 0; + while (read_bytes < mh.mh_chunksize) { + retval = rbuf_flush(netcam, buffer->ptr + buffer->used + read_bytes, + mh.mh_chunksize - read_bytes); + read_bytes += retval; + if (debug_level > CAMERA_INFO) + motion_log(-1, 0, "Read [%d/%d] chunk bytes, [%d/%d] total", + read_bytes, mh.mh_chunksize, buffer->used + read_bytes, + mh.mh_framesize); + + if (retval < (int)(mh.mh_chunksize - read_bytes)){ + /* motion_log(-1, 0, "Chunk incomplete, going to refill."); */ + if (netcam_mjpg_buffer_refill(netcam) < 0){ + return -1; + } + } + } + buffer->used += read_bytes; + + if (debug_level > CAMERA_INFO) + motion_log(-1, 0, "Chunk complete, buffer used [%d] bytes.", buffer->used); + + + /* Is our JPG image complete ? */ + if (mh.mh_framesize == buffer->used) { + + if (debug_level > CAMERA_INFO) + motion_log(-1, 0, "Image complete, buffer used [%d] bytes.", buffer->used); + + break; + } + /* motion_log(LOG_ERR, 0, " -> Rlen now at [%d] bytes", rlen); */ + } + + /* + * read is complete - set the current 'receiving' buffer atomically + * as 'latest', and make the buffer previously in 'latest' become + * the new 'receiving' + */ + if (gettimeofday(&curtime, NULL) < 0) { + motion_log(LOG_ERR, 1, "gettimeofday in netcam_read_jpeg"); + } + + netcam->receiving->image_time = curtime; + + /* + * Calculate our "running average" time for this netcam's + * frame transmissions (except for the first time). + * Note that the average frame time is held in microseconds. + */ + if (netcam->last_image.tv_sec) { + netcam->av_frame_time = (9.0 * netcam->av_frame_time + + 1000000.0 * (curtime.tv_sec - netcam->last_image.tv_sec) + + (curtime.tv_usec- netcam->last_image.tv_usec)) / 10.0; + + if (debug_level > CAMERA_INFO) + motion_log(-1, 0, "Calculated frame time %f", netcam->av_frame_time); + } + netcam->last_image = curtime; + + pthread_mutex_lock(&netcam->mutex); + + xchg = netcam->latest; + netcam->latest = netcam->receiving; + netcam->receiving = xchg; + netcam->imgcnt++; + /* + * We have a new frame ready. We send a signal so that + * any thread (e.g. the motion main loop) waiting for the + * next frame to become available may proceed. + */ + pthread_cond_signal(&netcam->pic_ready); + + pthread_mutex_unlock(&netcam->mutex); + + return 0; +} + + + +/** * netcam_read_ftp_jpeg * * This routine reads from a netcam using the FTP protocol. @@ -1509,7 +1762,8 @@ } -tfile_context *file_new_context(void) { +tfile_context *file_new_context(void) +{ tfile_context *ret; /* note that mymalloc will exit on any problem */ @@ -1521,7 +1775,8 @@ return ret; } -void file_free_context(tfile_context* ctxt) { +void file_free_context(tfile_context* ctxt) +{ if (ctxt == NULL) return; @@ -1531,8 +1786,8 @@ free(ctxt); } -static int netcam_setup_file(netcam_context_ptr netcam, struct url_t *url) { - +static int netcam_setup_file(netcam_context_ptr netcam, struct url_t *url) +{ if ((netcam->file = file_new_context()) == NULL) return -1; @@ -1595,7 +1850,7 @@ while (!netcam->finish) { if (netcam->response) { /* if html input */ - if (!netcam->caps.streaming) { + if (netcam->caps.streaming == NCS_UNSUPPORTED) { /* Non-streaming ie. jpeg */ if (!netcam->connect_keepalive || (netcam->connect_keepalive && netcam->keepalive_timeup)) { /* If keepalive flag set but time up, time to close this socket */ @@ -1632,7 +1887,7 @@ /* need to have a dynamic delay here */ continue; } - } else { /* Streaming */ + } else if (netcam->caps.streaming == NCS_MULTIPART) { /* Multipart streaming */ if (netcam_read_next_header(netcam) < 0) { if (netcam_connect(netcam, open_error) < 0) { if (!open_error) { /* log first error */ @@ -1662,6 +1917,11 @@ "camera re-connected"); open_error = 0; } + } else if (netcam->caps.streaming == NCS_BLOCK) { /* MJPG-block streaming */ + /* + * Since we cannot move in the stream here, because we will read past the + * MJPG-block-header, error handling is done while reading MJPG blocks. + */ } } if (netcam->get_image(netcam) < 0) { @@ -1685,7 +1945,7 @@ * If non-streaming, want to synchronize our thread with the * motion main-loop. */ - if (!netcam->caps.streaming) { + if (netcam->caps.streaming == NCS_UNSUPPORTED) { pthread_mutex_lock(&netcam->mutex); /* before anything else, check for system shutdown */ @@ -1732,7 +1992,8 @@ pthread_exit(NULL); } -static int netcam_setup_html(netcam_context_ptr netcam, struct url_t *url) { +static int netcam_http_build_url(netcam_context_ptr netcam, struct url_t *url) +{ struct context *cnt = netcam->cnt; const char *ptr; /* working var */ char *userpass; /* temp pointer to config value */ @@ -1745,7 +2006,7 @@ memset(netcam->response, 0, sizeof(struct rbuf)); if (debug_level > CAMERA_INFO) - motion_log(LOG_INFO, 0, "netcam_setup_html: Netcam has flags: HTTP1.0: %s HTTP1.1: %s Keep-Alive %s.", + motion_log(LOG_INFO, 0, "netcam_http_build_url: Netcam has flags: HTTP1.0: %s HTTP1.1: %s Keep-Alive %s.", netcam->connect_http_10 ? "1":"0", netcam->connect_http_11 ? "1":"0", netcam->connect_keepalive ? "ON":"OFF"); @@ -1834,13 +2095,13 @@ ix += strlen(ptr); - /* Now add the required number of characters for the close header - * or Keep-Alive header. We test the flag which can be unset if - * there is a problem (rather than the flag in the conf structure - * which is read-only. - */ + /* Now add the required number of characters for the close header + * or Keep-Alive header. We test the flag which can be unset if + * there is a problem (rather than the flag in the conf structure + * which is read-only. + */ - if (netcam->connect_keepalive) { + if (netcam->connect_keepalive) { ix += strlen(connect_req_keepalive); } else { ix += strlen(connect_req_close); @@ -1887,47 +2148,31 @@ motion_log(-1, 0, "End of camera connect string."); } - /* - * Our basic initialisation has been completed. Now we will attempt - * to connect with the camera so that we can then get a "header" - * in order to find out what kind of camera we are dealing with, - * as well as what are the picture dimensions. Note that for - * this initial connection, any failure will cause an error - * return from netcam_start (unlike later possible attempts at - * re-connecting, if the network connection is later interrupted). - */ - for (ix = 0; ix < MAX_HEADER_RETRIES; ix++) { - /* - * netcam_connect does an automatic netcam_close, so it's - * safe to include it as part of this loop - * (Not always true now Keep-Alive is implemented) - */ - if (debug_level > CAMERA_INFO) - motion_log(-1, 0, "netcam_setup_html: about to try to connect, time #%d", ix ); - if (netcam_connect(netcam, 0) != 0) { - motion_log(LOG_ERR, 0, "Failed to open camera - check your config and that netcamera is online"); + return 0; +} - /* Fatal error on startup */ - ix = MAX_HEADER_RETRIES; - break;; - } - if (netcam_read_first_header(netcam) >= 0) - break; - motion_log(LOG_ERR, 0, "Error reading first header - re-trying"); - } +static int netcam_setup_html(netcam_context_ptr netcam, struct url_t *url) +{ + /* + * This netcam is http-based, so build the required URL and + * structures, like the connection-string and so on. + */ + if (netcam_http_build_url(netcam, url) < 0) + return -1; - if (ix == MAX_HEADER_RETRIES) { - motion_log(LOG_ERR, 0, "Failed to read first camera header - giving up for now"); + /* + * Then we will send our http request and get headers. + */ + if (netcam_http_request(netcam) < 0) return -1; - } /* * If this is a streaming camera, we need to position just * past the boundary string and read the image header */ - if (netcam->caps.streaming) { + if (netcam->caps.streaming == NCS_MULTIPART) { if (netcam_read_next_header(netcam) < 0) { motion_log(LOG_ERR, 0, "Failed to read first stream header - giving up for now"); @@ -1936,12 +2181,44 @@ } if (debug_level > CAMERA_INFO) - motion_log(-1, 0, "netcam_setup_html: connected, going on to read image.", ix ); + motion_log(-1, 0, "netcam_setup_html: connected, going on to read image."); netcam->get_image = netcam_read_html_jpeg; return 0; } -static int netcam_setup_ftp(netcam_context_ptr netcam, struct url_t *url) { +static int netcam_setup_mjpg(netcam_context_ptr netcam, struct url_t *url) +{ + /* + * This netcam is http-based, so build the required URL and + * structures, like the connection-string and so on. + */ + if (netcam_http_build_url(netcam, url) != 0) + return -1; + + /* + * Then we will send our http request and get headers. + */ + if (netcam_http_request(netcam) < 0) + return -1; + + /* + * We have a special type of streaming camera + */ + netcam->caps.streaming = NCS_BLOCK; + + /* + * We are positionned right just at the start of the first MJPG + * header, so don't move anymore, initialization complete. + */ + if (debug_level > CAMERA_INFO) + motion_log(-1, 0, "netcam_setup_mjpg: connected, going on to read and decode MJPG chunks."); + netcam->get_image = netcam_read_mjpg_jpeg; + + return 0; +} + +static int netcam_setup_ftp(netcam_context_ptr netcam, struct url_t *url) +{ struct context *cnt = netcam->cnt; const char *ptr; @@ -2012,7 +2289,8 @@ * error reply from the system call. * */ -ssize_t netcam_recv(netcam_context_ptr netcam, void *buffptr, size_t buffsize) { +ssize_t netcam_recv(netcam_context_ptr netcam, void *buffptr, size_t buffsize) +{ ssize_t retval; fd_set fd_r; struct timeval selecttime; @@ -2089,7 +2367,7 @@ * netcam->mutex locked. */ - if (!netcam->caps.streaming) { + if (netcam->caps.streaming == NCS_UNSUPPORTED) { pthread_cond_signal(&netcam->cap_cond); } @@ -2219,7 +2497,7 @@ * motion main-loop with the camera-handling thread through a signal, * together with a flag to say "start your next capture". */ - if (!netcam->caps.streaming) { + if (netcam->caps.streaming == NCS_UNSUPPORTED) { pthread_mutex_lock(&netcam->mutex); netcam->start_capture = 1; pthread_cond_signal(&netcam->cap_cond); @@ -2396,9 +2674,14 @@ if (debug_level > CAMERA_INFO) motion_log(-1, 0, "netcam_start: now calling netcam_setup_file()"); retval = netcam_setup_file(netcam, &url); + } else if ((url.service) && (!strcmp(url.service, "mjpg")) ) { + if (debug_level > CAMERA_INFO) + motion_log(-1, 0, "netcam_start: now calling netcam_setup_mjpg()"); + strcpy(url.service, "http"); /* Put back a real URL service. */ + retval = netcam_setup_mjpg(netcam, &url); } else { motion_log(LOG_ERR, 0, "Invalid netcam service '%s' - " - "must be http or ftp", url.service); + "must be http, ftp, mjpg or file.", url.service); netcam_url_free(&url); return -1; }