/******************************************************************************
 *{@C
 * Copyright:   2018-2025 Paul Obermeier (obermeier@poSoft.de)
 *
 *              See the file "Mawt_License.txt" for information on
 *              usage and redistribution of this file, and for a
 *              DISCLAIMER OF ALL WARRANTIES.
 *
 * Description: Wrapper for FFMPEG usage.
 *
 *****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef _MSC_VER
    #define inline _inline
#endif

#include <libavutil/ffversion.h>
#include <libavutil/mem.h>
#include <libavutil/dict.h>
#include <libavutil/imgutils.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include "libavformat/avio.h"
#include <libswscale/swscale.h>

#include "mawtLib.h"
#include "mawtLibPrivate.h"

static int
    sAlign     = 8,
    sIsLibInit = 0,
    sDebugMode = 0;

#define ERR_MSG_LEN 200
static char
    sErrMsg[ERR_MSG_LEN];

static const char * GetAvErrMsg(int avErrorCode)
{
    static char avErrMsg[200];
    av_strerror(avErrorCode, avErrMsg, sizeof(avErrMsg));
    return avErrMsg;
}

VideoId MawtVideoOpen (const char *fileName)
{
    int     i;
    int     retVal;
    int64_t frameSize;
    AVStream *str;
    const AVCodec  *codec;
    VideoStruct videoId;
    VideoStruct *pVideoId;
    if (!sIsLibInit) {
#if 0
        avcodec_register_all();
        av_register_all();
#endif
        avformat_network_init();
        sErrMsg[0] = '\0';
        sIsLibInit = 1;
    }

    memset (&videoId, 0, sizeof (VideoStruct));
    strncpy (videoId.name, fileName, 100);

    retVal = avformat_open_input (&videoId.formatCtx, fileName, NULL, NULL);
    if (retVal != 0) {
        MAWT_SNPRINTF (sErrMsg, ERR_MSG_LEN, "Failed to open input file \"%s\" (FFMPEG error: %s)",
                 fileName, GetAvErrMsg(retVal));
        return NULL;
    }

    /* Retrieve stream information. */
    retVal = avformat_find_stream_info (videoId.formatCtx, NULL);
    if ( retVal < 0 ) {
        avformat_close_input (&videoId.formatCtx);
        MAWT_SNPRINTF (sErrMsg, ERR_MSG_LEN,
                "Failed to find stream info of file \"%s\" (FFMPEG error: %s)",
                fileName, GetAvErrMsg(retVal));
        return NULL;
    }

    /* Find the first video stream. */
    videoId.videoStream = -1;
    videoId.audioStream = -1;
    for (i = 0; i<videoId.formatCtx->nb_streams; ++i) {
        AVCodecParameters *params = videoId.formatCtx->streams[i]->codecpar;
        if (params->codec_type == AVMEDIA_TYPE_VIDEO && videoId.videoStream == -1) {
            videoId.videoStream = i;
            if (sDebugMode) {
                printf ("Found videoStream at %d\n", videoId.videoStream);
            }
        }
        if( params->codec_type == AVMEDIA_TYPE_AUDIO && videoId.audioStream == -1) {
            videoId.audioStream = i;
            if (sDebugMode) {
                printf ("Found audioStream at %d\n", videoId.audioStream);
            }
        }
    }
        
    if (videoId.audioStream != -1) {
        /* TODO Configure Audio */
    }

    if (videoId.videoStream == -1) {
        avformat_close_input (&videoId.formatCtx);
        MAWT_SNPRINTF (sErrMsg, ERR_MSG_LEN, "Failed to find a video stream in file %s", 
                 fileName);
        return NULL;
    }

    str = videoId.formatCtx->streams[videoId.videoStream];
    videoId.timeBase = av_q2d (str->time_base);
    if (sDebugMode) {
        printf ("timeBase : %.5f %.5f\n",  videoId.timeBase, 1.0/videoId.timeBase);
        printf ("time_base num/denum   : %d/%d\n", str->time_base.num, str->time_base.den);
        printf ("r_frame_rate num/denum: %d/%d\n", str->r_frame_rate.num, str->r_frame_rate.den);
        printf ("duration : %lld %lld\n",  videoId.formatCtx->duration, str->duration);
    }

    /* Get a pointer to the codec context for the video stream. */
    videoId.codecCtx = avcodec_alloc_context3(NULL);
    if (!videoId.codecCtx) {
        sprintf(sErrMsg, "Cannot alloc encoding context");
        return NULL;
    }

    if (avcodec_parameters_to_context(videoId.codecCtx, str->codecpar) < 0) {
        MAWT_SNPRINTF (sErrMsg, ERR_MSG_LEN, "Failed to create codec context in file %s", fileName);
        return NULL;
    }

    /* Find the decoder for the video stream. */
    codec = avcodec_find_decoder(str->codecpar->codec_id);
    if (codec == NULL) {
        avformat_close_input (&videoId.formatCtx);
        MAWT_SNPRINTF (sErrMsg, ERR_MSG_LEN, "Codec not found in file %s", fileName);
        return NULL;
    }

    /* Open codec. */
    if (avcodec_open2 (videoId.codecCtx, codec, NULL) < 0 ) {
        avformat_close_input (&videoId.formatCtx);
        MAWT_SNPRINTF (sErrMsg, ERR_MSG_LEN, "Could not open codec in file %s", fileName);
        return NULL;
    }

    videoId.frameRate = av_q2d( str->r_frame_rate );
    if (sDebugMode) {
        printf ("frameRate: %f\n", videoId.frameRate);
    }

    frameSize = (int64_t)( (double)str->time_base.den * (double)str->r_frame_rate.den /
                 (double)str->time_base.num / (double)str->r_frame_rate.num + 0.5);
 
    videoId.width  = videoId.codecCtx->width;
    videoId.height = videoId.codecCtx->height;
    videoId.numFrames = str->duration / frameSize;

    videoId.numTotalFrames  = (int)videoId.formatCtx->duration / 1000;
    videoId.numTotalFrames *= videoId.timeBase * videoId.frameRate;

    if (str->duration < 0) {
        videoId.numFrames = videoId.numTotalFrames;
    }

    /* Everything succeeded. Allocate a VideoStruct and copy over the
     * data from the local VideoStruct.
     */
    pVideoId = (VideoStruct *) malloc (sizeof (VideoStruct));
    if (!pVideoId) {
        MAWT_SNPRINTF (sErrMsg, ERR_MSG_LEN, "Cannot allocate memory for video structure");
        return NULL;
    }
    memcpy (pVideoId, &videoId, sizeof (VideoStruct));
    return pVideoId;
}

