/*******************************************************************************
*                                                                              *
*   SDL_ffmpeg is a library for basic multimedia functionality.                *
*   SDL_ffmpeg is based on ffmpeg.                                             *
*                                                                              *
*   Copyright (C) 2007  Arjan Houben                                           *
*                                                                              *
*   SDL_ffmpeg is free software: you can redistribute it and/or modify         *
*   it under the terms of the GNU Lesser General Public License as published   *
*   by the Free Software Foundation, either version 3 of the License, or any   *
*   later version.                                                             *
*                                                                              *
*   This program is distributed in the hope that it will be useful,            *
*   but WITHOUT ANY WARRANTY; without even the implied warranty of             *
*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the               *
*   GNU Lesser General Public License for more details.                        *
*                                                                              *
*   You should have received a copy of the GNU Lesser General Public License   *
*   along with this program.  If not, see <http://www.gnu.org/licenses/>.      *
*                                                                              *
*******************************************************************************/

/**
    @mainpage
    @version 1.3.2
    @author Arjan Houben

    SDL_ffmpeg is designed with ease of use in mind.
    Even the beginning programmer should be able to use this library
    so he or she can use multimedia in his/her program.
**/

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

#include <SDL.h>
#include <SDL_thread.h>

#ifdef __cplusplus
extern "C"
{
#endif
#include "libavformat/avformat.h"
#include "libavutil/mathematics.h"
#include "libswscale/swscale.h"
#ifdef __cplusplus
}
#endif

// FFmpeg compatibility
#ifndef AVCODEC_MAX_AUDIO_FRAME_SIZE
#define AVCODEC_MAX_AUDIO_FRAME_SIZE 192000
#endif

#include "SDL_ffmpeg.h"

#ifdef MSVC
#define snprintf( buf, count, format, ... )  _snprintf_s( buf, 512, count, format, __VA_ARGS__ )
#ifndef INT64_C
#define INT64_C(i) i
#endif
#endif

#ifdef __cplusplus
extern "C"
{
#endif

// Use audio conversion from Movie.cpp
extern int convert_audio(int in_samples, int in_channels, int in_stride,
                  enum AVSampleFormat in_fmt, const uint8_t *in_buf,
                  int out_samples, int out_channels, int out_stride,
                  enum AVSampleFormat out_fmt, uint8_t *out_buf);

#ifdef __cplusplus
}
#endif


/**
\cond
*/

/**
 *  Provide a fast way to get the correct context.
 *  \returns The context matching the input values.
 */
struct SwsContext* getContext( SDL_ffmpegConversionContext **context, int inWidth, int inHeight, enum PixelFormat inFormat, int outWidth, int outHeight, enum PixelFormat outFormat )
{
    SDL_ffmpegConversionContext *ctx = *context;

    if ( ctx )
    {
        /* check for a matching context */
        while ( ctx )
        {
            if ( ctx->inWidth == inWidth &&
                    ctx->inHeight == inHeight &&
                    ctx->inFormat == inFormat &&
                    ctx->outWidth == outWidth &&
                    ctx->outHeight == outHeight &&
                    ctx->outFormat == outFormat )
            {
                return ctx->context;
            }

            ctx = ctx->next;
        }

        ctx = *context;

        /* find the last context */
        while ( ctx ) ctx = ctx->next;

        /* allocate a new context */
        ctx = ( struct SDL_ffmpegConversionContext* ) malloc( sizeof( SDL_ffmpegConversionContext ) );
    }
    else
    {
        ctx = *context = ( struct SDL_ffmpegConversionContext* ) malloc( sizeof( SDL_ffmpegConversionContext ) );
    }

    /* fill context with correct information */
    ctx->context = sws_getContext( inWidth, inHeight, inFormat,
                                   outWidth, outHeight, outFormat,
                                   SWS_BILINEAR,
                                   0,
                                   0,
                                   0 );

    ctx->inWidth = inWidth;
    ctx->inHeight = inHeight;
    ctx->inFormat = inFormat;
    ctx->outWidth = outWidth;
    ctx->outHeight = outHeight;
    ctx->outFormat = outFormat;
    ctx->next = 0;

    return ctx->context;
}

uint32_t SDL_ffmpegInitWasCalled = 0;

/* error handling */
char SDL_ffmpegErrorMessage[ 512 ];

void SDL_ffmpegSetError( const char *error );

/* packet handling */
int SDL_ffmpegGetPacket( SDL_ffmpegFile* );

SDL_ffmpegPacket* SDL_ffmpegGetAudioPacket( SDL_ffmpegFile* );

SDL_ffmpegPacket* SDL_ffmpegGetVideoPacket( SDL_ffmpegFile* );

/* frame handling */
int SDL_ffmpegDecodeAudioFrame( SDL_ffmpegFile*, AVPacket*, SDL_ffmpegAudioFrame* );

int SDL_ffmpegDecodeVideoFrame( SDL_ffmpegFile*, AVPacket*, SDL_ffmpegVideoFrame* );

const SDL_ffmpegCodec SDL_ffmpegCodecAUTO =
{
    -1,
    720, 576,
    1, 25,
    6000000,
    -1, -1,
    -1,
    2, 48000,
    192000,
    -1, -1
};

const SDL_ffmpegCodec SDL_ffmpegCodecPALDVD =
{
    AV_CODEC_ID_MPEG2VIDEO,
    720, 576,
    1, 25,
    6000000,
    -1, -1,
    AV_CODEC_ID_MP2,
    2, 48000,
    192000,
    -1, -1
};

const SDL_ffmpegCodec SDL_ffmpegCodecPALDV =
{
    AV_CODEC_ID_DVVIDEO,
    720, 576,
    1, 25,
    6553600,
    -1, -1,
    AV_CODEC_ID_DVAUDIO,
    2, 48000,
    256000,
    -1, -1
};

SDL_ffmpegFile* SDL_ffmpegCreateFile()
{
    /* create SDL_ffmpegFile pointer */
    SDL_ffmpegFile *file = ( SDL_ffmpegFile* )malloc( sizeof( SDL_ffmpegFile ) );
    if ( !file )
    {
        SDL_ffmpegSetError( "could not allocate SDL_ffmpegFile" );
        return 0;
    }

    memset( file, 0, sizeof( SDL_ffmpegFile ) );

    file->streamMutex = SDL_CreateMutex();

    return file;
}

/**
\endcond
*/


/** \brief  Initializes the SDL_ffmpeg library

            This is done automatically when using SDL_ffmpegOpen or
            SDL_ffmpegCreateFile. This means that it is usualy unnescecairy
            to explicitly call this function
*/
void SDL_ffmpegInit()
{
    /* register all codecs */
    if ( !SDL_ffmpegInitWasCalled )
    {
        SDL_ffmpegInitWasCalled = 1;

        avcodec_register_all();
        av_register_all();
    }
}

/** \brief  Use this to free an SDL_ffmpegFile.

            This function stops the decoding thread if needed
            and flushes the buffers before releasing the memory.
\param      file SDL_ffmpegFile which needs to be removed
*/
void SDL_ffmpegFree( SDL_ffmpegFile *file )
{
    if ( !file ) return;

    SDL_ffmpegFlush( file );

    /* only write trailer when handling output streams */
    if ( file->type == SDL_ffmpegOutputStream )
    {
        av_write_trailer( file->_ffmpeg );
    }

    SDL_ffmpegStream *s = file->vs;
    while ( s )
    {
        SDL_ffmpegStream *old = s;

        s = s->next;

        SDL_DestroyMutex( old->mutex );

        while ( old->buffer )
        {
            SDL_ffmpegPacket *pack = old->buffer;

            old->buffer = old->buffer->next;

            av_free_packet( pack->data );

            av_free( pack->data );

            free( pack );
        }

        while ( old->conversionContext )
        {
            SDL_ffmpegConversionContext *ctx = old->conversionContext;

            old->conversionContext = old->conversionContext->next;

            sws_freeContext( ctx->context );

            free( ctx );
        }

        av_free( old->decodeFrame );

        if ( old->_ffmpeg ) avcodec_close( old->_ffmpeg->codec );

        free( old );
    }

    s = file->as;
    while ( s )
    {
        SDL_ffmpegStream *old = s;

        s = s->next;

        SDL_DestroyMutex( old->mutex );

        while ( old->buffer )
        {
            SDL_ffmpegPacket *pack = old->buffer;

            old->buffer = old->buffer->next;

            av_free_packet( pack->data );

            av_free( pack->data );

            free( pack );
        }

        av_free( old->sampleBuffer );

        if ( old->_ffmpeg ) avcodec_close( old->_ffmpeg->codec );

        free( old );
    }

    if ( file->_ffmpeg )
    {
        if ( file->type == SDL_ffmpegInputStream )
        {
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53,17,0)
            av_close_input_file( file->_ffmpeg );
#else
            avformat_close_input( &file->_ffmpeg );
#endif
        }
        else if ( file->type == SDL_ffmpegOutputStream )
        {
            avio_close( file->_ffmpeg->pb );

            av_free( file->_ffmpeg );
        }
    }

    SDL_DestroyMutex( file->streamMutex );

    free( file );
}


