/*M///////////////////////////////////////////////////////////////////////////////////////
//
//  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
//  By downloading, copying, installing or using the software you agree to this license.
//  If you do not agree to this license, do not download, install,
//  copy or use the software.
//
//
//                        Intel License Agreement
//                For Open Source Computer Vision Library
//
// Copyright (C) 2000, Intel Corporation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//   * Redistribution's of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//
//   * Redistribution's in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//
//   * The name of Intel Corporation may not be used to endorse or promote products
//     derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/

#include "_highgui.h"


extern "C" {
#include <ffmpeg/avformat.h>
}

#ifdef NDEBUG
#define CV_WARN(message) 
#else
#define CV_WARN(message) fprintf(stderr, "warning: %s (%s:%d)\n", message, __FILE__, __LINE__)
#endif

typedef struct CvCaptureAVI_FFMPEG
{
    CvCaptureVTable   * vtable;

    AVFormatContext   * ic;
    int                 video_stream;
    AVStream          * video_st;
    AVFrame           * picture;
    int64_t             picture_pts;
    AVFrame             rgb_picture;

    IplImage            frame;
} CvCaptureAVI_FFMPEG;

static void icvCloseAVI_FFMPEG( CvCaptureAVI_FFMPEG* capture )
{
    //cvFree( (void**)&(capture->entries) );
        
    if( capture->picture )
    av_free(capture->picture);

    if( capture->video_st )
    {
#if LIBAVFORMAT_BUILD > 4628
        avcodec_close( capture->video_st->codec );
#else
        avcodec_close( &capture->video_st->codec );
#endif
        capture->video_st = NULL;
    }

    if( capture->ic )
    {
        av_close_input_file(capture->ic);
        capture->ic = NULL;
    }

    if( capture->rgb_picture.data[0] )
        cvFree( &capture->rgb_picture.data[0] );

    memset( &capture->frame, 0, sizeof(capture->frame));
}


static int icvOpenAVI_FFMPEG( CvCaptureAVI_FFMPEG* capture, const char* filename )
{
    int err, valid = 0, video_index = -1, i;
    AVFormatContext *ic;

    capture->ic = NULL;
    capture->video_stream = -1;
    capture->video_st = NULL;
    /* register all codecs, demux and protocols */
    av_register_all();

    err = av_open_input_file(&ic, filename, NULL, 0, NULL);
    if (err < 0) {
	    CV_WARN("Error opening file");
	    goto exit_func;
    }
    capture->ic = ic;
    err = av_find_stream_info(ic);
    if (err < 0) {
	    CV_WARN("Could not find codec parameters");
	    goto exit_func;
    }
    for(i = 0; i < ic->nb_streams; i++) {
#if LIBAVFORMAT_BUILD > 4628
        AVCodecContext *enc = ic->streams[i]->codec;
#else
        AVCodecContext *enc = &ic->streams[i]->codec;
#endif
        AVCodec *codec;
    if( CODEC_TYPE_VIDEO == enc->codec_type && video_index < 0) {
        video_index = i;
        codec = avcodec_find_decoder(enc->codec_id);
        if (!codec ||
        avcodec_open(enc, codec) < 0)
        goto exit_func;
        capture->video_stream = i;
        capture->video_st = ic->streams[i];
        capture->picture = avcodec_alloc_frame();

        capture->rgb_picture.data[0] = (uchar*)cvAlloc(
                                avpicture_get_size( PIX_FMT_BGR24,
                                enc->width, enc->height ));
        avpicture_fill( (AVPicture*)&capture->rgb_picture, capture->rgb_picture.data[0],
                PIX_FMT_BGR24, enc->width, enc->height );

        cvInitImageHeader( &capture->frame, cvSize( enc->width,
                                   enc->height ), 8, 3, 0, 4 );
        cvSetData( &capture->frame, capture->rgb_picture.data[0],
                           capture->rgb_picture.linesize[0] );
        break;
    }
    }


    if(video_index >= 0)
    valid = 1;

exit_func:

    if( !valid )
        icvCloseAVI_FFMPEG( capture );

    return valid;
}


