/*********************************************************** * * The functions: * netcam_setup * netcam_next * netcam_cleanup * are called from video_common.c * * Additional note: Although this module is called netcam_rtsp, * it actually handles more camera types than just rtsp. * Within its current construct, it could be set up to handle * whatever types of capture devices that ffmpeg can use. * As of this writing it includes rtsp, http, files and v4l2. * ***********************************************************/ #include #include #include #include "motion.hpp" #include "logger.hpp" #include "util.hpp" #include "rotate.hpp" #include "netcam.hpp" #include "video_v4l2.hpp" /* Needed to validate palette for v4l2 via netcam */ #include "movie.hpp" static void netcam_check_buffsize(netcam_buff_ptr buff, size_t numbytes) { int min_size_to_alloc; int real_alloc; int new_size; if ((buff->size - buff->used) >= numbytes) return; min_size_to_alloc = numbytes - (buff->size - buff->used); real_alloc = ((min_size_to_alloc / NETCAM_BUFFSIZE) * NETCAM_BUFFSIZE); if ((min_size_to_alloc - real_alloc) > 0) real_alloc += NETCAM_BUFFSIZE; new_size = buff->size + real_alloc; MOTION_LOG(DBG, TYPE_NETCAM, NO_ERRNO ,_("expanding buffer from [%d/%d] to [%d/%d] bytes.") ,(int) buff->used, (int) buff->size ,(int) buff->used, new_size); buff->ptr =(char*) myrealloc(buff->ptr, new_size, "netcam_check_buf_size"); buff->size = new_size; } /* * The following three routines (netcam_url_match, netcam_url_parse and * netcam_url_free are for 'parsing' (i.e. separating into the relevant * components) the URL provided by the user. They make use of regular * expressions (which is outside the scope of this module, so detailed * comments are not provided). netcam_url_parse is called from netcam_start, * and puts the "broken-up" components of the URL into the "url" element of * the netcam_context structure. * * Note that the routines are not "very clever", but they work sufficiently * well for the limited requirements of this module. The expression: * (http)://(((.*):(.*))@)?([^/:]|[-.a-z0-9]+)(:([0-9]+))?($|(/[^:]*)) * requires * 1) a string which begins with 'http', followed by '://' * 2) optionally a '@' which is preceded by two strings * (with 0 or more characters each) separated by a ':' * [this is for an optional username:password] * 3) a string comprising alpha-numerics, '-' and '.' characters * [this is for the hostname] * 4) optionally a ':' followed by one or more numeric characters * [this is for an optional port number] * 5) finally, either an end of line or a series of segments, * each of which begins with a '/', and contains anything * except a ':' */ /** * netcam_url_match * * Finds the matched part of a regular expression * * Parameters: * * m A structure containing the regular expression to be used * input The input string * * Returns: The string which was matched * */ static char *netcam_url_match(regmatch_t m, const char *input) { char *match = NULL; int len; if (m.rm_so != -1) { len = m.rm_eo - m.rm_so; if ((match =(char*) mymalloc(len + 1)) != NULL) { strncpy(match, input + m.rm_so, len); match[len] = '\0'; } } return match; } static void netcam_url_invalid(struct url_t *parse_url){ MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO,_("Invalid URL. Can not parse values.")); parse_url->host =(char*) malloc(5); parse_url->service =(char*) malloc(5); parse_url->path =(char*) malloc(10); parse_url->userpass =(char*) malloc(10); parse_url->port = 0; sprintf(parse_url->host, "%s","????"); sprintf(parse_url->service, "%s","????"); sprintf(parse_url->path, "%s","INVALID"); sprintf(parse_url->userpass, "%s","INVALID"); } /** * netcam_url_parse * * parses a string containing a URL into it's components * * Parameters: * parse_url A structure which will receive the results * of the parsing * text_url The input string containing the URL * * Returns: Nothing * */ static void netcam_url_parse(struct url_t *parse_url, const char *text_url) { char *s; int i; const char *re = "(http|ftp|mjpg|mjpeg|rtsp|rtmp)://(((.*):(.*))@)?" "([^/:]|[-_.a-z0-9]+)(:([0-9]+))?($|(/[^*]*))"; regex_t pattbuf; regmatch_t matches[10]; if (!strncmp(text_url, "file", 4)) re = "(file)://(((.*):(.*))@)?([/:])?(:([0-9]+))?($|(/[^*]*))"; if (!strncmp(text_url, "jpeg", 4)) re = "(jpeg)://(((.*):(.*))@)?([/:])?(:([0-9]+))?($|(/[^*]*))"; if (!strncmp(text_url, "v4l2", 4)) re = "(v4l2)://(((.*):(.*))@)?([/:])?(:([0-9]+))?($|(/[^*]*))"; /* Note that log messages are commented out to avoid leaking info related * to user/host/pass etc. Keeing them in the code for easier debugging if * it is needed */ //MOTION_LOG(DBG, TYPE_NETCAM, NO_ERRNO, "Entry netcam_url_parse data %s",text_url); memset(parse_url, 0, sizeof(struct url_t)); /* * regcomp compiles regular expressions into a form that is * suitable for regexec searches * regexec matches the URL string against the regular expression * and returns an array of pointers to strings matching each match * within (). The results that we need are finally placed in parse_url. */ if (!regcomp(&pattbuf, re, REG_EXTENDED | REG_ICASE)) { if (regexec(&pattbuf, text_url, 10, matches, 0) != REG_NOMATCH) { for (i = 0; i < 10; i++) { if ((s = netcam_url_match(matches[i], text_url)) != NULL) { //MOTION_LOG(DBG, TYPE_NETCAM, NO_ERRNO, "Parse case %d data %s", i, s); switch (i) { case 1: parse_url->service = s; break; case 3: parse_url->userpass = s; break; case 6: parse_url->host = s; break; case 8: parse_url->port = atoi(s); free(s); break; case 9: parse_url->path = s; break; /* Other components ignored */ default: free(s); break; } } } } else { netcam_url_invalid(parse_url); } } else { netcam_url_invalid(parse_url); } if (((!parse_url->port) && (parse_url->service)) || ((parse_url->port > 65535) && (parse_url->service))) { if (mystreq(parse_url->service, "http")) parse_url->port = 80; else if (mystreq(parse_url->service, "ftp")) parse_url->port = 21; else if (mystreq(parse_url->service, "rtmp")) parse_url->port = 1935; else if (mystreq(parse_url->service, "rtsp")) parse_url->port = 554; MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO, _("Using port number %d"),parse_url->port); } regfree(&pattbuf); } /** * netcam_url_free * * General cleanup of the URL structure, called from netcam_cleanup. * * Parameters: * * parse_url Structure containing the parsed data. * * Returns: Nothing * */ static void netcam_url_free(struct url_t *parse_url) { free(parse_url->service); parse_url->service = NULL; free(parse_url->userpass); parse_url->userpass = NULL; free(parse_url->host); parse_url->host = NULL; free(parse_url->path); parse_url->path = NULL; } static int netcam_check_pixfmt(struct ctx_netcam *netcam){ /* Determine if the format is YUV420P */ int retcd; retcd = -1; if ((netcam->codec_context->pix_fmt == MY_PIX_FMT_YUV420P) || (netcam->codec_context->pix_fmt == MY_PIX_FMT_YUVJ420P)) retcd = 0; return retcd; } static void netcam_pktarray_free(struct ctx_netcam *netcam){ int indx; pthread_mutex_lock(&netcam->mutex_pktarray); if (netcam->pktarray_size > 0){ for(indx = 0; indx < netcam->pktarray_size; indx++) { if (netcam->pktarray[indx].packet.data != NULL) { mypacket_unref(netcam->pktarray[indx].packet); } } } free(netcam->pktarray); netcam->pktarray = NULL; netcam->pktarray_size = 0; netcam->pktarray_index = -1; pthread_mutex_unlock(&netcam->mutex_pktarray); } static void netcam_null_context(struct ctx_netcam *netcam){ netcam->swsctx = NULL; netcam->swsframe_in = NULL; netcam->swsframe_out = NULL; netcam->frame = NULL; netcam->codec_context = NULL; netcam->format_context = NULL; netcam->transfer_format = NULL; } static void netcam_close_context(struct ctx_netcam *netcam){ if (netcam->swsctx != NULL) sws_freeContext(netcam->swsctx); if (netcam->swsframe_in != NULL) myframe_free(netcam->swsframe_in); if (netcam->swsframe_out != NULL) myframe_free(netcam->swsframe_out); if (netcam->frame != NULL) myframe_free(netcam->frame); if (netcam->pktarray != NULL) netcam_pktarray_free(netcam); if (netcam->codec_context != NULL) myavcodec_close(netcam->codec_context); if (netcam->format_context != NULL) avformat_close_input(&netcam->format_context); if (netcam->transfer_format != NULL) avformat_close_input(&netcam->transfer_format); netcam_null_context(netcam); } static void netcam_pktarray_resize(struct ctx_cam *cam, int is_highres){ /* This is called from netcam_next and is on the motion loop thread * The netcam->mutex is locked around the call to this function. */ /* Remember that this is a ring and we have two threads chasing around it * the ffmpeg is writing out of this ring while we are filling it up. "Bad" * things will occur if the "add" thread catches up with the "write" thread. * We need this ring to be big enough so they don't collide. * The alternative is that we'd need to make a copy of the entire packet * array in the ffmpeg module and do our writing from that copy. The * downside is that is a lot to be copying around for each image we want * to write out. And putting a mutex on the array during adding function would * slow down the capture thread to the speed of the writing thread. And that * writing thread operates at the user specified FPS which could be really slow * ...So....make this array big enough so we never catch our tail. :) */ int64_t idnbr_last, idnbr_first; int indx; struct ctx_netcam *netcam; struct packet_item *tmp; int newsize; if (is_highres){ idnbr_last = cam->imgs.image_ring[cam->imgs.ring_out].idnbr_high; idnbr_first = cam->imgs.image_ring[cam->imgs.ring_in].idnbr_high; netcam = cam->netcam_high; } else { idnbr_last = cam->imgs.image_ring[cam->imgs.ring_out].idnbr_norm; idnbr_first = cam->imgs.image_ring[cam->imgs.ring_in].idnbr_norm; netcam = cam->netcam; } if (!netcam->passthrough) return; /* The 30 is arbitrary */ /* Double the size plus double last diff so we don't catch our tail */ newsize =((idnbr_first - idnbr_last) * 2 ) + ((netcam->idnbr - idnbr_last ) * 2); if (newsize < 30) newsize = 30; pthread_mutex_lock(&netcam->mutex_pktarray); if ((netcam->pktarray_size < newsize) || (netcam->pktarray_size < 30)){ tmp =(packet_item*) mymalloc(newsize * sizeof(struct packet_item)); if (netcam->pktarray_size > 0 ){ memcpy(tmp, netcam->pktarray, sizeof(struct packet_item) * netcam->pktarray_size); } for(indx = netcam->pktarray_size; indx < newsize; indx++) { av_init_packet(&tmp[indx].packet); tmp[indx].packet.data=NULL; tmp[indx].packet.size=0; tmp[indx].idnbr = 0; tmp[indx].iskey = FALSE; tmp[indx].iswritten = FALSE; } if (netcam->pktarray != NULL) free(netcam->pktarray); netcam->pktarray = tmp; netcam->pktarray_size = newsize; MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Resized packet array to %d"), netcam->cameratype,newsize); } pthread_mutex_unlock(&netcam->mutex_pktarray); } static void netcam_pktarray_add(struct ctx_netcam *netcam){ int indx_next; int retcd; char errstr[128]; pthread_mutex_lock(&netcam->mutex_pktarray); if (netcam->pktarray_size == 0){ pthread_mutex_unlock(&netcam->mutex_pktarray); return; } /* Recall pktarray_size is one based but pktarray is zero based */ if (netcam->pktarray_index == (netcam->pktarray_size-1) ){ indx_next = 0; } else { indx_next = netcam->pktarray_index + 1; } netcam->pktarray[indx_next].idnbr = netcam->idnbr; mypacket_unref(netcam->pktarray[indx_next].packet); av_init_packet(&netcam->pktarray[indx_next].packet); netcam->pktarray[indx_next].packet.data = NULL; netcam->pktarray[indx_next].packet.size = 0; retcd = mycopy_packet(&netcam->pktarray[indx_next].packet, &netcam->packet_recv); if ((netcam->interrupted) || (retcd < 0)) { av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: av_copy_packet: %s ,Interrupt: %s") ,netcam->cameratype ,errstr, netcam->interrupted ? _("True"):_("False")); mypacket_unref(netcam->pktarray[indx_next].packet); netcam->pktarray[indx_next].packet.data = NULL; netcam->pktarray[indx_next].packet.size = 0; } if (netcam->pktarray[indx_next].packet.flags & AV_PKT_FLAG_KEY) { netcam->pktarray[indx_next].iskey = TRUE; } else { netcam->pktarray[indx_next].iskey = FALSE; } netcam->pktarray[indx_next].iswritten = FALSE; netcam->pktarray[indx_next].timestamp_ts.tv_sec = netcam->img_recv->image_time.tv_sec; netcam->pktarray[indx_next].timestamp_ts.tv_nsec = netcam->img_recv->image_time.tv_nsec; netcam->pktarray_index = indx_next; pthread_mutex_unlock(&netcam->mutex_pktarray); } /* netcam_decode_video * * Return values: * <0 error * 0 invalid but continue * 1 valid data */ static int netcam_decode_video(struct ctx_netcam *netcam){ #if (LIBAVFORMAT_VERSION_MAJOR >= 58) || ((LIBAVFORMAT_VERSION_MAJOR == 57) && (LIBAVFORMAT_VERSION_MINOR >= 41)) int retcd; char errstr[128]; /* The Invalid data problem comes frequently. Usually at startup of rtsp cameras. * We now ignore those packets so this function would need to fail on a different error. * We should consider adding a maximum count of these errors and reset every time * we get a good image. */ if (netcam->finish) return 0; /* This just speeds up the shutdown time */ retcd = avcodec_send_packet(netcam->codec_context, &netcam->packet_recv); if ((netcam->interrupted) || (netcam->finish)) return -1; if (retcd == AVERROR_INVALIDDATA) { MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("Ignoring packet with invalid data")); return 0; } if (retcd < 0 && retcd != AVERROR_EOF){ av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("Error sending packet to codec: %s"), errstr); return -1; } retcd = avcodec_receive_frame(netcam->codec_context, netcam->frame); if ((netcam->interrupted) || (netcam->finish)) return -1; if (retcd == AVERROR(EAGAIN)) return 0; if (retcd == AVERROR_INVALIDDATA) { MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("Ignoring packet with invalid data")); return 0; } if (retcd < 0) { av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("Error receiving frame from codec: %s"), errstr); return -1; } return 1; #else int retcd; int check = 0; char errstr[128]; if (netcam->finish) return 0; /* This just speeds up the shutdown time */ retcd = avcodec_decode_video2(netcam->codec_context, netcam->frame, &check, &netcam->packet_recv); if ((netcam->interrupted) || (netcam->finish)) return -1; if (retcd == AVERROR_INVALIDDATA) { MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO, _("Ignoring packet with invalid data")); return 0; } if (retcd < 0) { av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO, _("Error decoding packet: %s"),errstr); return -1; } if (check == 0 || retcd == 0) return 0; return 1; #endif } static int netcam_decode_packet(struct ctx_netcam *netcam){ int frame_size; int retcd; if (netcam->finish) return -1; /* This just speeds up the shutdown time */ retcd = netcam_decode_video(netcam); if (retcd <= 0) return retcd; frame_size = myimage_get_buffer_size(netcam->codec_context->pix_fmt ,netcam->codec_context->width ,netcam->codec_context->height); netcam_check_buffsize(netcam->img_recv, frame_size); netcam_check_buffsize(netcam->img_latest, frame_size); retcd = myimage_copy_to_buffer(netcam->frame ,(uint8_t *)netcam->img_recv->ptr ,netcam->codec_context->pix_fmt ,netcam->codec_context->width ,netcam->codec_context->height ,frame_size); if ((retcd < 0) || (netcam->interrupted)) { MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("Error decoding video packet: Copying to buffer")); return -1; } netcam->img_recv->used = frame_size; return frame_size; } static int netcam_open_codec(struct ctx_netcam *netcam){ #if (LIBAVFORMAT_VERSION_MAJOR >= 58) || ((LIBAVFORMAT_VERSION_MAJOR == 57) && (LIBAVFORMAT_VERSION_MINOR >= 41)) int retcd; char errstr[128]; AVStream *st; AVCodec *decoder = NULL; if (netcam->finish) return -1; /* This just speeds up the shutdown time */ retcd = av_find_best_stream(netcam->format_context, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); if ((retcd < 0) || (netcam->interrupted)){ av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: av_find_best_stream: %s,Interrupt %s") ,netcam->cameratype, errstr, netcam->interrupted ? _("True"):_("False")); return -1; } netcam->video_stream_index = retcd; st = netcam->format_context->streams[netcam->video_stream_index]; decoder = avcodec_find_decoder(st->codecpar->codec_id); if ((decoder == NULL) || (netcam->interrupted)){ MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: avcodec_find_decoder: Failed,Interrupt %s") ,netcam->cameratype, netcam->interrupted ? _("True"):_("False")); return -1; } netcam->codec_context = avcodec_alloc_context3(decoder); if ((netcam->codec_context == NULL) || (netcam->interrupted)){ MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: avcodec_alloc_context3: Failed,Interrupt %s") ,netcam->cameratype, netcam->interrupted ? _("True"):_("False")); return -1; } retcd = avcodec_parameters_to_context(netcam->codec_context, st->codecpar); if ((retcd < 0) || (netcam->interrupted)) { av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: avcodec_parameters_to_context: %s,Interrupt %s") ,netcam->cameratype, errstr, netcam->interrupted ? _("True"):_("False")); return -1; } retcd = avcodec_open2(netcam->codec_context, decoder, NULL); if ((retcd < 0) || (netcam->interrupted)){ av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: avcodec_open2: %s,Interrupt %s") ,netcam->cameratype, errstr, netcam->interrupted ? _("True"):_("False")); return -1; } return 0; #else int retcd; char errstr[128]; AVStream *st; AVCodec *decoder = NULL; if (netcam->finish) return -1; /* This just speeds up the shutdown time */ retcd = av_find_best_stream(netcam->format_context, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); if ((retcd < 0) || (netcam->interrupted)){ av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: av_find_best_stream: %s,Interrupt %s") ,netcam->cameratype, errstr, netcam->interrupted ? _("True"):_("False")); return -1; } netcam->video_stream_index = retcd; st = netcam->format_context->streams[netcam->video_stream_index]; netcam->codec_context = st->codec; decoder = avcodec_find_decoder(netcam->codec_context->codec_id); if ((decoder == NULL) || (netcam->interrupted)) { MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: avcodec_find_decoder: Failed,Interrupt %s") ,netcam->cameratype, netcam->interrupted ? _("True"):_("False")); return -1; } retcd = avcodec_open2(netcam->codec_context, decoder, NULL); if ((retcd < 0) || (netcam->interrupted)){ av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: avcodec_open2: %s,Interrupt %s") ,netcam->cameratype, errstr, netcam->interrupted ? _("True"):_("False")); return -1; } return 0; #endif } static struct ctx_netcam *netcam_new_context(void){ struct ctx_netcam *ret; /* Note that mymalloc will exit on any problem. */ ret =(struct ctx_netcam*) mymalloc(sizeof(struct ctx_netcam)); memset(ret, 0, sizeof(struct ctx_netcam)); return ret; } static int netcam_interrupt(void *ctx){ struct ctx_netcam *netcam = (struct ctx_netcam *)ctx; if (netcam->finish){ netcam->interrupted = TRUE; return TRUE; } if (netcam->status == NETCAM_CONNECTED) { return FALSE; } else if (netcam->status == NETCAM_READINGIMAGE) { clock_gettime(CLOCK_REALTIME, &netcam->interruptcurrenttime); if ((netcam->interruptcurrenttime.tv_sec - netcam->interruptstarttime.tv_sec ) > netcam->interruptduration){ MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Camera reading (%s) timed out") , netcam->cameratype, netcam->camera_name); netcam->interrupted = TRUE; return TRUE; } else{ return FALSE; } } else { /* This is for NOTCONNECTED and RECONNECTING status. We give these * options more time because all the ffmpeg calls that are inside the * netcam_connect function will use the same start time. Otherwise we * would need to reset the time before each call to a ffmpeg function. */ clock_gettime(CLOCK_REALTIME, &netcam->interruptcurrenttime); if ((netcam->interruptcurrenttime.tv_sec - netcam->interruptstarttime.tv_sec ) > netcam->interruptduration){ MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Camera (%s) timed out") , netcam->cameratype, netcam->camera_name); netcam->interrupted = TRUE; return TRUE; } else{ return FALSE; } } /* should not be possible to get here */ return FALSE; } static int netcam_resize(struct ctx_netcam *netcam){ int retcd; char errstr[128]; uint8_t *buffer_out; if (netcam->finish) return -1; /* This just speeds up the shutdown time */ retcd=myimage_fill_arrays( netcam->swsframe_in ,(uint8_t*)netcam->img_recv->ptr ,netcam->codec_context->pix_fmt ,netcam->codec_context->width ,netcam->codec_context->height); if (retcd < 0) { if (netcam->status == NETCAM_NOTCONNECTED){ av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("Error allocating picture in: %s"), errstr); } netcam_close_context(netcam); return -1; } buffer_out=(uint8_t *)av_malloc(netcam->swsframe_size*sizeof(uint8_t)); retcd=myimage_fill_arrays( netcam->swsframe_out ,buffer_out ,MY_PIX_FMT_YUV420P ,netcam->imgsize.width ,netcam->imgsize.height); if (retcd < 0) { if (netcam->status == NETCAM_NOTCONNECTED){ av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("Error allocating picture out: %s"), errstr); } netcam_close_context(netcam); return -1; } retcd = sws_scale( netcam->swsctx ,(const uint8_t* const *)netcam->swsframe_in->data ,netcam->swsframe_in->linesize ,0 ,netcam->codec_context->height ,netcam->swsframe_out->data ,netcam->swsframe_out->linesize); if (retcd < 0) { if (netcam->status == NETCAM_NOTCONNECTED){ av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("Error resizing/reformatting: %s"), errstr); } netcam_close_context(netcam); return -1; } retcd=myimage_copy_to_buffer( netcam->swsframe_out ,(uint8_t *)netcam->img_recv->ptr ,MY_PIX_FMT_YUV420P ,netcam->imgsize.width ,netcam->imgsize.height ,netcam->swsframe_size); if (retcd < 0) { if (netcam->status == NETCAM_NOTCONNECTED){ av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("Error putting frame into output buffer: %s"), errstr); } netcam_close_context(netcam); return -1; } netcam->img_recv->used = netcam->swsframe_size; av_free(buffer_out); return 0; } static int netcam_read_image(struct ctx_netcam *netcam){ int size_decoded; int retcd; int haveimage; char errstr[128]; netcam_buff *xchg; if (netcam->finish) return -1; /* This just speeds up the shutdown time */ av_init_packet(&netcam->packet_recv); netcam->packet_recv.data = NULL; netcam->packet_recv.size = 0; netcam->interrupted=FALSE; clock_gettime(CLOCK_REALTIME, &netcam->interruptstarttime); netcam->interruptduration = 10; netcam->status = NETCAM_READINGIMAGE; netcam->img_recv->used = 0; size_decoded = 0; haveimage = FALSE; while ((!haveimage) && (!netcam->interrupted)) { retcd = av_read_frame(netcam->format_context, &netcam->packet_recv); if ((netcam->interrupted) || (retcd < 0)) { av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: av_read_frame: %s ,Interrupt: %s") ,netcam->cameratype ,errstr, netcam->interrupted ? _("True"):_("False")); mypacket_unref(netcam->packet_recv); netcam_close_context(netcam); return -1; } if (netcam->packet_recv.stream_index == netcam->video_stream_index){ /* For a high resolution pass-through we don't decode the image */ if (netcam->high_resolution && netcam->passthrough){ if (netcam->packet_recv.data != NULL) size_decoded = 1; } else { size_decoded = netcam_decode_packet(netcam); } } if (size_decoded > 0 ){ haveimage = TRUE; } else if (size_decoded == 0){ /* Did not fail, just didn't get anything. Try again */ mypacket_unref(netcam->packet_recv); av_init_packet(&netcam->packet_recv); netcam->packet_recv.data = NULL; netcam->packet_recv.size = 0; } else { mypacket_unref(netcam->packet_recv); netcam_close_context(netcam); return -1; } } clock_gettime(CLOCK_REALTIME, &netcam->img_recv->image_time); /* Skip status change on our first image to keep the "next" function waiting * until the handler thread gets going */ if (!netcam->first_image) netcam->status = NETCAM_CONNECTED; /* Skip resize/pix format for high pass-through */ if (!(netcam->high_resolution && netcam->passthrough)){ if ((netcam->imgsize.width != netcam->codec_context->width) || (netcam->imgsize.height != netcam->codec_context->height) || (netcam_check_pixfmt(netcam) != 0) ){ if (netcam_resize(netcam) < 0){ mypacket_unref(netcam->packet_recv); netcam_close_context(netcam); return -1; } } } pthread_mutex_lock(&netcam->mutex); netcam->idnbr++; if (netcam->passthrough) netcam_pktarray_add(netcam); if (!(netcam->high_resolution && netcam->passthrough)) { xchg = netcam->img_latest; netcam->img_latest = netcam->img_recv; netcam->img_recv = xchg; } pthread_mutex_unlock(&netcam->mutex); mypacket_unref(netcam->packet_recv); if (netcam->format_context->streams[netcam->video_stream_index]->avg_frame_rate.den > 0){ netcam->src_fps = ( (netcam->format_context->streams[netcam->video_stream_index]->avg_frame_rate.num / netcam->format_context->streams[netcam->video_stream_index]->avg_frame_rate.den) + 0.5); } return 0; } static int netcam_ntc(struct ctx_netcam *netcam){ if ((netcam->finish) || (!netcam->first_image)) return 0; if ((netcam->imgsize.width != netcam->codec_context->width) || (netcam->imgsize.height != netcam->codec_context->height) || (netcam_check_pixfmt(netcam) != 0) ){ MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, ""); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, "******************************************************"); if ((netcam->imgsize.width != netcam->codec_context->width) || (netcam->imgsize.height != netcam->codec_context->height)) { if (netcam_check_pixfmt(netcam) != 0) { MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("The network camera is sending pictures in a different")); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("size than specified in the config and also a ")); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("different picture format. The picture is being")); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("transcoded to YUV420P and into the size requested")); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("in the config file. If possible change netcam to")); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("be in YUV420P format and the size requested in the")); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("config to possibly lower CPU usage.")); } else { MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("The network camera is sending pictures in a different")); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("size than specified in the configuration file.")); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("The picture is being transcoded into the size ")); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("requested in the configuration. If possible change")); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("netcam or configuration to indicate the same size")); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("to possibly lower CPU usage.")); } MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("Netcam: %d x %d => Config: %d x %d") ,netcam->codec_context->width,netcam->codec_context->height ,netcam->imgsize.width,netcam->imgsize.height); } else { MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("The image sent is being ")); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("trancoded to YUV420P. If possible change netcam ")); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("picture format to YUV420P to possibly lower CPU usage.")); } MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, "******************************************************"); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, ""); } return 0; } static int netcam_open_sws(struct ctx_netcam *netcam){ if (netcam->finish) return -1; /* This just speeds up the shutdown time */ netcam->swsframe_in = myframe_alloc(); if (netcam->swsframe_in == NULL) { if (netcam->status == NETCAM_NOTCONNECTED){ MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO, _("Unable to allocate swsframe_in.")); } netcam_close_context(netcam); return -1; } netcam->swsframe_out = myframe_alloc(); if (netcam->swsframe_out == NULL) { if (netcam->status == NETCAM_NOTCONNECTED){ MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO, _("Unable to allocate swsframe_out.")); } netcam_close_context(netcam); return -1; } /* * The scaling context is used to change dimensions to config file and * also if the format sent by the camera is not YUV420. */ netcam->swsctx = sws_getContext( netcam->codec_context->width ,netcam->codec_context->height ,netcam->codec_context->pix_fmt ,netcam->imgsize.width ,netcam->imgsize.height ,MY_PIX_FMT_YUV420P ,SWS_BICUBIC,NULL,NULL,NULL); if (netcam->swsctx == NULL) { if (netcam->status == NETCAM_NOTCONNECTED){ MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO, _("Unable to allocate scaling context.")); } netcam_close_context(netcam); return -1; } netcam->swsframe_size = myimage_get_buffer_size( MY_PIX_FMT_YUV420P ,netcam->imgsize.width ,netcam->imgsize.height); if (netcam->swsframe_size <= 0) { if (netcam->status == NETCAM_NOTCONNECTED){ MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO, _("Error determining size of frame out")); } netcam_close_context(netcam); return -1; } /* the image buffers must be big enough to hold the final frame after resizing */ netcam_check_buffsize(netcam->img_recv, netcam->swsframe_size); netcam_check_buffsize(netcam->img_latest, netcam->swsframe_size); return 0; } static void netcam_set_http(struct ctx_netcam *netcam){ netcam->format_context->iformat = av_find_input_format("mjpeg"); MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Setting http input_format mjpeg"),netcam->cameratype); } static void netcam_set_rtsp(struct ctx_netcam *netcam){ if (netcam->rtsp_uses_tcp) { av_dict_set(&netcam->opts, "rtsp_transport", "tcp", 0); av_dict_set(&netcam->opts, "allowed_media_types", "video", 0); if (netcam->status == NETCAM_NOTCONNECTED) MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Setting rtsp transport to tcp"),netcam->cameratype); } else { av_dict_set(&netcam->opts, "rtsp_transport", "udp", 0); av_dict_set(&netcam->opts, "max_delay", "500000", 0); /* 100000 is the default */ if (netcam->status == NETCAM_NOTCONNECTED) MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Setting rtsp transport to udp"),netcam->cameratype); } } static void netcam_set_file(struct ctx_netcam *netcam){ /* This is a place holder for the moment. We will add into * this function any options that must be set for ffmpeg to * read a particular file. To date, it does not need any * additional options and works fine with defaults. */ MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Setting attributes to read file"),netcam->cameratype); } static void netcam_set_v4l2(struct ctx_netcam *netcam){ char optsize[10], optfmt[10], optfps[10]; char *fourcc; netcam->format_context->iformat = av_find_input_format("video4linux2"); fourcc=(char*)malloc(5*sizeof(char)); v4l2_palette_fourcc(netcam->v4l2_palette, fourcc); if (mystreq(fourcc,"MJPG")) { if (v4l2_palette_valid(netcam->path,netcam->v4l2_palette)){ sprintf(optfmt, "%s","mjpeg"); av_dict_set(&netcam->opts, "input_format", optfmt, 0); } else { sprintf(optfmt, "%s","default"); } } else if (mystreq(fourcc,"H264")){ if (v4l2_palette_valid(netcam->path,netcam->v4l2_palette)){ sprintf(optfmt, "%s","h264"); av_dict_set(&netcam->opts, "input_format", optfmt, 0); } else { sprintf(optfmt, "%s","default"); } } else { sprintf(optfmt, "%s","default"); } if (mystrne(optfmt,"default")) { if (v4l2_parms_valid(netcam->path ,netcam->v4l2_palette ,netcam->framerate ,netcam->imgsize.width ,netcam->imgsize.height)) { sprintf(optfps, "%d",netcam->framerate); av_dict_set(&netcam->opts, "framerate", optfps, 0); sprintf(optsize, "%dx%d",netcam->imgsize.width,netcam->imgsize.height); av_dict_set(&netcam->opts, "video_size", optsize, 0); } else { sprintf(optfps, "%s","default"); sprintf(optsize, "%s","default"); } } else { sprintf(optfps, "%s","default"); sprintf(optsize, "%s","default"); } if (netcam->status == NETCAM_NOTCONNECTED){ MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Requested v4l2_palette option: %d") ,netcam->cameratype,netcam->v4l2_palette); MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Requested FOURCC code: %s"),netcam->cameratype,fourcc); MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Setting v4l2 input_format: %s"),netcam->cameratype,optfmt); MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Setting v4l2 framerate: %s"),netcam->cameratype, optfps); MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Setting v4l2 video_size: %s"),netcam->cameratype, optsize); } free(fourcc); } static void netcam_set_path (struct ctx_cam *cam, struct ctx_netcam *netcam ) { char *userpass = NULL; struct url_t url; netcam->path = NULL; memset(&url, 0, sizeof(url)); if (netcam->high_resolution){ netcam_url_parse(&url, cam->conf.netcam_highres); } else { netcam_url_parse(&url, cam->conf.netcam_url); } if (cam->conf.netcam_proxy) { MOTION_LOG(WRN, TYPE_NETCAM, NO_ERRNO ,_("Proxies not supported using for %s"),url.service); } if (cam->conf.netcam_userpass != NULL) { userpass = mystrdup(cam->conf.netcam_userpass); } else if (url.userpass != NULL) { userpass = mystrdup(url.userpass); } if (mystreq(url.service, "v4l2")) { netcam->path =(char*) mymalloc(strlen(url.path) + 1); sprintf(netcam->path, "%s",url.path); MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("Setting up v4l2 via ffmpeg netcam")); } else if (mystreq(url.service, "file")) { netcam->path =(char*) mymalloc(strlen(url.path) + 1); sprintf(netcam->path, "%s",url.path); MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("Setting up file via ffmpeg netcam")); } else { if (mystreq(url.service, "mjpeg")) { sprintf(url.service, "%s","http"); MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("Setting up http via ffmpeg netcam")); } else { MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("Setting up %s via ffmpeg netcam"),url.service); } if (userpass != NULL) { netcam->path =(char*) mymalloc(strlen(url.service) + 3 + strlen(userpass) + 1 + strlen(url.host) + 6 + strlen(url.path) + 2 ); sprintf((char *)netcam->path, "%s://%s@%s:%d%s", url.service, userpass, url.host, url.port, url.path); } else { netcam->path =(char*) mymalloc(strlen(url.service) + 3 + strlen(url.host) + 6 + strlen(url.path) + 2); sprintf((char *)netcam->path, "%s://%s:%d%s", url.service, url.host, url.port, url.path); } } sprintf(netcam->service, "%s",url.service); netcam_url_free(&url); if (userpass) free (userpass); } static void netcam_set_parms (struct ctx_cam *cam, struct ctx_netcam *netcam ) { /* Set the parameters to be used with our camera */ if (netcam->high_resolution) { netcam->imgsize.width = 0; netcam->imgsize.height = 0; snprintf(netcam->cameratype,29, "%s",_("High resolution")); } else { netcam->imgsize.width = cam->conf.width; netcam->imgsize.height = cam->conf.height; snprintf(netcam->cameratype,29, "%s",_("Normal resolution")); } MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("Setting up %s stream."),netcam->cameratype); mycheck_passthrough(cam); /* In case it was turned on via webcontrol */ netcam->status = NETCAM_NOTCONNECTED; netcam->rtsp_uses_tcp =cam->conf.netcam_use_tcp; netcam->v4l2_palette = cam->conf.v4l2_palette; netcam->framerate = cam->conf.framerate; netcam->src_fps = cam->conf.framerate; /* Default to conf fps */ netcam->motapp = cam->motapp; netcam->conf = &cam->conf; netcam->camera_name = cam->conf.camera_name; netcam->img_recv =(netcam_buff_ptr) mymalloc(sizeof(netcam_buff)); netcam->img_recv->ptr =(char*) mymalloc(NETCAM_BUFFSIZE); netcam->img_latest =(netcam_buff_ptr) mymalloc(sizeof(netcam_buff)); netcam->img_latest->ptr =(char*) mymalloc(NETCAM_BUFFSIZE); netcam->pktarray_size = 0; netcam->pktarray_index = -1; netcam->pktarray = NULL; netcam->handler_finished = TRUE; netcam->first_image = TRUE; netcam->reconnect_count = 0; snprintf(netcam->threadname, 15, "%s",_("Unknown")); clock_gettime(CLOCK_REALTIME, &netcam->interruptstarttime); clock_gettime(CLOCK_REALTIME, &netcam->interruptcurrenttime); /* If this is the norm and we have a highres, then disable passthru on the norm */ if ((!netcam->high_resolution) && (cam->conf.netcam_highres)) { netcam->passthrough = FALSE; } else { netcam->passthrough = mycheck_passthrough(cam); } netcam->interruptduration = 5; netcam->interrupted = FALSE; clock_gettime(CLOCK_REALTIME, &netcam->frame_curr_tm); clock_gettime(CLOCK_REALTIME, &netcam->frame_prev_tm); netcam_set_path(cam, netcam); } static int netcam_set_dimensions (struct ctx_cam *cam) { cam->imgs.width = 0; cam->imgs.height = 0; cam->imgs.size_norm = 0; cam->imgs.motionsize = 0; cam->imgs.width_high = 0; cam->imgs.height_high = 0; cam->imgs.size_high = 0; if (cam->conf.width % 8) { MOTION_LOG(CRT, TYPE_NETCAM, NO_ERRNO ,_("Image width (%d) requested is not modulo 8."), cam->conf.width); cam->conf.width = cam->conf.width - (cam->conf.width % 8) + 8; MOTION_LOG(CRT, TYPE_NETCAM, NO_ERRNO ,_("Adjusting width to next higher multiple of 8 (%d)."), cam->conf.width); } if (cam->conf.height % 8) { MOTION_LOG(CRT, TYPE_NETCAM, NO_ERRNO ,_("Image height (%d) requested is not modulo 8."), cam->conf.height); cam->conf.height = cam->conf.height - (cam->conf.height % 8) + 8; MOTION_LOG(CRT, TYPE_NETCAM, NO_ERRNO ,_("Adjusting height to next higher multiple of 8 (%d)."), cam->conf.height); } /* Fill in camera details into context structure. */ cam->imgs.width = cam->conf.width; cam->imgs.height = cam->conf.height; cam->imgs.size_norm = (cam->conf.width * cam->conf.height * 3) / 2; cam->imgs.motionsize = cam->conf.width * cam->conf.height; return 0; } static int netcam_copy_stream(struct ctx_netcam *netcam){ /* Make a static copy of the stream information for use in passthrough processing */ #if (LIBAVFORMAT_VERSION_MAJOR >= 58) || ((LIBAVFORMAT_VERSION_MAJOR == 57) && (LIBAVFORMAT_VERSION_MINOR >= 41)) AVStream *transfer_stream, *stream_in; int retcd; pthread_mutex_lock(&netcam->mutex_transfer); if (netcam->transfer_format != NULL) avformat_close_input(&netcam->transfer_format); netcam->transfer_format = avformat_alloc_context(); transfer_stream = avformat_new_stream(netcam->transfer_format, NULL); stream_in = netcam->format_context->streams[netcam->video_stream_index]; retcd = avcodec_parameters_copy(transfer_stream->codecpar, stream_in->codecpar); if (retcd < 0){ MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("Unable to copy codec parameters")); pthread_mutex_unlock(&netcam->mutex_transfer); return -1; } transfer_stream->time_base = stream_in->time_base; pthread_mutex_unlock(&netcam->mutex_transfer); MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO, _("Stream copied for pass-through")); return 0; #elif (LIBAVFORMAT_VERSION_MAJOR >= 55) AVStream *transfer_stream, *stream_in; int retcd; pthread_mutex_lock(&netcam->mutex_transfer); if (netcam->transfer_format != NULL) avformat_close_input(&netcam->transfer_format); netcam->transfer_format = avformat_alloc_context(); transfer_stream = avformat_new_stream(netcam->transfer_format, NULL); stream_in = netcam->format_context->streams[netcam->video_stream_index]; retcd = avcodec_copy_context(transfer_stream->codec, stream_in->codec); if (retcd < 0){ MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO, _("Unable to copy codec parameters")); pthread_mutex_unlock(&netcam->mutex_transfer); return -1; } transfer_stream->time_base = stream_in->time_base; pthread_mutex_unlock(&netcam->mutex_transfer); MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO, _("Stream copied for pass-through")); return 0; #else /* This is disabled in the mycheck_passthrough but we need it here for compiling */ if (netcam != NULL) MOTION_LOG(INF, TYPE_ENCODER, NO_ERRNO, _("ffmpeg too old")); return -1; #endif } static int netcam_open_context(struct ctx_netcam *netcam){ int retcd; char errstr[128]; if (netcam->finish) return -1; if (netcam->path == NULL) { if (netcam->status == NETCAM_NOTCONNECTED){ MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO, _("Null path passed to connect")); } return -1; } netcam->opts = NULL; netcam->format_context = avformat_alloc_context(); netcam->format_context->interrupt_callback.callback = netcam_interrupt; netcam->format_context->interrupt_callback.opaque = netcam; netcam->interrupted = FALSE; clock_gettime(CLOCK_REALTIME, &netcam->interruptstarttime); netcam->interruptduration = 20; if (strncmp(netcam->service, "http", 4) == 0 ){ netcam_set_http(netcam); } else if (strncmp(netcam->service, "rtsp", 4) == 0 ){ netcam_set_rtsp(netcam); } else if (strncmp(netcam->service, "rtmp", 4) == 0 ){ netcam_set_rtsp(netcam); } else if (strncmp(netcam->service, "v4l2", 4) == 0 ){ netcam_set_v4l2(netcam); } else if (strncmp(netcam->service, "file", 4) == 0 ){ netcam_set_file(netcam); } else { av_dict_free(&netcam->opts); MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Invalid camera service"), netcam->cameratype); return -1; } /* * There is not many av functions above this (av_dict_free?) but we are not getting clean * interrupts or shutdowns via valgrind and they all point to issues with the avformat_open_input * right below so we make sure that we are not in a interrupt / finish situation before calling it */ if ((netcam->interrupted) || (netcam->finish) ){ if (netcam->status == NETCAM_NOTCONNECTED){ MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: Unable to open camera(%s)") , netcam->cameratype, netcam->camera_name); } av_dict_free(&netcam->opts); if (netcam->interrupted) netcam_close_context(netcam); return -1; } retcd = avformat_open_input(&netcam->format_context, netcam->path, NULL, &netcam->opts); if ((retcd < 0) || (netcam->interrupted) || (netcam->finish) ){ if (netcam->status == NETCAM_NOTCONNECTED){ av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: Unable to open camera(%s): %s") , netcam->cameratype, netcam->camera_name, errstr); } av_dict_free(&netcam->opts); if (netcam->interrupted) netcam_close_context(netcam); return -1; } av_dict_free(&netcam->opts); MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Opened camera(%s)"), netcam->cameratype, netcam->camera_name); /* fill out stream information */ retcd = avformat_find_stream_info(netcam->format_context, NULL); if ((retcd < 0) || (netcam->interrupted) || (netcam->finish) ){ if (netcam->status == NETCAM_NOTCONNECTED){ av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: Unable to find stream info: %s") ,netcam->cameratype, errstr); } netcam_close_context(netcam); return -1; } /* there is no way to set the avcodec thread names, but they inherit * our thread name - so temporarily change our thread name to the * desired name */ mythreadname_get(netcam->threadname); mythreadname_set("av",netcam->threadnbr,netcam->camera_name); retcd = netcam_open_codec(netcam); mythreadname_set(NULL, 0, netcam->threadname); if ((retcd < 0) || (netcam->interrupted) || (netcam->finish) ){ if (netcam->status == NETCAM_NOTCONNECTED){ av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: Unable to open codec context: %s") ,netcam->cameratype, errstr); } netcam_close_context(netcam); return -1; } if (netcam->codec_context->width <= 0 || netcam->codec_context->height <= 0) { MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: Camera image size is invalid"),netcam->cameratype); netcam_close_context(netcam); return -1; } if (netcam->high_resolution){ netcam->imgsize.width = netcam->codec_context->width; netcam->imgsize.height = netcam->codec_context->height; } else { if (netcam_open_sws(netcam) < 0) return -1; } netcam->frame = myframe_alloc(); if (netcam->frame == NULL) { if (netcam->status == NETCAM_NOTCONNECTED){ MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: Unable to allocate frame."),netcam->cameratype); } netcam_close_context(netcam); return -1; } if (netcam->passthrough){ retcd = netcam_copy_stream(netcam); if ((retcd < 0) || (netcam->interrupted)){ if (netcam->status == NETCAM_NOTCONNECTED){ MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: Failed to copy stream for pass-through.") ,netcam->cameratype); } netcam->passthrough = FALSE; } } /* Validate that the previous steps opened the camera */ retcd = netcam_read_image(netcam); if ((retcd < 0) || (netcam->interrupted)){ if (netcam->status == NETCAM_NOTCONNECTED){ MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: Failed to read first image"),netcam->cameratype); } netcam_close_context(netcam); return -1; } return 0; } static int netcam_connect(struct ctx_netcam *netcam){ if (netcam_open_context(netcam) < 0) return -1; if (netcam_ntc(netcam) < 0 ) return -1; if (netcam_read_image(netcam) < 0) return -1; /* We use the status for determining whether to grab a image from * the Motion loop(see "next" function). When we are initially starting, * we open and close the context and during this process we do not want the * Motion loop to start quite yet on this first image so we do * not set the status to connected */ if (!netcam->first_image) netcam->status = NETCAM_CONNECTED; MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO ,_("%s: Camera (%s) connected") , netcam->cameratype,netcam->camera_name); return 0; } static void netcam_shutdown(struct ctx_netcam *netcam){ if (netcam) { netcam_close_context(netcam); if (netcam->path != NULL) free(netcam->path); if (netcam->img_latest != NULL){ free(netcam->img_latest->ptr); free(netcam->img_latest); } if (netcam->img_recv != NULL){ free(netcam->img_recv->ptr); free(netcam->img_recv); } netcam->path = NULL; netcam->img_latest = NULL; netcam->img_recv = NULL; } } static void netcam_handler_wait(struct ctx_netcam *netcam){ /* This function slows down the handler loop to try to * get in sync with the main motion loop in the capturing * of images while also trying to not go so slow that the * connection to the network camera is lost and we end up * with lots of reconnects or fragmented images */ int framerate; long usec_maxrate, usec_delay; framerate = netcam->conf->framerate; if (framerate < 2) framerate = 2; if (mystreq(netcam->service,"file")) { /* For file processing, we try to match exactly the motion loop rate */ usec_maxrate = (1000000L / framerate); } else { /* We set the capture rate to be a bit faster than the frame rate. This * should provide the motion loop with a picture whenever it wants one. */ if (framerate < netcam->src_fps) framerate = netcam->src_fps; usec_maxrate = (1000000L / (framerate + 3)); } clock_gettime(CLOCK_REALTIME, &netcam->frame_curr_tm); usec_delay = usec_maxrate - ((netcam->frame_curr_tm.tv_sec - netcam->frame_prev_tm.tv_sec) * 1000000L) - ((netcam->frame_curr_tm.tv_nsec - netcam->frame_prev_tm.tv_nsec)/1000); if ((usec_delay > 0) && (usec_delay < 1000000L)){ SLEEP(0, usec_delay * 1000); } } static void netcam_handler_reconnect(struct ctx_netcam *netcam){ int retcd; if ((netcam->status == NETCAM_CONNECTED) || (netcam->status == NETCAM_READINGIMAGE)){ MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: Reconnecting with camera...."),netcam->cameratype); } netcam->status = NETCAM_RECONNECTING; /* * The retry count of 100 is arbritrary. * We want to try many times quickly to not lose too much information * before we go into the long wait phase */ retcd = netcam_connect(netcam); if (retcd < 0){ if (netcam->reconnect_count < 100){ netcam->reconnect_count++; } else if (netcam->reconnect_count == 100){ MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO ,_("%s: Camera did not reconnect."), netcam->cameratype); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO ,_("%s: Checking for camera every 10 seconds."),netcam->cameratype); netcam->reconnect_count++; SLEEP(10,0); } else { SLEEP(10,0); } } else { netcam->reconnect_count = 0; } } static void *netcam_handler(void *arg){ struct ctx_netcam *netcam =(struct ctx_netcam *) arg; netcam->handler_finished = FALSE; mythreadname_set("nc",netcam->threadnbr, netcam->camera_name); pthread_setspecific(tls_key_threadnr, (void *)((unsigned long)netcam->threadnbr)); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO ,_("%s: Camera handler thread [%d] started") ,netcam->cameratype, netcam->threadnbr); while (!netcam->finish) { if (!netcam->format_context) { /* We must have disconnected. Try to reconnect */ clock_gettime(CLOCK_REALTIME, &netcam->frame_prev_tm); netcam_handler_reconnect(netcam); continue; } else { /* We think we are connected...*/ clock_gettime(CLOCK_REALTIME, &netcam->frame_prev_tm); if (netcam_read_image(netcam) < 0) { if (!netcam->finish) { /* Nope. We are not or got bad image. Reconnect*/ netcam_handler_reconnect(netcam); } continue; } netcam_handler_wait(netcam); } } MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Handler loop finished."),netcam->cameratype); netcam_shutdown(netcam); /* Our thread is finished - decrement motion's thread count. */ pthread_mutex_lock(&netcam->motapp->global_lock); netcam->motapp->threads_running--; pthread_mutex_unlock(&netcam->motapp->global_lock); MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("netcam camera handler: finish set, exiting")); netcam->handler_finished = TRUE; pthread_exit(NULL); } static int netcam_start_handler(struct ctx_netcam *netcam){ int retcd; int wait_counter; pthread_attr_t handler_attribute; pthread_mutex_init(&netcam->mutex, NULL); pthread_mutex_init(&netcam->mutex_pktarray, NULL); pthread_mutex_init(&netcam->mutex_transfer, NULL); pthread_attr_init(&handler_attribute); pthread_attr_setdetachstate(&handler_attribute, PTHREAD_CREATE_DETACHED); pthread_mutex_lock(&netcam->motapp->global_lock); netcam->threadnbr = ++netcam->motapp->threads_running; pthread_mutex_unlock(&netcam->motapp->global_lock); retcd = pthread_create(&netcam->thread_id, &handler_attribute, &netcam_handler, netcam); if (retcd < 0) { MOTION_LOG(ALR, TYPE_NETCAM, SHOW_ERRNO ,_("%s: Error starting handler thread"),netcam->cameratype); pthread_attr_destroy(&handler_attribute); return -1; } pthread_attr_destroy(&handler_attribute); /* Now give a few tries to check that an image has been captured. * This ensures that by the time the setup routine exits, the * handler is completely set up and has images available */ wait_counter = 60; while (wait_counter > 0) { pthread_mutex_lock(&netcam->mutex); if (netcam->img_latest->ptr != NULL ) wait_counter = -1; pthread_mutex_unlock(&netcam->mutex); if (wait_counter > 0 ){ MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Waiting for first image from the handler."),netcam->cameratype); SLEEP(0,5000000); wait_counter--; } } /* Warn the user about a mismatch of camera FPS vs handler capture rate*/ if (netcam->conf->framerate < netcam->src_fps){ MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO , "Requested frame rate %d FPS is less than camera frame rate %d FPS" , netcam->conf->framerate,netcam->src_fps); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO , "Increasing capture rate to %d FPS to match camera." , netcam->src_fps); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO , "To lower CPU, change camera FPS to lower rate and decrease I frame interval." , netcam->src_fps); } return 0; } int netcam_setup(struct ctx_cam *cam){ int retcd; int indx_cam, indx_max; struct ctx_netcam *netcam; cam->netcam = NULL; cam->netcam_high = NULL; if (netcam_set_dimensions(cam) < 0 ) return -1; indx_cam = 1; indx_max = 1; if (cam->conf.netcam_highres) indx_max = 2; while (indx_cam <= indx_max){ if (indx_cam == 1){ cam->netcam = netcam_new_context(); if (cam->netcam == NULL) { MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("unable to create rtsp context")); return -1; } netcam = cam->netcam; netcam->high_resolution = FALSE; /* Set flag for this being the normal resolution camera */ } else { cam->netcam_high = netcam_new_context(); if (cam->netcam_high == NULL) { MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("unable to create rtsp high context")); return -1; } netcam = cam->netcam_high; netcam->high_resolution = TRUE; /* Set flag for this being the high resolution camera */ } netcam_null_context(netcam); netcam_set_parms(cam, netcam); if (netcam_connect(netcam) < 0) return -1; retcd = netcam_read_image(netcam); if (retcd < 0){ MOTION_LOG(CRT, TYPE_NETCAM, NO_ERRNO ,_("Failed trying to read first image - retval:%d"), retcd); netcam->status = NETCAM_NOTCONNECTED; return -1; } /* When running dual, there seems to be contamination across norm/high with codec functions. */ netcam_close_context(netcam); /* Close in this thread to open it again within handler thread */ netcam->status = NETCAM_RECONNECTING; /* Set as reconnecting to avoid excess messages when starting */ netcam->first_image = FALSE; /* Set flag that we are not processing our first image */ /* For normal resolution, we resize the image to the config parms so we do not need * to set the dimension parameters here (it is done in the set_parms). For high res * we must get the dimensions from the first image captured */ if (netcam->high_resolution){ cam->imgs.width_high = netcam->imgsize.width; cam->imgs.height_high = netcam->imgsize.height; } if (netcam_start_handler(netcam) < 0 ) return -1; indx_cam++; } return 0; } int netcam_next(struct ctx_cam *cam, struct ctx_image_data *img_data){ /* This is called from the motion loop thread */ if ((cam->netcam->status == NETCAM_RECONNECTING) || (cam->netcam->status == NETCAM_NOTCONNECTED)){ return 1; } pthread_mutex_lock(&cam->netcam->mutex); netcam_pktarray_resize(cam, FALSE); memcpy(img_data->image_norm , cam->netcam->img_latest->ptr , cam->netcam->img_latest->used); img_data->idnbr_norm = cam->netcam->idnbr; pthread_mutex_unlock(&cam->netcam->mutex); if (cam->netcam_high){ if ((cam->netcam_high->status == NETCAM_RECONNECTING) || (cam->netcam_high->status == NETCAM_NOTCONNECTED)) return 1; pthread_mutex_lock(&cam->netcam_high->mutex); netcam_pktarray_resize(cam, TRUE); if (!(cam->netcam_high->high_resolution && cam->netcam_high->passthrough)) { memcpy(img_data->image_high ,cam->netcam_high->img_latest->ptr ,cam->netcam_high->img_latest->used); } img_data->idnbr_high = cam->netcam_high->idnbr; pthread_mutex_unlock(&cam->netcam_high->mutex); } /* Rotate images if requested */ rotate_map(cam, img_data); return 0; } void netcam_cleanup(struct ctx_cam *cam, int init_retry_flag){ /* * If the init_retry_flag is not set this function was * called while retrying the initial connection and there is * no camera-handler started yet and thread_running must * not be decremented. */ int wait_counter; int indx_cam, indx_max; struct ctx_netcam *netcam; indx_cam = 1; indx_max = 1; if (cam->netcam_high) indx_max = 2; while (indx_cam <= indx_max) { if (indx_cam == 1){ netcam = cam->netcam; } else { netcam = cam->netcam_high; } if (netcam){ MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Shutting down network camera."),netcam->cameratype); /* Throw the finish flag in context and wait a bit for it to finish its work and close everything * This is shutting down the thread so for the moment, we are not worrying about the * cross threading and protecting these variables with mutex's */ netcam->finish = TRUE; netcam->interruptduration = 0; wait_counter = 0; while ((!netcam->handler_finished) && (wait_counter < 10)) { SLEEP(1,0); wait_counter++; } if (!netcam->handler_finished) { MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: No response from handler thread."),netcam->cameratype); /* Last resort. Kill the thread. Not safe for posix but if no response, what to do...*/ /* pthread_kill(netcam->thread_id); */ pthread_cancel(netcam->thread_id); pthread_kill(netcam->thread_id, SIGVTALRM); /* This allows the cancel to be processed */ if (!init_retry_flag){ pthread_mutex_lock(&netcam->motapp->global_lock); netcam->motapp->threads_running--; pthread_mutex_unlock(&netcam->motapp->global_lock); } } /* If we never connect we don't have a handler but we still need to clean up some */ netcam_shutdown(netcam); pthread_mutex_destroy(&netcam->mutex); pthread_mutex_destroy(&netcam->mutex_pktarray); pthread_mutex_destroy(&netcam->mutex_transfer); free(netcam); netcam = NULL; if (indx_cam == 1){ MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO ,_("Normal resolution: Shut down complete.")); } else { MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO ,_("High resolution: Shut down complete.")); } } indx_cam++; } cam->netcam = NULL; cam->netcam_high = NULL; }