/** \brief  Use this to free an SDL_ffmpegAudioFrame.

            This releases all buffers which where allocated in SDL_ffmpegCreateAudioFrame
\param      frame SDL_ffmpegAudioFrame which needs to be deleted
*/
void SDL_ffmpegFreeAudioFrame( SDL_ffmpegAudioFrame* frame )
{
    av_free( frame->buffer );

    free( frame );
}


/** \brief  Use this to free an SDL_ffmpegVideoFrame.

            This releases all buffers which where allocated in SDL_ffmpegCreateVideoFrame
\param      frame SDL_ffmpegVideoFrame which needs to be deleted
*/
void SDL_ffmpegFreeVideoFrame( SDL_ffmpegVideoFrame* frame )
{
    if ( !frame ) return;

    if ( frame->surface ) SDL_FreeSurface( frame->surface );

    if ( frame->overlay ) SDL_FreeYUVOverlay( frame->overlay );

    free( frame );
}


/** \brief  Use this to open the multimedia file of your choice.

            This function is used to open a multimedia file.
            When the file could be opened, but no decodable streams where detected
            this function still returns a pointer to a valid SDL_ffmpegFile.
\param      filename string containing the location of the file
\returns    a pointer to a SDL_ffmpegFile structure, or NULL if a file could not be opened
*/
SDL_ffmpegFile* SDL_ffmpegOpen( const char* filename )
{
    SDL_ffmpegInit();

    /* open new ffmpegFile */
    SDL_ffmpegFile *file = SDL_ffmpegCreateFile();
    if ( !file ) return 0;

    /* information about format is stored in file->_ffmpeg */
    file->type = SDL_ffmpegInputStream;

    /* open the file */
    if ( avformat_open_input(&file->_ffmpeg, filename, NULL, NULL) != 0 )
    {
        char c[512];
        snprintf( c, 512, "could not open \"%s\"", filename );
        SDL_ffmpegSetError( c );
        free( file );
        return 0;
    }

    /* retrieve format information */
    if ( avformat_find_stream_info( file->_ffmpeg, NULL ) < 0 )
    {
        char c[512];
        snprintf( c, 512, "could not retrieve file info for \"%s\"", filename );
        SDL_ffmpegSetError( c );
        free( file );
        return 0;
    }

    /* iterate through all the streams and store audio/video streams */
    for ( uint32_t i = 0; i < file->_ffmpeg->nb_streams; i++ )
    {
        /* disable all streams by default */
        file->_ffmpeg->streams[i]->discard = AVDISCARD_ALL;

        if ( file->_ffmpeg->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO )
        {
            /* if this is a packet of the correct type we create a new stream */
            SDL_ffmpegStream* stream = ( SDL_ffmpegStream* )malloc( sizeof( SDL_ffmpegStream ) );

            if ( stream )
            {
                /* we set our stream to zero */
                memset( stream, 0, sizeof( SDL_ffmpegStream ) );

                /* save unique streamid */
                stream->id = i;

                /* _ffmpeg holds data about streamcodec */
                stream->_ffmpeg = file->_ffmpeg->streams[i];

                /* get the correct decoder for this stream */
                AVCodec *codec = avcodec_find_decoder( stream->_ffmpeg->codec->codec_id );

                if ( !codec )
                {
                    free( stream );
                    SDL_ffmpegSetError( "could not find video codec" );
                }
                else if ( avcodec_open2( file->_ffmpeg->streams[i]->codec, codec, NULL ) < 0 )
                {
                    free( stream );
                    SDL_ffmpegSetError( "could not open video codec" );
                }
                else
                {
                    stream->mutex = SDL_CreateMutex();

#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,0)
                    stream->decodeFrame = avcodec_alloc_frame();
#else
                    stream->decodeFrame = av_frame_alloc();
#endif

                    SDL_ffmpegStream **s = &file->vs;
                    while ( *s )
                    {
                        *s = ( *s )->next;
                    }

                    *s = stream;

                    file->videoStreams++;
                }
            }
        }
        else if ( file->_ffmpeg->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO )
        {
            /* if this is a packet of the correct type we create a new stream */
            SDL_ffmpegStream* stream = ( SDL_ffmpegStream* )malloc( sizeof( SDL_ffmpegStream ) );

            if ( stream )
            {
                /* we set our stream to zero */
                memset( stream, 0, sizeof( SDL_ffmpegStream ) );

                /* save unique streamid */
                stream->id = i;

                /* _ffmpeg holds data about streamcodec */
                stream->_ffmpeg = file->_ffmpeg->streams[i];

                /* get the correct decoder for this stream */
                AVCodec *codec = avcodec_find_decoder( file->_ffmpeg->streams[i]->codec->codec_id );

                if ( !codec )
                {
                    free( stream );
                    SDL_ffmpegSetError( "could not find audio codec" );
                }
                else if ( avcodec_open2( file->_ffmpeg->streams[i]->codec, codec, NULL ) < 0 )
                {
                    free( stream );
                    SDL_ffmpegSetError( "could not open audio codec" );
                }
                else
                {
                    stream->mutex = SDL_CreateMutex();

                    stream->sampleBuffer = ( int8_t* )av_malloc( AVCODEC_MAX_AUDIO_FRAME_SIZE * sizeof( int16_t ) );
                    stream->sampleBufferSize = 0;
                    stream->sampleBufferOffset = 0;
                    stream->sampleBufferTime = AV_NOPTS_VALUE;

                    SDL_ffmpegStream **s = &file->as;
                    while ( *s )
                    {
                        *s = ( *s )->next;
                    }

                    *s = stream;

                    file->audioStreams++;
                }
            }
        }
    }

    return file;
}

#ifdef SDL_FF_WRITE
/** \brief  Use this to create the multimedia file of your choice.

            This function is used to create a multimedia file.

\param      filename string containing the location to which the data will be written
\returns    a pointer to a SDL_ffmpegFile structure, or NULL if a file could not be opened
*/
SDL_ffmpegFile* SDL_ffmpegCreate( const char* filename )
{
    SDL_ffmpegInit();

    SDL_ffmpegFile *file = SDL_ffmpegCreateFile();

    file->_ffmpeg = avformat_alloc_context();

    /* guess output format based on filename */
#if ( LIBAVFORMAT_VERSION_MAJOR <= 52 && LIBAVFORMAT_VERSION_MINOR <= 45 )
    file->_ffmpeg->oformat = guess_format( 0, filename, 0 );
#else
    file->_ffmpeg->oformat = av_guess_format( 0, filename, 0 );
#endif

    if ( !file->_ffmpeg->oformat )
    {
#if ( LIBAVFORMAT_VERSION_MAJOR <= 52 && LIBAVFORMAT_VERSION_MINOR <= 45 )
        file->_ffmpeg->oformat = guess_format( "dvd", 0, 0 );
#else
        file->_ffmpeg->oformat = av_guess_format( "dvd", 0, 0 );
#endif
    }

    /* preload as shown in ffmpeg.c */
    file->_ffmpeg->preload = ( int )( 0.5 * AV_TIME_BASE );

    /* max delay as shown in ffmpeg.c */
    file->_ffmpeg->max_delay = ( int )( 0.7 * AV_TIME_BASE );

    /* open the output file, if needed */
    if ( url_fopen( &file->_ffmpeg->pb, filename, AVIO_FLAG_WRITE ) < 0 )
    {
        char c[512];
        snprintf( c, 512, "could not open \"%s\"", filename );
        SDL_ffmpegSetError( c );
        SDL_ffmpegFree( file );
        return 0;
    }

    file->type = SDL_ffmpegOutputStream;

    return file;
}
#endif


