/* * * ffmpeg.c * * This software is distributed under the GNU Public License version 2 * See also the file 'COPYING'. * * The contents of this file has been derived from output_example.c * and apiexample.c from the FFmpeg distribution. * * This file has been modified so that only major versions greater than * 53 are supported. * Note that while the conditions are based upon LIBAVFORMAT, not all of the changes are * specific to libavformat.h. Some changes could be related to other components of ffmpeg. * This is for simplicity. The avformat version has historically changed at the same time * as the other components so it is easier to have a single version number to track rather * than the particular version numbers which are associated with each component. * The libav variant also has different apis with the same major/minor version numbers. * As such, it is occasionally necessary to look at the microversion number. Numbers * greater than 100 for micro version indicate ffmpeg whereas numbers less than 100 * indicate libav */ #include "translate.h" #include "motion.h" #ifdef HAVE_FFMPEG /**************************************************************************** * The section below is the "my" section of functions. * These are designed to be extremely simple version specific * variants of the libav functions. ****************************************************************************/ #if (LIBAVFORMAT_VERSION_MAJOR >= 55) || ((LIBAVFORMAT_VERSION_MAJOR == 54) && (LIBAVFORMAT_VERSION_MINOR > 6)) #define MY_FLAG_READ AVIO_FLAG_READ #define MY_FLAG_WRITE AVIO_FLAG_WRITE #define MY_FLAG_READ_WRITE AVIO_FLAG_READ_WRITE #else //Older versions #define MY_FLAG_READ URL_RDONLY #define MY_FLAG_WRITE URL_WRONLY #define MY_FLAG_READ_WRITE URL_RDWR #endif /*********************************************/ #if (LIBAVFORMAT_VERSION_MAJOR >= 56) #define MY_CODEC_ID_MSMPEG4V2 AV_CODEC_ID_MSMPEG4V2 #define MY_CODEC_ID_FLV1 AV_CODEC_ID_FLV1 #define MY_CODEC_ID_FFV1 AV_CODEC_ID_FFV1 #define MY_CODEC_ID_NONE AV_CODEC_ID_NONE #define MY_CODEC_ID_MPEG2VIDEO AV_CODEC_ID_MPEG2VIDEO #define MY_CODEC_ID_H264 AV_CODEC_ID_H264 #define MY_CODEC_ID_HEVC AV_CODEC_ID_HEVC #else #define MY_CODEC_ID_MSMPEG4V2 CODEC_ID_MSMPEG4V2 #define MY_CODEC_ID_FLV1 CODEC_ID_FLV1 #define MY_CODEC_ID_FFV1 CODEC_ID_FFV1 #define MY_CODEC_ID_NONE CODEC_ID_NONE #define MY_CODEC_ID_MPEG2VIDEO CODEC_ID_MPEG2VIDEO #define MY_CODEC_ID_H264 CODEC_ID_H264 #define MY_CODEC_ID_HEVC CODEC_ID_H264 #endif /*********************************************/ #if (LIBAVCODEC_VERSION_MAJOR >= 57) #define MY_CODEC_FLAG_GLOBAL_HEADER AV_CODEC_FLAG_GLOBAL_HEADER #define MY_CODEC_FLAG_QSCALE AV_CODEC_FLAG_QSCALE #else #define MY_CODEC_FLAG_GLOBAL_HEADER CODEC_FLAG_GLOBAL_HEADER #define MY_CODEC_FLAG_QSCALE CODEC_FLAG_QSCALE #endif /*********************************************/ AVFrame *my_frame_alloc(void){ AVFrame *pic; #if (LIBAVFORMAT_VERSION_MAJOR >= 55) pic = av_frame_alloc(); #else pic = avcodec_alloc_frame(); #endif return pic; } /*********************************************/ void my_frame_free(AVFrame *frame){ #if (LIBAVFORMAT_VERSION_MAJOR >= 55) av_frame_free(&frame); #else av_freep(&frame); #endif } /*********************************************/ int my_image_get_buffer_size(enum MyPixelFormat pix_fmt, int width, int height){ int retcd = 0; #if (LIBAVFORMAT_VERSION_MAJOR >= 57) int align = 1; retcd = av_image_get_buffer_size(pix_fmt, width, height, align); #else retcd = avpicture_get_size(pix_fmt, width, height); #endif return retcd; } /*********************************************/ int my_image_copy_to_buffer(AVFrame *frame, uint8_t *buffer_ptr, enum MyPixelFormat pix_fmt,int width, int height,int dest_size){ int retcd = 0; #if (LIBAVFORMAT_VERSION_MAJOR >= 57) int align = 1; retcd = av_image_copy_to_buffer((uint8_t *)buffer_ptr,dest_size ,(const uint8_t * const*)frame,frame->linesize,pix_fmt,width,height,align); #else retcd = avpicture_layout((const AVPicture*)frame,pix_fmt,width,height ,(unsigned char *)buffer_ptr,dest_size); #endif return retcd; } /*********************************************/ int my_image_fill_arrays(AVFrame *frame,uint8_t *buffer_ptr,enum MyPixelFormat pix_fmt,int width,int height){ int retcd = 0; #if (LIBAVFORMAT_VERSION_MAJOR >= 57) int align = 1; retcd = av_image_fill_arrays( frame->data ,frame->linesize ,buffer_ptr ,pix_fmt ,width ,height ,align ); #else retcd = avpicture_fill( (AVPicture *)frame ,buffer_ptr ,pix_fmt ,width ,height); #endif return retcd; } /*********************************************/ void my_packet_unref(AVPacket pkt){ #if (LIBAVFORMAT_VERSION_MAJOR >= 57) av_packet_unref(&pkt); #else av_free_packet(&pkt); #endif } /*********************************************/ void my_avcodec_close(AVCodecContext *codec_context){ #if (LIBAVFORMAT_VERSION_MAJOR >= 58) || ((LIBAVFORMAT_VERSION_MAJOR == 57) && (LIBAVFORMAT_VERSION_MINOR >= 41)) avcodec_free_context(&codec_context); #else avcodec_close(codec_context); #endif } /*********************************************/ int my_copy_packet(AVPacket *dest_pkt, AVPacket *src_pkt){ #if (LIBAVFORMAT_VERSION_MAJOR >= 55) return av_packet_ref(dest_pkt, src_pkt); #else /* Old versions of libav do not support copying packet * We therefore disable the pass through recording and * for this function, simply do not do anything */ if (dest_pkt == src_pkt ){ return 0; } else { return 0; } #endif } /*********************************************/ /**************************************************************************** **************************************************************************** ****************************************************************************/ static int ffmpeg_timelapse_exists(const char *fname){ FILE *file; file = fopen(fname, "r"); if (file) { fclose(file); return 1; } return 0; } static int ffmpeg_timelapse_append(struct ffmpeg *ffmpeg, AVPacket pkt){ FILE *file; file = fopen(ffmpeg->filename, "a"); if (!file) return -1; fwrite(pkt.data,1,pkt.size,file); fclose(file); return 0; } #if (LIBAVFORMAT_VERSION_MAJOR < 58) /* TODO Determine if this is even needed for old versions. Per * documentation for version 58, 'av_lockmgr_register This function does nothing' */ static int ffmpeg_lockmgr_cb(void **arg, enum AVLockOp op){ pthread_mutex_t *mutex = *arg; int err; switch (op) { case AV_LOCK_CREATE: mutex = malloc(sizeof(*mutex)); if (!mutex) return AVERROR(ENOMEM); if ((err = pthread_mutex_init(mutex, NULL))) { free(mutex); return AVERROR(err); } *arg = mutex; return 0; case AV_LOCK_OBTAIN: if ((err = pthread_mutex_lock(mutex))) return AVERROR(err); return 0; case AV_LOCK_RELEASE: if ((err = pthread_mutex_unlock(mutex))) return AVERROR(err); return 0; case AV_LOCK_DESTROY: if (mutex) pthread_mutex_destroy(mutex); free(mutex); *arg = NULL; return 0; } return 1; } #endif static void ffmpeg_free_context(struct ffmpeg *ffmpeg){ if (ffmpeg->picture != NULL){ my_frame_free(ffmpeg->picture); ffmpeg->picture = NULL; } if (ffmpeg->ctx_codec != NULL){ my_avcodec_close(ffmpeg->ctx_codec); ffmpeg->ctx_codec = NULL; } if (ffmpeg->oc != NULL){ avformat_free_context(ffmpeg->oc); ffmpeg->oc = NULL; } } static int ffmpeg_get_oformat(struct ffmpeg *ffmpeg){ size_t codec_name_len = strcspn(ffmpeg->codec_name, ":"); char *codec_name = malloc(codec_name_len + 1); if (codec_name == NULL) { MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO ,_("Failed to allocate memory for codec name")); ffmpeg_free_context(ffmpeg); return -1; } memcpy(codec_name, ffmpeg->codec_name, codec_name_len); codec_name[codec_name_len] = 0; /* Only the newer codec and containers can handle the really fast FPS */ if (((strcmp(codec_name, "msmpeg4") == 0) || (strcmp(codec_name, "mpeg4") == 0) || (strcmp(codec_name, "swf") == 0) ) && (ffmpeg->fps >50)){ MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO ,_("The frame rate specified is too high for the ffmpeg movie type specified. " "Choose a different ffmpeg container or lower framerate.")); ffmpeg_free_context(ffmpeg); free(codec_name); return -1; } if (ffmpeg->tlapse == TIMELAPSE_APPEND){ ffmpeg->oc->oformat = av_guess_format ("mpeg2video", NULL, NULL); if (ffmpeg->oc->oformat) ffmpeg->oc->oformat->video_codec = MY_CODEC_ID_MPEG2VIDEO; strncat(ffmpeg->filename, ".mpg", 4); if (!ffmpeg->oc->oformat) { MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO ,_("ffmpeg_video_codec option value %s is not supported"), codec_name); ffmpeg_free_context(ffmpeg); free(codec_name); return -1; } free(codec_name); return 0; } if (strcmp(codec_name, "mpeg4") == 0) { ffmpeg->oc->oformat = av_guess_format("avi", NULL, NULL); strncat(ffmpeg->filename, ".avi", 4); } if (strcmp(codec_name, "msmpeg4") == 0) { ffmpeg->oc->oformat = av_guess_format("avi", NULL, NULL); strncat(ffmpeg->filename, ".avi", 4); if (ffmpeg->oc->oformat) ffmpeg->oc->oformat->video_codec = MY_CODEC_ID_MSMPEG4V2; } if (strcmp(codec_name, "swf") == 0) { ffmpeg->oc->oformat = av_guess_format("swf", NULL, NULL); strncat(ffmpeg->filename, ".swf", 4); } if (strcmp(codec_name, "flv") == 0) { ffmpeg->oc->oformat = av_guess_format("flv", NULL, NULL); strncat(ffmpeg->filename, ".flv", 4); if (ffmpeg->oc->oformat) ffmpeg->oc->oformat->video_codec = MY_CODEC_ID_FLV1; } if (strcmp(codec_name, "ffv1") == 0) { ffmpeg->oc->oformat = av_guess_format("avi", NULL, NULL); strncat(ffmpeg->filename, ".avi", 4); if (ffmpeg->oc->oformat) ffmpeg->oc->oformat->video_codec = MY_CODEC_ID_FFV1; } if (strcmp(codec_name, "mov") == 0) { ffmpeg->oc->oformat = av_guess_format("mov", NULL, NULL); strncat(ffmpeg->filename, ".mov", 4); } if (strcmp(codec_name, "mp4") == 0) { ffmpeg->oc->oformat = av_guess_format("mp4", NULL, NULL); strncat(ffmpeg->filename, ".mp4", 4); if (ffmpeg->oc->oformat) ffmpeg->oc->oformat->video_codec = MY_CODEC_ID_H264; } if (strcmp(codec_name, "mkv") == 0) { ffmpeg->oc->oformat = av_guess_format("matroska", NULL, NULL); strncat(ffmpeg->filename, ".mkv", 4); if (ffmpeg->oc->oformat) ffmpeg->oc->oformat->video_codec = MY_CODEC_ID_H264; } if (strcmp(codec_name, "hevc") == 0) { ffmpeg->oc->oformat = av_guess_format("mp4", NULL, NULL); strncat(ffmpeg->filename, ".mp4", 4); if (ffmpeg->oc->oformat) ffmpeg->oc->oformat->video_codec = MY_CODEC_ID_HEVC; } //Check for valid results if (!ffmpeg->oc->oformat) { MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO ,_("codec option value %s is not supported"), codec_name); ffmpeg_free_context(ffmpeg); free(codec_name); return -1; } if (ffmpeg->oc->oformat->video_codec == MY_CODEC_ID_NONE) { MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not get the codec")); ffmpeg_free_context(ffmpeg); free(codec_name); return -1; } free(codec_name); return 0; } static int ffmpeg_encode_video(struct ffmpeg *ffmpeg){ #if (LIBAVFORMAT_VERSION_MAJOR >= 58) || ((LIBAVFORMAT_VERSION_MAJOR == 57) && (LIBAVFORMAT_VERSION_MINOR >= 41)) //ffmpeg version 3.1 and after int retcd = 0; char errstr[128]; retcd = avcodec_send_frame(ffmpeg->ctx_codec, ffmpeg->picture); if (retcd < 0 ){ av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO ,_("Error sending frame for encoding:%s"),errstr); return -1; } retcd = avcodec_receive_packet(ffmpeg->ctx_codec, &ffmpeg->pkt); if (retcd == AVERROR(EAGAIN)){ //Buffered packet. Throw special return code av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(DBG, TYPE_ENCODER, NO_ERRNO ,_("Receive packet threw EAGAIN returning -2 code :%s"),errstr); my_packet_unref(ffmpeg->pkt); return -2; } if (retcd < 0 ){ av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO ,_("Error receiving encoded packet video:%s"),errstr); //Packet is freed upon failure of encoding return -1; } return 0; #elif (LIBAVFORMAT_VERSION_MAJOR >= 55) || ((LIBAVFORMAT_VERSION_MAJOR == 54) && (LIBAVFORMAT_VERSION_MINOR > 6)) int retcd = 0; char errstr[128]; int got_packet_ptr; retcd = avcodec_encode_video2(ffmpeg->ctx_codec, &ffmpeg->pkt, ffmpeg->picture, &got_packet_ptr); if (retcd < 0 ){ av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("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(ffmpeg->pkt); return -2; } return 0; #else int retcd = 0; uint8_t *video_outbuf; int video_outbuf_size; video_outbuf_size = (ffmpeg->ctx_codec->width +16) * (ffmpeg->ctx_codec->height +16) * 1; video_outbuf = mymalloc(video_outbuf_size); retcd = avcodec_encode_video(ffmpeg->video_st->codec, video_outbuf, video_outbuf_size, ffmpeg->picture); if (retcd < 0 ){ MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Error encoding video")); my_packet_unref(ffmpeg->pkt); return -1; } if (retcd == 0 ){ // No bytes encoded => buffered=>special handling my_packet_unref(ffmpeg->pkt); return -2; } // Encoder did not provide metadata, set it up manually ffmpeg->pkt.size = retcd; ffmpeg->pkt.data = video_outbuf; if (ffmpeg->picture->key_frame == 1) ffmpeg->pkt.flags |= AV_PKT_FLAG_KEY; ffmpeg->pkt.pts = ffmpeg->picture->pts; ffmpeg->pkt.dts = ffmpeg->pkt.pts; free(video_outbuf); return 0; #endif } static int ffmpeg_set_pts(struct ffmpeg *ffmpeg, const struct timeval *tv1){ int64_t pts_interval; if (ffmpeg->tlapse != TIMELAPSE_NONE) { ffmpeg->last_pts++; ffmpeg->picture->pts = ffmpeg->last_pts; } else { pts_interval = ((1000000L * (tv1->tv_sec - ffmpeg->start_time.tv_sec)) + tv1->tv_usec - ffmpeg->start_time.tv_usec); if (pts_interval < 0){ /* This can occur when we have pre-capture frames. Reset start time of video. */ ffmpeg_reset_movie_start_time(ffmpeg, tv1); pts_interval = 0; } ffmpeg->picture->pts = av_rescale_q(pts_interval,(AVRational){1, 1000000L},ffmpeg->video_st->time_base) + ffmpeg->base_pts; if (ffmpeg->test_mode == TRUE){ MOTION_LOG(INF, TYPE_ENCODER, NO_ERRNO ,_("PTS %"PRId64" Base PTS %"PRId64" ms interval %"PRId64" timebase %d-%d") ,ffmpeg->picture->pts,ffmpeg->base_pts,pts_interval ,ffmpeg->video_st->time_base.num,ffmpeg->video_st->time_base.den); } if (ffmpeg->picture->pts <= ffmpeg->last_pts){ //We have a problem with our motion loop timing and sending frames or the rounding into the PTS. if (ffmpeg->test_mode == TRUE){ MOTION_LOG(INF, TYPE_ENCODER, NO_ERRNO, _("BAD TIMING!! Frame skipped.")); } return -1; } ffmpeg->last_pts = ffmpeg->picture->pts; } return 0; } static int ffmpeg_set_pktpts(struct ffmpeg *ffmpeg, const struct timeval *tv1){ int64_t pts_interval; if (ffmpeg->tlapse != TIMELAPSE_NONE) { ffmpeg->last_pts++; ffmpeg->pkt.pts = ffmpeg->last_pts; } else { pts_interval = ((1000000L * (tv1->tv_sec - ffmpeg->start_time.tv_sec)) + tv1->tv_usec - ffmpeg->start_time.tv_usec); if (pts_interval < 0){ /* This can occur when we have pre-capture frames. Reset start time of video. */ ffmpeg_reset_movie_start_time(ffmpeg, tv1); pts_interval = 0; } ffmpeg->pkt.pts = av_rescale_q(pts_interval,(AVRational){1, 1000000L},ffmpeg->video_st->time_base) + ffmpeg->base_pts; if (ffmpeg->test_mode == TRUE){ MOTION_LOG(INF, TYPE_ENCODER, NO_ERRNO ,_("PTS %"PRId64" Base PTS %"PRId64" ms interval %"PRId64" timebase %d-%d Change %d") ,ffmpeg->pkt.pts ,ffmpeg->base_pts,pts_interval ,ffmpeg->video_st->time_base.num ,ffmpeg->video_st->time_base.den ,(ffmpeg->pkt.pts-ffmpeg->last_pts) ); } if (ffmpeg->pkt.pts <= ffmpeg->last_pts){ //We have a problem with our motion loop timing and sending frames or the rounding into the PTS. if (ffmpeg->test_mode == TRUE){ MOTION_LOG(INF, TYPE_ENCODER, NO_ERRNO, _("BAD TIMING!! Frame skipped.")); } return -1; } ffmpeg->last_pts = ffmpeg->pkt.pts; ffmpeg->pkt.dts=ffmpeg->pkt.pts; } return 0; } static int ffmpeg_set_quality(struct ffmpeg *ffmpeg){ ffmpeg->opts = 0; if (ffmpeg->quality > 100) ffmpeg->quality = 100; if (ffmpeg->ctx_codec->codec_id == MY_CODEC_ID_H264 || ffmpeg->ctx_codec->codec_id == MY_CODEC_ID_HEVC){ if (ffmpeg->quality <= 0) ffmpeg->quality = 45; // default to 45% quality av_dict_set(&ffmpeg->opts, "preset", "ultrafast", 0); av_dict_set(&ffmpeg->opts, "tune", "zerolatency", 0); if ((strcmp(ffmpeg->codec->name, "h264_omx") == 0) || (strcmp(ffmpeg->codec->name, "mpeg4_omx") == 0)) { // H264 OMX encoder quality can only be controlled via bit_rate // bit_rate = ffmpeg->width * ffmpeg->height * ffmpeg->fps * quality_factor ffmpeg->quality = (ffmpeg->width * ffmpeg->height * ffmpeg->fps * ffmpeg->quality) >> 7; // Clip bit rate to min if (ffmpeg->quality < 4000) // magic number ffmpeg->quality = 4000; ffmpeg->ctx_codec->profile = FF_PROFILE_H264_HIGH; ffmpeg->ctx_codec->bit_rate = ffmpeg->quality; } else { // Control other H264 encoders quality via CRF char crf[10]; ffmpeg->quality = (int)(( (100-ffmpeg->quality) * 51)/100); snprintf(crf, 10, "%d", ffmpeg->quality); av_dict_set(&ffmpeg->opts, "crf", crf, 0); } } else { /* The selection of 8000 is a subjective number based upon viewing output files */ if (ffmpeg->quality > 0){ ffmpeg->quality =(int)(((100-ffmpeg->quality)*(100-ffmpeg->quality)*(100-ffmpeg->quality) * 8000) / 1000000) + 1; ffmpeg->ctx_codec->flags |= MY_CODEC_FLAG_QSCALE; ffmpeg->ctx_codec->global_quality=ffmpeg->quality; } } MOTION_LOG(INF, TYPE_ENCODER, NO_ERRNO ,_("%s codec vbr/crf/bit_rate: %d"), ffmpeg->codec->name, ffmpeg->quality); return 0; } static int ffmpeg_codec_is_blacklisted(const char *codec_name){ static const char *blacklisted_codec[] = { /* h264_omx & ffmpeg combination locks up on Raspberry Pi. * To use h264_omx encoder and workaround the lock up issue: * - disable input_zerocopy in ffmpeg omx.c:omx_encode_init function. * - remove the "h264_omx" from this blacklist. * More information: https://github.com/Motion-Project/motion/issues/433 */ "h264_omx", }; size_t i; for (i = 0; i < sizeof(blacklisted_codec)/sizeof(blacklisted_codec[0]); i++) { if (strcmp(codec_name, blacklisted_codec[i]) == 0) return 1; } return 0; } static int ffmpeg_set_codec(struct ffmpeg *ffmpeg){ int retcd; char errstr[128]; int chkrate; size_t codec_name_len = strcspn(ffmpeg->codec_name, ":"); ffmpeg->codec = NULL; if (ffmpeg->codec_name[codec_name_len]) { if (ffmpeg_codec_is_blacklisted(&ffmpeg->codec_name[codec_name_len+1])) { MOTION_LOG(WRN, TYPE_ENCODER, NO_ERRNO ,_("Preferred codec %s has been blacklisted") ,&ffmpeg->codec_name[codec_name_len+1]); } else { ffmpeg->codec = avcodec_find_encoder_by_name(&ffmpeg->codec_name[codec_name_len+1]); if ((ffmpeg->oc->oformat) && (ffmpeg->codec != NULL)) { ffmpeg->oc->oformat->video_codec = ffmpeg->codec->id; } else if (ffmpeg->codec == NULL) { MOTION_LOG(WRN, TYPE_ENCODER, NO_ERRNO ,_("Preferred codec %s not found") ,&ffmpeg->codec_name[codec_name_len+1]); } } } if (!ffmpeg->codec) ffmpeg->codec = avcodec_find_encoder(ffmpeg->oc->oformat->video_codec); if (!ffmpeg->codec) { MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO ,_("Codec %s not found"), ffmpeg->codec_name); ffmpeg_free_context(ffmpeg); return -1; } if (ffmpeg->codec_name[codec_name_len]) MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO,_("Using codec %s"), ffmpeg->codec->name); #if (LIBAVFORMAT_VERSION_MAJOR >= 58) || ((LIBAVFORMAT_VERSION_MAJOR == 57) && (LIBAVFORMAT_VERSION_MINOR >= 41)) //If we provide the codec to this, it results in a memory leak. ffmpeg ticket: 5714 ffmpeg->video_st = avformat_new_stream(ffmpeg->oc, NULL); if (!ffmpeg->video_st) { MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not alloc stream")); ffmpeg_free_context(ffmpeg); return -1; } ffmpeg->ctx_codec = avcodec_alloc_context3(ffmpeg->codec); if (ffmpeg->ctx_codec == NULL) { MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Failed to allocate decoder!")); ffmpeg_free_context(ffmpeg); return -1; } #else ffmpeg->video_st = avformat_new_stream(ffmpeg->oc, ffmpeg->codec); if (!ffmpeg->video_st) { MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not alloc stream")); ffmpeg_free_context(ffmpeg); return -1; } ffmpeg->ctx_codec = ffmpeg->video_st->codec; #endif if (ffmpeg->tlapse != TIMELAPSE_NONE) { ffmpeg->ctx_codec->gop_size = 1; } else { if (ffmpeg->fps <= 5){ ffmpeg->ctx_codec->gop_size = 1; } else if (ffmpeg->fps > 30){ ffmpeg->ctx_codec->gop_size = 15; } else { ffmpeg->ctx_codec->gop_size = (ffmpeg->fps / 2); } ffmpeg->gop_cnt = ffmpeg->ctx_codec->gop_size - 1; } /* For certain containers, setting the fps to very low numbers results in ** a very poor quality playback. We can set the FPS to a higher number and ** then let the PTS display the frames correctly. */ if ((ffmpeg->tlapse == TIMELAPSE_NONE) && (ffmpeg->fps <= 5)){ if ((strcmp(ffmpeg->codec_name, "msmpeg4") == 0) || (strcmp(ffmpeg->codec_name, "flv") == 0) || (strcmp(ffmpeg->codec_name, "mov") == 0) || (strcmp(ffmpeg->codec_name, "mp4") == 0) || (strcmp(ffmpeg->codec_name, "hevc") == 0) || (strcmp(ffmpeg->codec_name, "mpeg4") == 0)) { MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO, "Low fps. Encoding %d frames into a %d frames container.", ffmpeg->fps, 10); ffmpeg->fps = 10; } } ffmpeg->ctx_codec->codec_id = ffmpeg->oc->oformat->video_codec; ffmpeg->ctx_codec->codec_type = AVMEDIA_TYPE_VIDEO; ffmpeg->ctx_codec->bit_rate = ffmpeg->bps; ffmpeg->ctx_codec->width = ffmpeg->width; ffmpeg->ctx_codec->height = ffmpeg->height; ffmpeg->ctx_codec->time_base.num = 1; ffmpeg->ctx_codec->time_base.den = ffmpeg->fps; ffmpeg->ctx_codec->pix_fmt = MY_PIX_FMT_YUV420P; ffmpeg->ctx_codec->max_b_frames = 0; if (strcmp(ffmpeg->codec_name, "ffv1") == 0){ ffmpeg->ctx_codec->strict_std_compliance = -2; ffmpeg->ctx_codec->level = 3; } ffmpeg->ctx_codec->flags |= MY_CODEC_FLAG_GLOBAL_HEADER; retcd = ffmpeg_set_quality(ffmpeg); if (retcd < 0){ MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Unable to set quality")); return -1; } retcd = avcodec_open2(ffmpeg->ctx_codec, ffmpeg->codec, &ffmpeg->opts); if (retcd < 0) { if (ffmpeg->codec->supported_framerates) { const AVRational *fps = ffmpeg->codec->supported_framerates; while (fps->num) { MOTION_LOG(INF, TYPE_ENCODER, NO_ERRNO ,_("Reported FPS Supported %d/%d"), fps->num, fps->den); fps++; } } chkrate = 1; while ((chkrate < 36) && (retcd != 0)) { ffmpeg->ctx_codec->time_base.den = chkrate; retcd = avcodec_open2(ffmpeg->ctx_codec, ffmpeg->codec, &ffmpeg->opts); chkrate++; } if (retcd < 0){ av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not open codec %s"),errstr); av_dict_free(&ffmpeg->opts); ffmpeg_free_context(ffmpeg); return -1; } } av_dict_free(&ffmpeg->opts); return 0; } static int ffmpeg_set_stream(struct ffmpeg *ffmpeg){ #if (LIBAVFORMAT_VERSION_MAJOR >= 58) || ((LIBAVFORMAT_VERSION_MAJOR == 57) && (LIBAVFORMAT_VERSION_MINOR >= 41)) int retcd; char errstr[128]; retcd = avcodec_parameters_from_context(ffmpeg->video_st->codecpar,ffmpeg->ctx_codec); if (retcd < 0) { av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO ,_("Failed to copy decoder parameters!: %s"), errstr); ffmpeg_free_context(ffmpeg); return -1; } #endif ffmpeg->video_st->time_base = (AVRational){1, ffmpeg->fps}; return 0; } static int ffmpeg_set_picture(struct ffmpeg *ffmpeg){ ffmpeg->picture = my_frame_alloc(); if (!ffmpeg->picture) { MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("could not alloc frame")); ffmpeg_free_context(ffmpeg); return -1; } /* Take care of variable bitrate setting. */ if (ffmpeg->quality) ffmpeg->picture->quality = ffmpeg->quality; ffmpeg->picture->linesize[0] = ffmpeg->ctx_codec->width; ffmpeg->picture->linesize[1] = ffmpeg->ctx_codec->width / 2; ffmpeg->picture->linesize[2] = ffmpeg->ctx_codec->width / 2; ffmpeg->picture->format = ffmpeg->ctx_codec->pix_fmt; ffmpeg->picture->width = ffmpeg->ctx_codec->width; ffmpeg->picture->height = ffmpeg->ctx_codec->height; return 0; } static int ffmpeg_set_outputfile(struct ffmpeg *ffmpeg){ int retcd; char errstr[128]; #if (LIBAVFORMAT_VERSION_MAJOR < 58) snprintf(ffmpeg->oc->filename, sizeof(ffmpeg->oc->filename), "%s", ffmpeg->filename); #endif /* Open the output file, if needed. */ if ((ffmpeg_timelapse_exists(ffmpeg->filename) == 0) || (ffmpeg->tlapse != TIMELAPSE_APPEND)) { if (!(ffmpeg->oc->oformat->flags & AVFMT_NOFILE)) { if (avio_open(&ffmpeg->oc->pb, ffmpeg->filename, MY_FLAG_WRITE) < 0) { if (errno == ENOENT) { if (create_path(ffmpeg->filename) == -1) { ffmpeg_free_context(ffmpeg); return -1; } if (avio_open(&ffmpeg->oc->pb, ffmpeg->filename, MY_FLAG_WRITE) < 0) { MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO ,_("error opening file %s"), ffmpeg->filename); ffmpeg_free_context(ffmpeg); return -1; } /* Permission denied */ } else if (errno == EACCES) { MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO ,_("Permission denied. %s"),ffmpeg->filename); ffmpeg_free_context(ffmpeg); return -1; } else { MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO ,_("Error opening file %s"), ffmpeg->filename); ffmpeg_free_context(ffmpeg); return -1; } } } /* Write the stream header, For the TIMELAPSE_APPEND * we write the data via standard file I/O so we close the * items here */ retcd = avformat_write_header(ffmpeg->oc, NULL); if (retcd < 0){ av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO ,_("Could not write ffmpeg header %s"),errstr); ffmpeg_free_context(ffmpeg); return -1; } if (ffmpeg->tlapse == TIMELAPSE_APPEND) { av_write_trailer(ffmpeg->oc); avio_close(ffmpeg->oc->pb); } } return 0; } static int ffmpeg_flush_codec(struct ffmpeg *ffmpeg){ #if (LIBAVFORMAT_VERSION_MAJOR >= 58) || ((LIBAVFORMAT_VERSION_MAJOR == 57) && (LIBAVFORMAT_VERSION_MINOR >= 41)) //ffmpeg version 3.1 and after int retcd; int recv_cd = 0; char errstr[128]; if (ffmpeg->passthrough){ return 0; } retcd = 0; recv_cd = 0; if (ffmpeg->tlapse == TIMELAPSE_NONE) { retcd = avcodec_send_frame(ffmpeg->ctx_codec, NULL); if (retcd < 0 ){ av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO ,_("Error entering draining mode:%s"),errstr); return -1; } while (recv_cd != AVERROR_EOF){ av_init_packet(&ffmpeg->pkt); ffmpeg->pkt.data = NULL; ffmpeg->pkt.size = 0; recv_cd = avcodec_receive_packet(ffmpeg->ctx_codec, &ffmpeg->pkt); if (recv_cd != AVERROR_EOF){ if (recv_cd < 0){ av_strerror(recv_cd, errstr, sizeof(errstr)); MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO ,_("Error draining codec:%s"),errstr); my_packet_unref(ffmpeg->pkt); return -1; } retcd = av_write_frame(ffmpeg->oc, &ffmpeg->pkt); if (retcd < 0) { MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO ,_("Error writing draining video frame")); return -1; } } my_packet_unref(ffmpeg->pkt); } } return 0; #else /* Dummy to kill warnings. No draining in older ffmpeg versions */ if (ffmpeg) { return 0; } else{ return 0; } #endif } static int ffmpeg_put_frame(struct ffmpeg *ffmpeg, const struct timeval *tv1){ int retcd; av_init_packet(&ffmpeg->pkt); ffmpeg->pkt.data = NULL; ffmpeg->pkt.size = 0; retcd = ffmpeg_set_pts(ffmpeg, tv1); if (retcd < 0) { //If there is an error, it has already been reported. my_packet_unref(ffmpeg->pkt); return 0; } retcd = ffmpeg_encode_video(ffmpeg); if (retcd != 0){ if (retcd != -2){ MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Error while encoding picture")); } my_packet_unref(ffmpeg->pkt); return retcd; } if (ffmpeg->tlapse == TIMELAPSE_APPEND) { retcd = ffmpeg_timelapse_append(ffmpeg, ffmpeg->pkt); } else { retcd = av_write_frame(ffmpeg->oc, &ffmpeg->pkt); } my_packet_unref(ffmpeg->pkt); if (retcd < 0) { MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Error while writing video frame")); return -1; } return retcd; } static void ffmpeg_passthru_reset(struct ffmpeg *ffmpeg){ /* Reset the written flag at start of each event */ int indx; pthread_mutex_lock(&ffmpeg->rtsp_data->mutex_pktarray); for(indx = 0; indx < ffmpeg->rtsp_data->pktarray_size; indx++) { ffmpeg->rtsp_data->pktarray[indx].iswritten = FALSE; } pthread_mutex_unlock(&ffmpeg->rtsp_data->mutex_pktarray); } static void ffmpeg_passthru_write(struct ffmpeg *ffmpeg, int indx){ /* Write the packet in the buffer at indx to file */ char errstr[128]; int retcd; av_init_packet(&ffmpeg->pkt); ffmpeg->pkt.data = NULL; ffmpeg->pkt.size = 0; ffmpeg->rtsp_data->pktarray[indx].iswritten = TRUE; retcd = my_copy_packet(&ffmpeg->pkt, &ffmpeg->rtsp_data->pktarray[indx].packet); if (retcd < 0) { av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(INF, TYPE_ENCODER, NO_ERRNO, "av_copy_packet: %s",errstr); my_packet_unref(ffmpeg->pkt); return; } retcd = ffmpeg_set_pktpts(ffmpeg, &ffmpeg->rtsp_data->pktarray[indx].timestamp_tv); if (retcd < 0) { my_packet_unref(ffmpeg->pkt); return; } retcd = av_write_frame(ffmpeg->oc, &ffmpeg->pkt); my_packet_unref(ffmpeg->pkt); if (retcd < 0) { av_strerror(retcd, errstr, sizeof(errstr)); MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO ,_("Error while writing video frame: %s"),errstr); return; } } static int ffmpeg_passthru_put(struct ffmpeg *ffmpeg, struct image_data *img_data){ int idnbr_image, idnbr_lastwritten, idnbr_stop, idnbr_firstkey; int indx, indx_lastwritten, indx_firstkey; if (ffmpeg->rtsp_data == NULL) return -1; if ((ffmpeg->rtsp_data->status == RTSP_NOTCONNECTED ) || (ffmpeg->rtsp_data->status == RTSP_RECONNECTING ) ){ return 0; } if (ffmpeg->high_resolution){ idnbr_image = img_data->idnbr_high; } else { idnbr_image = img_data->idnbr_norm; } pthread_mutex_lock(&ffmpeg->rtsp_data->mutex_pktarray); idnbr_lastwritten = 0; idnbr_firstkey = idnbr_image; idnbr_stop = 0; indx_lastwritten = -1; indx_firstkey = -1; for(indx = 0; indx < ffmpeg->rtsp_data->pktarray_size; indx++) { if ((ffmpeg->rtsp_data->pktarray[indx].iswritten) && (ffmpeg->rtsp_data->pktarray[indx].idnbr > idnbr_lastwritten)){ idnbr_lastwritten=ffmpeg->rtsp_data->pktarray[indx].idnbr; indx_lastwritten = indx; } if ((ffmpeg->rtsp_data->pktarray[indx].idnbr > idnbr_stop) && (ffmpeg->rtsp_data->pktarray[indx].idnbr <= idnbr_image)){ idnbr_stop=ffmpeg->rtsp_data->pktarray[indx].idnbr; } if ((ffmpeg->rtsp_data->pktarray[indx].iskey) && (ffmpeg->rtsp_data->pktarray[indx].idnbr <= idnbr_firstkey)){ idnbr_firstkey=ffmpeg->rtsp_data->pktarray[indx].idnbr; indx_firstkey = indx; } } if (idnbr_stop == 0){ pthread_mutex_unlock(&ffmpeg->rtsp_data->mutex_pktarray); return 0; } if (indx_lastwritten != -1){ indx = indx_lastwritten; } else if (indx_firstkey != -1) { indx = indx_firstkey; } else { indx = 0; } while (TRUE){ if ((!ffmpeg->rtsp_data->pktarray[indx].iswritten) && (ffmpeg->rtsp_data->pktarray[indx].packet.size > 0) && (ffmpeg->rtsp_data->pktarray[indx].idnbr > idnbr_lastwritten) && (ffmpeg->rtsp_data->pktarray[indx].idnbr <= idnbr_image)) { ffmpeg_passthru_write(ffmpeg, indx); } if (ffmpeg->rtsp_data->pktarray[indx].idnbr == idnbr_stop) break; indx++; if (indx == ffmpeg->rtsp_data->pktarray_size ) indx = 0; } pthread_mutex_unlock(&ffmpeg->rtsp_data->mutex_pktarray); return 0; } static int ffmpeg_passthru_codec(struct ffmpeg *ffmpeg){ int retcd; AVStream *stream_in; if (ffmpeg->rtsp_data == NULL){ MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("RTSP context not available.")); return -1; } pthread_mutex_lock(&ffmpeg->rtsp_data->mutex_transfer); if ((ffmpeg->rtsp_data->status == RTSP_NOTCONNECTED ) || (ffmpeg->rtsp_data->status == RTSP_RECONNECTING ) ){ MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO ,_("rtsp camera not ready for pass-through.")); pthread_mutex_unlock(&ffmpeg->rtsp_data->mutex_transfer); return -1; } if (strcmp(ffmpeg->codec_name, "mp4") != 0){ MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO ,_("pass-through mode enabled. Changing to MP4 container.")); ffmpeg->codec_name = "mp4"; } retcd = ffmpeg_get_oformat(ffmpeg); if (retcd < 0 ) { MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not get codec!")); pthread_mutex_unlock(&ffmpeg->rtsp_data->mutex_transfer); return -1; } #if (LIBAVFORMAT_VERSION_MAJOR >= 58) || ((LIBAVFORMAT_VERSION_MAJOR == 57) && (LIBAVFORMAT_VERSION_MINOR >= 41)) stream_in = ffmpeg->rtsp_data->transfer_format->streams[0]; ffmpeg->oc->oformat->video_codec = stream_in->codecpar->codec_id; ffmpeg->video_st = avformat_new_stream(ffmpeg->oc, NULL); if (!ffmpeg->video_st) { MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not alloc stream")); pthread_mutex_unlock(&ffmpeg->rtsp_data->mutex_transfer); return -1; } retcd = avcodec_parameters_copy(ffmpeg->video_st->codecpar, stream_in->codecpar); if (retcd < 0){ MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Unable to copy codec parameters")); pthread_mutex_unlock(&ffmpeg->rtsp_data->mutex_transfer); return -1; } ffmpeg->video_st->codecpar->codec_tag = 0; #elif (LIBAVFORMAT_VERSION_MAJOR >= 55) stream_in = ffmpeg->rtsp_data->transfer_format->streams[0]; ffmpeg->video_st = avformat_new_stream(ffmpeg->oc, stream_in->codec->codec); if (!ffmpeg->video_st) { MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not alloc stream")); pthread_mutex_unlock(&ffmpeg->rtsp_data->mutex_transfer); return -1; } retcd = avcodec_copy_context(ffmpeg->video_st->codec, stream_in->codec); if (retcd < 0){ MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Unable to copy codec parameters")); pthread_mutex_unlock(&ffmpeg->rtsp_data->mutex_transfer); return -1; } ffmpeg->video_st->codec->flags |= MY_CODEC_FLAG_GLOBAL_HEADER; ffmpeg->video_st->codec->codec_tag = 0; #else /* This is disabled in the util_check_passthrough but we need it here for compiling */ pthread_mutex_unlock(&ffmpeg->rtsp_data->mutex_transfer); MOTION_LOG(INF, TYPE_ENCODER, NO_ERRNO, _("Pass-through disabled. ffmpeg too old")); return -1; #endif ffmpeg->video_st->time_base = stream_in->time_base; pthread_mutex_unlock(&ffmpeg->rtsp_data->mutex_transfer); MOTION_LOG(INF, TYPE_ENCODER, NO_ERRNO, "Pass-through stream opened"); return 0; } void ffmpeg_avcodec_log(void *ignoreme ATTRIBUTE_UNUSED, int errno_flag ATTRIBUTE_UNUSED, const char *fmt, va_list vl){ char buf[1024]; char *end; /* Valgrind occasionally reports use of uninitialized values in here when we interrupt * some rtsp functions. The offending value is either fmt or vl and seems to be from a * debug level of av functions. To address it we flatten the message after we know * the log level. Now we put the avcodec messages to INF level since their error * are not necessarily our errors. */ if (errno_flag <= AV_LOG_WARNING){ /* 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; } MOTION_LOG(INF, TYPE_ENCODER, NO_ERRNO, "%s", buf); } } #endif /* HAVE_FFMPEG */ /**************************************************************************** **************************************************************************** ****************************************************************************/ void ffmpeg_global_init(void){ #ifdef HAVE_FFMPEG MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO ,_("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); #if (LIBAVFORMAT_VERSION_MAJOR < 58) /* TODO: Determine if this is even needed for older versions */ av_register_all(); avcodec_register_all(); #endif avformat_network_init(); avdevice_register_all(); av_log_set_callback((void *)ffmpeg_avcodec_log); #if (LIBAVFORMAT_VERSION_MAJOR < 58) /* TODO: Determine if this is even needed for older versions */ int ret; ret = av_lockmgr_register(ffmpeg_lockmgr_cb); if (ret < 0) { MOTION_LOG(EMG, TYPE_ALL, SHOW_ERRNO, _("av_lockmgr_register failed (%d)"), ret); exit(1); } #endif #else /* No FFMPEG */ MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO, _("No ffmpeg functionality included")); #endif /* HAVE_FFMPEG */ } void ffmpeg_global_deinit(void) { #ifdef HAVE_FFMPEG avformat_network_deinit(); #if (LIBAVFORMAT_VERSION_MAJOR < 58) /* TODO Determine if this is even needed for old versions */ if (av_lockmgr_register(NULL) < 0) { MOTION_LOG(EMG, TYPE_ALL, SHOW_ERRNO ,_("av_lockmgr_register reset failed on cleanup")); } #endif #else /* No FFMPEG */ MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO, _("No ffmpeg functionality included")); #endif /* HAVE_FFMPEG */ } int ffmpeg_open(struct ffmpeg *ffmpeg){ #ifdef HAVE_FFMPEG int retcd; ffmpeg->oc = avformat_alloc_context(); if (!ffmpeg->oc) { MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not allocate output context")); ffmpeg_free_context(ffmpeg); return -1; } if (ffmpeg->passthrough) { retcd = ffmpeg_passthru_codec(ffmpeg); if (retcd < 0 ) { MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not setup passthru!")); ffmpeg_free_context(ffmpeg); return -1; } ffmpeg_passthru_reset(ffmpeg); } else { retcd = ffmpeg_get_oformat(ffmpeg); if (retcd < 0 ) { MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not get codec!")); ffmpeg_free_context(ffmpeg); return -1; } retcd = ffmpeg_set_codec(ffmpeg); if (retcd < 0 ) { MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Failed to allocate codec!")); return -1; } retcd = ffmpeg_set_stream(ffmpeg); if (retcd < 0){ MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not set the stream")); return -1; } retcd = ffmpeg_set_picture(ffmpeg); if (retcd < 0){ MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not set the stream")); return -1; } } retcd = ffmpeg_set_outputfile(ffmpeg); if (retcd < 0){ MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not set the stream")); return -1; } return 0; #else /* No FFMPEG */ if (ffmpeg) { MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO, _("No ffmpeg functionality included")); } return -1; #endif /* HAVE_FFMPEG */ } void ffmpeg_close(struct ffmpeg *ffmpeg){ #ifdef HAVE_FFMPEG if (ffmpeg != NULL) { if (ffmpeg_flush_codec(ffmpeg) < 0){ MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Error flushing codec")); } if (ffmpeg->oc->pb != NULL){ if (ffmpeg->tlapse != TIMELAPSE_APPEND) { av_write_trailer(ffmpeg->oc); } if (!(ffmpeg->oc->oformat->flags & AVFMT_NOFILE)) { if (ffmpeg->tlapse != TIMELAPSE_APPEND) { avio_close(ffmpeg->oc->pb); } } } ffmpeg_free_context(ffmpeg); } #else if (ffmpeg != NULL) free(ffmpeg); #endif // HAVE_FFMPEG } int ffmpeg_put_image(struct ffmpeg *ffmpeg, struct image_data *img_data, const struct timeval *tv1){ #ifdef HAVE_FFMPEG int retcd = 0; int cnt = 0; unsigned char *image; if (ffmpeg->passthrough) { retcd = ffmpeg_passthru_put(ffmpeg, img_data); return retcd; } if (ffmpeg->picture) { if (ffmpeg->high_resolution){ image = img_data->image_high; } else { image = img_data->image_norm; } /* Setup pointers and line widths. */ ffmpeg->picture->data[0] = image; ffmpeg->picture->data[1] = image + (ffmpeg->ctx_codec->width * ffmpeg->ctx_codec->height); ffmpeg->picture->data[2] = ffmpeg->picture->data[1] + ((ffmpeg->ctx_codec->width * ffmpeg->ctx_codec->height) / 4); ffmpeg->gop_cnt ++; if (ffmpeg->gop_cnt == ffmpeg->ctx_codec->gop_size ){ ffmpeg->picture->pict_type = AV_PICTURE_TYPE_I; ffmpeg->picture->key_frame = 1; ffmpeg->gop_cnt = 0; } else { ffmpeg->picture->pict_type = AV_PICTURE_TYPE_P; ffmpeg->picture->key_frame = 0; } /* 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 */ retcd = ffmpeg_put_frame(ffmpeg, tv1); while ((retcd == -2) && (ffmpeg->tlapse != TIMELAPSE_NONE)) { retcd = ffmpeg_put_frame(ffmpeg, tv1); cnt++; if (cnt > 50){ MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO ,_("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, _("Buffered packet")); } } return retcd; #else if (ffmpeg && img_data && tv1) { MOTION_LOG(DBG, TYPE_ENCODER, NO_ERRNO, _("No ffmpeg support")); } return 0; #endif // HAVE_FFMPEG } void ffmpeg_reset_movie_start_time(struct ffmpeg *ffmpeg, const struct timeval *tv1){ #ifdef HAVE_FFMPEG int64_t one_frame_interval = av_rescale_q(1,(AVRational){1, ffmpeg->fps},ffmpeg->video_st->time_base); if (one_frame_interval <= 0) one_frame_interval = 1; ffmpeg->base_pts = ffmpeg->last_pts + one_frame_interval; ffmpeg->start_time.tv_sec = tv1->tv_sec; ffmpeg->start_time.tv_usec = tv1->tv_usec; #else if (ffmpeg && tv1) { MOTION_LOG(DBG, TYPE_ENCODER, NO_ERRNO, _("No ffmpeg support")); } #endif // HAVE_FFMPEG }