/* * This file is part of MotionPlus. * * MotionPlus is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * MotionPlus is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with MotionPlus. If not, see . * * Copyright 2020-2023 MotionMrDave@gmail.com * */ /*********************************************************** * * The functions: * netcam_setup * netcam_next * netcam_cleanup * are called from video_common.c which is on the main thread * ***********************************************************/ #include #include #include #include "motionplus.hpp" #include "conf.hpp" #include "logger.hpp" #include "util.hpp" #include "rotate.hpp" #include "netcam.hpp" #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; 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(ctx_url *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(ctx_url *parse_url, const char *text_url) { char *s; int i; const char *re = "(.*)://(((.*):(.*))@)?" "([^/:]|[-_.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, "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(ctx_url)); /* * 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(ctx_url *parse_url) { myfree(&parse_url->service); myfree(&parse_url->userpass); myfree(&parse_url->host); myfree(&parse_url->path); } static void netcam_free_pkt(ctx_netcam *netcam) { mypacket_free(netcam->packet_recv); netcam->packet_recv = NULL; } static int netcam_check_pixfmt(ctx_netcam *netcam) { /* Determine if the format is YUV420P */ int retcd; retcd = -1; if (((enum AVPixelFormat)netcam->frame->format == MY_PIX_FMT_YUV420P) || ((enum AVPixelFormat)netcam->frame->format == MY_PIX_FMT_YUVJ420P)) { retcd = 0; } return retcd; } static void netcam_pktarray_free(ctx_netcam *netcam) { int indx; pthread_mutex_lock(&netcam->mutex_pktarray); if (netcam->pktarray_size > 0) { for(indx = 0; indx < netcam->pktarray_size; indx++) { mypacket_free(netcam->pktarray[indx].packet); netcam->pktarray[indx].packet = NULL; } } myfree(&netcam->pktarray); netcam->pktarray_size = 0; netcam->pktarray_index = -1; pthread_mutex_unlock(&netcam->mutex_pktarray); } static void netcam_null_context(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; netcam->hw_device_ctx = NULL; } static void netcam_close_context(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); if (netcam->hw_device_ctx != NULL) av_buffer_unref(&netcam->hw_device_ctx); netcam_null_context(netcam); } static void netcam_pktarray_resize(ctx_dev *cam, bool 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; ctx_netcam *netcam; ctx_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) * 1 ) + ((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 =(ctx_packet_item*) mymalloc(newsize * sizeof(ctx_packet_item)); if (netcam->pktarray_size > 0 ) { memcpy(tmp, netcam->pktarray, sizeof(ctx_packet_item) * netcam->pktarray_size); } for(indx = netcam->pktarray_size; indx < newsize; indx++) { tmp[indx].packet = NULL; tmp[indx].packet = mypacket_alloc(tmp[indx].packet); tmp[indx].idnbr = 0; tmp[indx].iskey = false; tmp[indx].iswritten = false; } myfree(&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(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_free(netcam->pktarray[indx_next].packet); netcam->pktarray[indx_next].packet = NULL; netcam->pktarray[indx_next].packet = mypacket_alloc(netcam->pktarray[indx_next].packet); 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_free(netcam->pktarray[indx_next].packet); netcam->pktarray[indx_next].packet = NULL; } 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_index = indx_next; pthread_mutex_unlock(&netcam->mutex_pktarray); } static int netcam_decode_sw(ctx_netcam *netcam) { #if (MYFFVER >= 57041) int retcd; char errstr[128]; retcd = avcodec_receive_frame(netcam->codec_context, netcam->frame); if ((netcam->interrupted) || (netcam->finish) || (retcd < 0)) { if (retcd == AVERROR(EAGAIN)) { retcd = 0; } else if (retcd == AVERROR_INVALIDDATA) { MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Ignoring packet with invalid data") ,netcam->cameratype); retcd = 0; } else if (retcd < 0) { av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Rec frame error: %s") ,netcam->cameratype, errstr); retcd = -1; } else { retcd = -1; } return retcd; } return 1; #else (void)netcam; return -1; #endif } static int netcam_decode_vaapi(ctx_netcam *netcam) { #if ( MYFFVER >= 57083) int retcd; char errstr[128]; AVFrame *hw_frame = NULL; hw_frame = myframe_alloc(); retcd = avcodec_receive_frame(netcam->codec_context, hw_frame); if ((netcam->interrupted) || (netcam->finish) || (retcd < 0)) { if (retcd == AVERROR(EAGAIN)) { retcd = 0; } else if (retcd == AVERROR_INVALIDDATA) { MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Ignoring packet with invalid data") ,netcam->cameratype); retcd = 0; } else if (retcd < 0) { av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Rec frame error: %s") ,netcam->cameratype, errstr); retcd = -1; } else { retcd = -1; } myframe_free(hw_frame); return retcd; } netcam->frame->format=AV_PIX_FMT_YUV420P; retcd = av_hwframe_transfer_data(netcam->frame, hw_frame, 0); if (retcd < 0) { MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Error transferring HW decoded to system memory") ,netcam->cameratype); return -1; } myframe_free(hw_frame); return 1; #else (void)netcam; return -1; #endif } /* netcam_decode_video * * Return values: * <0 error * 0 invalid but continue * 1 valid data */ static int netcam_decode_video(ctx_netcam *netcam) { #if (MYFFVER >= 57041) 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)) { MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Interrupted or finish on send") ,netcam->cameratype); return -1; } if (retcd == AVERROR_INVALIDDATA) { MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Send ignoring packet with invalid data") ,netcam->cameratype); return 0; } if (retcd < 0 && retcd != AVERROR_EOF) { av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Error sending packet to codec: %s") ,netcam->cameratype, errstr); return -1; } if (netcam->hw_type == AV_HWDEVICE_TYPE_VAAPI) { retcd = netcam_decode_vaapi(netcam); } else { retcd = netcam_decode_sw(netcam); } return retcd; #else int retcd; int check = 0; char errstr[128]; (void)netcam_decode_sw; (void)netcam_decode_vaapi; 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(ctx_netcam *netcam) { int frame_size; int retcd; if (netcam->finish) { return -1; /* This just speeds up the shutdown time */ } if (netcam->packet_recv->stream_index == netcam->audio_stream_index) { MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: Error decoding video packet...it is audio") ,netcam->cameratype); } retcd = netcam_decode_video(netcam); if (retcd <= 0) { return retcd; } frame_size = myimage_get_buffer_size((enum AVPixelFormat) netcam->frame->format ,netcam->frame->width ,netcam->frame->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 ,(enum AVPixelFormat) netcam->frame->format ,netcam->frame->width ,netcam->frame->height ,frame_size); if ((retcd < 0) || (netcam->interrupted)) { MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: Error decoding video packet: Copying to buffer") ,netcam->cameratype); return -1; } netcam->img_recv->used = frame_size; return frame_size; } static void netcam_hwdecoders(ctx_netcam *netcam) { #if ( MYFFVER >= 57083) /* High Res pass through does not decode images into frames*/ if (netcam->high_resolution && netcam->passthrough) { return; } if ((netcam->hw_type == AV_HWDEVICE_TYPE_NONE) && (netcam->first_image)) { MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: HW Devices: ") , netcam->cameratype); while((netcam->hw_type = av_hwdevice_iterate_types(netcam->hw_type)) != AV_HWDEVICE_TYPE_NONE){ if (netcam->hw_type == AV_HWDEVICE_TYPE_VAAPI) { MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: %s (available)") , netcam->cameratype , av_hwdevice_get_type_name(netcam->hw_type)); } else { MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: %s (not implemented)") , netcam->cameratype , av_hwdevice_get_type_name(netcam->hw_type)); } } } return; #else (void)netcam; return; #endif } static enum AVPixelFormat netcam_getfmt_vaapi(AVCodecContext *avctx, const enum AVPixelFormat *pix_fmts) { #if ( MYFFVER >= 57083) const enum AVPixelFormat *p; (void)avctx; for (p = pix_fmts; *p != -1; p++) { if (*p == AV_PIX_FMT_VAAPI) { return *p; } } MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO,_("Failed to get vaapi pix format")); return AV_PIX_FMT_NONE; #else (void)avctx; (void)pix_fmts; return AV_PIX_FMT_NONE; #endif } static void netcam_decoder_error(ctx_netcam *netcam, int retcd, const char* fnc_nm) { char errstr[128]; int indx; if (netcam->interrupted) { MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: Interrupted"),netcam->cameratype); } else { if (retcd < 0) { av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: %s: %s"),netcam->cameratype,fnc_nm, errstr); } else { MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: %s: Failed"), netcam->cameratype,fnc_nm); } } if (mystrne(netcam->decoder_nm,"NULL")) { MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO ,_("%s: Decoder %s did not work.") ,netcam->cameratype, netcam->decoder_nm); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO ,_("%s: Ignoring and removing the user requested decoder %s") ,netcam->cameratype, netcam->decoder_nm); for (indx = 0; indx < netcam->params->params_count; indx++) { if (mystreq(netcam->params->params_array[indx].param_name,"decoder") ) { myfree(&netcam->params->params_array[indx].param_value); netcam->params->params_array[indx].param_value = (char*)mymalloc(5); snprintf(netcam->params->params_array[indx].param_value, 5, "%s","NULL"); break; } } if (netcam->high_resolution) { util_parms_update(netcam->params, netcam->conf->netcam_high_params); } else { util_parms_update(netcam->params, netcam->conf->netcam_params); } myfree(&netcam->decoder_nm); netcam->decoder_nm = (char*)mymalloc(5); snprintf(netcam->decoder_nm, 5, "%s","NULL"); } } static int netcam_init_vaapi(ctx_netcam *netcam) { #if ( MYFFVER >= 57083) int retcd; MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Initializing vaapi decoder"),netcam->cameratype); netcam->hw_type = av_hwdevice_find_type_by_name("vaapi"); if (netcam->hw_type == AV_HWDEVICE_TYPE_NONE) { MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO,_("%s: Unable to find vaapi hw device") , netcam->cameratype); netcam_decoder_error(netcam, 0, "av_hwdevice"); return -1; } netcam->codec_context = avcodec_alloc_context3(netcam->decoder); if ((netcam->codec_context == NULL) || (netcam->interrupted)) { netcam_decoder_error(netcam, 0, "avcodec_alloc_context3"); return -1; } retcd = avcodec_parameters_to_context(netcam->codec_context,netcam->strm->codecpar); if ((retcd < 0) || (netcam->interrupted)) { netcam_decoder_error(netcam, retcd, "avcodec_parameters_to_context"); return -1; } netcam->hw_pix_fmt = AV_PIX_FMT_VAAPI; netcam->codec_context->get_format = netcam_getfmt_vaapi; av_opt_set_int(netcam->codec_context, "refcounted_frames", 1, 0); netcam->codec_context->sw_pix_fmt = AV_PIX_FMT_YUV420P; netcam->codec_context->hwaccel_flags= AV_HWACCEL_FLAG_ALLOW_PROFILE_MISMATCH | AV_HWACCEL_FLAG_IGNORE_LEVEL; retcd = av_hwdevice_ctx_create(&netcam->hw_device_ctx, netcam->hw_type, NULL, NULL, 0); if (retcd < 0) { netcam_decoder_error(netcam, retcd, "hwctx"); return -1; } netcam->codec_context->hw_device_ctx = av_buffer_ref(netcam->hw_device_ctx); return 0; #else (void)netcam; (void)netcam_getfmt_vaapi; return -1; #endif } static int netcam_init_swdecoder(ctx_netcam *netcam) { #if ( MYFFVER >= 57041) int retcd; MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Initializing decoder"),netcam->cameratype); if (mystrne(netcam->decoder_nm,"NULL")) { netcam->decoder = avcodec_find_decoder_by_name(netcam->decoder_nm); if (netcam->decoder == NULL) { netcam_decoder_error(netcam, 0, "avcodec_find_decoder_by_name"); } else { MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO,_("%s: Using decoder %s") ,netcam->cameratype, netcam->decoder_nm); } } if (netcam->decoder == NULL) { netcam->decoder = avcodec_find_decoder(netcam->strm->codecpar->codec_id); } if ((netcam->decoder == NULL) || (netcam->interrupted)) { netcam_decoder_error(netcam, 0, "avcodec_find_decoder"); return -1; } netcam->codec_context = avcodec_alloc_context3(netcam->decoder); if ((netcam->codec_context == NULL) || (netcam->interrupted)) { netcam_decoder_error(netcam, 0, "avcodec_alloc_context3"); return -1; } retcd = avcodec_parameters_to_context(netcam->codec_context, netcam->strm->codecpar); if ((retcd < 0) || (netcam->interrupted)) { netcam_decoder_error(netcam, retcd, "avcodec_parameters_to_context"); return -1; } netcam->codec_context->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK; netcam->codec_context->err_recognition = AV_EF_EXPLODE; return 0; #else int retcd; netcam->codec_context = netcam->strm->codec; netcam->decoder = avcodec_find_decoder(netcam->codec_context->codec_id); if ((netcam->decoder == NULL) || (netcam->interrupted)) { netcam_decoder_error(netcam, 0, "avcodec_find_decoder"); return -1; } retcd = avcodec_open2(netcam->codec_context, netcam->decoder, NULL); if ((retcd < 0) || (netcam->interrupted)) { netcam_decoder_error(netcam, retcd, "avcodec_open2"); return -1; } return 0; #endif } static int netcam_open_codec(ctx_netcam *netcam) { #if ( MYFFVER >= 57041) int retcd; if (netcam->finish) { return -1; /* This just speeds up the shutdown time */ } netcam_hwdecoders(netcam); retcd = av_find_best_stream(netcam->format_context , AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0); if ((retcd < 0) || (netcam->interrupted)) { netcam->audio_stream_index = -1; } else { netcam->audio_stream_index = retcd; } netcam->decoder = NULL; retcd = av_find_best_stream(netcam->format_context , AVMEDIA_TYPE_VIDEO, -1, -1, &netcam->decoder, 0); if ((retcd < 0) || (netcam->interrupted)) { netcam_decoder_error(netcam, retcd, "av_find_best_stream"); return -1; } netcam->video_stream_index = retcd; netcam->strm = netcam->format_context->streams[netcam->video_stream_index]; if (mystrceq(netcam->decoder_nm,"vaapi")) { if (netcam_init_vaapi(netcam) < 0) { netcam_decoder_error(netcam, retcd, "hwvaapi_init"); return -1; } } else { netcam_init_swdecoder(netcam); } retcd = avcodec_open2(netcam->codec_context, netcam->decoder, NULL); if ((retcd < 0) || (netcam->interrupted)) { netcam_decoder_error(netcam, retcd, "avcodec_open2"); return -1; } MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Decoder opened"),netcam->cameratype); return 0; #else int retcd; (void)netcam_init_vaapi; if (netcam->finish) { /* This just speeds up the shutdown time */ return -1; } netcam_hwdecoders(netcam); netcam->decoder = NULL; retcd = av_find_best_stream(netcam->format_context, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); if ((retcd < 0) || (netcam->interrupted)) { netcam_decoder_error(netcam, retcd, "av_find_best_stream"); return -1; } netcam->video_stream_index = retcd; netcam->strm = netcam->format_context->streams[netcam->video_stream_index]; retcd = netcam_init_swdecoder(netcam); return retcd; #endif } static ctx_netcam *netcam_new_context(void) { ctx_netcam *ret; /* Note that mymalloc will exit on any problem. */ ret =(ctx_netcam*) mymalloc(sizeof(ctx_netcam)); memset(ret, 0, sizeof(ctx_netcam)); return ret; } static int netcam_interrupt(void *ctx) { /* Must return as an int since this is a callback to a C function */ ctx_netcam *netcam = (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_MONOTONIC, &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_MONOTONIC, &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_open_sws(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 , _("%s: Unable to allocate swsframe_in.") , netcam->cameratype); } 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 , _("%s: Unable to allocate swsframe_out.") , netcam->cameratype); } 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. */ if (netcam_check_pixfmt(netcam) != 0) { MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO , _("%s: Pixel format %d will be converted.") , netcam->cameratype, netcam->frame->format); } netcam->swsctx = sws_getContext( netcam->frame->width ,netcam->frame->height ,(enum AVPixelFormat)netcam->frame->format ,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 , _("%s: Unable to allocate scaling context.") , netcam->cameratype); } 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 , _("%s: Error determining size of frame out") , netcam->cameratype); } 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 int netcam_resize(ctx_netcam *netcam) { int retcd; char errstr[128]; uint8_t *buffer_out; if (netcam->finish) { return -1; /* This just speeds up the shutdown time */ } if (netcam->swsctx == NULL) { if (netcam_open_sws(netcam) < 0) { return -1; } } retcd=myimage_fill_arrays( netcam->swsframe_in ,(uint8_t*)netcam->img_recv->ptr ,(enum AVPixelFormat)netcam->frame->format ,netcam->frame->width ,netcam->frame->height); if (retcd < 0) { if (netcam->status == NETCAM_NOTCONNECTED) { av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: Error allocating picture in: %s") , netcam->cameratype, 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 ,_("%s: Error allocating picture out: %s") , netcam->cameratype, 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->frame->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 ,_("%s: Error resizing/reformatting: %s") , netcam->cameratype, 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 ,_("%s: Error putting frame into output buffer: %s") , netcam->cameratype, 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(ctx_netcam *netcam) { int size_decoded, retcd, errcnt, nodata; bool haveimage; char errstr[128]; netcam_buff *xchg; if (netcam->finish) { return -1; /* This just speeds up the shutdown time */ } netcam->packet_recv = mypacket_alloc(netcam->packet_recv); netcam->interrupted=false; clock_gettime(CLOCK_MONOTONIC, &netcam->interruptstarttime); netcam->interruptduration = 10; netcam->status = NETCAM_READINGIMAGE; netcam->img_recv->used = 0; size_decoded = 0; errcnt = 0; haveimage = false; nodata = 0; while ((!haveimage) && (!netcam->interrupted)) { retcd = av_read_frame(netcam->format_context, netcam->packet_recv); if (retcd < 0 ) { errcnt++; } if ((netcam->interrupted) || (errcnt > 1)) { if (netcam->interrupted) { MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Interrupted") ,netcam->cameratype); } else { av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: av_read_frame: %s" ) ,netcam->cameratype,errstr); } netcam_free_pkt(netcam); netcam_close_context(netcam); return -1; } else { errcnt = 0; if ((netcam->packet_recv->stream_index == netcam->video_stream_index) || (netcam->packet_recv->stream_index == netcam->audio_stream_index)) { /* For a high resolution pass-through we don't decode the image */ if ((netcam->high_resolution && netcam->passthrough) || (netcam->packet_recv->stream_index != netcam->video_stream_index)) { 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 */ netcam_free_pkt(netcam); netcam->packet_recv = mypacket_alloc(netcam->packet_recv); /* The 1000 is arbitrary */ nodata++; if (nodata > 1000) { netcam_close_context(netcam); return -1; } } else { netcam_free_pkt(netcam); netcam_close_context(netcam); return -1; } } } clock_gettime(CLOCK_MONOTONIC, &netcam->img_recv->image_time); netcam->last_stream_index = netcam->packet_recv->stream_index; netcam->last_pts = netcam->packet_recv->pts; if (!netcam->first_image) { netcam->status = NETCAM_CONNECTED; } /* Skip resize/pix format for high pass-through */ if (!(netcam->high_resolution && netcam->passthrough) && (netcam->packet_recv->stream_index == netcam->video_stream_index)) { if ((netcam->imgsize.width != netcam->frame->width) || (netcam->imgsize.height != netcam->frame->height) || (netcam_check_pixfmt(netcam) != 0)) { if (netcam_resize(netcam) < 0) { netcam_free_pkt(netcam); 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) && (netcam->packet_recv->stream_index == netcam->video_stream_index)) { xchg = netcam->img_latest; netcam->img_latest = netcam->img_recv; netcam->img_recv = xchg; } pthread_mutex_unlock(&netcam->mutex); netcam_free_pkt(netcam); 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); if (netcam->capture_rate < 1) { netcam->capture_rate = netcam->src_fps + 1; if (netcam->pts_adj == false) { MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: capture_rate not specified in netcam_params. Using %d") ,netcam->cameratype,netcam->capture_rate); } } } else { if (netcam->capture_rate < 1) { netcam->capture_rate = netcam->conf->framerate; if (netcam->pts_adj == false) { MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: capture_rate not specified in netcam_params. Using framerate %d") ,netcam->cameratype, netcam->capture_rate); } } } return 0; } static int netcam_ntc(ctx_netcam *netcam) { if ((netcam->finish) || (!netcam->first_image)) { return 0; } /* High Res pass through does not decode images into frames*/ if (netcam->high_resolution && netcam->passthrough) { return 0; } if ((netcam->imgsize.width != netcam->frame->width) || (netcam->imgsize.height != netcam->frame->height)) { MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, ""); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, "******************************************************"); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("The network camera is sending pictures at %dx%d") , netcam->frame->width,netcam->frame->height); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("resolution but config is %dx%d. If possible change") , netcam->imgsize.width,netcam->imgsize.height); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("the netcam or config so that the image height and")); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("width are the same to lower the CPU usage.")); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, "******************************************************"); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, ""); } return 0; } static void netcam_set_options(ctx_netcam *netcam) { int indx; char *tmpval; /* The log messages are a bit short in this function intentionally. * The function name is printed in each message so that is being * considered as part of the message. */ tmpval = (char*)mymalloc(PATH_MAX); if (mystreq(netcam->service, "rtsp") || mystreq(netcam->service, "rtsps") || mystreq(netcam->service, "rtmp")) { MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO,_("%s: Setting rtsp/rtmp") ,netcam->cameratype); util_parms_add_default(netcam->params,"rtsp_transport","tcp"); //util_parms_add_default(netcam->params,"allowed_media_types", "video"); } else if (mystreq(netcam->service, "http") || mystreq(netcam->service, "https")) { MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Setting input_format mjpeg"),netcam->cameratype); netcam->format_context->iformat = av_find_input_format("mjpeg"); util_parms_add_default(netcam->params,"reconnect_on_network_error","1"); util_parms_add_default(netcam->params,"reconnect_at_eof","1"); util_parms_add_default(netcam->params,"reconnect","1"); util_parms_add_default(netcam->params,"multiple_requests","1"); util_parms_add_default(netcam->params,"reconnect_streamed","1"); } else if (mystreq(netcam->service, "v4l2")) { MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Setting input_format video4linux2"),netcam->cameratype); netcam->format_context->iformat = av_find_input_format("video4linux2"); sprintf(tmpval,"%d",netcam->conf->framerate); util_parms_add_default(netcam->params,"framerate", tmpval); sprintf(tmpval,"%dx%d",netcam->conf->width, netcam->conf->height); util_parms_add_default(netcam->params,"video_size", tmpval); /* Allow a bit more time for the v4l2 device to start up */ //netcam->motapp-> ->watchdog = 60; netcam->interruptduration = 55; } else if (mystreq(netcam->service, "file")) { MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Setting up movie file"),netcam->cameratype); } else { MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO,_("%s: Setting up %s") ,netcam->cameratype, netcam->service); } free(tmpval); /* Write the options to the context, while skipping the Motion ones */ for (indx = 0; indx < netcam->params->params_count; indx++) { if (mystrne(netcam->params->params_array[indx].param_name,"decoder") && mystrne(netcam->params->params_array[indx].param_name,"capture_rate")) { av_dict_set(&netcam->opts , netcam->params->params_array[indx].param_name , netcam->params->params_array[indx].param_value , 0); if (netcam->status == NETCAM_NOTCONNECTED) { MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO,_("%s: option: %s = %s") ,netcam->cameratype ,netcam->params->params_array[indx].param_name ,netcam->params->params_array[indx].param_value ); } } } } static void netcam_set_path (ctx_dev *cam, ctx_netcam *netcam ) { char userpass[PATH_MAX]; ctx_url url; int retcd; netcam->path = NULL; memset(&url, 0, sizeof(url)); memset(userpass,0,PATH_MAX); if (netcam->high_resolution) { netcam_url_parse(&url, cam->conf->netcam_high_url.c_str()); } else { netcam_url_parse(&url, cam->conf->netcam_url.c_str()); } if (cam->conf->netcam_userpass != "") { cam->conf->netcam_userpass.copy(userpass, PATH_MAX); } else if (url.userpass != NULL) { retcd = snprintf(userpass,PATH_MAX,"%s",url.userpass); if ((retcd <0) || (retcd>=PATH_MAX)) { MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO,_("Error getting 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")); } 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")); } else { MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("Setting up %s "),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); } static void netcam_set_parms (ctx_dev *cam, ctx_netcam *netcam ) { /* Set the parameters to be used with our camera */ int indx, val_len; netcam->motapp = cam->motapp; netcam->conf = cam->conf; pthread_mutex_lock(&netcam->motapp->global_lock); netcam->threadnbr = ++netcam->motapp->threads_running; pthread_mutex_unlock(&netcam->motapp->global_lock); if (netcam->high_resolution) { netcam->imgsize.width = 0; netcam->imgsize.height = 0; snprintf(netcam->cameratype, 29, "%s",_("High")); netcam->params = (ctx_params*)mymalloc(sizeof(ctx_params)); netcam->params->update_params = true; util_parms_parse(netcam->params, cam->conf->netcam_high_params); } else { netcam->imgsize.width = cam->conf->width; netcam->imgsize.height = cam->conf->height; snprintf(netcam->cameratype, 29, "%s",_("Norm")); netcam->params = (ctx_params*)mymalloc(sizeof(ctx_params)); netcam->params->update_params = true; util_parms_parse(netcam->params, cam->conf->netcam_params); } MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: Setting up camera."),netcam->cameratype); netcam->status = NETCAM_NOTCONNECTED; cam->conf->device_name.copy(netcam->camera_name,PATH_MAX); mycheck_passthrough(cam); util_parms_add_default(netcam->params,"decoder","NULL"); 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->packet_recv = NULL; netcam->handler_finished = true; netcam->first_image = true; netcam->reconnect_count = 0; netcam->src_fps = -1; /* Default to neg so we know it has not been set */ netcam->pts_adj = false; netcam->capture_rate = -1; netcam->video_stream_index = -1; netcam->audio_stream_index = -1; netcam->last_stream_index = -1; for (indx = 0; indx < netcam->params->params_count; indx++) { if (mystreq(netcam->params->params_array[indx].param_name,"decoder")) { val_len = strlen(netcam->params->params_array[indx].param_value) + 1; netcam->decoder_nm = (char*)mymalloc(val_len); snprintf(netcam->decoder_nm, val_len , "%s",netcam->params->params_array[indx].param_value); } if (mystreq(netcam->params->params_array[indx].param_name,"capture_rate")) { if (mystreq(netcam->params->params_array[indx].param_value,"pts")) { netcam->pts_adj = true; } else { netcam->capture_rate = atoi(netcam->params->params_array[indx].param_value); } } } /* If this is the norm and we have a highres, then disable passthru on the norm */ if ((!netcam->high_resolution) && (cam->conf->netcam_high_url != "")) { netcam->passthrough = false; } else { netcam->passthrough = mycheck_passthrough(cam); } snprintf(netcam->threadname, 15, "%s",_("Unknown")); clock_gettime(CLOCK_MONOTONIC, &netcam->interruptstarttime); clock_gettime(CLOCK_MONOTONIC, &netcam->interruptcurrenttime); netcam->interruptduration = 5; netcam->interrupted = false; clock_gettime(CLOCK_MONOTONIC, &netcam->frame_curr_tm); clock_gettime(CLOCK_MONOTONIC, &netcam->frame_prev_tm); netcam_set_path(cam, netcam); } static void netcam_set_dimensions (ctx_dev *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 ,_("%s: 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; } static int netcam_copy_stream(ctx_netcam *netcam) { /* Make a static copy of the stream information for use in passthrough processing */ #if (MYFFVER >= 57041) AVStream *transfer_stream, *stream_in; int retcd, indx; pthread_mutex_lock(&netcam->mutex_transfer); if (netcam->transfer_format != NULL) { avformat_close_input(&netcam->transfer_format); } netcam->transfer_format = avformat_alloc_context(); for (indx = 0; indx < (int)netcam->format_context->nb_streams; indx++) { if ((netcam->audio_stream_index == indx) || (netcam->video_stream_index == indx)) { transfer_stream = avformat_new_stream(netcam->transfer_format, NULL); stream_in = netcam->format_context->streams[indx]; retcd = avcodec_parameters_copy(transfer_stream->codecpar, stream_in->codecpar); if (retcd < 0) { MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: Unable to copy codec parameters") , netcam->cameratype); pthread_mutex_unlock(&netcam->mutex_transfer); return -1; } transfer_stream->time_base = stream_in->time_base; transfer_stream->avg_frame_rate = stream_in->avg_frame_rate; } } 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(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_MONOTONIC, &netcam->interruptstarttime); netcam->interruptduration = 20; netcam_set_options(netcam); 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); 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); } else { av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO ,_("%s: Connected and 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; } 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; } netcam->connection_pts = AV_NOPTS_VALUE; return 0; } static int netcam_connect(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); MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO , _("%s: Netcam capture FPS is %d.") , netcam->cameratype, netcam->capture_rate); if (netcam->src_fps > 0) { MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO , _("%s: Camera source is %d FPS") , netcam->cameratype, netcam->src_fps); } else { MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO , _("%s: Unable to determine the camera source FPS.") , netcam->cameratype); } if (netcam->capture_rate < netcam->src_fps) { MOTION_LOG(WRN, TYPE_NETCAM, NO_ERRNO , _("%s: Capture FPS less than camera FPS. Decoding errors will occur.") , netcam->cameratype); MOTION_LOG(WRN, TYPE_NETCAM, NO_ERRNO , _("%s: Capture FPS should be greater than camera FPS.") , netcam->cameratype); } if (netcam->audio_stream_index != -1) { /* The following is not technically precise but we want to convey in simple terms * that the capture rate must go faster to account for the additional packets read * for the audio stream. The technically correct process is that our wait timer in * the handler is only triggered when the last packet is a video stream */ MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: An audio stream was detected. Capture_rate increased to compensate.") ,netcam->cameratype); } } return 0; } static void netcam_shutdown(ctx_netcam *netcam) { if (netcam) { netcam_close_context(netcam); netcam->status = NETCAM_NOTCONNECTED; myfree(&netcam->path); if (netcam->img_latest != NULL) { free(netcam->img_latest->ptr); free(netcam->img_latest); netcam->img_latest = NULL; } if (netcam->img_recv != NULL) { free(netcam->img_recv->ptr); free(netcam->img_recv); netcam->img_recv = NULL; } myfree(&netcam->decoder_nm); util_parms_free(netcam->params); myfree(&netcam->params); } } static void netcam_handler_wait(ctx_netcam *netcam) { int64_t usec_ltncy; AVRational tbase; struct timespec tmp_tm; clock_gettime(CLOCK_MONOTONIC, &tmp_tm); if (netcam->capture_rate < 1) { netcam->capture_rate = 1; } tbase = netcam->format_context->streams[netcam->video_stream_index]->time_base; if (tbase.num == 0) { tbase.num = 1; } /* FPS calculation from last frame capture */ netcam->frame_curr_tm = tmp_tm; usec_ltncy = (1000000 / netcam->capture_rate) - ((netcam->frame_curr_tm.tv_sec - netcam->frame_prev_tm.tv_sec) * 1000000) - ((netcam->frame_curr_tm.tv_nsec - netcam->frame_prev_tm.tv_nsec)/1000); /* Adjust to clock and pts timer */ if (netcam->pts_adj == true) { if (netcam->connection_pts == AV_NOPTS_VALUE) { netcam->connection_pts = netcam->last_pts; clock_gettime(CLOCK_MONOTONIC, &netcam->connection_tm); return; } if (netcam->last_pts != AV_NOPTS_VALUE) { usec_ltncy += + (av_rescale(netcam->last_pts, 1000000, tbase.den/tbase.num) - av_rescale(netcam->connection_pts, 1000000, tbase.den/tbase.num)) -(((tmp_tm.tv_sec - netcam->connection_tm.tv_sec) * 1000000) + ((tmp_tm.tv_nsec - netcam->connection_tm.tv_nsec) / 1000)); } } if ((usec_ltncy > 0) && (usec_ltncy < 1000000L)) { SLEEP(0, usec_ltncy * 1000); } } static void netcam_handler_reconnect(ctx_netcam *netcam) { int retcd, indx; 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; /* When doing passthrough movies, any movies in progress * must end and start again because the cache'd packets and timing * on them gets all messed up. So we loop through the list of cameras * and find the pointer that matches our passed in netcam. */ if (netcam->passthrough == true) { for (indx=0; indxmotapp->cam_cnt; indx++) { if ((netcam->motapp->cam_list[indx]->netcam == netcam) || (netcam->motapp->cam_list[indx]->netcam_high == netcam)) { netcam->motapp->cam_list[indx]->event_stop = true; } } } /* * 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) && (netcam->reconnect_count <= 199)) { 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 if (netcam->reconnect_count >= 200) { 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 minutes."),netcam->cameratype); SLEEP(600,0); } else { netcam->reconnect_count++; SLEEP(600,0); } } else { netcam->reconnect_count = 0; } } static void *netcam_handler(void *arg) { ctx_netcam *netcam =(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_MONOTONIC, &netcam->frame_prev_tm); netcam_handler_reconnect(netcam); continue; } else { /* We think we are connected...*/ clock_gettime(CLOCK_MONOTONIC, &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; } if (netcam->last_stream_index == netcam->video_stream_index) { netcam_handler_wait(netcam); } } } MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO ,_("%s: 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 ,_("%s: Exiting"),netcam->cameratype); netcam->handler_finished = true; pthread_exit(NULL); } static int netcam_start_handler(ctx_netcam *netcam) { int retcd, 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); 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--; } } return 0; } void netcam_cleanup(ctx_dev *cam) { int wait_counter; int indx_cam, indx_max; ctx_netcam *netcam; indx_cam = 1; if (cam->netcam_high) { indx_max = 2; } else { indx_max = 1; } 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); 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.*/ pthread_cancel(netcam->thread_id); pthread_kill(netcam->thread_id, SIGVTALRM); pthread_mutex_lock(&netcam->motapp->global_lock); netcam->motapp->threads_running--; pthread_mutex_unlock(&netcam->motapp->global_lock); } netcam_shutdown(netcam); pthread_mutex_destroy(&netcam->mutex); pthread_mutex_destroy(&netcam->mutex_pktarray); pthread_mutex_destroy(&netcam->mutex_transfer); myfree(&netcam); if (indx_cam == 1) { MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO ,_("Norm: Shut down complete.")); } else { MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO ,_("High: Shut down complete.")); } } indx_cam++; } cam->netcam = NULL; cam->netcam_high = NULL; cam->device_status = STATUS_CLOSED; } void netcam_start(ctx_dev *cam) { int indx_cam, indx_max; ctx_netcam *netcam; MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO,_("Opening Netcam")); cam->netcam = NULL; cam->netcam_high = NULL; netcam_set_dimensions(cam); indx_cam = 1; if (cam->conf->netcam_high_url != "") { indx_max = 2; } else { indx_max = 1; } 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")); netcam_cleanup(cam); return; } 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")); netcam_cleanup(cam); return; } 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) { netcam_cleanup(cam); return; } if (netcam_read_image(netcam) != 0) { MOTION_LOG(CRT, TYPE_NETCAM, NO_ERRNO ,_("Failed trying to read first image")); netcam->status = NETCAM_NOTCONNECTED; netcam_cleanup(cam); return; } /* 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 ) { netcam_cleanup(cam); return; } indx_cam++; } cam->device_status = STATUS_OPENED; return; } /* netcam_next (Called from the motion loop thread) */ int netcam_next(ctx_dev *cam, ctx_image_data *img_data) { if ((cam == NULL) || (cam->netcam == NULL)) { return CAPTURE_FAILURE; } if ((cam->netcam->status == NETCAM_RECONNECTING) || (cam->netcam->status == NETCAM_NOTCONNECTED)) { return CAPTURE_ATTEMPTED; } 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 != NULL) { if ((cam->netcam_high->status == NETCAM_RECONNECTING) || (cam->netcam_high->status == NETCAM_NOTCONNECTED)) { return CAPTURE_ATTEMPTED; } pthread_mutex_lock(&cam->netcam_high->mutex); netcam_pktarray_resize(cam, true); if (!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_map(cam, img_data); return CAPTURE_SUCCESS; }