#ifdef SDL_FF_WRITE
/** \brief  Use this to add a SDL_ffmpegVideoFrame to file

            By adding frames to file, a video stream is build. If an audio stream
            is present, syncing of both streams needs to be done by user.
\param      file SDL_ffmpegFile to which a frame needs to be added.
\param      frame SDL_ffmpegVideoFrame which will be added to the stream.
\returns    0 if frame was added, non-zero if an error occured.
*/
int SDL_ffmpegAddVideoFrame( SDL_ffmpegFile *file, SDL_Surface *frame )
{
    /* when accesing audio/video stream, streamMutex should be locked */
    SDL_LockMutex( file->streamMutex );

    if ( !file->videoStream || !frame || !frame->format )
    {
        SDL_UnlockMutex( file->streamMutex );
        return -1;
    }

    int pitch [] =
    {
        frame->pitch,
        0
    };

    const uint8_t *const data [] =
    {
        (uint8_t *)frame->pixels,
        0
    };

    switch ( frame->format->BitsPerPixel )
    {
        case 24:
            sws_scale( getContext( &file->videoStream->conversionContext,
                                   frame->w, frame->h, PIX_FMT_RGB24,
                                   file->videoStream->_ffmpeg->codec->width,
                                   file->videoStream->_ffmpeg->codec->height,
                                   file->videoStream->_ffmpeg->codec->pix_fmt ),
                       data,
                       pitch,
                       0,
                       frame->h,
                       file->videoStream->encodeFrame->data,
                       file->videoStream->encodeFrame->linesize );
            break;
        case 32:
            sws_scale( getContext( &file->videoStream->conversionContext,
                                   frame->w, frame->h, PIX_FMT_BGR32,
                                   file->videoStream->_ffmpeg->codec->width,
                                   file->videoStream->_ffmpeg->codec->height,
                                   file->videoStream->_ffmpeg->codec->pix_fmt ),
                       data,
                       pitch,
                       0,
                       frame->h,
                       file->videoStream->encodeFrame->data,
                       file->videoStream->encodeFrame->linesize );
            break;
        default:
            break;
    }

    /* PAL = upper field first
    file->videoStream->encodeFrame->top_field_first = 1;
    */

    int out_size = avcodec_encode_video( file->videoStream->_ffmpeg->codec, file->videoStream->encodeFrameBuffer, file->videoStream->encodeFrameBufferSize, file->videoStream->encodeFrame );

    /* if zero size, it means the image was buffered */
    if ( out_size > 0 )
    {
        AVPacket pkt;
        av_init_packet( &pkt );

        /* set correct stream index for this packet */
        pkt.stream_index = file->videoStream->_ffmpeg->index;
        /* set keyframe flag if needed */
        if ( file->videoStream->_ffmpeg->codec->coded_frame->key_frame ) pkt.flags |= AV_PKT_FLAG_KEY;
        /* write encoded data into packet */
        pkt.data = file->videoStream->encodeFrameBuffer;
        /* set the correct size of this packet */
        pkt.size = out_size;
        /* set the correct duration of this packet */
        pkt.duration = AV_TIME_BASE / file->videoStream->_ffmpeg->time_base.den;

        /* if needed info is available, write pts for this packet */
        if ( file->videoStream->_ffmpeg->codec->coded_frame->pts != AV_NOPTS_VALUE )
        {
            pkt.pts = av_rescale_q( file->videoStream->_ffmpeg->codec->coded_frame->pts, file->videoStream->_ffmpeg->codec->time_base, file->videoStream->_ffmpeg->time_base );
        }

        av_write_frame( file->_ffmpeg, &pkt );

        av_free_packet( &pkt );

        file->videoStream->frameCount++;
    }

    SDL_UnlockMutex( file->streamMutex );

    return 0;
}
#endif


#ifdef SDL_FF_WRITE
/** \brief  Use this to add a SDL_ffmpegAudioFrame to file

            By adding frames to file, an audio stream is build. If a video stream
            is present, syncing of both streams needs to be done by user.
\param      file SDL_ffmpegFile to which a frame needs to be added.
\param      frame SDL_ffmpegAudioFrame which will be added to the stream.
\returns    0 if frame was added, non-zero if an error occured.
*/
int SDL_ffmpegAddAudioFrame( SDL_ffmpegFile *file, SDL_ffmpegAudioFrame *frame )
{
    /* when accesing audio/video stream, streamMutex should be locked */
    SDL_LockMutex( file->streamMutex );

    if ( !file  || !file->audioStream || !frame )
    {
        SDL_UnlockMutex( file->streamMutex );
        return -1;
    }

    AVPacket pkt;

    /* initialize a packet to write */
    av_init_packet( &pkt );

    /* set correct stream index for this packet */
    pkt.stream_index = file->audioStream->_ffmpeg->index;

    /* set keyframe flag if needed */
    pkt.flags |= AV_PKT_FLAG_KEY;

    /* set the correct size of this packet */
    pkt.size = avcodec_encode_audio( file->audioStream->_ffmpeg->codec, ( uint8_t* )file->audioStream->sampleBuffer, file->audioStream->sampleBufferSize, ( int16_t* )frame->buffer );

    /* write encoded data into packet */
    pkt.data = ( uint8_t* )file->audioStream->sampleBuffer;

    /* if needed info is available, write pts for this packet */
    if ( file->audioStream->_ffmpeg->codec->coded_frame->pts != AV_NOPTS_VALUE )
    {
        pkt.pts = av_rescale_q( file->audioStream->_ffmpeg->codec->coded_frame->pts, file->audioStream->_ffmpeg->codec->time_base, file->audioStream->_ffmpeg->time_base );
    }

    /* write packet to stream */
    av_write_frame( file->_ffmpeg, &pkt );

    av_free_packet( &pkt );

    file->audioStream->frameCount++;

    SDL_UnlockMutex( file->streamMutex );

    return 0;
}
#endif

/** \brief  Use this to create a SDL_ffmpegAudioFrame

            With this frame, you can receive audio data from the stream using
            SDL_ffmpegGetAudioFrame.
\param      file SDL_ffmpegFile for which a frame needs to be created
\param      bytes When current active audio stream is an input stream, this holds
                  the size of the buffer which will be allocated. In case of an
                  output stream, this value is ignored.
\returns    Pointer to SDL_ffmpegAudioFrame, or NULL if no frame could be created
*/
SDL_ffmpegAudioFrame* SDL_ffmpegCreateAudioFrame( SDL_ffmpegFile *file, uint32_t bytes )
{
    /* when accesing audio/video stream, streamMutex should be locked */
    SDL_LockMutex( file->streamMutex );

    if ( !file || !file->audioStream || ( !bytes && file->type == SDL_ffmpegInputStream ) )
    {
        SDL_UnlockMutex( file->streamMutex );
        return 0;
    }

    /* allocate new frame */
    SDL_ffmpegAudioFrame *frame = ( SDL_ffmpegAudioFrame* )malloc( sizeof( SDL_ffmpegAudioFrame ) );
    memset( frame, 0, sizeof( SDL_ffmpegAudioFrame ) );

    if ( file->type == SDL_ffmpegOutputStream )
    {
        bytes = file->audioStream->encodeAudioInputSize * 2 * file->audioStream->_ffmpeg->codec->channels;
    }

    SDL_UnlockMutex( file->streamMutex );

    /* set capacity of new frame */
    frame->capacity = bytes;

    /* allocate buffer */
    frame->buffer = ( uint8_t* )av_malloc( bytes );

    /* initialize a non-valid timestamp */
    frame->pts = AV_NOPTS_VALUE;

    return frame;
}


/** \brief  Use this to create a SDL_ffmpegVideoFrame

            In order to receive video data, either SDL_ffmpegVideoFrame.surface or
            SDL_ffmpegVideoFrame.overlay need to be set by user.
\returns    Pointer to SDL_ffmpegVideoFrame, or NULL if no frame could be created
*/
SDL_ffmpegVideoFrame* SDL_ffmpegCreateVideoFrame()
{
    SDL_ffmpegVideoFrame *frame = ( SDL_ffmpegVideoFrame* )malloc( sizeof( SDL_ffmpegVideoFrame ) );

    memset( frame, 0, sizeof( SDL_ffmpegVideoFrame ) );

    return frame;
}


/** \brief  Use this to get new video data from file.

            Using this function, you can retreive video data from file. This data
            gets written to frame.
\param      file SDL_ffmpegFile from which the data is required
\param      frame SDL_ffmpegVideoFrame to which the data will be written
\returns    non-zero when a frame was retreived, zero otherwise
*/
int SDL_ffmpegGetVideoFrame( SDL_ffmpegFile* file, SDL_ffmpegVideoFrame *frame )
{
    /* when accesing audio/video stream, streamMutex should be locked */
    SDL_LockMutex( file->streamMutex );

    if ( !frame || !file || !file->videoStream )
    {
        SDL_UnlockMutex( file->streamMutex );
        return 0;
    }

    SDL_LockMutex( file->videoStream->mutex );

    /* assume current frame is empty */
    frame->ready = 0;
    frame->last = 0;

    /* get new packet */
    SDL_ffmpegPacket *pack = SDL_ffmpegGetVideoPacket( file );

    while ( !pack && !frame->last )
    {
        pack = SDL_ffmpegGetVideoPacket( file );

        frame->last = SDL_ffmpegGetPacket( file );
    }

    while ( pack && !frame->ready )
    {
        /* when a frame is received, frame->ready will be set */
        SDL_ffmpegDecodeVideoFrame( file, pack->data, frame );

        /* destroy used packet */
        av_free_packet( pack->data );

        av_free( pack->data );

        free( pack );

        pack = SDL_ffmpegGetVideoPacket( file );

        while ( !pack && !frame->last )
        {
            pack = SDL_ffmpegGetVideoPacket( file );

            frame->last = SDL_ffmpegGetPacket( file );
        }
    }

    /* pack retreived, but was not used, push it back in the buffer */
    if ( pack )
    {
        /* take current buffer as next pointer */
        pack->next = file->videoStream->buffer;

        /* store pack as current buffer */
        file->videoStream->buffer = pack;
    }
    else if ( !frame->ready && frame->last )
    {
        /* check if there is still a frame in the buffer */
        SDL_ffmpegDecodeVideoFrame( file, 0, frame );
    }

    SDL_UnlockMutex( file->videoStream->mutex );

    SDL_UnlockMutex( file->streamMutex );

    return frame->ready;
}