static int icvGrabFrameAVI_FFMPEG( CvCaptureAVI_FFMPEG* capture )
{
    int valid=0;
    static bool bFirstTime = true;
    static AVPacket pkt;
    int got_picture;

    // First time we're called, set packet.data to NULL to indicate it
    // doesn't have to be freed
    if (bFirstTime) {
        bFirstTime = false;
        pkt.data = NULL;
    }

    if( !capture || !capture->ic || !capture->video_st )
        return 0;

    // free last packet if exist
    if (pkt.data != NULL) {
        av_free_packet (&pkt);
    }

    // get the next frame
    while ((0 == valid) && (av_read_frame(capture->ic, &pkt) >= 0)) {
		if( pkt.stream_index != capture->video_stream ) continue;

#if LIBAVFORMAT_BUILD > 4628
        avcodec_decode_video(capture->video_st->codec, 
                             capture->picture, &got_picture, 
                             pkt.data, pkt.size);
#else
        avcodec_decode_video(&capture->video_st->codec, 
                             capture->picture, &got_picture, 
                             pkt.data, pkt.size);
#endif

        if (got_picture) {
            // we have a new picture, so memorize it
            capture->picture_pts = pkt.pts;
            valid = 1;
        }
    }
    
    // return if we have a new picture or not
    return valid;
}


static const IplImage* icvRetrieveFrameAVI_FFMPEG( CvCaptureAVI_FFMPEG* capture )
{
    if( !capture || !capture->video_st || !capture->picture->data[0] )
    return 0;
#if LIBAVFORMAT_BUILD > 4628
    img_convert( (AVPicture*)&capture->rgb_picture, PIX_FMT_BGR24,
                 (AVPicture*)capture->picture,
                 capture->video_st->codec->pix_fmt,
                 capture->video_st->codec->width,
                 capture->video_st->codec->height );
#else
    img_convert( (AVPicture*)&capture->rgb_picture, PIX_FMT_BGR24,
                 (AVPicture*)capture->picture,
                 capture->video_st->codec.pix_fmt,
                 capture->video_st->codec.width,
                 capture->video_st->codec.height );
#endif
    return &capture->frame;
}


static int icvSetPropertyAVI_FFMPEG( CvCaptureAVI_FFMPEG* capture,
                                     int property_id, double value );

static double icvGetPropertyAVI_FFMPEG( CvCaptureAVI_FFMPEG* capture, int property_id )
{
    if( !capture || !capture->video_st || !capture->picture->data[0] )
    return 0;

    int64_t timestamp;
    timestamp = capture->picture_pts;

    switch( property_id )
    {
    case CV_CAP_PROP_POS_MSEC:
        if(capture->ic->start_time != static_cast<double>(AV_NOPTS_VALUE))
        return (double)(timestamp - capture->ic->start_time)*1000/(double)AV_TIME_BASE;
        break;
    case CV_CAP_PROP_POS_FRAMES:
    if(capture->video_st->cur_dts != static_cast<double>(AV_NOPTS_VALUE))
        return (double)capture->video_st->cur_dts-1;
    break;
    case CV_CAP_PROP_POS_AVI_RATIO:
    if(capture->ic->start_time != static_cast<double>(AV_NOPTS_VALUE) && capture->ic->duration != static_cast<double>(AV_NOPTS_VALUE))
        return (double)(timestamp-capture->ic->start_time)/(double)capture->ic->duration;
    break;
    case CV_CAP_PROP_FRAME_WIDTH:
        return capture->frame.width;
    break;
    case CV_CAP_PROP_FRAME_HEIGHT:
        return capture->frame.height;
    break;
    case CV_CAP_PROP_FPS:
#if LIBAVCODEC_BUILD > 4753
        return av_q2d (capture->video_st->r_frame_rate);
#else
        return (double)capture->video_st->codec.frame_rate
            / (double)capture->video_st->codec.frame_rate_base;
#endif
    break;
    case CV_CAP_PROP_FOURCC:
#if LIBAVFORMAT_BUILD > 4628
        return (double)capture->video_st->codec->codec_tag;
#else
        return (double)capture->video_st->codec.codec_tag;
#endif
    break;
    }
    return 0;
}