void MawtVideoClose (VideoId videoId)
{
    if (videoId) {
        if (videoId->rgbFrame != NULL) {
            av_frame_free (&videoId->rgbFrame);
        }
        if (videoId->codecCtx != NULL) {
#if 0
            avcodec_close (videoId->codecCtx);
#else
            avcodec_free_context(&videoId->codecCtx);
#endif
        }
        if (videoId->formatCtx != NULL) {
            avformat_close_input (&videoId->formatCtx);
        }
        free (videoId);
    }
}

int MawtVideoStart (VideoId videoId, int width, int height)
{
    int numBytes;
    AVStream *str;

    if (videoId->codecCtx == 0) {
        MAWT_SNPRINTF (sErrMsg, ERR_MSG_LEN, "Acquisition can not be started (no video opened)");
        return -1;
    }

    videoId->rgbFrame = av_frame_alloc();
    if (videoId->rgbFrame == NULL ) {
        MAWT_SNPRINTF (sErrMsg, ERR_MSG_LEN, "Failed to allocate memory for a video frame");
        return -1;
    }

    /* Determine required buffer size and allocate buffer. */
    numBytes = av_image_get_buffer_size (AV_PIX_FMT_RGB24, 
                                   videoId->codecCtx->width,
                                   videoId->codecCtx->height, 1);
    videoId->outWidth  = width  <= 0? videoId->width:  width;
    videoId->outHeight = height <= 0? videoId->height: height;

    /* Get an image context converter. */
    str = videoId->formatCtx->streams[videoId->videoStream];
    videoId->swsCtx = sws_getContext (videoId->codecCtx->width,
                                      videoId->codecCtx->height,
                                      str->codecpar->format,
                                      videoId->outWidth, videoId->outHeight,
                                      AV_PIX_FMT_RGB24, SWS_BICUBIC,
                                      NULL, NULL, NULL);

    if (videoId->swsCtx == NULL) {
        MAWT_SNPRINTF (sErrMsg, ERR_MSG_LEN, "Cannot initialize the conversion context");
        return -1;
    }
    videoId->started = 1;
    return videoId->outWidth * videoId->outHeight * 3;
}