/** \brief  Get the desired audio stream from file.

            This returns a pointer to the requested stream. With this stream pointer you can
            get information about the stream, like language, samplerate, size etc.
            Based on this information you can choose the stream you want to use.
\param      file SDL_ffmpegFile from which the information is required
\param      audioID is the stream you whish to select.
\returns    Pointer to SDL_ffmpegStream, or NULL if selected stream could not be found
*/
SDL_ffmpegStream* SDL_ffmpegGetAudioStream( SDL_ffmpegFile *file, uint32_t audioID )
{
    /* check if we have any audiostreams */
    if ( !file || !file->audioStreams ) return 0;

    SDL_ffmpegStream *s = file->as;

    /* return audiostream linked to audioID */
    for ( uint32_t i = 0; i < audioID && s; i++ ) s = s->next;

    return s;
}


/** \brief  Select an audio stream from file.

            Use this function to select an audio stream for decoding.
            Using SDL_ffmpegGetAudioStream you can get information about the streams.
            Based on that you can chose the stream you want.
\param      file SDL_ffmpegFile on which an action is required
\param      audioID is the stream you whish to select. negative values de-select any audio stream.
\returns    -1 on error, otherwise 0
*/
int SDL_ffmpegSelectAudioStream( SDL_ffmpegFile* file, int audioID )
{
    if ( !file ) return -1;

    /* when changing audio/video stream, streamMutex should be locked */
    SDL_LockMutex( file->streamMutex );

    /* check if we have any audiostreams and if the requested ID is available */
    if ( !file->audioStreams || audioID >= ( int )file->audioStreams )
    {
        SDL_UnlockMutex( file->streamMutex );

        SDL_ffmpegSetError( "requested audio stream ID is not available in file" );
        return -1;
    }

    /* set all audio streams to discard */
    SDL_ffmpegStream *stream = file->as;

    /* discard by default */
    while ( stream && stream->_ffmpeg )
    {
        stream->_ffmpeg->discard = AVDISCARD_ALL;
        stream = stream->next;
    }

    if ( audioID < 0 )
    {
        /* reset audiostream */
        file->audioStream = 0;
    }
    else
    {
        /* set current audiostream to stream linked to audioID */
        file->audioStream = file->as;

        for ( int i = 0; i < audioID && file->audioStream; i++ ) file->audioStream = file->audioStream->next;

        /* active stream need not be discarded */
        file->audioStream->_ffmpeg->discard = AVDISCARD_DEFAULT;
    }

    SDL_UnlockMutex( file->streamMutex );

    return 0;
}


/** \brief  Get the desired video stream from file.

            This returns a pointer to the requested stream. With this stream pointer you can
            get information about the stream, like language, samplerate, size etc.
            Based on this information you can choose the stream you want to use.
\param      file SDL_ffmpegFile from which the information is required
\param      videoID is the stream you whish to select.
\returns    Pointer to SDL_ffmpegStream, or NULL if selected stream could not be found
*/
SDL_ffmpegStream* SDL_ffmpegGetVideoStream( SDL_ffmpegFile *file, uint32_t videoID )
{
    /* check if we have any audiostreams */
    if ( !file || !file->videoStreams ) return 0;

    /* check if the requested id is possible */
    if ( videoID >= file->videoStreams ) return 0;

    SDL_ffmpegStream *s = file->vs;

    /* return audiostream linked to audioID */
    for ( uint32_t i = 0; i < videoID && s; i++ ) s = s->next;

    return s;
}


/** \brief  Select a video stream from file.

            Use this function to select a video stream for decoding.
            Using SDL_ffmpegGetVideoStream you can get information about the streams.
            Based on that you can chose the stream you want.
\param      file SDL_ffmpegFile on which an action is required
\param      videoID is the stream you whish to select.
\returns    -1 on error, otherwise 0
*/
int SDL_ffmpegSelectVideoStream( SDL_ffmpegFile* file, int videoID )
{
    if ( !file ) return -1;

    /* when changing audio/video stream, streamMutex should be locked */
    SDL_LockMutex( file->streamMutex );

    /* check if we have any videostreams */
    if ( videoID >= ( int )file->videoStreams )
    {
        SDL_UnlockMutex( file->streamMutex );

        SDL_ffmpegSetError( "requested video stream ID is not available in file" );

        return -1;
    }

    /* set all video streams to discard */
    SDL_ffmpegStream *stream = file->vs;

    /* discard by default */
    while ( stream && stream->_ffmpeg )
    {
        stream->_ffmpeg->discard = AVDISCARD_ALL;
        stream = stream->next;
    }

    if ( videoID < 0 )
    {
        /* reset videostream */
        file->videoStream = 0;
    }
    else
    {
        /* set current videostream to stream linked to videoID */
        file->videoStream = file->vs;

        /* keep searching for correct videostream */
        for ( int i = 0; i < videoID && file->videoStream; i++ ) file->videoStream = file->videoStream->next;

        /* active stream need not be discarded */
        file->videoStream->_ffmpeg->discard = AVDISCARD_DEFAULT;
    }

    SDL_UnlockMutex( file->streamMutex );

    return 0;
}

/** \brief  Seek to a certain point in file.

            Tries to seek to specified point in file.
\param      file SDL_ffmpegFile on which an action is required
\param      timestamp is represented in milliseconds.
\returns    -1 on error, otherwise 0
*/
int SDL_ffmpegSeek( SDL_ffmpegFile* file, uint64_t timestamp )
{
    if ( !file ) return -1;

    if ( SDL_ffmpegDuration( file ) < timestamp )
    {
        SDL_ffmpegSetError( "can not seek past end of file" );

        return -1;
    }

    /* convert milliseconds to AV_TIME_BASE units */
    uint64_t seekPos = timestamp * ( AV_TIME_BASE / 1000 );

    /* AVSEEK_FLAG_BACKWARD means we jump to the first keyframe before seekPos */
    av_seek_frame( file->_ffmpeg, -1, seekPos, AVSEEK_FLAG_BACKWARD );

    /* set minimal timestamp to decode */
    file->minimalTimestamp = timestamp;

    /* flush buffers */
    SDL_ffmpegFlush( file );

    return 0;
}

/** \brief  Seek to a relative point in file.

            Tries to seek to new location, based on current location in file.
\param      file SDL_ffmpegFile on which an action is required
\param      timestamp is represented in milliseconds.
\returns    -1 on error, otherwise 0
*/
int SDL_ffmpegSeekRelative( SDL_ffmpegFile *file, int64_t timestamp )
{
    /* same thing as normal seek, just take into account the current position */
    return SDL_ffmpegSeek( file, SDL_ffmpegGetPosition( file ) + timestamp );
}

/**
\cond
*/
int SDL_ffmpegFlush( SDL_ffmpegFile *file )
{
    /* check for file and permission to flush buffers */
    if ( !file ) return -1;

    /* when accesing audio/video stream, streamMutex should be locked */
    SDL_LockMutex( file->streamMutex );

    /* if we have a valid audio stream, we flush it */
    if ( file->audioStream )
    {
        SDL_LockMutex( file->audioStream->mutex );

        SDL_ffmpegPacket *pack = file->audioStream->buffer;

        while ( pack )
        {
            SDL_ffmpegPacket *old = pack;

            pack = pack->next;

            av_free_packet( old->data );

            av_free( old->data );

            free( old );
        }

        file->audioStream->buffer = 0;

        /* flush internal ffmpeg buffers */
        if ( file->audioStream->_ffmpeg )
        {
            avcodec_flush_buffers( file->audioStream->_ffmpeg->codec );
        }

        SDL_UnlockMutex( file->audioStream->mutex );
    }

    /* if we have a valid video stream, we flush some more */
    if ( file->videoStream )
    {
        SDL_LockMutex( file->videoStream->mutex );

        SDL_ffmpegPacket *pack = file->videoStream->buffer;

        while ( pack )
        {
            SDL_ffmpegPacket *old = pack;

            pack = pack->next;

            av_free_packet( old->data );

            av_free( old->data );

            free( old );
        }

        file->videoStream->buffer = 0;

        /* flush internal ffmpeg buffers */
        if ( file->videoStream->_ffmpeg ) avcodec_flush_buffers( file->videoStream->_ffmpeg->codec );

        SDL_UnlockMutex( file->videoStream->mutex );
    }

    SDL_UnlockMutex( file->streamMutex );

    return 0;
}
/**
\endcond
*/

