From 28f1abf75bce4624030c677caf04155f827779e9 Mon Sep 17 00:00:00 2001 From: MrDave Date: Mon, 26 Dec 2016 11:32:03 -0700 Subject: [PATCH] Regression without ffmpeg fix This commit resolves a regression error when selecting the without-ffmpeg option. 1. The ffmpeg configuration options are now valid whether or not the user has included ffmpeg. Instead the user is notified when ffmpeg options are requested. 2. The initial consolidation of the HAVE_FFMPEG duplicated the public functions in the #else (no ffmpeg) section. This commit changes this so that the #IF are inside of the public functions so we don't have to worry about keeping the parameters the same between them. 3. The timevalue was added to the ffmpeg header for the situation when ffmpeg is not included. --- conf.c | 2 - ffmpeg.c | 848 ++++++++++++++++++++++++++++--------------------------- ffmpeg.h | 1 + 3 files changed, 437 insertions(+), 414 deletions(-) diff --git a/conf.c b/conf.c index ed97d0284..3d3ada0ff 100644 --- a/conf.c +++ b/conf.c @@ -1036,7 +1036,6 @@ config_param config_params[] = { copy_string, print_string }, -#ifdef HAVE_FFMPEG { "movie_filename", "# File path for motion triggered ffmpeg films (movies) relative to target_dir\n" @@ -1062,7 +1061,6 @@ config_param config_params[] = { copy_string, print_string }, -#endif /* HAVE_FFMPEG */ { "ipv6_enabled", "\n############################################################\n" diff --git a/ffmpeg.c b/ffmpeg.c index 948ecc87b..1f32e1ebc 100644 --- a/ffmpeg.c +++ b/ffmpeg.c @@ -167,6 +167,7 @@ static int timelapse_exists(const char *fname){ } return 0; } + static int timelapse_append(struct ffmpeg *ffmpeg, AVPacket pkt){ FILE *file; @@ -217,39 +218,6 @@ static int ffmpeg_lockmgr_cb(void **arg, enum AVLockOp op) return 1; } -/** - * ffmpeg_init - * Initializes for libavformat. - * - * Returns - * Function returns nothing. - */ -void ffmpeg_init(void){ - int ret; - - MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO, - "%s: ffmpeg libavcodec version %d.%d.%d" - " libavformat version %d.%d.%d" - , LIBAVCODEC_VERSION_MAJOR, LIBAVCODEC_VERSION_MINOR, LIBAVCODEC_VERSION_MICRO - , LIBAVFORMAT_VERSION_MAJOR, LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO); - - av_register_all(); - avcodec_register_all(); - avformat_network_init(); - av_log_set_callback((void *)ffmpeg_avcodec_log); - - ret = av_lockmgr_register(ffmpeg_lockmgr_cb); - if (ret < 0) - { - MOTION_LOG(EMG, TYPE_ALL, SHOW_ERRNO, "%s: av_lockmgr_register failed (%d)", ret); - exit(1); - } -} - -void ffmpeg_finalise(void) { - avformat_network_deinit(); -} - /** * get_oformat * Obtains the output format used for the specified codec. For mpeg4 codecs, @@ -321,109 +289,399 @@ static AVOutputFormat *get_oformat(const char *codec, char *filename){ return of; } + /** - * ffmpeg_open - * Opens an mpeg file using the new libavformat method. Both mpeg1 - * and mpeg4 are supported. However, if the current ffmpeg version doesn't allow - * mpeg1 with non-standard framerate, the open will fail. Timelapse is a special - * case and is tested separately. + * ffmpeg_cleanups + * Clean up ffmpeg struct if something was wrong. + * + * Returns + * Function returns nothing. + */ +void ffmpeg_cleanups(struct ffmpeg *ffmpeg){ + + /* Close each codec */ + if (ffmpeg->video_st) { + avcodec_close(AVSTREAM_CODEC_PTR(ffmpeg->video_st)); + } + free(ffmpeg->video_outbuf); + av_freep(&ffmpeg->picture); + avformat_free_context(ffmpeg->oc); + free(ffmpeg); +} + +/** + * ffmpeg_put_frame + * Encodes and writes a video frame using the av_write_frame API. This is + * a helper function for ffmpeg_put_image and ffmpeg_put_other_image. * * Returns - * A new allocated ffmpeg struct or NULL if any error happens. + * Number of bytes written or -1 if any error happens. */ -struct ffmpeg *ffmpeg_open(const char *ffmpeg_video_codec, char *filename, - unsigned char *y, unsigned char *u, unsigned char *v, - int width, int height, int rate, int bps, int vbr, int tlapse, - const struct timeval *tv1) -{ - AVCodecContext *c; - AVCodec *codec; - struct ffmpeg *ffmpeg; +int ffmpeg_put_frame(struct ffmpeg *ffmpeg, AVFrame *pic, const struct timeval *tv1){ +/** + * Since the logic,return values and conditions changed so + * dramatically between versions, the encoding of the frame + * is 100% blocked based upon Libav/FFMpeg version + */ +#if (LIBAVFORMAT_VERSION_MAJOR >= 55) || ((LIBAVFORMAT_VERSION_MAJOR == 54) && (LIBAVFORMAT_VERSION_MINOR > 6)) int retcd; + int got_packet_ptr; + AVPacket pkt; char errstr[128]; - AVDictionary *opts = 0; - - /* - * Allocate space for our ffmpeg structure. This structure contains all the - * codec and image information we need to generate movies. - */ - ffmpeg = mymalloc(sizeof(struct ffmpeg)); + int64_t pts_interval; - ffmpeg->tlapse = tlapse; - /* Store codec name in ffmpeg->codec, with buffer overflow check. */ - snprintf(ffmpeg->codec, sizeof(ffmpeg->codec), "%s", ffmpeg_video_codec); + av_init_packet(&pkt); /* Init static structure. */ + if (ffmpeg->oc->oformat->flags & AVFMT_RAWPICTURE) { + pkt.stream_index = ffmpeg->video_st->index; + pkt.flags |= AV_PKT_FLAG_KEY; + pkt.data = (uint8_t *)pic; + pkt.size = sizeof(AVPicture); + } else { + pkt.data = NULL; + pkt.size = 0; + retcd = avcodec_encode_video2(AVSTREAM_CODEC_PTR(ffmpeg->video_st), + &pkt, pic, &got_packet_ptr); + if (retcd < 0 ){ + av_strerror(retcd, errstr, sizeof(errstr)); + MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: Error encoding video:%s",errstr); + //Packet is freed upon failure of encoding + return -1; + } + if (got_packet_ptr == 0){ + //Buffered packet. Throw special return code + my_packet_unref(pkt); + return -2; + } + } + if (ffmpeg->tlapse == TIMELAPSE_APPEND) { + retcd = timelapse_append(ffmpeg, pkt); + } else if (ffmpeg->tlapse == TIMELAPSE_NEW) { + retcd = av_write_frame(ffmpeg->oc, &pkt); + } else { + pts_interval = ((1000000L * (tv1->tv_sec - ffmpeg->start_time.tv_sec)) + tv1->tv_usec - ffmpeg->start_time.tv_usec) + 10000; - /* Allocation the output media context. */ - ffmpeg->oc = avformat_alloc_context(); +// MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: interval:%d img_sec:%d img_usec:%d strt_sec:%d strt_usec:%d " +// ,pts_interval,tv1->tv_sec,tv1->tv_usec,ffmpeg->start_time.tv_sec,ffmpeg->start_time.tv_usec); - if (!ffmpeg->oc) { - MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: Could not allocate output context"); - ffmpeg_cleanups(ffmpeg); - return NULL; + if (pts_interval < 0){ + /* This can occur when we have pre-capture frames. Reset start time of video. */ + ffmpeg->start_time.tv_sec = tv1->tv_sec ; + ffmpeg->start_time.tv_usec = tv1->tv_usec ; + pts_interval = 1; + } + pkt.pts = av_rescale_q(pts_interval,(AVRational){1, 1000000L},ffmpeg->video_st->time_base); + if (pkt.pts <= ffmpeg->last_pts) pkt.pts = ffmpeg->last_pts + 1; + pkt.dts = pkt.pts; + retcd = av_write_frame(ffmpeg->oc, &pkt); + ffmpeg->last_pts = pkt.pts; } + // MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: pts:%d dts:%d stream:%d interval %d",pkt.pts,pkt.dts,ffmpeg->video_st->time_base.den,pts_interval); + my_packet_unref(pkt); - /* Setup output format */ - if (ffmpeg->tlapse == TIMELAPSE_APPEND){ - ffmpeg->oc->oformat = get_oformat("tlapse", filename); - } else { - ffmpeg->oc->oformat = get_oformat(ffmpeg_video_codec, filename); - } - if (!ffmpeg->oc->oformat) { + if (retcd != 0) { + MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: Error while writing video frame"); ffmpeg_cleanups(ffmpeg); - return NULL; + return -1; } - snprintf(ffmpeg->oc->filename, sizeof(ffmpeg->oc->filename), "%s", filename); + return retcd; - ffmpeg->video_st = NULL; - if (ffmpeg->oc->oformat->video_codec != MY_CODEC_ID_NONE) { +#else // Old versions of Libav/FFmpeg + int retcd; + AVPacket pkt; - codec = avcodec_find_encoder(ffmpeg->oc->oformat->video_codec); - if (!codec) { - MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Codec %s not found", ffmpeg_video_codec); - ffmpeg_cleanups(ffmpeg); - return NULL; + av_init_packet(&pkt); /* Init static structure. */ + pkt.stream_index = ffmpeg->video_st->index; + if (ffmpeg->oc->oformat->flags & AVFMT_RAWPICTURE) { + // Raw video case. + pkt.size = sizeof(AVPicture); + pkt.data = (uint8_t *)pic; + pkt.flags |= AV_PKT_FLAG_KEY; + } else { + retcd = avcodec_encode_video(AVSTREAM_CODEC_PTR(ffmpeg->video_st), + ffmpeg->video_outbuf, + ffmpeg->video_outbuf_size, pic); + if (retcd < 0 ){ + MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: Error encoding video"); + my_packet_unref(pkt); + return -1; } - - ffmpeg->video_st = avformat_new_stream(ffmpeg->oc, codec); - if (!ffmpeg->video_st) { - MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: Could not alloc stream"); - ffmpeg_cleanups(ffmpeg); - return NULL; + if (retcd == 0 ){ + // No bytes encoded => buffered=>special handling + my_packet_unref(pkt); + return -2; } + + pkt.size = retcd; + pkt.data = ffmpeg->video_outbuf; + pkt.pts = AVSTREAM_CODEC_PTR(ffmpeg->video_st)->coded_frame->pts; + if (AVSTREAM_CODEC_PTR(ffmpeg->video_st)->coded_frame->key_frame) + pkt.flags |= AV_PKT_FLAG_KEY; + } + if (ffmpeg->tlapse == TIMELAPSE_APPEND) { + retcd = timelapse_append(ffmpeg, pkt); } else { - /* We did not get a proper video codec. */ - MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Could not get the codec"); - ffmpeg_cleanups(ffmpeg); - return NULL; + retcd = av_write_frame(ffmpeg->oc, &pkt); } + my_packet_unref(pkt); - /* Only the newer codec and containers can handle the really fast FPS */ - if (((strcmp(ffmpeg_video_codec, "msmpeg4") == 0) || - (strcmp(ffmpeg_video_codec, "mpeg4") == 0) || - (strcmp(ffmpeg_video_codec, "swf") == 0) ) && (rate >100)){ - MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s The frame rate specified is too high for the ffmpeg movie type specified. Choose a different ffmpeg container or lower framerate. "); + if (retcd != 0) { + MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: Error while writing video frame"); ffmpeg_cleanups(ffmpeg); + return -1; + } + + return retcd; + +#endif +} + +/** + * ffmpeg_prepare_frame + * Allocates and prepares a picture frame by setting up the U, Y and V pointers in + * the frame according to the passed pointers. + * + * Returns + * NULL If the allocation fails. + * + * The returned AVFrame pointer must be freed after use. + */ +AVFrame *ffmpeg_prepare_frame(struct ffmpeg *ffmpeg, unsigned char *y, + unsigned char *u, unsigned char *v) +{ + AVFrame *picture; + + picture = my_frame_alloc(); + + if (!picture) { + MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: Could not alloc frame"); return NULL; } - ffmpeg->c = c = AVSTREAM_CODEC_PTR(ffmpeg->video_st); - c->codec_id = ffmpeg->oc->oformat->video_codec; - c->codec_type = AVMEDIA_TYPE_VIDEO; - c->bit_rate = bps; - c->width = width; - c->height = height; - c->time_base.num = 1; - c->time_base.den = rate; - c->gop_size = 12; - c->pix_fmt = MY_PIX_FMT_YUV420P; - c->max_b_frames = 0; + /* Take care of variable bitrate setting. */ + if (ffmpeg->vbr) + picture->quality = ffmpeg->vbr; - if (vbr > 100) vbr = 100; - if (c->codec_id == MY_CODEC_ID_H264 || - c->codec_id == MY_CODEC_ID_HEVC){ + /* Setup pointers and line widths. */ + picture->data[0] = y; + picture->data[1] = u; + picture->data[2] = v; + picture->linesize[0] = ffmpeg->c->width; + picture->linesize[1] = ffmpeg->c->width / 2; + picture->linesize[2] = ffmpeg->c->width / 2; + + picture->format = ffmpeg->c->pix_fmt; + picture->width = ffmpeg->c->width; + picture->height = ffmpeg->c->height; + + return picture; +} +/** + * ffmpeg_avcodec_log + * Handle any logging output from the ffmpeg library avcodec. + * + * Parameters + * *ignoreme A pointer we will ignore + * errno_flag The error number value + * fmt Text message to be used for log entry in printf() format. + * ap List of variables to be used in formatted message text. + * + * Returns + * Function returns nothing. + */ +void ffmpeg_avcodec_log(void *ignoreme ATTRIBUTE_UNUSED, int errno_flag, const char *fmt, va_list vl) +{ + char buf[1024]; + char *end; + + /* Flatten the message coming in from avcodec. */ + vsnprintf(buf, sizeof(buf), fmt, vl); + end = buf + strlen(buf); + if (end > buf && end[-1] == '\n') + { + *--end = 0; + } + + /* If the debug_level is correct then send the message to the motion logging routine. + * While it is not really desired to look for specific text in the message, there does + * not seem another option. The specific messages indicated are lost camera which we + * have our own message and UE golomb is not something that is possible for us to fix. + * It is caused by the stream sent from the source camera + */ + if(strstr(buf, "No route to host") == NULL){ + if (strstr(buf, "Invalid UE golomb") != NULL) { + MOTION_LOG(DBG, TYPE_ENCODER, NO_ERRNO, "%s: %s", buf); + } else if (errno_flag <= AV_LOG_ERROR) { + MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: %s", buf); + } else if (errno_flag <= AV_LOG_WARNING) { + MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO, "%s: %s", buf); + } else if (errno_flag < AV_LOG_DEBUG){ + MOTION_LOG(INF, TYPE_ENCODER, NO_ERRNO, "%s: %s", buf); + } + } +} + +#endif /* HAVE_FFMPEG */ + +/**************************************************************************** + **************************************************************************** + ****************************************************************************/ + +/** + * ffmpeg_init + * Initializes for libavformat. + * + * Returns + * Function returns nothing. + */ +void ffmpeg_init(void){ +#ifdef HAVE_FFMPEG + int ret; + + MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO, + "%s: ffmpeg libavcodec version %d.%d.%d" + " libavformat version %d.%d.%d" + , LIBAVCODEC_VERSION_MAJOR, LIBAVCODEC_VERSION_MINOR, LIBAVCODEC_VERSION_MICRO + , LIBAVFORMAT_VERSION_MAJOR, LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO); + + av_register_all(); + avcodec_register_all(); + avformat_network_init(); + av_log_set_callback((void *)ffmpeg_avcodec_log); + + ret = av_lockmgr_register(ffmpeg_lockmgr_cb); + if (ret < 0) + { + MOTION_LOG(EMG, TYPE_ALL, SHOW_ERRNO, "%s: av_lockmgr_register failed (%d)", ret); + exit(1); + } + +#else /* No FFMPEG */ + + MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO,"%s: No ffmpeg functionality included"); + +#endif /* HAVE_FFMPEG */ +} + +void ffmpeg_finalise(void) { +#ifdef HAVE_FFMPEG + + avformat_network_deinit(); + +#else /* No FFMPEG */ + + MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO,"%s: No ffmpeg functionality included"); + +#endif /* HAVE_FFMPEG */ +} + +/** + * ffmpeg_open + * Opens an mpeg file using the new libavformat method. Both mpeg1 + * and mpeg4 are supported. However, if the current ffmpeg version doesn't allow + * mpeg1 with non-standard framerate, the open will fail. Timelapse is a special + * case and is tested separately. + * + * Returns + * A new allocated ffmpeg struct or NULL if any error happens. + */ +struct ffmpeg *ffmpeg_open(const char *ffmpeg_video_codec, char *filename, + unsigned char *y, unsigned char *u, unsigned char *v, + int width, int height, int rate, int bps, int vbr, int tlapse, + const struct timeval *tv1) +{ +#ifdef HAVE_FFMPEG + + AVCodecContext *c; + AVCodec *codec; + struct ffmpeg *ffmpeg; + int retcd; + char errstr[128]; + AVDictionary *opts = 0; + + /* + * Allocate space for our ffmpeg structure. This structure contains all the + * codec and image information we need to generate movies. + */ + ffmpeg = mymalloc(sizeof(struct ffmpeg)); + + ffmpeg->tlapse = tlapse; + + /* Store codec name in ffmpeg->codec, with buffer overflow check. */ + snprintf(ffmpeg->codec, sizeof(ffmpeg->codec), "%s", ffmpeg_video_codec); + + /* Allocation the output media context. */ + ffmpeg->oc = avformat_alloc_context(); + + if (!ffmpeg->oc) { + MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: Could not allocate output context"); + ffmpeg_cleanups(ffmpeg); + return NULL; + } + + /* Setup output format */ + if (ffmpeg->tlapse == TIMELAPSE_APPEND){ + ffmpeg->oc->oformat = get_oformat("tlapse", filename); + } else { + ffmpeg->oc->oformat = get_oformat(ffmpeg_video_codec, filename); + } + if (!ffmpeg->oc->oformat) { + ffmpeg_cleanups(ffmpeg); + return NULL; + } + + snprintf(ffmpeg->oc->filename, sizeof(ffmpeg->oc->filename), "%s", filename); + + ffmpeg->video_st = NULL; + if (ffmpeg->oc->oformat->video_codec != MY_CODEC_ID_NONE) { + + codec = avcodec_find_encoder(ffmpeg->oc->oformat->video_codec); + if (!codec) { + MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Codec %s not found", ffmpeg_video_codec); + ffmpeg_cleanups(ffmpeg); + return NULL; + } + + ffmpeg->video_st = avformat_new_stream(ffmpeg->oc, codec); + if (!ffmpeg->video_st) { + MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: Could not alloc stream"); + ffmpeg_cleanups(ffmpeg); + return NULL; + } + } else { + /* We did not get a proper video codec. */ + MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Could not get the codec"); + ffmpeg_cleanups(ffmpeg); + return NULL; + } + + /* Only the newer codec and containers can handle the really fast FPS */ + if (((strcmp(ffmpeg_video_codec, "msmpeg4") == 0) || + (strcmp(ffmpeg_video_codec, "mpeg4") == 0) || + (strcmp(ffmpeg_video_codec, "swf") == 0) ) && (rate >100)){ + MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s The frame rate specified is too high for the ffmpeg movie type specified. Choose a different ffmpeg container or lower framerate. "); + ffmpeg_cleanups(ffmpeg); + return NULL; + } + + ffmpeg->c = c = AVSTREAM_CODEC_PTR(ffmpeg->video_st); + c->codec_id = ffmpeg->oc->oformat->video_codec; + c->codec_type = AVMEDIA_TYPE_VIDEO; + c->bit_rate = bps; + c->width = width; + c->height = height; + c->time_base.num = 1; + c->time_base.den = rate; + c->gop_size = 12; + c->pix_fmt = MY_PIX_FMT_YUV420P; + c->max_b_frames = 0; + + if (vbr > 100) vbr = 100; + + if (c->codec_id == MY_CODEC_ID_H264 || + c->codec_id == MY_CODEC_ID_HEVC){ if (vbr > 0) { ffmpeg->vbr = (int)(( (100-vbr) * 51)/100); } else { @@ -558,37 +816,45 @@ struct ffmpeg *ffmpeg_open(const char *ffmpeg_video_codec, char *filename, } return ffmpeg; + +#else /* No FFMPEG */ + + MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO,"%s: No ffmpeg functionality included"); + + struct ffmpeg *ffmpeg; + ffmpeg = mymalloc(sizeof(struct ffmpeg)); + + ffmpeg_video_codec = ffmpeg_video_codec; + filename = filename; + y = y; + u = u; + v = v; + width = width; + height = height; + rate = rate; + bps = bps; + vbr = vbr; + tlapse = tlapse; + ffmpeg->dummy = 0; + return ffmpeg; + +#endif /* HAVE_FFMPEG */ + } + /** - * ffmpeg_cleanups - * Clean up ffmpeg struct if something was wrong. + * ffmpeg_close + * Closes a video file. * * Returns * Function returns nothing. */ -void ffmpeg_cleanups(struct ffmpeg *ffmpeg){ +void ffmpeg_close(struct ffmpeg *ffmpeg){ +#ifdef HAVE_FFMPEG - /* Close each codec */ - if (ffmpeg->video_st) { - avcodec_close(AVSTREAM_CODEC_PTR(ffmpeg->video_st)); - } - free(ffmpeg->video_outbuf); - av_freep(&ffmpeg->picture); - avformat_free_context(ffmpeg->oc); - free(ffmpeg); -} -/** - * ffmpeg_close - * Closes a video file. - * - * Returns - * Function returns nothing. - */ -void ffmpeg_close(struct ffmpeg *ffmpeg){ - - if (ffmpeg->tlapse != TIMELAPSE_APPEND) { - av_write_trailer(ffmpeg->oc); - } + if (ffmpeg->tlapse != TIMELAPSE_APPEND) { + av_write_trailer(ffmpeg->oc); + } /* Close each codec */ if (ffmpeg->video_st) { avcodec_close(AVSTREAM_CODEC_PTR(ffmpeg->video_st)); @@ -602,43 +868,12 @@ void ffmpeg_close(struct ffmpeg *ffmpeg){ } } avformat_free_context(ffmpeg->oc); - free(ffmpeg); - -} -/** - * ffmpeg_put_image - * Puts the image pointed to by ffmpeg->picture. - * - * Returns - * value returned by ffmpeg_put_frame call. - */ -int ffmpeg_put_image(struct ffmpeg *ffmpeg, const struct timeval *tv1){ - /* A return code of -2 is thrown by the put_frame - * when a image is buffered. For timelapse, we absolutely - * never want a frame buffered so we keep sending back the - * the same pic until it flushes or fails in a different way - */ - int retcd; - int cnt = 0; +#endif // HAVE_FFMPEG - retcd = ffmpeg_put_frame(ffmpeg, ffmpeg->picture, tv1); - while ((retcd == -2) && (ffmpeg->tlapse != TIMELAPSE_NONE)) { - retcd = ffmpeg_put_frame(ffmpeg, ffmpeg->picture, tv1); - cnt++; - if (cnt > 50){ - MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Excessive attempts to clear buffered packet"); - retcd = -1; - } - } - //non timelapse buffered is ok - if (retcd == -2){ - retcd = 0; - MOTION_LOG(DBG, TYPE_ENCODER, NO_ERRNO, "%s: Buffered packet"); - } - - return retcd; + free(ffmpeg); } + /** * ffmpeg_put_other_image * Puts an arbitrary picture defined by y, u and v. @@ -650,6 +885,7 @@ int ffmpeg_put_image(struct ffmpeg *ffmpeg, const struct timeval *tv1){ */ int ffmpeg_put_other_image(struct ffmpeg *ffmpeg, unsigned char *y, unsigned char *u, unsigned char *v, const struct timeval *tv1){ +#ifdef HAVE_FFMPEG AVFrame *picture; int retcd = 0; int cnt = 0; @@ -679,268 +915,56 @@ int ffmpeg_put_other_image(struct ffmpeg *ffmpeg, unsigned char *y, } av_free(picture); } - return retcd; -} -/** - * ffmpeg_put_frame - * Encodes and writes a video frame using the av_write_frame API. This is - * a helper function for ffmpeg_put_image and ffmpeg_put_other_image. - * - * Returns - * Number of bytes written or -1 if any error happens. - */ -int ffmpeg_put_frame(struct ffmpeg *ffmpeg, AVFrame *pic, const struct timeval *tv1){ -/** - * Since the logic,return values and conditions changed so - * dramatically between versions, the encoding of the frame - * is 100% blocked based upon Libav/FFMpeg version - */ -#if (LIBAVFORMAT_VERSION_MAJOR >= 55) || ((LIBAVFORMAT_VERSION_MAJOR == 54) && (LIBAVFORMAT_VERSION_MINOR > 6)) - int retcd; - int got_packet_ptr; - AVPacket pkt; - char errstr[128]; - int64_t pts_interval; - - av_init_packet(&pkt); /* Init static structure. */ - if (ffmpeg->oc->oformat->flags & AVFMT_RAWPICTURE) { - pkt.stream_index = ffmpeg->video_st->index; - pkt.flags |= AV_PKT_FLAG_KEY; - pkt.data = (uint8_t *)pic; - pkt.size = sizeof(AVPicture); - } else { - pkt.data = NULL; - pkt.size = 0; - retcd = avcodec_encode_video2(AVSTREAM_CODEC_PTR(ffmpeg->video_st), - &pkt, pic, &got_packet_ptr); - if (retcd < 0 ){ - av_strerror(retcd, errstr, sizeof(errstr)); - MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: Error encoding video:%s",errstr); - //Packet is freed upon failure of encoding - return -1; - } - if (got_packet_ptr == 0){ - //Buffered packet. Throw special return code - my_packet_unref(pkt); - return -2; - } - } - if (ffmpeg->tlapse == TIMELAPSE_APPEND) { - retcd = timelapse_append(ffmpeg, pkt); - } else if (ffmpeg->tlapse == TIMELAPSE_NEW) { - retcd = av_write_frame(ffmpeg->oc, &pkt); - } else { - pts_interval = ((1000000L * (tv1->tv_sec - ffmpeg->start_time.tv_sec)) + tv1->tv_usec - ffmpeg->start_time.tv_usec) + 10000; - -// MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: interval:%d img_sec:%d img_usec:%d strt_sec:%d strt_usec:%d " -// ,pts_interval,tv1->tv_sec,tv1->tv_usec,ffmpeg->start_time.tv_sec,ffmpeg->start_time.tv_usec); - - if (pts_interval < 0){ - /* This can occur when we have pre-capture frames. Reset start time of video. */ - ffmpeg->start_time.tv_sec = tv1->tv_sec ; - ffmpeg->start_time.tv_usec = tv1->tv_usec ; - pts_interval = 1; - } - pkt.pts = av_rescale_q(pts_interval,(AVRational){1, 1000000L},ffmpeg->video_st->time_base); - if (pkt.pts <= ffmpeg->last_pts) pkt.pts = ffmpeg->last_pts + 1; - pkt.dts = pkt.pts; - retcd = av_write_frame(ffmpeg->oc, &pkt); - ffmpeg->last_pts = pkt.pts; - } - // MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: pts:%d dts:%d stream:%d interval %d",pkt.pts,pkt.dts,ffmpeg->video_st->time_base.den,pts_interval); - my_packet_unref(pkt); - - if (retcd != 0) { - MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: Error while writing video frame"); - ffmpeg_cleanups(ffmpeg); - return -1; - } - - return retcd; - -#else // Old versions of Libav/FFmpeg - int retcd; - AVPacket pkt; - - av_init_packet(&pkt); /* Init static structure. */ - pkt.stream_index = ffmpeg->video_st->index; - if (ffmpeg->oc->oformat->flags & AVFMT_RAWPICTURE) { - // Raw video case. - pkt.size = sizeof(AVPicture); - pkt.data = (uint8_t *)pic; - pkt.flags |= AV_PKT_FLAG_KEY; - } else { - retcd = avcodec_encode_video(AVSTREAM_CODEC_PTR(ffmpeg->video_st), - ffmpeg->video_outbuf, - ffmpeg->video_outbuf_size, pic); - if (retcd < 0 ){ - MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: Error encoding video"); - my_packet_unref(pkt); - return -1; - } - if (retcd == 0 ){ - // No bytes encoded => buffered=>special handling - my_packet_unref(pkt); - return -2; - } - - pkt.size = retcd; - pkt.data = ffmpeg->video_outbuf; - pkt.pts = AVSTREAM_CODEC_PTR(ffmpeg->video_st)->coded_frame->pts; - if (AVSTREAM_CODEC_PTR(ffmpeg->video_st)->coded_frame->key_frame) - pkt.flags |= AV_PKT_FLAG_KEY; - } - if (ffmpeg->tlapse == TIMELAPSE_APPEND) { - retcd = timelapse_append(ffmpeg, pkt); - } else { - retcd = av_write_frame(ffmpeg->oc, &pkt); - } - my_packet_unref(pkt); - - if (retcd != 0) { - MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: Error while writing video frame"); - ffmpeg_cleanups(ffmpeg); - return -1; - } +#else - return retcd; + ffmpeg = ffmpeg; + y = y; + u = u; + v = v; + return 0; -#endif +#endif // HAVE_FFMPEG } /** - * ffmpeg_prepare_frame - * Allocates and prepares a picture frame by setting up the U, Y and V pointers in - * the frame according to the passed pointers. - * - * Returns - * NULL If the allocation fails. - * - * The returned AVFrame pointer must be freed after use. - */ -AVFrame *ffmpeg_prepare_frame(struct ffmpeg *ffmpeg, unsigned char *y, - unsigned char *u, unsigned char *v) -{ - AVFrame *picture; - - picture = my_frame_alloc(); - - if (!picture) { - MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: Could not alloc frame"); - return NULL; - } - - /* Take care of variable bitrate setting. */ - if (ffmpeg->vbr) - picture->quality = ffmpeg->vbr; - - - /* Setup pointers and line widths. */ - picture->data[0] = y; - picture->data[1] = u; - picture->data[2] = v; - picture->linesize[0] = ffmpeg->c->width; - picture->linesize[1] = ffmpeg->c->width / 2; - picture->linesize[2] = ffmpeg->c->width / 2; - - picture->format = ffmpeg->c->pix_fmt; - picture->width = ffmpeg->c->width; - picture->height = ffmpeg->c->height; - - return picture; -} -/** - * ffmpeg_avcodec_log - * Handle any logging output from the ffmpeg library avcodec. - * - * Parameters - * *ignoreme A pointer we will ignore - * errno_flag The error number value - * fmt Text message to be used for log entry in printf() format. - * ap List of variables to be used in formatted message text. + * ffmpeg_put_image + * Puts the image pointed to by ffmpeg->picture. * * Returns - * Function returns nothing. + * value returned by ffmpeg_put_frame call. */ -void ffmpeg_avcodec_log(void *ignoreme ATTRIBUTE_UNUSED, int errno_flag, const char *fmt, va_list vl) -{ - char buf[1024]; - char *end; - - /* Flatten the message coming in from avcodec. */ - vsnprintf(buf, sizeof(buf), fmt, vl); - end = buf + strlen(buf); - if (end > buf && end[-1] == '\n') - { - *--end = 0; - } +int ffmpeg_put_image(struct ffmpeg *ffmpeg, const struct timeval *tv1){ - /* If the debug_level is correct then send the message to the motion logging routine. - * While it is not really desired to look for specific text in the message, there does - * not seem another option. The specific messages indicated are lost camera which we - * have our own message and UE golomb is not something that is possible for us to fix. - * It is caused by the stream sent from the source camera +#ifdef HAVE_FFMPEG + /* A return code of -2 is thrown by the put_frame + * when a image is buffered. For timelapse, we absolutely + * never want a frame buffered so we keep sending back the + * the same pic until it flushes or fails in a different way */ - if(strstr(buf, "No route to host") == NULL){ - if (strstr(buf, "Invalid UE golomb") != NULL) { - MOTION_LOG(DBG, TYPE_ENCODER, NO_ERRNO, "%s: %s", buf); - } else if (errno_flag <= AV_LOG_ERROR) { - MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: %s", buf); - } else if (errno_flag <= AV_LOG_WARNING) { - MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO, "%s: %s", buf); - } else if (errno_flag < AV_LOG_DEBUG){ - MOTION_LOG(INF, TYPE_ENCODER, NO_ERRNO, "%s: %s", buf); + int retcd; + int cnt = 0; + + retcd = ffmpeg_put_frame(ffmpeg, ffmpeg->picture, tv1); + while ((retcd == -2) && (ffmpeg->tlapse != TIMELAPSE_NONE)) { + retcd = ffmpeg_put_frame(ffmpeg, ffmpeg->picture, tv1); + cnt++; + if (cnt > 50){ + MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Excessive attempts to clear buffered packet"); + retcd = -1; } } -} - - -#else /* No FFMPEG */ -/**************************************************************************** - **************************************************************************** - ****************************************************************************/ -void ffmpeg_init(void){ - MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO,"%s: No ffmpeg functionality included"); -} -void ffmpeg_finalise(void) {} -struct ffmpeg *ffmpeg_open(const char *ffmpeg_video_codec, char *filename, - unsigned char *y, unsigned char *u, unsigned char *v, - int width, int height, int rate, int bps, int vbr, int tlapse) -{ - struct ffmpeg *ffmpeg; - ffmpeg = mymalloc(sizeof(struct ffmpeg)); - - ffmpeg_video_codec = ffmpeg_video_codec; - filename = filename; - y = y; - u = u; - v = v; - width = width; - height = height; - rate = rate; - bps = bps; - vbr = vbr; - tlapse = tlapse; - ffmpeg->dummy = 0; - return ffmpeg; + //non timelapse buffered is ok + if (retcd == -2){ + retcd = 0; + MOTION_LOG(DBG, TYPE_ENCODER, NO_ERRNO, "%s: Buffered packet"); + } -} -void ffmpeg_close(struct ffmpeg *ffmpeg){ - free(ffmpeg); -} -int ffmpeg_put_other_image(struct ffmpeg *ffmpeg, unsigned char *y, - unsigned char *u, unsigned char *v){ - ffmpeg = ffmpeg; - y = y; - u = u; - v = v; - return 0; -} -int ffmpeg_put_image(struct ffmpeg *ffmpeg){ + return retcd; +#else ffmpeg = ffmpeg; return 0; +#endif // HAVE_FFMPEG } -#endif /* HAVE_FFMPEG */ diff --git a/ffmpeg.h b/ffmpeg.h index 8bf1d3a7f..9d3276e03 100644 --- a/ffmpeg.h +++ b/ffmpeg.h @@ -66,6 +66,7 @@ void my_packet_unref(AVPacket pkt); struct ffmpeg { void *udata; int dummy; + struct timeval start_time; }; #endif /* HAVE_FFMPEG */