void MawtVideoStop (VideoId videoId)
{
    if (videoId) {
        videoId->started = 0;
        av_free (videoId->rgbFrame);
        videoId->rgbFrame = NULL;
    }
}

Bool MawtVideoGetImage (VideoId videoId, void *img, Bool flip, unsigned int num)
{
    if (! VideoSeekFrame (videoId, num)) {
        return 0;
    }
    return MawtVideoGetNextImage (videoId, img, flip);
}

Bool MawtVideoGetNextImage (VideoId videoId, void *img, Bool flip) 
{
    AVFrame * frameRGB;

    if (!videoId->started) {
        MAWT_SNPRINTF (sErrMsg, ERR_MSG_LEN, "Video stream was not started");
        return 0;
    }

    if (! VideoNextFrame (videoId)) {
        return 0;
    }
    if (sDebugMode) {
        printf ("MawtVideoGetNextImage (frame %d)\n", videoId->curFrame); fflush (stdout);
    }

    /* Assign appropriate parts of the buffer to image planes in frameRGB. */
#if 0
    avpicture_fill (&frameRGB, (uint8_t*)img, AV_PIX_FMT_RGB24, 
                    videoId->outWidth, videoId->outHeight);
#else
    frameRGB = av_frame_alloc();
    if (!frameRGB) {
        printf("Error allocating rgbVideoFrame\n");
        return 0;
    }

    // int rgbFrameNumberOfBytes = av_image_get_buffer_size(
    //     AV_PIX_FMT_RGB24, videoId->outWidth,videoId->outHeight, 1);
    // uint8_t * rgbFrameBuffer = av_malloc (rgbFrameNumberOfBytes);
    int retValue = av_image_fill_arrays (
        frameRGB->data, frameRGB->linesize,
        (uint8_t*)img, AV_PIX_FMT_RGB24,
        videoId->outWidth, videoId->outHeight, 1);
    if (retValue < 0) {
        printf("Error filling image array\n");
        return 0;
    }
#endif

    if (flip) {
        frameRGB->data[0] = frameRGB->data[0] + \
                           frameRGB->linesize[0]*(videoId->outHeight-1);
        frameRGB->linesize[0] = -frameRGB->linesize[0];
    }
    sws_scale (videoId->swsCtx, (const uint8_t* const *) videoId->rgbFrame->data,
               videoId->rgbFrame->linesize, 0,
               videoId->codecCtx->height, frameRGB->data, frameRGB->linesize);
    return 1;
}

int MawtVideoGetNumFrames (VideoId videoId)
{
    if (videoId) {
        return videoId->numFrames;
    }
    return -1;
}

double MawtVideoGetFramerate (VideoId videoId)
{
    if (videoId) {
        return videoId->frameRate;
    }
    return -1.0;
}

unsigned int MawtVideoGetPts (VideoId videoId)
{
    if (videoId) {
        return videoId->videoPts;
    }
    return 0;
}

int MawtVideoGetWidth (VideoId videoId)
{
    if (videoId) {
        return videoId->width;
    }
    return -1;
}

int MawtVideoGetHeight (VideoId videoId)
{
    if (videoId) {
        return videoId->height;
    }
    return -1;
}

int MawtGetDebugMode ()
{
    return sDebugMode;
}

void MawtSetDebugMode (int debugMode)
{
    sDebugMode = debugMode;
}

const char * MawtGetErrorMessage (void)
{
    return sErrMsg;
}

const char * MawtGetVersion (void)
{
    return MAWT_VERSION;
}