/** \brief  Use this to get a pointer to a SDL_ffmpegAudioFrame.

            If you receive a frame, it is valid until you receive a new frame, or
            until the file is freed, using SDL_ffmpegFree( SDL_ffmpegFile* ).
            I you use data from the frame, you should adjust the size member by
            the amount of data used in bytes. This is needed so that SDL_ffmpeg can
            calculate the next frame.
\param      file SDL_ffmpegFile from which the information is required
\param      frame The frame to which the data will be decoded.
\returns    Pointer to SDL_ffmpegAudioFrame, or NULL if no frame was available.
*/
int SDL_ffmpegGetAudioFrame( SDL_ffmpegFile *file, SDL_ffmpegAudioFrame *frame )
{
    if ( !file || !frame ) return -1;

    /* when accesing audio/video stream, streamMutex should be locked */
    SDL_LockMutex( file->streamMutex );

    if ( !file->audioStream )
    {
        SDL_UnlockMutex( file->streamMutex );

        SDL_ffmpegSetError( "no valid audio stream selected" );
        return 0;
    }

    /* lock audio buffer */
    SDL_LockMutex( file->audioStream->mutex );

    /* reset frame end pointer and size */
    frame->last = 0;
    frame->size = 0;

    /* get new packet */
    SDL_ffmpegPacket *pack = SDL_ffmpegGetAudioPacket( file );

    while ( !pack && !frame->last )
    {
        pack = SDL_ffmpegGetAudioPacket( file );

        frame->last = SDL_ffmpegGetPacket( file );
    }

    /* SDL_ffmpegDecodeAudioFrame will return true if data from pack was used
       frame will be updated with the new data */
    while ( pack && SDL_ffmpegDecodeAudioFrame( file, pack->data, frame ) )
    {
        /* destroy used packet */
        av_free_packet( pack->data );

        av_free( pack->data );

        free( pack );

        pack = 0;

        /* check if new packet is required */
        if ( frame->size < frame->capacity )
        {
            /* try to get a new packet */
            pack = SDL_ffmpegGetAudioPacket( file );

            while ( !pack && !frame->last )
            {
                pack = SDL_ffmpegGetAudioPacket( file );

                frame->last = SDL_ffmpegGetPacket( file );
            }
        }
    }

    /* pack retreived, but was not used, push it back in the buffer */
    if ( pack )
    {
        /* take current buffer as next pointer */
        pack->next = file->audioStream->buffer;

        /* store pack as current buffer */
        file->audioStream->buffer = pack;
    }

    /* unlock audio buffer */
    SDL_UnlockMutex( file->audioStream->mutex );

    SDL_UnlockMutex( file->streamMutex );

    return ( frame->size == frame->capacity );
}


/** \brief  Returns the current position of the file in milliseconds.

\param      file SDL_ffmpegFile from which the information is required
\returns    -1 on error, otherwise the length of the file in milliseconds
*/
int64_t SDL_ffmpegGetPosition( SDL_ffmpegFile *file )
{
    if ( !file ) return -1;

    /* when accesing audio/video stream, streamMutex should be locked */
    SDL_LockMutex( file->streamMutex );

    int64_t pos = 0;

    if ( file->audioStream )
    {
        pos = file->audioStream->lastTimeStamp;
    }

    if ( file->videoStream && file->videoStream->lastTimeStamp > pos )
    {
        pos = file->videoStream->lastTimeStamp;
    }

    SDL_UnlockMutex( file->streamMutex );

    /* return the current playposition of our file */
    return pos;
}


/** \brief  Returns the frame rate of the stream as a fraction.

            This retreives the frame rate of the supplied stream.
            For example, a framerate of 25 frames per second will have a nominator
            of 1, and a denominator of 25.
\param      stream SDL_ffmpegStream from which the information is required.
\param      nominator Nominator part of the fraction. Can be 0 if exact value of
                      nominator is not required.
\param      denominator Denominator part of the fraction. Can be 0 if exact value
                        of denominator is not required.
\returns    The result of nominator / denominator as floating point value.
*/
float SDL_ffmpegGetFrameRate( SDL_ffmpegStream *stream, int *nominator, int *denominator )
{
    if ( stream && stream->_ffmpeg && stream->_ffmpeg->codec )
    {
        AVRational frate;
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(55,12,100)
        frate = stream->_ffmpeg->r_frame_rate;
#elif defined(av_stream_get_r_frame_rate)
        frate = av_stream_get_r_frame_rate(stream->_ffmpeg);
#else
        frate = stream->_ffmpeg->avg_frame_rate;
#endif
        if ( nominator ) *nominator = frate.num;

        if ( denominator ) *denominator = frate.den;

        return ( float )frate.num / frate.den;
    }
    else
    {
        SDL_ffmpegSetError( "could not retreive frame rate from stream" );

        if ( nominator ) *nominator = 0;

        if ( denominator ) *denominator = 0;
    }

    return 0.0;
}

/** \brief  This can be used to get a SDL_AudioSpec based on values found in file

            This returns a SDL_AudioSpec, if you have selected a valid audio
            stream. Otherwise, all values are set to NULL.
\param      file SDL_ffmpegFile from which the information is required
\param      samples Amount of samples required every time the callback is called.
            Lower values mean less latency, but please note that SDL has a minimal value.
\param      callback Pointer to callback function
\returns    SDL_AudioSpec with values set according to the selected audio stream.
            If no valid audio stream was available, all values of returned SDL_AudioSpec are set to 0
*/
SDL_AudioSpec SDL_ffmpegGetAudioSpec( SDL_ffmpegFile *file, uint16_t samples, SDL_ffmpegCallback callback )
{
    /* create audio spec */
    SDL_AudioSpec spec;

    memset( &spec, 0, sizeof( SDL_AudioSpec ) );

    if ( !file ) return spec;

    /* when accesing audio/video stream, streamMutex should be locked */
    SDL_LockMutex( file->streamMutex );

    /* if we have a valid audiofile, we can use its data to create a
       more appropriate audio spec */
    if ( file->audioStream )
    {
        spec.format = AUDIO_S16SYS;
        spec.samples = samples;
        spec.userdata = file;
        spec.callback = callback;
        spec.freq = file->audioStream->_ffmpeg->codec->sample_rate;
        spec.channels = ( uint8_t )file->audioStream->_ffmpeg->codec->channels;
    }
    else
    {
        SDL_ffmpegSetError( "no valid audio stream selected" );
    }

    SDL_UnlockMutex( file->streamMutex );

    return spec;
}


/** \brief  Returns the Duration of the file in milliseconds.

            Please note that this value is guestimated by FFmpeg, it may differ from
            actual playing time.
\param      file SDL_ffmpegFile from which the information is required
\returns    -1 on error, otherwise the length of the file in milliseconds
*/
uint64_t SDL_ffmpegDuration( SDL_ffmpegFile *file )
{
    if ( !file ) return 0;

    if ( file->type == SDL_ffmpegInputStream )
    {
        /* returns the duration of the entire file, please note that ffmpeg doesn't
           always get this value right! so don't bet your life on it... */
        return file->_ffmpeg->duration / ( AV_TIME_BASE / 1000 );
    }

    if ( file->type == SDL_ffmpegOutputStream )
    {
        uint64_t v = SDL_ffmpegVideoDuration( file );
        uint64_t a = SDL_ffmpegAudioDuration( file );

        if ( v > a ) return v;
        return a;
    }

    return 0;
}


/** \brief  Returns the duration of the audio stream in milliseconds.

            This value can be used to sync two output streams.
\param      file SDL_ffmpegFile from which the information is required
\returns    -1 on error, otherwise the length of the file in milliseconds
*/
uint64_t SDL_ffmpegAudioDuration( SDL_ffmpegFile *file )
{
    if ( !file ) return 0;

    /* when accesing audio/video stream, streamMutex should be locked */
    SDL_LockMutex( file->streamMutex );

    uint64_t duration = 0;

    if ( !file->audioStream )
    {
        if ( file->type == SDL_ffmpegInputStream )
        {
            duration = av_rescale( 1000 * file->audioStream->_ffmpeg->duration, file->audioStream->_ffmpeg->time_base.num, file->audioStream->_ffmpeg->time_base.den );
        }
        else if ( file->type == SDL_ffmpegOutputStream )
        {
            duration = file->audioStream->frameCount * file->audioStream->encodeAudioInputSize / ( file->audioStream->_ffmpeg->codec->sample_rate / 1000 );
        }
    }
    else
    {
        SDL_ffmpegSetError( "no valid audio stream selected" );
    }

    SDL_UnlockMutex( file->streamMutex );

    return duration;
}


