/*
 *
 * 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 "config.h"

#ifdef HAVE_FFMPEG

#include "ffmpeg.h"
#include "motion.h"

#define AVSTREAM_CODEC_PTR(avs_ptr) (avs_ptr->codec)


/****************************************************************************
 *  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
/*********************************************/
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
}
/*********************************************/

/****************************************************************************
 ****************************************************************************
 ****************************************************************************/
/**
 * timelapse_exists
 *      Determines whether the timelapse file exists
 *
 * Returns
 *      0:  File doesn't exist
 *      1:  File exists
 */
static int timelapse_exists(const char *fname){
    FILE *file;
    file = fopen(fname, "r");
    if (file)
    {
        fclose(file);
        return 1;
    }
    return 0;
}
static int timelapse_append(struct ffmpeg *ffmpeg, AVPacket pkt){
    FILE *file;

    file = fopen(ffmpeg->oc->filename, "a");
    if (!file) return -1;

    fwrite(pkt.data,1,pkt.size,file);

    fclose(file);

    return 0;
}

/** locking callback for use with ffmpeg's av_lockmgr_register */
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;
}

/**
 * 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,
 *      the format is avi; for mpeg1 codec, the format is mpeg. The filename has
 *      to be passed, because it gets the appropriate extension appended onto it.
 *
 *  Returns
 *      AVOutputFormat pointer or NULL if any error happens.
 */