const char * MawtGetFfmpegVersion (void)
{
    return FFMPEG_VERSION;
}

void MawtVideoLock (VideoId videoId)
{
}

void MawtVideoUnlock (VideoId videoId)
{
}

/* Internal functions not exported via Tcl. */

Bool VideoNextFrame (VideoId videoId)
{
    uint64_t dts, pts;
    int frameFinished = 0;
    AVStream *str;
    int retVal;
    do {
        AVPacket pkt;
        int found_video_pkt = 0;
        int found_audio_pkt = (videoId->audioStream != -1);

        /* Find a frame in the current video stream. */
        do {
            if (av_read_frame (videoId->formatCtx, &pkt) < 0) {
                MAWT_SNPRINTF (sErrMsg, ERR_MSG_LEN, "Failed to read frame from video stream");
                return 0;
            }
            if (pkt.stream_index == videoId->videoStream) {
                found_video_pkt = 1;
                break;
            }
            if (pkt.stream_index == videoId->audioStream) {
                found_audio_pkt = 1; 
                break;
            }
        } while (1);

        if (found_video_pkt) {
            /* Decode time of last frame. */
            dts = pkt.dts;
            /* Presentation time of the last frame. */
            pts = 0;
            if (pkt.dts != AV_NOPTS_VALUE) {
                pts = pkt.dts;
            } else {
                pts = 0;
            }
            videoId->videoPts = pts;

            /* Decode video frame. */
            retVal = avcodec_send_packet(videoId->codecCtx, &pkt);
            if (retVal < 0) {
                MAWT_SNPRINTF(sErrMsg, ERR_MSG_LEN, "Error: Could not send packet %d for decoding (%s)", videoId->curFrame-1, GetAvErrMsg(retVal));
                return 0;
            }
 
            while (retVal >= 0) {
                retVal = avcodec_receive_frame(videoId->codecCtx, videoId->rgbFrame);
                if (retVal == AVERROR(EAGAIN) || retVal == AVERROR_EOF) {
                    frameFinished = 1;
                } else if (retVal < 0) {
                    MAWT_SNPRINTF(sErrMsg, ERR_MSG_LEN, "Error: Could not receive frame %d (%s)", videoId->curFrame-1, GetAvErrMsg(retVal));
                    return 0;
                }
                frameFinished = 1;
		break;
            }
            /* Free last packet. */
            av_packet_unref (&pkt);
        } else {
            /* OPA AUDIO put_audio_packet (&pkt); */
        }
    } while (frameFinished == 0);
    /* Process audio */
    /* OPA AUDIO Process_audio_packets(); */

    str = videoId->formatCtx->streams[videoId->videoStream];
    dts += (int64_t) str->time_base.den * str->r_frame_rate.den /
           str->time_base.num / str->r_frame_rate.num;

    videoId->curFrame++;
    return 1;
}