/** \brief  Returns the duration of the video stream in milliseconds.

            This value can be used to sync two output streams.
\param      file SDL_ffmpegFile from which the information is required
\returns    -1 on error, otherwise the length of the file in milliseconds
*/
uint64_t SDL_ffmpegVideoDuration( SDL_ffmpegFile *file )
{
    if ( !file ) return 0;

    /* when accesing audio/video stream, streamMutex should be locked */
    SDL_LockMutex( file->streamMutex );

    uint64_t duration = 0;

    if ( file->videoStream )
    {
        if ( file->type == SDL_ffmpegInputStream )
        {
            duration = av_rescale( 1000 * file->videoStream->_ffmpeg->duration, file->videoStream->_ffmpeg->time_base.num, file->videoStream->_ffmpeg->time_base.den );
        }
        else if ( file->type == SDL_ffmpegOutputStream )
        {
            duration = av_rescale( 1000 * file->videoStream->frameCount, file->videoStream->_ffmpeg->codec->time_base.num, file->videoStream->_ffmpeg->codec->time_base.den );
        }
    }
    else
    {
        SDL_ffmpegSetError( "no valid video stream selected" );
    }

    SDL_UnlockMutex( file->streamMutex );

    return duration;
}

/** \brief  retreive the width/height of a frame beloning to file

            With this function you can get the width and height of a frame, belonging to
            your file. If there is no (valid) videostream selected w and h default
            to 0. Please not that you will have to make sure the pointers are
            allocated.

\param      file SDL_ffmpegFile from which the information is required
\param      w width
\param      h height
\returns    -1 on error, otherwise 0
*/
int SDL_ffmpegGetVideoSize( SDL_ffmpegFile *file, int *w, int *h )
{
    if ( !file || !w || !h ) return -1;

    /* when accesing audio/video stream, streamMutex should be locked */
    SDL_LockMutex( file->streamMutex );

    /* if we have a valid video file selected, we use it
       if not, we send default values and return.
       by checking the return value you can check if you got a valid size */
    if ( file->videoStream )
    {
        *w = file->videoStream->_ffmpeg->codec->width;
        *h = file->videoStream->_ffmpeg->codec->height;

        SDL_UnlockMutex( file->streamMutex );

        return 0;
    }
    else
    {
        SDL_ffmpegSetError( "no valid video stream selected" );
    }

    *w = 0;
    *h = 0;

    SDL_UnlockMutex( file->streamMutex );

    return -1;
}


/** \brief  This is used to check if a valid audio stream is selected.

\param      file SDL_ffmpegFile from which the information is required
\returns    non-zero if a valid video stream is selected, otherwise 0
*/
int SDL_ffmpegValidAudio( SDL_ffmpegFile* file )
{
    /* this function is used to check if we selected a valid audio stream */
    return ( file && file->audioStream );
}


/** \brief  This is used to check if a valid video stream is selected.

\param      file SDL_ffmpegFile from which the information is required
\returns    non-zero if a valid video stream is selected, otherwise 0
*/
int SDL_ffmpegValidVideo( SDL_ffmpegFile* file )
{
    /* this function is used to check if we selected a valid video stream */
    return ( file && file->videoStream );
}


#ifdef SDL_FF_WRITE
/** \brief  This is used to add a video stream to file

\param      file SDL_ffmpegFile to which the stream will be added
\param      codec SDL_ffmpegCodec describing the type encoding to be used by this
                  stream.
\returns    The stream which was added, or NULL if no stream could be added.
*/
SDL_ffmpegStream* SDL_ffmpegAddVideoStream( SDL_ffmpegFile *file, SDL_ffmpegCodec codec )
{
    /* add a video stream */
    AVStream *stream = av_new_stream( file->_ffmpeg, 0 );
    if ( !stream )
    {
        SDL_ffmpegSetError( "could not allocate video stream" );
        return 0;
    }

    stream->codec = avcodec_alloc_context();

    avcodec_get_context_defaults2( stream->codec, AVMEDIA_TYPE_VIDEO );

    if ( codec.videoCodecID < 0 )
    {
        stream->codec->codec_id = file->_ffmpeg->oformat->video_codec;
    }
    else
    {
        stream->codec->codec_id = ( enum CodecID ) codec.videoCodecID;
    }

    stream->codec->codec_type = AVMEDIA_TYPE_VIDEO;

    stream->codec->bit_rate = codec.videoBitrate;

    /* resolution must be a multiple of two */
    stream->codec->width = codec.width;
    stream->codec->height = codec.height;

    /* set time_base */
    stream->codec->time_base.num = codec.framerateNum;
    stream->codec->time_base.den = codec.framerateDen;

    /* emit one intra frame every twelve frames at most */
    stream->codec->gop_size = 12;

    /* set pixel format */
    stream->codec->pix_fmt = PIX_FMT_YUV420P;

    /* set mpeg2 codec parameters */
    if ( stream->codec->codec_id == AV_CODEC_ID_MPEG2VIDEO )
    {
        stream->codec->max_b_frames = 2;
    }

    /* set mpeg1 codec parameters */
    if ( stream->codec->codec_id == AV_CODEC_ID_MPEG1VIDEO )
    {
        /* needed to avoid using macroblocks in which some coeffs overflow
           this doesnt happen with normal video, it just happens here as the
           motion of the chroma plane doesnt match the luma plane */
        stream->codec->mb_decision = 2;
    }

    /* some formats want stream headers to be separate */
    if ( file->_ffmpeg->oformat->flags & AVFMT_GLOBALHEADER )
    {
        stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
    }

    /* find the video encoder */
    AVCodec *videoCodec = avcodec_find_encoder( stream->codec->codec_id );
    if ( !videoCodec )
    {
        SDL_ffmpegSetError( "video codec not found" );
        return 0;
    }

    /* open the codec */
    if ( avcodec_open( stream->codec, videoCodec ) < 0 )
    {
        SDL_ffmpegSetError( "could not open video codec" );
        return 0;
    }

    /* create a new stream */
    SDL_ffmpegStream *str = ( SDL_ffmpegStream* )malloc( sizeof( SDL_ffmpegStream ) );

    if ( str )
    {
        /* we set our stream to zero */
        memset( str, 0, sizeof( SDL_ffmpegStream ) );

        str->id = file->audioStreams + file->videoStreams;

        /* _ffmpeg holds data about streamcodec */
        str->_ffmpeg = stream;

        str->mutex = SDL_CreateMutex();

        str->encodeFrame = avcodec_alloc_frame();

        uint8_t *picture_buf;
        int size = avpicture_get_size( stream->codec->pix_fmt, stream->codec->width, stream->codec->height );
        picture_buf = ( uint8_t* )av_malloc( size + FF_INPUT_BUFFER_PADDING_SIZE );
        avpicture_fill(( AVPicture* )str->encodeFrame, picture_buf, stream->codec->pix_fmt, stream->codec->width, stream->codec->height );

        str->encodeFrameBufferSize = stream->codec->width * stream->codec->height * 4 + FF_INPUT_BUFFER_PADDING_SIZE;

        str->encodeFrameBuffer = ( uint8_t* )av_malloc( str->encodeFrameBufferSize );

        file->videoStreams++;

        /* find correct place to save the stream */
        SDL_ffmpegStream **s = &file->vs;
        while ( *s )
        {
            *s = ( *s )->next;
        }

        *s = str;

        if ( av_set_parameters( file->_ffmpeg, 0 ) < 0 )
        {
            SDL_ffmpegSetError( "could not set encoding parameters" );
        }

        /* try to write a header */
        av_write_header( file->_ffmpeg );
    }

    return str;
}
#endif