static int icvSetPropertyAVI_FFMPEG( CvCaptureAVI_FFMPEG* capture,
                                     int property_id, double value )
{
    if( !capture || !capture->video_st || !capture->picture->data[0] )
    return 0;
    switch( property_id )
    {
#if 0    
    case CV_CAP_PROP_POS_MSEC:
    case CV_CAP_PROP_POS_FRAMES:
    case CV_CAP_PROP_POS_AVI_RATIO:
        {
        int64_t timestamp = AV_NOPTS_VALUE;
        switch( property_id )
            {
        case CV_CAP_PROP_POS_FRAMES:
        if(capture->ic->start_time != AV_NOPTS_VALUE) {
            value *= (double)capture->video_st->codec.frame_rate_base
            / (double)capture->video_st->codec.frame_rate;
            timestamp = capture->ic->start_time+(int64_t)(value*AV_TIME_BASE);
        }
        break;
        case CV_CAP_PROP_POS_MSEC:
        if(capture->ic->start_time != AV_NOPTS_VALUE)
            timestamp = capture->ic->start_time+(int64_t)(value*AV_TIME_BASE/1000);
        break;
        case CV_CAP_PROP_POS_AVI_RATIO:
        if(capture->ic->start_time != AV_NOPTS_VALUE && capture->ic->duration != AV_NOPTS_VALUE)
            timestamp = capture->ic->start_time+(int64_t)(value*capture->ic->duration);
        break;
        }
        if(timestamp != AV_NOPTS_VALUE) {
        //printf("timestamp=%g\n",(double)timestamp);
        int ret = av_seek_frame(capture->ic, -1, timestamp, 0);
        if (ret < 0) {
            fprintf(stderr, "HIGHGUI ERROR: AVI: could not seek to position %0.3f\n", 
                (double)timestamp / AV_TIME_BASE);
            return 0;
        }
        }
    }
        break;
#endif  
    default:
        return 0;
    }

    return 1;
}

static CvCaptureVTable captureAVI_FFMPEG_vtable = 
{
    6,
    (CvCaptureCloseFunc)icvCloseAVI_FFMPEG,
    (CvCaptureGrabFrameFunc)icvGrabFrameAVI_FFMPEG,
    (CvCaptureRetrieveFrameFunc)icvRetrieveFrameAVI_FFMPEG,
    (CvCaptureGetPropertyFunc)icvGetPropertyAVI_FFMPEG,
    (CvCaptureSetPropertyFunc)icvSetPropertyAVI_FFMPEG,
    (CvCaptureGetDescriptionFunc)0
};


CvCapture* cvCaptureFromFile_FFMPEG( const char* filename )
{
    CvCaptureAVI_FFMPEG* capture = 0;

    if( filename )
    {
        capture = (CvCaptureAVI_FFMPEG*)cvAlloc( sizeof(*capture));
        memset( capture, 0, sizeof(*capture));

        capture->vtable = &captureAVI_FFMPEG_vtable;

        if( !icvOpenAVI_FFMPEG( capture, filename ))
            cvReleaseCapture( (CvCapture**)&capture );
    }

    return (CvCapture*)capture;
}

///////////////// FFMPEG CvVideoWriter implementation //////////////////////////
typedef struct CvAVI_FFMPEG_Writer
{
	AVOutputFormat *fmt;
	AVFormatContext *oc;
    uint8_t         * outbuf;
    uint32_t          outbuf_size;
    FILE            * outfile;
    AVFrame         * picture;
    AVFrame         * rgb_picture;
    uint8_t         * picbuf;
	AVStream        * video_st;
} CvAVI_FFMPEG_Writer;

/**
 * the following function is a modified version of code
 * found in ffmpeg-0.4.9-pre1/output_example.c
 */
static AVFrame * icv_alloc_picture_FFMPEG(int pix_fmt, int width, int height, bool alloc)
{
	AVFrame * picture;
	uint8_t * picture_buf;
	int size;

	picture = avcodec_alloc_frame();
	if (!picture)
		return NULL;
	size = avpicture_get_size(pix_fmt, width, height);
	if(alloc){
		picture_buf = (uint8_t *) cvAlloc(size);
		if (!picture_buf) 
		{
			av_free(picture);
			return NULL;
		}
		avpicture_fill((AVPicture *)picture, picture_buf, 
				pix_fmt, width, height);
	}
	else {
	}
	return picture;
}