static AVOutputFormat *get_oformat(const char *codec, char *filename){
    const char *ext;
    AVOutputFormat *of = NULL;
    /*
     * Here, we use guess_format to automatically setup the codec information.
     * If we are using msmpeg4, manually set that codec here.
     * We also dynamically add the file extension to the filename here.
     */
    if (strcmp(codec, "tlapse") == 0) {
        ext = ".mpg";
        of = av_guess_format ("mpeg2video", NULL, NULL);
        if (of) of->video_codec = MY_CODEC_ID_MPEG2VIDEO;
    } else if (strcmp(codec, "mpeg4") == 0) {
        ext = ".avi";
        of = av_guess_format("avi", NULL, NULL);
    } else if (strcmp(codec, "msmpeg4") == 0) {
        ext = ".avi";
        of = av_guess_format("avi", NULL, NULL);
        /* Manually override the codec id. */
        if (of) of->video_codec = MY_CODEC_ID_MSMPEG4V2;
    } else if (strcmp(codec, "swf") == 0) {
        ext = ".swf";
        of = av_guess_format("swf", NULL, NULL);
    } else if (strcmp(codec, "flv") == 0) {
        ext = ".flv";
        of = av_guess_format("flv", NULL, NULL);
        of->video_codec = MY_CODEC_ID_FLV1;
    } else if (strcmp(codec, "ffv1") == 0) {
        ext = ".avi";
        of = av_guess_format("avi", NULL, NULL);
        if (of) of->video_codec = MY_CODEC_ID_FFV1;
    } else if (strcmp(codec, "mov") == 0) {
        ext = ".mov";
        of = av_guess_format("mov", NULL, NULL);
	} else if (strcmp (codec, "mp4") == 0){
      ext = ".mp4";
      of = av_guess_format ("mp4", NULL, NULL);
      of->video_codec = MY_CODEC_ID_H264;
    } else if (strcmp (codec, "mkv") == 0){
      ext = ".mkv";
      of = av_guess_format ("matroska", NULL, NULL);
      of->video_codec = MY_CODEC_ID_H264;
    } else if (strcmp (codec, "hevc") == 0){
      ext = ".mp4";
      of = av_guess_format ("mp4", NULL, NULL);
      of->video_codec = MY_CODEC_ID_HEVC;
    } else {
        MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: ffmpeg_video_codec option value"
                   " %s is not supported", codec);
        return NULL;
    }

    if (!of) {
        MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Could not guess format for %s", codec);
        return NULL;
    }

    /* The 4 allows for ".avi" or ".mpg" to be appended. */
    strncat(filename, ext, 4);

    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.
 *
 *  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)
{
    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 {
            ffmpeg->vbr = 28;
        }
        av_dict_set(&opts, "preset", "ultrafast", 0);
        char crf[4];
        snprintf(crf, 4, "%d",ffmpeg->vbr);
        av_dict_set(&opts, "crf", crf, 0);
        av_dict_set(&opts, "tune", "zerolatency", 0);
    } else {
        /* The selection of 8000 in the else is a subjective number based upon viewing output files */
        if (vbr > 0){
            ffmpeg->vbr =(int)(((100-vbr)*(100-vbr)*(100-vbr) * 8000) / 1000000) + 1;
            c->flags |= CODEC_FLAG_QSCALE;
            c->global_quality=ffmpeg->vbr;
        }
    }

    MOTION_LOG(INF, TYPE_ENCODER, NO_ERRNO, "%s vbr/crf for codec: %d", ffmpeg->vbr);

    if (strcmp(ffmpeg_video_codec, "ffv1") == 0) c->strict_std_compliance = -2;
    c->flags |= CODEC_FLAG_GLOBAL_HEADER;

    retcd = avcodec_open2(c, codec, &opts);
    if (retcd < 0) {
        if (codec->supported_framerates) {
            const AVRational *fps = codec->supported_framerates;
            while (fps->num) {
                MOTION_LOG(INF, TYPE_ENCODER, NO_ERRNO, "%s Reported FPS Supported %d/%d", fps->num, fps->den);
                fps++;
            }
        }
        int chkrate = 1;
        while ((chkrate < 36) && (retcd != 0)) {
            c->time_base.den = chkrate;
            retcd = avcodec_open2(c, codec, &opts);
            chkrate++;
        }
        if (retcd < 0){
            av_strerror(retcd, errstr, sizeof(errstr));
            MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Could not open codec %s",errstr);
            av_dict_free(&opts);
            ffmpeg_cleanups(ffmpeg);
            return NULL;
        }

    }
    av_dict_free(&opts);
    MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO, "%s Selected Output FPS %d", c->time_base.den);

    ffmpeg->last_pts = 0;
    ffmpeg->video_st->time_base.num = 1;
    ffmpeg->video_st->time_base.den = 1000;
    if ((strcmp(ffmpeg_video_codec, "swf") == 0) ||
        (ffmpeg->tlapse != TIMELAPSE_NONE) ) {
        ffmpeg->video_st->time_base.num = 1;
        ffmpeg->video_st->time_base.den = rate;
        if ((rate > 50) && (strcmp(ffmpeg_video_codec, "swf") == 0)){
            MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO, "%s The FPS could be too high for the SWF container.  Consider other choices.");
        }
    }

    ffmpeg->video_outbuf = NULL;
    if (!(ffmpeg->oc->oformat->flags & AVFMT_RAWPICTURE)) {
        ffmpeg->video_outbuf_size = ffmpeg->c->width * 512;
        ffmpeg->video_outbuf = mymalloc(ffmpeg->video_outbuf_size);
    }

    ffmpeg->picture = my_frame_alloc();

    if (!ffmpeg->picture) {
        MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: could not alloc frame");
        ffmpeg_cleanups(ffmpeg);
        return NULL;
    }

    /* Set the frame data. */
    ffmpeg->picture->data[0] = y;
    ffmpeg->picture->data[1] = u;
    ffmpeg->picture->data[2] = v;
    ffmpeg->picture->linesize[0] = ffmpeg->c->width;
    ffmpeg->picture->linesize[1] = ffmpeg->c->width / 2;
    ffmpeg->picture->linesize[2] = ffmpeg->c->width / 2;

    /* Open the output file, if needed. */
    if ((timelapse_exists(filename) == 0) || (ffmpeg->tlapse != TIMELAPSE_APPEND)) {
        if (!(ffmpeg->oc->oformat->flags & AVFMT_NOFILE)) {
            if (avio_open(&ffmpeg->oc->pb, filename, MY_FLAG_WRITE) < 0) {
                if (errno == ENOENT) {
                    if (create_path(filename) == -1) {
                        ffmpeg_cleanups(ffmpeg);
                        return NULL;
                    }
                    if (avio_open(&ffmpeg->oc->pb, filename, MY_FLAG_WRITE) < 0) {
                        MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: error opening file %s", filename);
                        ffmpeg_cleanups(ffmpeg);
                        return NULL;
                    }
                    /* Permission denied */
                } else if (errno ==  EACCES) {
                    MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO,"%s: Permission denied. %s",filename);
                    ffmpeg_cleanups(ffmpeg);
                    return NULL;
                } else {
                    MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: Error opening file %s", filename);
                    ffmpeg_cleanups(ffmpeg);
                    return NULL;
                }
            }
        }
        gettimeofday(&ffmpeg->start_time, NULL);

        /* 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, "%s: Could not write ffmpeg header %s",errstr);
            ffmpeg_cleanups(ffmpeg);
            return NULL;
        }
        if (ffmpeg->tlapse == TIMELAPSE_APPEND) {
            av_write_trailer(ffmpeg->oc);
            avio_close(ffmpeg->oc->pb);
        }

    }
    return ffmpeg;
}
/**
 * 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_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);
    }
    /* Close each codec */
    if (ffmpeg->video_st) {
        avcodec_close(AVSTREAM_CODEC_PTR(ffmpeg->video_st));
    }
    av_freep(&ffmpeg->picture);
    free(ffmpeg->video_outbuf);

    if (!(ffmpeg->oc->oformat->flags & AVFMT_NOFILE)) {
        if (ffmpeg->tlapse != TIMELAPSE_APPEND) {
            avio_close(ffmpeg->oc->pb);
        }
    }
    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){

    /* 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;

    retcd = ffmpeg_put_frame(ffmpeg, ffmpeg->picture);
    while ((retcd == -2) && (ffmpeg->tlapse != TIMELAPSE_NONE)) {
        retcd = ffmpeg_put_frame(ffmpeg, ffmpeg->picture);
        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;
}
/**
 * ffmpeg_put_other_image
 *      Puts an arbitrary picture defined by y, u and v.
 *
 * Returns
 *      Number of bytes written by ffmpeg_put_frame
 *      -1 if any error happens in ffmpeg_put_frame
 *       0 if error allocating picture.
 */
int ffmpeg_put_other_image(struct ffmpeg *ffmpeg, unsigned char *y,
                            unsigned char *u, unsigned char *v){
    AVFrame *picture;
    int retcd = 0;
    int cnt = 0;

    /* Allocate the encoded raw picture. */
    picture = ffmpeg_prepare_frame(ffmpeg, y, u, v);

    if (picture) {
        /* 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, picture);
        while ((retcd == -2) && (ffmpeg->tlapse != TIMELAPSE_NONE)) {
            retcd = ffmpeg_put_frame(ffmpeg, picture);
            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");
        }
        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){
/**
 * 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];
    struct timeval tv1;
    int64_t pts_interval;

    gettimeofday(&tv1, NULL);

    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;
        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;
    }

    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;
    }

    /* 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.
 *
 * 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 */