#ifdef SDL_FF_WRITE
/** \brief  This is used to add a video stream to file

\param      file SDL_ffmpegFile to which the stream will be added
\param      codec SDL_ffmpegCodec describing the type encoding to be used by this
                  stream.
\returns    The stream which was added, or NULL if no stream could be added.
*/
SDL_ffmpegStream* SDL_ffmpegAddAudioStream( SDL_ffmpegFile *file, SDL_ffmpegCodec codec )
{
    // add an audio stream
    AVStream *stream = av_new_stream( file->_ffmpeg, 1 );
    if ( !stream )
    {
        SDL_ffmpegSetError( "could not allocate audio stream" );
        return 0;
    }

    if ( codec.audioCodecID < 0 )
    {
        stream->codec->codec_id = file->_ffmpeg->oformat->audio_codec;
    }
    else
    {
        stream->codec->codec_id = ( enum CodecID ) codec.audioCodecID;
    }

    stream->codec->codec_type = AVMEDIA_TYPE_AUDIO;
    stream->codec->bit_rate = codec.audioBitrate;
    stream->codec->sample_rate = codec.sampleRate;
    stream->codec->sample_fmt = AV_SAMPLE_FMT_S16;
    stream->codec->channels = codec.channels;

    // find the audio encoder
    AVCodec *audioCodec = avcodec_find_encoder( stream->codec->codec_id );
    if ( !audioCodec )
    {
        SDL_ffmpegSetError( "audio codec not found" );
        return 0;
    }

    // open the codec
    if ( avcodec_open( stream->codec, audioCodec ) < 0 )
    {
        SDL_ffmpegSetError( "could not open audio codec" );
        return 0;
    }

    /* some formats want stream headers to be separate */
    if ( file->_ffmpeg->oformat->flags & AVFMT_GLOBALHEADER )
    {
        stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
    }


    /* create a new stream */
    SDL_ffmpegStream *str = ( SDL_ffmpegStream* )malloc( sizeof( SDL_ffmpegStream ) );

    if ( str )
    {
        str->_ffmpeg = stream;

        /* we set our stream to zero */
        memset( str, 0, sizeof( SDL_ffmpegStream ) );

        str->id = file->audioStreams + file->videoStreams;

        /* _ffmpeg holds data about streamcodec */
        str->_ffmpeg = stream;

        str->mutex = SDL_CreateMutex();

        str->sampleBufferSize = 10000;

        str->sampleBuffer = ( int8_t* )av_malloc( str->sampleBufferSize );

        /* ugly hack for PCM codecs (will be removed ASAP with new PCM
           support to compute the input frame size in samples */
        if ( stream->codec->frame_size <= 1 )
        {
            str->encodeAudioInputSize = str->sampleBufferSize / stream->codec->channels;

            switch ( stream->codec->codec_id )
            {
                case AV_CODEC_ID_PCM_S16LE:
                case AV_CODEC_ID_PCM_S16BE:
                case AV_CODEC_ID_PCM_U16LE:
                case AV_CODEC_ID_PCM_U16BE:
                    str->encodeAudioInputSize >>= 1;
                    break;
                default:
                    break;
            }
        }
        else
        {
            str->encodeAudioInputSize = stream->codec->frame_size;
        }

        file->audioStreams++;

        /* find correct place to save the stream */
        SDL_ffmpegStream **s = &file->as;
        while ( *s )
        {
            *s = ( *s )->next;
        }

        *s = str;

        if ( av_set_parameters( file->_ffmpeg, 0 ) < 0 )
        {
            SDL_ffmpegSetError( "could not set encoding parameters" );
            return 0;
        }

        /* try to write a header */
        av_write_header( file->_ffmpeg );
    }

    return str;
}
#endif


/** \brief  Use this function to query if an error occured

\returns    non-zero when an error occured
*/
int SDL_ffmpegError()
{
    return SDL_ffmpegErrorMessage[ 0 ];
}


/** \brief  Use this function to get the last error string

\returns    When no error was found, NULL is returned
*/
const char* SDL_ffmpegGetError()
{
    return SDL_ffmpegErrorMessage;
}


/** \brief  Use this function to clear all standing errors

*/
void SDL_ffmpegClearError()
{
    SDL_ffmpegErrorMessage[ 0 ] = 0;
}

/**
\cond
*/

void SDL_ffmpegSetError( const char *error )
{
    if ( !error ) return;

    if ( snprintf( SDL_ffmpegErrorMessage, 512, "%s", error ) >= 511 )
    {
        SDL_ffmpegErrorMessage[ 511 ] = 0;
    }
}

int SDL_ffmpegGetPacket( SDL_ffmpegFile *file )
{
    /* entering this function, streamMutex should have been locked */

    /* create a packet for our data */
    AVPacket *pack = ( AVPacket* )av_malloc( sizeof( AVPacket ) );

    /* initialize packet */
    av_init_packet( pack );

    /* read a packet from the file */
    int decode = av_read_frame( file->_ffmpeg, pack );

    /* if we did not get a packet, we probably reached the end of the file */
    if ( decode < 0 )
    {
        av_free( pack );

        /* signal EOF */
        return 1;
    }

    /* we got a packet, lets handle it */

    /* try to allocate the packet */
    if ( av_dup_packet( pack ) )
    {
        /* error allocating packet */
        av_free_packet( pack );
    }
    else
    {
        /* If it's a packet from either of our streams, return it */
        if ( file->audioStream && pack->stream_index == file->audioStream->id )
        {
            /* prepare packet */
            SDL_ffmpegPacket *temp = ( SDL_ffmpegPacket* )malloc( sizeof( SDL_ffmpegPacket ) );
            temp->data = pack;
            temp->next = 0;

            SDL_ffmpegPacket **p = &file->audioStream->buffer;

            while ( *p )
            {
                p = &( *p )->next;
            }

            *p = temp;
        }
        else if ( file->videoStream && pack->stream_index == file->videoStream->id )
        {
            /* prepare packet */
            SDL_ffmpegPacket *temp = ( SDL_ffmpegPacket* )malloc( sizeof( SDL_ffmpegPacket ) );
            temp->data = pack;
            temp->next = 0;

//            SDL_LockMutex( file->videoStream->mutex );

            SDL_ffmpegPacket **p = &file->videoStream->buffer;

            while ( *p )
            {
                p = &( *p )->next;
            }

            *p = temp;

//            SDL_UnlockMutex( file->videoStream->mutex );
        }
        else
        {
            av_free_packet( pack );
        }
    }

    return 0;
}

SDL_ffmpegPacket* SDL_ffmpegGetAudioPacket( SDL_ffmpegFile *file )
{
    if ( !file->audioStream ) return 0;

    /* file->audioStream->mutex should be locked before entering this function */

    SDL_ffmpegPacket *pack = 0;

    /* check if there are still packets in buffer */
    if ( file->audioStream->buffer )
    {
        pack = file->audioStream->buffer;

        file->audioStream->buffer = pack->next;
    }

    /* if a packet was found, return it */
    return pack;
}

SDL_ffmpegPacket* SDL_ffmpegGetVideoPacket( SDL_ffmpegFile *file )
{
    if ( !file->videoStream ) return 0;

    /* file->videoStream->mutex should be locked before entering this function */

    SDL_ffmpegPacket *pack = 0;

    /* check if there are still packets in buffer */
    if ( file->videoStream->buffer )
    {
        pack = file->videoStream->buffer;

        file->videoStream->buffer = pack->next;
    }

    /* if a packet was found, return it */
    return pack;
}