Bool VideoSeekFrame (VideoId videoId, unsigned int num)
{
    unsigned int frameNr;
    int64_t frameSize;
    int64_t timestamp;
    AVStream *str;
    int iframe = 0;
    VideoSeekEnum sm = VIDEO_SEEK_ABS;

    if ((videoId->formatCtx == 0) || (videoId->codecCtx == 0)) {
        MAWT_SNPRINTF (sErrMsg, ERR_MSG_LEN, "Video stream not open");
        return 0;
    }

    switch( sm ) {
        case VIDEO_SEEK_ABS:
            frameNr = num;
            break;
        case VIDEO_SEEK_FORWARD:
            frameNr = videoId->curFrame + num;
            break;
        case VIDEO_SEEK_BACKWARD:
            frameNr = videoId->curFrame - num;
            break;
        case VIDEO_SEEK_END:
            frameNr = videoId->numFrames - num;
            break;
    }

    if (frameNr >= videoId->numFrames) {
        MAWT_SNPRINTF (sErrMsg, ERR_MSG_LEN, "Invalid frame number (%d) specified", frameNr);
        return 0;
    }

    if (!iframe) {
        videoId->curFrame = frameNr;
    }
    str = videoId->formatCtx->streams[videoId->videoStream];
    frameSize = (int64_t)str->time_base.den * str->r_frame_rate.den /
                 str->time_base.num / str->r_frame_rate.num;
    timestamp = (int64_t)str->time_base.den * videoId->curFrame * 
                str->r_frame_rate.den /
                str->time_base.num / str->r_frame_rate.num + str->start_time;

    if (timestamp > frameSize/2) {
        timestamp -= frameSize/2;
    } else {
        timestamp = 0;
    }

    if (iframe) {
        if (av_seek_frame (videoId->formatCtx, videoId->videoStream,
                           timestamp, AVSEEK_FLAG_BACKWARD) < 0 ) {
            MAWT_SNPRINTF (sErrMsg, ERR_MSG_LEN, "Failed to seek to timestamp %lld", timestamp);
            return 0;
        }
        do {
            if (! VideoNextFrame (videoId)) {
                return 0;
            }
        } while (frameNr > videoId->curFrame);
        videoId->curFrame = frameNr;
    } else {
        if (av_seek_frame (videoId->formatCtx, videoId->videoStream,
                           timestamp, AVSEEK_FLAG_ANY|AVSEEK_FLAG_BACKWARD) < 0 ) {
            MAWT_SNPRINTF (sErrMsg, ERR_MSG_LEN, "Failed to seek to timestamp %lld", timestamp);
            return 0;
        }
    }
    return 1;
}