/* add a video output stream */
static AVStream *icv_add_video_stream_FFMPEG(AVFormatContext *oc, int codec_tag, int w, int h, int bitrate, double fps, int pixel_format)
{
	AVCodecContext *c;
	AVStream *st;
    int codec_id;
	int frame_rate, frame_rate_base;
	AVCodec *codec;
	

	st = av_new_stream(oc, 0);
	if (!st) {
		CV_WARN("Could not allocate stream");
		return NULL;
	}

#if LIBAVFORMAT_BUILD > 4628
	c = st->codec;
#else
	c = &(st->codec);
#endif
#if LIBAVFORMAT_BUILD > 4621 
	codec_id = av_guess_codec(oc->oformat, NULL, oc->filename, NULL, CODEC_TYPE_VIDEO);
#else
	codec_id = oc->oformat->video_codec;
#endif

    if(codec_tag) c->codec_tag=codec_tag;

	c->codec_id = (CodecID) codec_id;
	codec = avcodec_find_encoder(c->codec_id);

	c->codec_type = CODEC_TYPE_VIDEO;

	/* put sample parameters */
	c->bit_rate = bitrate;

	/* resolution must be a multiple of two */
	c->width = w;
	c->height = h;

	/* time base: 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
       identically 1. */
	frame_rate=cvRound(fps);
	frame_rate_base=1;
	while (fabs((double)frame_rate/frame_rate_base) - fps > 0.001){
		frame_rate_base*=10;
		frame_rate=cvRound(fps*frame_rate_base);
	}
#if LIBAVFORMAT_BUILD > 4752
    c->time_base.den = frame_rate;
    c->time_base.num = frame_rate_base;
	/* adjust time base for supported framerates */
	if(codec && codec->supported_framerates){
		const AVRational *p= codec->supported_framerates;
		AVRational req= (AVRational){frame_rate, frame_rate_base};
		const AVRational *best=NULL;
		AVRational best_error= (AVRational){INT_MAX, 1};
		for(; p->den!=0; p++){
			AVRational error= av_sub_q(req, *p);
			if(error.num <0) error.num *= -1;
			if(av_cmp_q(error, best_error) < 0){
				best_error= error;
				best= p;
			}
		}
		c->time_base.den= best->num;
		c->time_base.num= best->den;
	}
#else
	c->frame_rate = frame_rate;
	c->frame_rate_base = frame_rate_base;
#endif

	c->gop_size = 12; /* emit one intra frame every twelve frames at most */
	c->pix_fmt = (PixelFormat) pixel_format;
	if (c->codec_id == CODEC_ID_MPEG2VIDEO) {
        /* just for testing, we also add B frames */
        c->max_b_frames = 2;
    }
    if (c->codec_id == 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 */
        c->mb_decision=2;
    }
    // some formats want stream headers to be seperate
    if(!strcmp(oc->oformat->name, "mp4") || !strcmp(oc->oformat->name, "mov") || !strcmp(oc->oformat->name,  "3gp"))
        c->flags |= CODEC_FLAG_GLOBAL_HEADER;

    return st;
}