int SDL_ffmpegDecodeAudioFrame( SDL_ffmpegFile *file, AVPacket *pack, SDL_ffmpegAudioFrame *frame )
{
    uint8_t *data = pack->data;
    int size = pack->size;
    int audioSize = AVCODEC_MAX_AUDIO_FRAME_SIZE * sizeof( int16_t );

    int channels = file->audioStream->_ffmpeg->codec->channels;
    enum AVSampleFormat in_fmt = file->audioStream->_ffmpeg->codec->sample_fmt;
    int in_bps = av_get_bytes_per_sample(in_fmt);
    enum AVSampleFormat out_fmt = AV_SAMPLE_FMT_S16;
    int out_bps = av_get_bytes_per_sample(out_fmt);

    /* check if there is still data in the buffer */
    if ( file->audioStream->sampleBufferSize )
    {
        /* set new pts */
        if ( !frame->size ) frame->pts = file->audioStream->sampleBufferTime;

        /* calculate free space in frame */
        int fs = frame->capacity - frame->size;

        /* check the amount of data which needs to be copied */
        int in_samples = file->audioStream->sampleBufferSize / (channels * in_bps);
        int out_samples = fs / (channels * out_bps);
        
        if (out_samples < in_samples)
        {
            /* copy data from sampleBuffer into frame buffer until frame buffer is full */
            int written = convert_audio(out_samples, channels, file->audioStream->sampleBufferStride,
                                        in_fmt, (uint8_t *)(file->audioStream->sampleBuffer + file->audioStream->sampleBufferOffset),
                                        out_samples, channels, -1,
                                        out_fmt, frame->buffer + frame->size);
            
            /* mark the amount of bytes still in the buffer */
            file->audioStream->sampleBufferSize -= out_samples * channels * in_bps;

            /* move offset accordingly */
            if (av_sample_fmt_is_planar(in_fmt))
                file->audioStream->sampleBufferOffset += out_samples * in_bps;
            else
                file->audioStream->sampleBufferOffset += out_samples * in_bps * channels;

            /* update framesize */
            frame->size += written;
        }
        else
        {
            /* copy data from sampleBuffer into frame buffer until sampleBuffer is empty */
            int written = convert_audio(in_samples, channels, file->audioStream->sampleBufferStride,
                                        in_fmt, (uint8_t *)(file->audioStream->sampleBuffer + file->audioStream->sampleBufferOffset),
                                        in_samples, channels, -1,
                                        out_fmt, frame->buffer + frame->size);

            /* update framesize */
            frame->size += written;

            /* at this point, samplebuffer should have been handled */
            file->audioStream->sampleBufferSize = 0;

            /* no more data in buffer, reset offset */
            file->audioStream->sampleBufferOffset = 0;
        }

        /* return 0 to signal caller that 'pack' was not used */
        if ( frame->size == frame->capacity ) return 0;
    }


    /* calculate pts to determine wheter or not this frame should be stored */
    file->audioStream->sampleBufferTime = av_rescale(( pack->dts - file->audioStream->_ffmpeg->start_time ) * 1000, file->audioStream->_ffmpeg->time_base.num, file->audioStream->_ffmpeg->time_base.den );

    while ( size > 0 )
    {
        /* Decode the packet */
        AVCodecContext *avctx = file->audioStream->_ffmpeg->codec;
        AVFrame dframe;
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
        avcodec_get_frame_defaults(&dframe);
#endif
        int got_frame = 0;
        int len = avcodec_decode_audio4( avctx, &dframe, &got_frame, pack );
        
        if (len < 0 || !got_frame)
        {
            SDL_ffmpegSetError( "error decoding audio frame" );
            break;
        }
        
        int planar = av_sample_fmt_is_planar( avctx->sample_fmt );
        int plane_size;
        int data_size = av_samples_get_buffer_size( &plane_size, avctx->channels, dframe.nb_samples, avctx->sample_fmt, 1 );
        if ( data_size > 10000 )
        {
            SDL_ffmpegSetError( "too much data in decoded audio frame" );
            break;
        }
        memcpy( file->audioStream->sampleBuffer, dframe.extended_data[0], plane_size );
        audioSize = plane_size;
        if ( planar && avctx->channels > 1 )
        {
            int8_t *out = file->audioStream->sampleBuffer + plane_size;
            int ch;
            for ( ch = 1; ch < avctx->channels; ch++ )
            {
                memcpy( out, dframe.extended_data[ch], plane_size );
                out += plane_size;
                audioSize += plane_size;
            }
        }

        /* change pointers */
        data += len;
        size -= len;
    }

    {
        /* set new pts */
        if ( !frame->size ) frame->pts = file->audioStream->sampleBufferTime;

        /* save stride of data we just grabbed */
        file->audioStream->sampleBufferStride = audioSize / channels;
        
        /* room in frame */
        int fs = frame->capacity - frame->size;

        /* check if there is room at all */
        if ( fs )
        {
            /* check the amount of data which needs to be copied */
            int in_samples = audioSize / (channels * in_bps);
            int out_samples = fs / (channels * out_bps);
            
            if (out_samples < in_samples)
            {
                /* copy data from sampleBuffer into frame buffer until frame buffer is full */
                int written = convert_audio(out_samples, channels, file->audioStream->sampleBufferStride,
                                            in_fmt, (uint8_t *)(file->audioStream->sampleBuffer),
                                            out_samples, channels, -1,
                                            out_fmt, frame->buffer + frame->size);

                /* mark the amount of bytes still in the buffer */
                file->audioStream->sampleBufferSize = ((in_samples - out_samples) * channels * in_bps);

                /* set the offset so the remaining data can be found */
                if (av_sample_fmt_is_planar(in_fmt))
                    file->audioStream->sampleBufferOffset = out_samples * in_bps;
                else
                    file->audioStream->sampleBufferOffset = out_samples * in_bps * channels;

                /* update framesize */
                frame->size += written;
            }
            else
            {
                /* copy data from sampleBuffer into frame buffer until sampleBuffer is empty */
                int written = convert_audio(in_samples, channels, file->audioStream->sampleBufferStride,
                                            in_fmt, (uint8_t *)(file->audioStream->sampleBuffer),
                                            in_samples, channels, -1,
                                            out_fmt, frame->buffer + frame->size);

                /* mark the amount of bytes still in the buffer */
                file->audioStream->sampleBufferSize = 0;

                /* reset buffer offset */
                file->audioStream->sampleBufferOffset = 0;

                /* update framesize */
                frame->size += written;
            }
        }
        else
        {
            /* no room in frame, mark samplebuffer as full */
            file->audioStream->sampleBufferSize = audioSize;

            /* reset buffer offset */
            file->audioStream->sampleBufferOffset = 0;
        }
    }

    /* pack was used, return 1 */
    return 1;
}

int SDL_ffmpegDecodeVideoFrame( SDL_ffmpegFile* file, AVPacket *pack, SDL_ffmpegVideoFrame *frame )
{
    int got_frame = 0;

    if ( pack )
    {
        /* usefull when dealing with B frames */
        if ( pack->dts == AV_NOPTS_VALUE )
        {
            /* if we did not get a valid timestamp, we make one up based on the last
               valid timestamp + the duration of a frame */
            frame->pts = file->videoStream->lastTimeStamp + av_rescale( 1000 * pack->duration, file->videoStream->_ffmpeg->time_base.num, file->videoStream->_ffmpeg->time_base.den );
        }
        else
        {
            /* write timestamp into the buffer */
            frame->pts = av_rescale(( pack->dts - file->videoStream->_ffmpeg->start_time ) * 1000, file->videoStream->_ffmpeg->time_base.num, file->videoStream->_ffmpeg->time_base.den );
        }

        /* Decode the packet */
#if ( ( LIBAVCODEC_VERSION_MAJOR <= 52 ) && ( LIBAVCODEC_VERSION_MINOR <= 20 ) )
        avcodec_decode_video( file->videoStream->_ffmpeg->codec, file->videoStream->decodeFrame, &got_frame, pack->data, pack->size );
#else
        avcodec_decode_video2( file->videoStream->_ffmpeg->codec, file->videoStream->decodeFrame, &got_frame, pack );
#endif
    }
    else
    {
        /* check if there is still a frame left in the buffer */

#if ( LIBAVCODEC_VERSION_MAJOR <= 52 && LIBAVCODEC_VERSION_MINOR <= 20 )
        avcodec_decode_video( file->videoStream->_ffmpeg->codec, file->videoStream->decodeFrame, &got_frame, 0, 0 );
#else
        AVPacket temp;
        av_init_packet( &temp );
        temp.data = 0;
        temp.size = 0;
        temp.stream_index = file->videoStream->_ffmpeg->index;
        avcodec_decode_video2( file->videoStream->_ffmpeg->codec, file->videoStream->decodeFrame, &got_frame, &temp );
#endif
    }

    /* if we did not get a frame, we return */
    if ( got_frame )
    {
        /* convert YUV 420 to YUYV 422 data */
        if ( frame->overlay && frame->overlay->format == SDL_YUY2_OVERLAY )
        {
            int pitch[] =
            {
                frame->overlay->pitches[ 0 ],
                frame->overlay->pitches[ 1 ],
                frame->overlay->pitches[ 2 ]
            };

            sws_scale( getContext( &file->videoStream->conversionContext,
                                   file->videoStream->_ffmpeg->codec->width,
                                   file->videoStream->_ffmpeg->codec->height,
                                   file->videoStream->_ffmpeg->codec->pix_fmt,
                                   frame->overlay->w, frame->overlay->h,
                                   PIX_FMT_YUYV422 ),
                       ( const uint8_t* const* )file->videoStream->decodeFrame->data,
                       file->videoStream->decodeFrame->linesize,
                       0,
                       file->videoStream->_ffmpeg->codec->height,
                       ( uint8_t* const* )frame->overlay->pixels,
                       pitch );
        }

        /* convert YUV to RGB data */
        if ( frame->surface && frame->surface->format )
        {
            int pitch = frame->surface->pitch;

            switch ( frame->surface->format->BitsPerPixel )
            {
                case 32:
                    sws_scale( getContext( &file->videoStream->conversionContext,
                                           file->videoStream->_ffmpeg->codec->width,
                                           file->videoStream->_ffmpeg->codec->height,
                                           file->videoStream->_ffmpeg->codec->pix_fmt,
                                           frame->surface->w, frame->surface->h,
                                           PIX_FMT_RGB32 ),
                               ( const uint8_t* const* )file->videoStream->decodeFrame->data,
                               file->videoStream->decodeFrame->linesize,
                               0,
                               file->videoStream->_ffmpeg->codec->height,
                               ( uint8_t* const* )&frame->surface->pixels,
                               &pitch );
                    break;
                case 24:
                    sws_scale( getContext( &file->videoStream->conversionContext,
                                           file->videoStream->_ffmpeg->codec->width,
                                           file->videoStream->_ffmpeg->codec->height,
                                           file->videoStream->_ffmpeg->codec->pix_fmt,
                                           frame->surface->w, frame->surface->h,
                                           PIX_FMT_RGB24 ),
                               ( const uint8_t* const* )file->videoStream->decodeFrame->data,
                               file->videoStream->decodeFrame->linesize,
                               0,
                               file->videoStream->_ffmpeg->codec->height,
                               ( uint8_t* const* )&frame->surface->pixels,
                               &pitch );
                    break;
                default:
                    break;
            }
        }

        /* we write the lastTimestamp we got */
        file->videoStream->lastTimeStamp = frame->pts;

        /* flag this frame as ready */
        frame->ready = 1;
    }

    return frame->ready;
}
/**
\endcond
*/