VideoId MawtVideoCreate(const char * fileName, int width, int height, int fps, float bitrate)
{
    VideoStruct *pVideoId;
    int retVal;
    const AVCodec *codec = NULL;
    AVDictionary *opt = NULL;
    AVFormatContext *oc;
    const AVOutputFormat *fmt;

    if (sDebugMode) {
        printf("MawtVideoCreate %s %dx%d (%d fps, %.1f MBits/s)\n", 
               fileName, width, height, fps, bitrate); fflush(stdout);
    }

    pVideoId = (VideoStruct *)malloc(sizeof (VideoStruct));
    if (!pVideoId) {
        MAWT_SNPRINTF(sErrMsg, ERR_MSG_LEN, "Cannot allocate memory for video structure");
        return NULL;
    }
    memset(pVideoId, 0, sizeof (VideoStruct));
    strncpy(pVideoId->name, fileName, 1024);
    pVideoId->width     = width;
    pVideoId->height    = height;
    pVideoId->outWidth  = width;
    pVideoId->outHeight = height;
    if ( width % sAlign ) {
        pVideoId->outWidth = width + ( sAlign - width % sAlign );
        if (sDebugMode) {
            printf("  Adjusted width from %d to %d\n", width, pVideoId->outWidth);
        }
    }
    pVideoId->curFrame = 0;

    /* Allocate the output media context. */
    avformat_alloc_output_context2(&oc, NULL, NULL, fileName);
    if (!oc) {
        MAWT_SNPRINTF(sErrMsg, ERR_MSG_LEN, "Cannot create output format");
        return NULL;
    }
    fmt = oc->oformat;

    pVideoId->formatCtx = oc;
    pVideoId->outFmt = fmt;

    if (sDebugMode) {
        av_log_set_level(AV_LOG_DEBUG);
    }

    codec = avcodec_find_encoder(fmt->video_codec);
    if (!codec) {
        MAWT_SNPRINTF(sErrMsg, ERR_MSG_LEN, "Cannot find encoder \"%s\"", avcodec_get_name(fmt->video_codec));
        return NULL;
    }

    pVideoId->stream = avformat_new_stream(oc, NULL);
    if (!pVideoId->stream) {
        fprintf(stderr, "Could not allocate stream\n");
        exit(1);
    }
    pVideoId->stream->id = oc->nb_streams-1;

    pVideoId->codecCtx = avcodec_alloc_context3(codec);
    if (!codec) {
        MAWT_SNPRINTF(sErrMsg, ERR_MSG_LEN, "Cannot alloc encoding context");
        return NULL;
    }

    pVideoId->codecCtx->codec_id = fmt->video_codec;
    pVideoId->codecCtx->bit_rate = (int) (bitrate * 1000.0 * 1000.0);
    /* Resolution must be a multiple of two. */
    pVideoId->codecCtx->width  = pVideoId->outWidth;
    pVideoId->codecCtx->height = pVideoId->outHeight;
    /* timebase: This is the fundamental unit of time (in seconds) in terms
     * of which frame timestamps are represented. For fixed-fps content,
     * timebase should be 1/framerate and timestamp increments should be
     * identical to 1.
     */
    pVideoId->codecCtx->time_base.num = 1;
    pVideoId->codecCtx->time_base.den = fps;
    pVideoId->codecCtx->gop_size = 10;
    pVideoId->codecCtx->max_b_frames = 0;
    pVideoId->codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;

    /* Some formats want stream headers to be separate. */
    if (oc->oformat->flags & AVFMT_GLOBALHEADER) {
        pVideoId->codecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }

    retVal = avcodec_open2(pVideoId->codecCtx, codec, NULL);
    if (retVal < 0) {
        MAWT_SNPRINTF(sErrMsg, ERR_MSG_LEN, "Error: Cannot open encoder (%s)", GetAvErrMsg(retVal));
        return NULL;
    }

    /* Allocate memory for each RGB frame, which will be lately converted to YUV. */
    pVideoId->rgbFrame = av_frame_alloc();
    if (!pVideoId->rgbFrame) {
        MAWT_SNPRINTF(sErrMsg, ERR_MSG_LEN, "Error: Could not allocate RGB video frame\n");
        return NULL;
    }
    pVideoId->rgbFrame->format = AV_PIX_FMT_RGB24;
    pVideoId->rgbFrame->width  = pVideoId->outWidth;
    pVideoId->rgbFrame->height = pVideoId->outHeight;
    retVal = av_frame_get_buffer(pVideoId->rgbFrame, 0);
    if (retVal < 0) {
        MAWT_SNPRINTF(sErrMsg, ERR_MSG_LEN, "Error: Cannot get buffer for RGB frame (%s)", GetAvErrMsg(retVal));
        return NULL;
    }

    /* Allocate memory for each conversion output YUV frame. */
    pVideoId->yuvFrame = av_frame_alloc();
    if (!pVideoId->yuvFrame) {
        MAWT_SNPRINTF(sErrMsg, ERR_MSG_LEN, "Error: Could not allocate YUV video frame\n");
        return NULL;
    }
    pVideoId->yuvFrame->format = AV_PIX_FMT_YUV420P;
    pVideoId->yuvFrame->width  = pVideoId->outWidth;
    pVideoId->yuvFrame->height = pVideoId->outHeight;
    retVal = av_frame_get_buffer(pVideoId->yuvFrame, 0);
    if (retVal < 0) {
        MAWT_SNPRINTF(sErrMsg, ERR_MSG_LEN, "Error: Cannot get buffer for YUV frame (%s)", GetAvErrMsg(retVal));
        return NULL;
    }

    /* Copy the stream parameters to the muxer. */
    retVal = avcodec_parameters_from_context(pVideoId->stream->codecpar, pVideoId->codecCtx);
    if (retVal < 0) {
        MAWT_SNPRINTF(sErrMsg, ERR_MSG_LEN, "Could not copy the stream parameters (%s)", GetAvErrMsg(retVal));
        return NULL;
    }

    if (sDebugMode) {
        av_dump_format(pVideoId->formatCtx, 0, fileName, 1);
    }

    /* Open the output file. */
    if (!(fmt->flags & AVFMT_NOFILE)) {
        retVal = avio_open(&oc->pb, fileName, AVIO_FLAG_WRITE);
        if (retVal < 0) {
            MAWT_SNPRINTF(sErrMsg, ERR_MSG_LEN, "Could not open \"%s\": %s\n", fileName, GetAvErrMsg(retVal));
            return NULL;
        }
    }

    /* Write the stream header, if any. */
    retVal = avformat_write_header(oc, &opt);
    if (retVal < 0) {
        MAWT_SNPRINTF(sErrMsg, ERR_MSG_LEN, "Error occurred when opening output file: %s\n", GetAvErrMsg(retVal));
        return NULL;
    }

    /* The image can be allocated by any means and av_image_alloc() is
     * just the most convenient way, if av_malloc() is to be used.
     */
    retVal = av_image_alloc(pVideoId->rgbFrame->data, pVideoId->rgbFrame->linesize,
                            pVideoId->outWidth, pVideoId->outHeight, AV_PIX_FMT_RGB24, sAlign);
    if (retVal < 0) {
        MAWT_SNPRINTF(sErrMsg, ERR_MSG_LEN, "Error: Could not allocate RGB raw picture buffer (%s)", GetAvErrMsg(retVal));
        return NULL;
    }
    retVal = av_image_alloc(pVideoId->yuvFrame->data, pVideoId->yuvFrame->linesize,
                            pVideoId->outWidth, pVideoId->outHeight, AV_PIX_FMT_YUV420P, sAlign);
    if (retVal < 0) {
        MAWT_SNPRINTF(sErrMsg, ERR_MSG_LEN, "Error: Could not allocate RGB raw picture buffer (%s)", GetAvErrMsg(retVal));
        return NULL;
    }

    /* Prepare to convert RGB images to YUV frames. */
    pVideoId->swsCtx = sws_getContext(pVideoId->outWidth, pVideoId->outHeight, AV_PIX_FMT_RGB24,
                                      pVideoId->outWidth, pVideoId->outHeight, AV_PIX_FMT_YUV420P,
                                      SWS_FAST_BILINEAR, NULL, NULL, NULL);

    return pVideoId;
}