/// Create a video writer object that uses FFMPEG
CV_IMPL CvVideoWriter* cvCreateVideoWriter( const char * filename, int fourcc,
		double fps, CvSize frameSize, int /*is_color*/ )
{
	CV_FUNCNAME("cvCreateVideoWriter");

	CvAVI_FFMPEG_Writer * writer = NULL;
	
	__BEGIN__;

	// check arguments
	assert (filename);
	assert (fps > 0);
	assert (frameSize.width > 0  &&  frameSize.height > 0);

	// allocate memory for structure...
	writer = (CvAVI_FFMPEG_Writer *) cvAlloc( sizeof(CvAVI_FFMPEG_Writer));
	memset (writer, 0, sizeof (*writer));

	// tell FFMPEG to register codecs
	av_register_all ();

	/* auto detect the output format from the name. default is mpeg. */
	writer->fmt = guess_format(NULL, filename, NULL);
	if (!writer->fmt) {
		CV_ERROR( CV_StsUnsupportedFormat, "Could not deduce output format from file extension");
		//writer->fmt = guess_format("mpeg", NULL, NULL);
	}

	// alloc memory for context 
	writer->oc = av_alloc_format_context();
	assert (writer->oc);

	/* set file name */
	writer->oc->oformat = writer->fmt;
	snprintf(writer->oc->filename, sizeof(writer->oc->filename), "%s", filename);

	// TODO -- safe to ignore output audio stream?
	writer->video_st = icv_add_video_stream_FFMPEG(writer->oc, fourcc, frameSize.width, frameSize.height, 800000, fps, PIX_FMT_YUV420P);


	/* set the output parameters (must be done even if no
       parameters). */
    if (av_set_parameters(writer->oc, NULL) < 0) {
		CV_ERROR(CV_StsBadArg, "Invalid output format parameters");
    }

    dump_format(writer->oc, 0, filename, 1);

    /* now that all the parameters are set, we can open the audio and
       video codecs and allocate the necessary encode buffers */
    if (!writer->video_st){
		CV_ERROR(CV_StsBadArg, "Couldn't open video stream");
	}

    AVCodec *codec;
    AVCodecContext *c;

#if LIBAVFORMAT_BUILD > 4628
    c = (writer->video_st->codec);
#else
    c = &(writer->video_st->codec);
#endif

    /* find the video encoder */
    codec = avcodec_find_encoder(c->codec_id);
    if (!codec) {
		CV_ERROR(CV_StsBadArg, "codec not found");
    }

    /* open the codec */
    if (avcodec_open(c, codec) < 0) {
		char errtext[256];
		sprintf(errtext, "Could not open codec '%s'", codec->name);
		CV_ERROR(CV_StsBadArg, errtext);
    }

	//	printf("Using codec %s\n", codec->name);
    writer->outbuf = NULL;

    if (!(writer->oc->oformat->flags & AVFMT_RAWPICTURE)) {
        /* allocate output buffer */
        /* XXX: API change will be done */
        writer->outbuf_size = 200000;
        writer->outbuf = (uint8_t *) malloc(writer->outbuf_size);
    }

	bool need_color_convert;
        need_color_convert = c->pix_fmt != PIX_FMT_BGR24;

    /* allocate the encoded raw picture */
    writer->picture = icv_alloc_picture_FFMPEG(c->pix_fmt, c->width, c->height, need_color_convert);
    if (!writer->picture) {
		CV_ERROR(CV_StsNoMem, "Could not allocate picture");
    }

    /* if the output format is not YUV420P, then a temporary YUV420P
       picture is needed too. It is then converted to the required
       output format */
    writer->rgb_picture = NULL;
    if ( need_color_convert ) {
        writer->rgb_picture = icv_alloc_picture_FFMPEG(PIX_FMT_BGR24, c->width, c->height, false);
        if (!writer->rgb_picture) {
			CV_ERROR(CV_StsNoMem, "Could not allocate picture");
        }
    }

	/* open the output file, if needed */
    if (!(writer->fmt->flags & AVFMT_NOFILE)) {
        if (url_fopen(&writer->oc->pb, filename, URL_WRONLY) < 0) {
			CV_ERROR(CV_StsBadArg, "Couldn't open output file for writing");
        }
    }

    /* write the stream header, if any */
    av_write_header( writer->oc );
	

	__END__;

	// return what we got
	return (CvVideoWriter *) writer;
}

int icv_av_write_frame_FFMPEG( AVFormatContext * oc, AVStream * video_st, uint8_t * outbuf, uint32_t outbuf_size, AVFrame * picture ){
	CV_FUNCNAME("icv_av_write_frame_FFMPEG");

#if LIBAVFORMAT_BUILD > 4628
	AVCodecContext * c = video_st->codec;
#else
	AVCodecContext * c = &(video_st->codec);
#endif
	int out_size;
	int ret;

	__BEGIN__;

    if (oc->oformat->flags & AVFMT_RAWPICTURE) {
        /* raw video case. The API will change slightly in the near
           futur for that */
        AVPacket pkt;
        av_init_packet(&pkt);

        pkt.flags |= PKT_FLAG_KEY;
        pkt.stream_index= video_st->index;
        pkt.data= (uint8_t *)picture;
        pkt.size= sizeof(AVPicture);

        ret = av_write_frame(oc, &pkt);
    } else {
        /* encode the image */
        out_size = avcodec_encode_video(c, outbuf, outbuf_size, picture);
        /* if zero size, it means the image was buffered */
        if (out_size > 0) {
            AVPacket pkt;
            av_init_packet(&pkt);
#if LIBAVFORMAT_BUILD > 4752 
            pkt.pts = av_rescale_q(c->coded_frame->pts, c->time_base, video_st->time_base);
#else 
			pkt.pts = c->coded_frame->pts;
#endif
            if(c->coded_frame->key_frame)
                pkt.flags |= PKT_FLAG_KEY;
            pkt.stream_index= video_st->index;
            pkt.data= outbuf;
            pkt.size= out_size;

            /* write the compressed frame in the media file */
            ret = av_write_frame(oc, &pkt);
        } else {
            ret = 0;
        }
    }
    if (ret != 0) {
		CV_ERROR(CV_StsError, "Error while writing video frame");
	}

	__END__;
	return CV_StsOk;
}