Bool MawtVideoWriteNextImage(VideoId videoId, void *img, int width, int height, Bool flip)
{
    int x, y;
    int gotOutput;
    int retVal;
    uint8_t * imgPtr;
    uint8_t * dataPtr;

    /* If supplied size of image does not match the size of the created video,
     * adjust width and height, so no memory overwrite can take place.
     */
    int outHeight = height < videoId->outHeight? height: videoId->outHeight;
    int outWidth  = width  < videoId->outWidth?  width:  videoId->outWidth;

    if (sDebugMode) {
        printf("VideoWriteNextImage (Frame: %d Size: %dx%d Flip: %d)\n",
               videoId->curFrame, width, height, flip); fflush(stdout);
        printf("VideoWriteNextImage (Using size %dx%d)\n", outWidth, outHeight); fflush(stdout);
    }

    /* The AVFrame data will be stored as RGBRGBRGB... row-wise, from left to right and from top to bottom.
     * If the supplied image is smaller than the video size, clear the video buffer first.
     */
    dataPtr = videoId->rgbFrame->data[0];
    if( videoId->outWidth > width) {
        memset( dataPtr, 0, videoId->outHeight * videoId->outWidth * 3 );
    }
    if (flip) {
        for (y = 0; y < outHeight; y++) {
            imgPtr = (uint8_t*)img + y * width * 3;
            for (x = 0; x < outWidth * 3; x++) {
                *dataPtr++ = *imgPtr++;
            }
            if( videoId->outWidth > width) {
                dataPtr += (videoId->outWidth - width ) * 3;
            }
        }
    } else {
        for (y = outHeight - 1; y >= 0; y--) {
            imgPtr = (uint8_t*)img + y * width * 3;
            for (x = 0; x < outWidth * 3; x++) {
                *dataPtr++ = *imgPtr++;
            }
            if( videoId->outWidth > width) {
                dataPtr += (videoId->outWidth - width ) * 3;
            }
        }
    }
    /* Not actually scaling anything, but just converting the RGB data to YUV and store it in pVideoId->yuvFrame. */
    sws_scale(videoId->swsCtx, (const uint8_t * const*)videoId->rgbFrame->data, videoId->rgbFrame->linesize, 0, videoId->outHeight, 
              videoId->yuvFrame->data, videoId->yuvFrame->linesize); 
    
    videoId->packet = av_packet_alloc();

    /* The PTS of the frame are just in a reference unit, unrelated to the format we are using.
     * We set them, for instance, as the corresponding frame number.
     */
    videoId->yuvFrame->pts = videoId->curFrame;
    videoId->curFrame++;
    
#if 0
    retVal = avcodec_encode_video2(videoId->codecCtx, &videoId->packet, videoId->yuvFrame, &gotOutput);
    if (retVal < 0) {
        MAWT_SNPRINTF(sErrMsg, ERR_MSG_LEN, "Error: Could not encode frame %d (%s)", videoId->curFrame-1, GetAvErrMsg(retVal));
        return 0;
    }
    if (gotOutput) {
        if (sDebugMode) {
            printf("  Write frame %3d (size=%5d)\n", videoId->curFrame - 1, videoId->packet.size); fflush(stdout);
        }
        /* Rescale output packet timestamp values from codec to stream timebase. */
        av_packet_rescale_ts(&videoId->packet, videoId->codecCtx->time_base, videoId->stream->time_base);
        videoId->packet.stream_index = videoId->stream->index;

        /* Write the compressed frame to the media file. */
        retVal = av_interleaved_write_frame(videoId->formatCtx, &videoId->packet);
        if (retVal < 0) {
            MAWT_SNPRINTF(sErrMsg, ERR_MSG_LEN, "Error: Could not write frame %d (%s)", videoId->curFrame-1, GetAvErrMsg(retVal));
            return 0;
        }
        return 1;
    }
#else
    retVal = avcodec_send_frame(videoId->codecCtx, videoId->yuvFrame);
    if (retVal < 0) {
        MAWT_SNPRINTF(sErrMsg, ERR_MSG_LEN, "Error: Could not send frame %d for encoding (%s)", videoId->curFrame-1, GetAvErrMsg(retVal));
        return 0;
    }
 
    while (retVal >= 0) {
        retVal = avcodec_receive_packet(videoId->codecCtx, videoId->packet);
        if (retVal == AVERROR(EAGAIN) || retVal == AVERROR_EOF) {
            return 1;
        } else if (retVal < 0) {
            MAWT_SNPRINTF(sErrMsg, ERR_MSG_LEN, "Error: Could not receive packet %d (%s)", videoId->curFrame-1, GetAvErrMsg(retVal));
            return 0;
        }
        if (sDebugMode) {
            printf("  Write frame %3d (size=%5d)\n", videoId->curFrame - 1, videoId->packet->size); fflush(stdout);
        }

        /* Write the compressed frame to the media file. */
        retVal = av_interleaved_write_frame(videoId->formatCtx, videoId->packet);
        if (retVal < 0) {
            MAWT_SNPRINTF(sErrMsg, ERR_MSG_LEN, "Error: Could not write frame %d (%s)", videoId->curFrame-1, GetAvErrMsg(retVal));
            return 0;
        }
        av_packet_unref(videoId->packet);
        return 1;
    }
#endif
    MAWT_SNPRINTF(sErrMsg, ERR_MSG_LEN, "Error: No output when encoding frame %d (%s)", videoId->curFrame-1, GetAvErrMsg(retVal));
    return 0;
}

Bool MawtVideoFinish(VideoId videoId)
{
    int retVal;

    if (videoId) {
        if (sDebugMode) {
            printf("VideoFinish (frame %d)\n", videoId->curFrame); fflush(stdout);
        }
        av_write_trailer(videoId->formatCtx);
        retVal = avio_close(videoId->formatCtx->pb);
        if (retVal < 0) {
            MAWT_SNPRINTF(sErrMsg, ERR_MSG_LEN, "Could not close \"%s\": %s\n", videoId->name, GetAvErrMsg(retVal));
            return 0;
        }
        if (videoId->rgbFrame != NULL) {
            av_frame_free(&videoId->rgbFrame);
        }
        if (videoId->yuvFrame != NULL) {
            av_frame_free(&videoId->yuvFrame);
        }
        if (videoId->codecCtx != NULL) {
#if 0
            avcodec_close(videoId->codecCtx);
#else
            avcodec_free_context(&videoId->codecCtx);
#endif
            av_free(videoId->codecCtx);
        }
        if (videoId->swsCtx != NULL) {
            sws_freeContext(videoId->swsCtx);
        }
        free(videoId);
    }
    return 1;
}