/// write a frame with FFMPEG
CV_IMPL int cvWriteFrame( CvVideoWriter * writer, const IplImage * image )
{
	int ret = 0;

	CV_FUNCNAME("cvWriteFrame");

	__BEGIN__;

	// typecast from opaque data type to implemented struct
	CvAVI_FFMPEG_Writer * mywriter = (CvAVI_FFMPEG_Writer*) writer;
#if LIBAVFORMAT_BUILD > 4628
    AVCodecContext *c = mywriter->video_st->codec;
#else
	AVCodecContext *c = &(mywriter->video_st->codec);
#endif
	// check parameters
	assert ( image );
	assert ( image->nChannels == 3 );
	assert ( image->depth == IPL_DEPTH_8U );


	// check if buffer sizes match, i.e. image has expected format (size, channels, bitdepth, alignment)
	assert (image->imageSize == avpicture_get_size (PIX_FMT_BGR24, image->width, image->height));

	if (c->pix_fmt != PIX_FMT_BGR24 ) {
		assert( mywriter->rgb_picture );
		// let rgb_picture point to the raw data buffer of 'image'
		avpicture_fill((AVPicture *)mywriter->rgb_picture, (uint8_t *) image->imageData, 
				PIX_FMT_BGR24, image->width, image->height);

		// convert to the color format needed by the codec
		if( img_convert((AVPicture *)mywriter->picture, c->pix_fmt,
					(AVPicture *)mywriter->rgb_picture, PIX_FMT_BGR24, 
					image->width, image->height) < 0){
			CV_ERROR(CV_StsUnsupportedFormat, "FFMPEG::img_convert pixel format conversion from BGR24 not handled");
		}
	}
	else{
		avpicture_fill((AVPicture *)mywriter->picture, (uint8_t *) image->imageData,
				PIX_FMT_BGR24, image->width, image->height);
	}

	ret = icv_av_write_frame_FFMPEG( mywriter->oc, mywriter->video_st, mywriter->outbuf, mywriter->outbuf_size, mywriter->picture);

	__END__;
	return ret;
}

/// close video output stream and free associated memory
CV_IMPL void cvReleaseVideoWriter( CvVideoWriter ** writer )
{
	int i;

	// nothing to do if already released
	if ( !(*writer) )
		return;
	
	// release data structures in reverse order
	CvAVI_FFMPEG_Writer * mywriter = (CvAVI_FFMPEG_Writer*)(*writer);

	/* no more frame to compress. The codec has a latency of a few
	   frames if using B frames, so we get the last frames by
	   passing the same picture again */
	// TODO -- do we need to account for latency here? 

	/* write the trailer, if any */
	av_write_trailer(mywriter->oc);

	// free pictures
#if LIBAVFORMAT_BUILD > 4628
	if( mywriter->video_st->codec->pix_fmt != PIX_FMT_BGR24){
#else
	if( mywriter->video_st->codec.pix_fmt != PIX_FMT_BGR24){
#endif
		cvFree(&(mywriter->picture->data[0]));
	}
	av_free(mywriter->picture);

    if (mywriter->rgb_picture) {
        av_free(mywriter->rgb_picture);
    }

	/* close codec */
#if LIBAVFORMAT_BUILD > 4628
	avcodec_close(mywriter->video_st->codec);
#else
	avcodec_close(&(mywriter->video_st->codec));
#endif
    
	av_free(mywriter->outbuf);

	/* free the streams */
	for(i = 0; i < mywriter->oc->nb_streams; i++) {
		av_freep(&mywriter->oc->streams[i]->codec);
		av_freep(&mywriter->oc->streams[i]);
	}

	if (!(mywriter->fmt->flags & AVFMT_NOFILE)) {
		/* close the output file */
		url_fclose(&mywriter->oc->pb);
	}

	/* free the stream */
	av_free(mywriter->oc);

	/* free cvVideoWriter */
	cvFree ( writer );

	// mark as released
	(*writer) = 0;
}
