// ffmpeg_dll.cpp : Definiert die exportierten Funktionen für die DLL-Anwendung.
//

#include "stdafx.h"
#include "ffmpeg_dll.h"
#include "../../ffmpeg/include/stdint.h"
#include "../../ffmpeg/include/inttypes.h"
#include "../amr_wb/code/enc/include/interf_enc.h"
#include "../amr_nb/code/main/interf_enc.h"
#include <iostream>
#include <fstream>
using namespace std;

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

#define inline _inline
#define snprintf _snprintf

#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include <memory.h>

#undef exit
#define STREAM_PIX_FMT PIX_FMT_YUV420P /* default pix_fmt */
#define PICTURE_PIX_FMT PIX_FMT_ARGB /* source pix_fmt */

static int sws_flags = SWS_BICUBIC;

// Logging variables
static ofstream s_logFile;


#define MAXPICS 8
#define MAXPICSIZE 2097152
static unsigned int gstc_picBufIdx = 0;
static char gstc_picBufs[ MAXPICS ][MAXPICSIZE];
static unsigned int gstc_u4MaxQueueSize = 0;


static unsigned int gstc_u4Mallocs = 0;
static unsigned int gstc_u4MaxMallocs = 0;



DWORD WINAPI Cffmpeg_dll_thread(LPVOID param);

/**
*	Write ffmpeg logging to the ffmpeg logging file.
*/
static void log_callback(void *ptr, int level, const char *fmt, va_list vargs)
{
	static char message[8192];   
	const char *module = NULL;

	// We don't want every nonsense in here
	if (level > AV_LOG_WARNING)
		return;

	// Get module name
    if (ptr)
    {
        AVClass *avc = *(AVClass**) ptr;
        module = avc->item_name(ptr);
    }

	// Create the actual message
	vsnprintf_s(message, sizeof(message), fmt, vargs);

	// Skip the "non-strictly-monotonic PTS"
	// No idea what that is, seems it cannot be fixed.
	if (strncmp(message,"non-strictly-monotonic PTS", 10) == 0)
		return;

	// Append the message to the logfile
	if (module)
	{
		s_logFile << module << " -------------------" << endl;
	}
	s_logFile << "lvl: " << level << endl << "msg: " << message << endl;
}

/*
* add an audio output stream
*/
static AVStream *add_audio_stream(Cffmpeg_dll * ptr, AVFormatContext *oc, enum CodecID codec_id )
{
	AVCodecContext *c;
	AVStream *st;
	AVCodec* codec;

	// Get correct codec
	codec = avcodec_find_encoder(codec_id);
	if (!codec) {
		av_log(NULL, AV_LOG_ERROR, "%s","Codec not found\n");
		exit(1);
	}

	// Create stream
	st = avformat_new_stream(oc, codec);
	if (!st) {
		av_log(NULL, AV_LOG_ERROR, "%s","Could not alloc stream\n");
		exit(1);
	}

	c = st->codec;
	c->codec = codec;
	c->codec_id = codec_id;
	c->codec_type = AVMEDIA_TYPE_AUDIO;

	/* put sample parameters */
	c->sample_fmt = ptr->sample_fmt;
	c->bit_rate = ptr->audio_bit_rate;
	c->sample_rate = ptr->sample_rate;
	c->channels = ptr->channels;

	// some formats want stream headers to be separate
	if(oc->oformat->flags & AVFMT_GLOBALHEADER)
		c->flags |= CODEC_FLAG_GLOBAL_HEADER;

	return st;
}

static void open_audio( Cffmpeg_dll * ptr, AVFormatContext *oc, AVStream *st )
{
	AVCodecContext *c;

	c = st->codec;

	/* open it */
	// NEWFFMPEG
	if (avcodec_open2(c, NULL, NULL) < 0) {
		av_log(c, AV_LOG_ERROR, "%s","could not open audio codec\n");
		exit(1);
	}

	/* init signal generator */
	ptr->t = 0;
	ptr->tincr = (float)(2 * M_PI * 110.0 / c->sample_rate);
	/* increment frequency by 110 Hz per second */
	ptr->tincr2 = (float)(2 * M_PI * 110.0 / c->sample_rate / c->sample_rate);

	ptr->audio_outbuf_size = 10000;
	ptr->audio_outbuf = (uint8_t *) av_malloc(ptr->audio_outbuf_size);

	if (c->codec->capabilities & CODEC_CAP_VARIABLE_FRAME_SIZE)
       ptr-> audio_input_frame_size = 10000;
    else
       ptr->audio_input_frame_size = c->frame_size;
    ptr->samples = (int16_t *) av_malloc(ptr->audio_input_frame_size *
										av_get_bytes_per_sample(c->sample_fmt) *
										c->channels);

	/* ugly hack for PCM codecs (will be removed ASAP with new PCM
	support to compute the input frame size in samples */
	/*if (c->frame_size <= 1) {
		ptr->audio_input_frame_size = ptr->audio_outbuf_size / c->channels;
		switch(st->codec->codec_id) {
		case CODEC_ID_PCM_S16LE:
		case CODEC_ID_PCM_S16BE:
		case CODEC_ID_PCM_U16LE:
		case CODEC_ID_PCM_U16BE:
			ptr->audio_input_frame_size >>= 1;
			break;
		default:
			break;
		}
	} else {
		ptr->audio_input_frame_size = c->frame_size;
	}
	ptr->samples = (int16_t *) av_malloc(ptr->audio_input_frame_size * 2 * c->channels);*/
}

/* prepare a 16 bit dummy audio frame of 'frame_size' samples and
'nb_channels' channels */
static void get_audio_frame( Cffmpeg_dll* ptr, int16_t* samples, int frame_size, int nb_channels, int16_t sample_val ) {

	if( ! ptr->fp_sound_input ) sample_val = 0;
	if ( sample_val == 0 ) memset( samples, 0, sizeof( int16_t ) * frame_size * nb_channels );
	else int numread = fread( samples, sizeof( int16_t ), frame_size * nb_channels, ptr->fp_sound_input );
};

static void write_audio_frame( Cffmpeg_dll * ptr, AVFormatContext *oc, AVStream *st, int16_t sample_val )
{
	AVCodecContext *c;
	AVPacket pkt;
	av_init_packet(&pkt);

	c = st->codec;

	get_audio_frame( ptr, ptr->samples, ptr->audio_input_frame_size, c->channels, sample_val );

	pkt.size= avcodec_encode_audio( c, ptr->audio_outbuf, ptr->audio_outbuf_size, ptr->samples );

	if (c->coded_frame && c->coded_frame->pts != AV_NOPTS_VALUE)
		pkt.pts= av_rescale_q(c->coded_frame->pts, c->time_base, st->time_base);
	pkt.flags |= AV_PKT_FLAG_KEY;
	pkt.stream_index= st->index;
	pkt.data= ptr->audio_outbuf;

	/* write the compressed frame in the media file */
	if (av_write_frame(oc, &pkt) != 0) {
		av_log(c, AV_LOG_ERROR, "%s","Error while writing audio frame\n");
		exit(1);
	}
}

static void close_audio( Cffmpeg_dll * ptr, AVFormatContext *oc, AVStream *st )
{
	//switch ( ptr->audio_codec_id )
	//{
	//case CODEC_ID_AAC :
	//	break;
	//case CODEC_ID_AMR_NB :
	//	break;
	//case CODEC_ID_AMR_WB :
	//	break;
	//case CODEC_ID_MP3 :
	//	break;
	//}

	avcodec_close( st->codec );
	av_free( ptr->samples );
	ptr->samples = NULL;
	av_free( ptr->audio_outbuf );
	ptr->audio_outbuf = NULL;
}

/* add a video output stream */
static AVStream *add_video_stream( Cffmpeg_dll * ptr, AVFormatContext *oc, enum CodecID codec_id )
{
	AVCodecContext *c;
	AVStream *st;  
	AVCodec* codec;

	// Get correct codec
	codec = avcodec_find_encoder(codec_id);
	if (!codec) {
		av_log(NULL, AV_LOG_ERROR, "%s","Video codec not found\n");
		exit(1);
	}

	// Create stream
	st = avformat_new_stream(oc, codec);
	if (!st) {
		av_log(NULL, AV_LOG_ERROR, "%s","Could not alloc stream\n");
		exit(1);
	}

	c = st->codec;
 
	/* Get default values */
	codec = avcodec_find_encoder(codec_id);
	if (!codec) {
		av_log(NULL, AV_LOG_ERROR, "%s","Video codec not found (default values)\n");
		exit(1);
	}
	avcodec_get_context_defaults3(c, codec);

	c->codec_id = codec_id;
	c->codec_type = AVMEDIA_TYPE_VIDEO;

	/* put sample parameters */
	c->bit_rate = ptr->video_bit_rate;
	av_log(NULL, AV_LOG_ERROR, " Bit rate: %i", c->bit_rate);

    if (codec_id == CODEC_ID_H264) 
    {
        c->qmin = ptr->qmin;
        c->qmax = ptr->qmax;
        c->me_method = ptr->me_method;
        c->me_subpel_quality = ptr->me_subpel_quality;
        c->i_quant_factor = ptr->i_quant_factor;
        c->qcompress = ptr->qcompress;
        c->max_qdiff = ptr->max_qdiff;

		// We need to set the level and profile to get videos that play (hopefully) on all platforms
		//c->level = 30;
		c->profile = FF_PROFILE_H264_CONSTRAINED_BASELINE;
    }

    /* resolution must be a multiple of two */
	c->width = ptr->dstWidth; 
	c->height = ptr->dstHeight; 

	/* 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. */
	c->time_base.den = ptr->fps;
	c->time_base.num = 1;
	c->gop_size = ptr->fps;
	c->pix_fmt = STREAM_PIX_FMT;
	c->max_b_frames = 0;
	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 does not happen with normal video, it just happens here as
		the motion of the chroma plane does not match the luma plane. */
		c->mb_decision=2;
	}
	// some formats want stream headers to be separate
	if(oc->oformat->flags & AVFMT_GLOBALHEADER)
		c->flags |= CODEC_FLAG_GLOBAL_HEADER;

	return st;
}

static AVFrame *alloc_picture( enum PixelFormat pix_fmt, int width, int height )
{
	AVFrame *picture;
	uint8_t *picture_buf;
	int size;

	picture = avcodec_alloc_frame();
	if (!picture)
		return NULL;
	size = avpicture_get_size(pix_fmt, width, height);
	picture_buf = (uint8_t *) av_malloc(size);
	if (!picture_buf) {
		av_free(picture);
		return NULL;
	}
	avpicture_fill((AVPicture *)picture, picture_buf,
		pix_fmt, width, height);
	return picture;
}

static void open_video( Cffmpeg_dll * ptr, AVFormatContext *oc, AVStream *st )
{
	AVCodecContext *c;
	AVCodec* codec;

	c = st->codec;
    c->thread_count = ptr->threads;
	
	codec = avcodec_find_encoder(c->codec_id);
	if (!codec) {
		av_log(NULL, AV_LOG_ERROR, "%s","Video codec not found (default values)\n");
		exit(1);
	}

	/* open the codec */
	if (avcodec_open2(c, codec, NULL) < 0) {
		av_log(c, AV_LOG_ERROR, "%s","could not open video codec\n");
		exit(1);
	}

	ptr->video_outbuf = NULL;
	if (!(oc->oformat->flags & AVFMT_RAWPICTURE)) {
		/* allocate output buffer */
		/* XXX: API change will be done */
		/* buffers passed into lav* can be allocated any way you prefer,
		as long as they're aligned enough for the architecture, and
		they're freed appropriately (such as using av_free for buffers
		allocated with av_malloc) */
		ptr->video_outbuf_size = 200000;
		ptr->video_outbuf = (uint8_t *) av_malloc( ptr->video_outbuf_size );
	}

	/* allocate the encoded raw picture */
	ptr->picture = alloc_picture( c->pix_fmt, c->width, c->height );
	if ( !ptr->picture ) 
	{
		av_log(c, AV_LOG_ERROR, "%s","Could not allocate picture\n");
		exit(1);
	}

	/* if the output format is not YUV420P, then a temporary YUV420P
	picture is needed too. It is then converted to the required
	output format */
	ptr->tmp_picture = NULL;
	if ( c->pix_fmt != PICTURE_PIX_FMT ) 
	{
		ptr->tmp_picture = alloc_picture( PICTURE_PIX_FMT, ptr->srcWidth, ptr->srcHeight );
		if ( !ptr->tmp_picture ) 
		{
			av_log(c, AV_LOG_ERROR, "%s","Could not allocate temporary picture\n");
			exit(1);
		}
	}
}

/* copy the bytes of the source image resorted in to the destination image */
static void fill_image( AVFrame *pict, void * pic, int pic_size, int frame_index, int width, int height )
{
	int pic_idx = pic_size-( width*4 )-3;
	char * pict_dst = (char *)pict->data[0];
	char* pic_c = (char*)pic;

	for( int i = 0; i < height; i++ )
	{
		if ( pic_idx-width*4 < 0 ) break;
		int idx_dst = i * width * 4;
		for ( int j = 0; j < width; j++ )
		{
			int dst_idx = idx_dst+(j*4);
			int pict_idx = pic_idx+(j*4);
			pict_dst[dst_idx+0] = pic_c[pict_idx+0]; // alpha
			pict_dst[dst_idx+1] = pic_c[pict_idx+3]; // blue
			pict_dst[dst_idx+2] = pic_c[pict_idx+2]; // green
			pict_dst[dst_idx+3] = pic_c[pict_idx+1]; // red
		}
		pic_idx -= width*4;
	}

	pict->data[0] = (uint8_t *)pict_dst;
}

static void write_video_frame( Cffmpeg_dll * ptr, AVFormatContext *oc, AVStream *st, int lastFrame, void * pic, int pic_size )
{
	int out_size, ret;
	AVCodecContext *c;
	//AVFrame* tempPic;
	static struct SwsContext *img_convert_ctx = NULL;

	c = st->codec;

	if (ptr->frame_count >= lastFrame ) 
	{
		/* 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 */
	} 
	else 
	{
		if (c->pix_fmt != PICTURE_PIX_FMT) 
		{
			/* as we only generate a YUV420P picture, we must convert it
			to the codec pixel format if needed */
			if (ptr->img_convert_ctx == NULL) 
			{
				ptr->img_convert_ctx = sws_getContext( ptr->srcWidth, ptr->srcHeight,
					PICTURE_PIX_FMT,
					c->width, c->height,
					c->pix_fmt,
					sws_flags, NULL, NULL, NULL );
				if (ptr->img_convert_ctx == NULL) 
				{
					av_log(c, AV_LOG_ERROR, "%s","Cannot initialize the conversion context\n");
					exit(1);
				}
			}
			fill_image(ptr->tmp_picture, pic, pic_size, ptr->frame_count, ptr->srcWidth, ptr->srcHeight );

			sws_scale(ptr->img_convert_ctx, ptr->tmp_picture->data, ptr->tmp_picture->linesize,
				0, ptr->srcHeight, ptr->picture->data, ptr->picture->linesize);
		} 
		else 
		{
			//fill_yuv_image(picture, frame_count, c->width, c->height);
			ptr->picture->data[0]= (uint8_t*)pic;
		}
	}


	if (oc->oformat->flags & AVFMT_RAWPICTURE) 
	{
		/* raw video case. The API will change slightly in the near
		futur for that */
		AVPacket pkt;
		
		(&pkt);

		pkt.flags |= AV_PKT_FLAG_KEY;
		pkt.stream_index= st->index;
		pkt.data= (uint8_t *)ptr->picture;
		pkt.size= sizeof(AVPicture);

		//ret = av_interleaved_write_frame( oc, &pkt ); // Seems to cause a memory leak
		ret = av_write_frame(oc, &pkt);
	} 
	else 
	{
		/*char buffer[4096];
		sprintf(buffer, "C:\\zoobe\\testing\\yuvfile%i.yuv", ptr->frame_count);
		ofstream testFile;
		testFile.open(buffer, ios_base::binary | ios_base::out | ios_base::trunc);
		testFile.write((char*)ptr->picture->data[0], ptr->picture->linesize[0]*360);
		testFile.write((char*)ptr->picture->data[1], ptr->picture->linesize[1]*360/2);
		testFile.write((char*)ptr->picture->data[2], ptr->picture->linesize[2]*360/2);
		testFile.close();*/

		//Sleep(3);
		/* encode the image */
		out_size = avcodec_encode_video( c, ptr->video_outbuf, ptr->video_outbuf_size, ptr->picture );
		/* if zero size, it means the image was buffered */
		if (out_size > 0) 
		{
			AVPacket pkt;
			av_init_packet(&pkt);
			if (c->coded_frame->pts != AV_NOPTS_VALUE)
				pkt.pts = av_rescale_q(c->coded_frame->pts, c->time_base, st->time_base);
			if(c->coded_frame->key_frame)
				pkt.flags |= AV_PKT_FLAG_KEY;
			pkt.stream_index= st->index;
			pkt.data= ptr->video_outbuf;
			pkt.size= out_size;

			/* write the compressed frame in the media file */
			//ret = av_interleaved_write_frame(oc, &pkt); // Seems to cause a memory leak
			ret = av_write_frame(oc, &pkt); 
		} 
		else 
		{
			ret = 0;
		}
	}
	if (ret != 0) 
	{
		av_log(c, AV_LOG_ERROR, "%s","Error while writing video frame\n");
		exit(1);
	}
	ptr->frame_count++;
}

static void close_video( Cffmpeg_dll * ptr, AVFormatContext *oc, AVStream *st )
{
    /* flush buffer */
	/*
    int out_size = avcodec_encode_video( st->codec, ptr->video_outbuf, ptr->video_outbuf_size, NULL );
	if (out_size > 0) 
	{
		AVPacket pkt;
		av_init_packet(&pkt);
		pkt.stream_index= st->index;
		pkt.data= ptr->video_outbuf;
		pkt.size= out_size;
		av_interleaved_write_frame( oc, &pkt );
    }
	*/

	avcodec_close( st->codec );
	av_free( ptr->picture->data[0] );
	av_free( ptr->picture);
	ptr->picture = NULL;
	if ( ptr->tmp_picture ) 
	{
		av_free( ptr->tmp_picture->data[0] );
		av_free( ptr->tmp_picture );
		ptr->tmp_picture = NULL;
	}
	av_free( ptr->video_outbuf );
	ptr->video_outbuf = NULL;
}

static CodecID GetCodecByString( char * codec )
{
	CodecID res = CODEC_ID_NONE;
	
	if ( strcmp(codec, "Mpeg4-Part 10") == 0 )
	{
		res = CODEC_ID_H264; // CODEC_ID_MPEG4;
	} 
	else if ( strcmp(codec, "MPEG2") == 0 )
	{
		res = CODEC_ID_MPEG2VIDEO;
	}
	else if ( strcmp(codec, "MP2") == 0 )
	{
		res = CODEC_ID_MP2;
	}
	else if ( strcmp(codec, "PCM-S16LE") == 0 )
	{
		res = CODEC_ID_PCM_S16LE;
	}
	else if ( strcmp(codec, "Mpeg4-Part 2") == 0 )
	{
		res = CODEC_ID_MPEG4;
	}
	else if ( strcmp(codec, "h263") == 0 )
	{
		res = CODEC_ID_H263;
	}
	else if ( strcmp(codec, "AAC-LC/HE") == 0 )
	{
		res = CODEC_ID_AAC;
	}
	else if ( strcmp(codec, "AMR-NB") == 0 )
	{
		res = CODEC_ID_AMR_NB;
	}
	else if ( strcmp(codec, "AMR-WB") == 0 )
	{
		res = CODEC_ID_AMR_WB;
	}
	else if ( strcmp(codec, "MP3") == 0 )
	{
		res = CODEC_ID_MP3;
	}

	return res;
}

FFMPEG_DLL_API Cffmpeg_dll * Create_Cffmpeg_dll()
{
	return ( Cffmpeg_dll* )new Cffmpeg_dll;
}

FFMPEG_DLL_API int Delete_Cffmpeg_dll( Cffmpeg_dll * ptr )
{
	delete ptr;
	return 0;
}

//
// Dies ist das Beispiel einer exportierten Funktion.
FFMPEG_DLL_API int InitVideo_ffmpeg_dll(Cffmpeg_dll * ptr, 
										const char * filename, 
										const char * soundname,
										const char * logfilePath)
{
	if ( ptr->fmt ) ptr->fmt = NULL;
	if ( ptr->oc ) ptr->oc = NULL;
	if( ptr->audio_st ) ptr->audio_st = NULL;
	if ( ptr->video_st ) ptr->video_st = NULL;
	if ( ptr->audio_pts > 0.0 ) ptr->audio_pts = 0.0;
	if( ptr->video_pts > 0.0 ) ptr->video_pts = 0.0f;

	gstc_u4Mallocs = 0;

	// Open the logfile
	s_logFile.open(logfilePath, ios::app | ios::out);
	s_logFile << "-----------------------------------------------------" << endl;
	s_logFile << "Initializing new ffmpeg video" << endl;

	/* create pic queue */
    ptr->pictureQueue = new std::queue<Picture>;

	//avcodec_init();
	avcodec_register_all();
	av_register_all();

	// Set log texts to be handled by our own function
	av_log_set_callback(log_callback);
	av_log_set_level(AV_LOG_WARNING);

	s_logFile << "libav and log callback init complete." << endl;

	/* auto detect the output format from the name. default is
	mpeg. */
	ptr->fmt = av_guess_format( NULL, filename, NULL );
	ptr->audio_codec_id = GetCodecByString(ptr->audioCodec);
	if (ptr->audio_codec_id == CODEC_ID_NONE)
	{
		ptr->audio_codec_id = CODEC_ID_AAC;
	}
	ptr->video_codec_id = GetCodecByString(ptr->videoCodec);
	if (ptr->video_codec_id == CODEC_ID_NONE)
	{
		ptr->video_codec_id = CODEC_ID_H264;
	}

	if ( !ptr->fmt ) 
	{
		printf("Could not deduce output format from file extension: using MPEG.\n");
		ptr->fmt = av_guess_format( "mpeg", NULL, NULL );
	}
	if ( !ptr->fmt ) 
	{
		fprintf( stderr, "Could not find suitable output format\n" );
		return 1;
	}

	ptr->fmt->video_codec = ptr->video_codec_id;
	ptr->fmt->audio_codec = ptr->audio_codec_id;

	s_logFile << "Got correct formats: " << ptr->video_codec_id << "/" << ptr->audio_codec_id << endl;

	/* open the sound input file, if needed */
	if ( strlen( soundname ) > 0 ) 
	{
		if ( fopen_s( &ptr->fp_sound_input, soundname, "rb" ) != 0  ) {

			av_log(ptr->oc, AV_LOG_ERROR, "Could not open '%s'\n", soundname );
			ptr->fp_sound_input = NULL;
			ptr->channels = 1;
		}
		else {

			// Header operations
			// See here: http://de.wikipedia.org/wiki/RIFF_WAVE

			// Get the header size of the audio file
			// 16 is initial size, until the rest-of-the-header-size value comes
			// Skip to and read the value
			int headerRest = 0;
			fseek(ptr->fp_sound_input, 16, SEEK_SET);	
			fread((void*)&headerRest, 4, 1, ptr->fp_sound_input);		

			// Get the number of channels
			// Skip to and read the value
			fseek(ptr->fp_sound_input, 2, SEEK_CUR);
			fread((void*)&ptr->channels, 2, 1, ptr->fp_sound_input);
			s_logFile << "Audio channels: " << ptr->channels << endl;

			// Skip to the end of the header 
			// - 4 because of the last seek & read
			// + 8 because of the header of the data section
			fseek(ptr->fp_sound_input, headerRest - 4 + 8, SEEK_CUR);
			s_logFile << "Opened audio input file." << endl;
		};
	};

	

	/* allocate the output media context */
	// NEWFFMPEG
	//ptr->oc = avformat_alloc_context();
	avformat_alloc_output_context2(&(ptr->oc), NULL, NULL, filename);
	if ( !ptr->oc ) 
	{
		av_log(ptr->oc, AV_LOG_ERROR, "%s","Memory error\n");
		return 1;
	}
	ptr->oc->oformat = ptr->fmt;

	// Create the filename
	// NEWFFMPEG
	//_snprintf_s( ptr->oc->filename, sizeof( ptr->oc->filename), strlen(filename), "%s", filename );

	/* add the audio and video streams using the format codecs
	and initialize the codecs */
	ptr->video_st = NULL;
	ptr->audio_st = NULL;
	if ( ptr->fmt->video_codec != CODEC_ID_NONE ) 
	{
		ptr->video_st = add_video_stream( ptr, ptr->oc, ptr->fmt->video_codec );
	}
	if ( ptr->fmt->audio_codec != CODEC_ID_NONE ) 
	{
		ptr->audio_st = add_audio_stream( ptr, ptr->oc, ptr->fmt->audio_codec );
	}

	s_logFile << "Added video and audio stream." << endl;

	/* set the output parameters (must be done even if no
	parameters). */
	// NEWFFMPEG
	/*if ( av_set_parameters( ptr->oc, NULL) < 0 ) 
	{
		fprintf( stderr, "Invalid output format parameters\n" );
		return 1;
	}*/

	// Responsible for dumping some facts/metadata/etc. to the logging
	av_dump_format(ptr->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 ( ptr->video_st )
		open_video(ptr, ptr->oc, ptr->video_st);
	if ( ptr->audio_st )
		open_audio(ptr, ptr->oc, ptr->audio_st);

	s_logFile << "Opened video and audio codecs." << endl;

	/* open the output file, if needed */
	if ( !( ptr->fmt->flags & AVFMT_NOFILE ) ) 
	{
	// NEWFFMPEG
		/*if (url_fopen( &ptr->oc->pb, filename, URL_WRONLY ) < 0) */
		if (avio_open( &ptr->oc->pb, filename, AVIO_FLAG_WRITE) < 0) 
		{
			s_logFile << "Could not open" << filename << "." << endl;
			fprintf( stderr, "Could not open '%s'\n", filename );
			return 1;
		}
	}

	s_logFile << "Opened output file." << endl;

	/* write the stream header, if any */
	// NEWFFMPEG
	//av_write_header( ptr->oc );
	avformat_write_header(ptr->oc, NULL);

	s_logFile << "Written header. Initialization complete. Starting own thread now." << endl;
	s_logFile << "-----------------------------------------------------" << endl;

	/* Write all audio */
	if ( ptr->fp_sound_input != NULL )
	{
		while ( !feof(ptr->fp_sound_input) )
		{
			write_audio_frame( ptr, ptr->oc, ptr->audio_st, -1 );
		}
	}

    /* start encoder thread */
    ptr->thread = CreateThread(0, 0, Cffmpeg_dll_thread, ptr, 0, 0);

	ptr->video_finished = FALSE;
	return 0;
}

int EncodePictureReal_ffmpeg_dll( Cffmpeg_dll * ptr, int frame, void * pic, int pic_size, int vStart, int soundOffset, int lStart, int lEnd, int vEnd );


FFMPEG_DLL_API int EncodePicture_ffmpeg_dll( Cffmpeg_dll * ptr, int frame, void * pic, int pic_size, int vStart, int soundOffset, int lStart, int lEnd, int vEnd )
{
    Picture picture;
    picture.frame = frame;
    if( pic_size >= MAXPICSIZE ) {
		
		picture.pic = malloc(pic_size);
		picture.bFree = true;
		++gstc_u4Mallocs;
	}
	else {
	
		picture.pic = gstc_picBufs[ gstc_picBufIdx++ ];
		if( ! ( gstc_picBufIdx < MAXPICS ) ) gstc_picBufIdx = 0;
		picture.bFree = false;
	};

    memcpy(picture.pic, pic, pic_size);
    picture.pic_size = pic_size;
    picture.vStart = vStart;
    picture.soundOffset = soundOffset;
    picture.lStart = lStart;
    picture.lEnd = lEnd;
    picture.vEnd = vEnd;

    ptr->QueuePicture(picture);

    if (frame >= vEnd && !ptr->video_finished )
	{
		CloseVideo_ffmpeg_dll( ptr );
    }

    return 0;
}

int EncodePictureReal_ffmpeg_dll( Cffmpeg_dll * ptr, int frame, void * pic, int pic_size, int vStart, int soundOffset, int lStart, int lEnd, int vEnd )
{
	bool picEncoded = false;

	if ( frame > vEnd ) return false;

	double delay = (double)soundOffset * ptr->video_st->time_base.num / ptr->video_st->time_base.den;
	while (!picEncoded)
	{
		/* compute current audio and video time */
		if ( ptr->audio_st )
		{
			ptr->audio_pts = (double)ptr->audio_st->pts.val * ptr->audio_st->time_base.num / ptr->audio_st->time_base.den;
		}
		else
		{
			ptr->audio_pts = 0.0;
		}

		if ( ptr->video_st )
		{
			ptr->video_pts = (double)ptr->video_st->pts.val * ptr->video_st->time_base.num / ptr->video_st->time_base.den;
		}
		else
		{
			ptr->video_pts = 0.0;
		}

		/* write audio and video frames */
		if (false && !ptr->video_st && ( ptr->video_st && ptr->audio_st && ptr->audio_pts < ptr->video_pts) ) 
		{
			if ( ptr->audio_pts < delay )
			{
				// Write silence (yeah, 0 is silence!)
				write_audio_frame( ptr, ptr->oc, ptr->audio_st, 0 );
			} 
			else
			{
				// Write audio (yeah, -1 is actual audio!)
				write_audio_frame( ptr, ptr->oc, ptr->audio_st, -1 );
			}
		} 
		else 
		{
			write_video_frame( ptr, ptr->oc, ptr->video_st, vEnd, pic, pic_size );
			picEncoded = true;
		}
	}
	return 0;
}

FFMPEG_DLL_API int CloseVideo_ffmpeg_dll( Cffmpeg_dll * ptr )
{
	s_logFile << "CloseVideo_ffmpeg_dll( Cffmpeg_dll * ptr = " << ptr << " ) entry." << endl;
	s_logFile << "max queue size so far = " << gstc_u4MaxQueueSize << endl;

	if( gstc_u4Mallocs > gstc_u4MaxMallocs ) gstc_u4MaxMallocs = gstc_u4Mallocs;

	s_logFile << "mallocs = " << gstc_u4Mallocs << ", max mallocs so far = " << gstc_u4Mallocs << endl;

	//if (ptr->video_finished) return 0;

    /* wait for encoder thread to finish */
	if( ptr->thread ) {

		WaitForSingleObject(ptr->thread, INFINITE);
		CloseHandle(ptr->thread);
		ptr->thread = 0;
	};

	/* write the trailer, if any.  the trailer must be written
	* before you close the CodecContexts open when you wrote the
	* header; otherwise write_trailer may try to use memory that
	* was freed on av_codec_close() */
	if( ptr->oc ) av_write_trailer( ptr->oc );

	/* close each codec */
	if ( ptr->video_st && ptr->oc ) close_video( ptr, ptr->oc, ptr->video_st );
	ptr->video_st = NULL;
	if ( ptr->audio_st ) close_audio( ptr, NULL, ptr->audio_st );
	ptr->audio_st = NULL;

	if( !( ptr->fmt->flags & AVFMT_NOFILE ) )  {

		if( ptr->oc ) {

			if( ptr->oc->pb ) {
				/* close the output file */
	// NEWFFMPEG url_fclose
				avio_close( ptr->oc->pb );
				ptr->oc->pb = NULL;
			};
		};
	}

	if ( ptr->fp_sound_input ) {

		fclose( ptr->fp_sound_input );
		ptr->fp_sound_input = NULL;
	}

	/* free the context, includes streams */
	if( ptr->oc ) avformat_free_context( ptr->oc );
	ptr->oc = NULL;
    ptr->video_finished = TRUE; 

	/* free sws context */
	if( ptr->img_convert_ctx ) sws_freeContext(ptr->img_convert_ctx);
	ptr->img_convert_ctx = 0;

	/* delete pic queue */
    if( ptr->pictureQueue ) delete ptr->pictureQueue;
	ptr->pictureQueue = NULL;

	if( s_logFile.is_open() ) {

		s_logFile << "CloseVideo_ffmpeg_dll( Cffmpeg_dll * ptr = " << ptr << " ) exit." << endl;
		// Close the logfile
		s_logFile.close();
	};

	return 0;
}

// Dies ist der Konstruktor einer Klasse, die exportiert wurde.
// Siehe ffmpeg_dll.h für die Klassendefinition.
Cffmpeg_dll::Cffmpeg_dll():
			skippedFirst(false),
            thread(NULL),
			t( 0.0f ),
			tincr( 0.0f ),
			tincr2( 0.0f ),
			samples( NULL ),
			audio_outbuf( NULL ),
			audio_outbuf_size( 0 ),
			audio_input_frame_size( 0 ),
			fp_sound_input( NULL ),
			picture( NULL ),
			tmp_picture( NULL ),
			video_outbuf( NULL ),
			frame_count( 0 ),
			video_outbuf_size( 0 ),
			fmt( NULL ),
			oc( NULL ),
			audio_st( NULL ), 
			video_st( NULL ),
			audio_pts( 0.0 ), 
			video_pts( 0.0 ),
			srcWidth( 640 ),
			srcHeight( 360 ),
			dstWidth( 640 ),
			dstHeight( 360 ),
			img_convert_ctx( NULL ),
			audio_codec_id( CODEC_ID_NONE ),
			video_codec_id( CODEC_ID_NONE ),
			video_bit_rate( 400000 ),
            qmin(10),
            qmax(30),
            me_method(ME_HEX),
            me_subpel_quality(5),
            i_quant_factor(0.71f),
            qcompress(0.6f),
            max_qdiff(4),
			fps(30),

			sample_fmt( AV_SAMPLE_FMT_S16 ),
			audio_bit_rate( 128000 ),
			sample_rate( 44100 ),
			channels( 1 ),


			video_finished( TRUE ),

            threads(0)
{
    InitializeCriticalSection(&this->queueLock);
}
// Dies ist der Destruktor einer Klasse, die exportiert wurde.
// Siehe ffmpeg_dll.h für die Klassendefinition.
Cffmpeg_dll::~Cffmpeg_dll()
{
    DeleteCriticalSection(&this->queueLock);


    /* wait for encoder thread to finish */
	if( this->thread ) {

		WaitForSingleObject(this->thread, INFINITE);
		CloseHandle(this->thread);
		this->thread = 0;
	};

	/* close each codec */
	if ( this->video_st && this->oc ) close_video( this, this->oc, this->video_st );
	this->video_st = NULL;
	if ( this->audio_st ) close_audio( this, NULL, this->audio_st );
	this->audio_st = NULL;

	if( !( this->fmt->flags & AVFMT_NOFILE ) )  {

		if( this->oc ) {

			if( this->oc->pb ) {
	// NEWFFMPEG
				/* close the output file */
				avio_close( this->oc->pb );
				this->oc->pb = NULL;
			};
		};
	}

	if ( this->fp_sound_input ) {

		fclose( this->fp_sound_input );
		this->fp_sound_input = NULL;
	}

	/* free the context, includes streams */
	if( this->oc ) avformat_free_context( this->oc );
	this->oc = NULL;
    this->video_finished = TRUE; 

	/* free sws context */
	if( this->img_convert_ctx ) sws_freeContext(this->img_convert_ctx);
	this->img_convert_ctx = 0;

	/* delete pic queue */
    if( this->pictureQueue ) delete this->pictureQueue;
	this->pictureQueue = NULL;

	if( s_logFile.is_open() ) {

		s_logFile << "Cffmpeg_dll::~Cffmpeg_dll() exit." << endl;
		// Close the logfile
		s_logFile.close();
	};

	return;
}

int
Cffmpeg_dll::QueuePicture(Picture& pic)
{
    EnterCriticalSection(&this->queueLock);
    this->pictureQueue->push( pic );

	if( this->pictureQueue->size() > gstc_u4MaxQueueSize ) gstc_u4MaxQueueSize = this->pictureQueue->size();

    LeaveCriticalSection(&this->queueLock);
    return 0;
}

DWORD WINAPI 
Cffmpeg_dll_thread(LPVOID param)
{
    Cffmpeg_dll *ptr = (Cffmpeg_dll *)param;
    bool done = false;
    do {
        EnterCriticalSection(&ptr->queueLock);

        while(!ptr->pictureQueue->empty()) 
        {
            Picture& pic = ptr->pictureQueue->front();
            ptr->pictureQueue->pop();
            EncodePictureReal_ffmpeg_dll(ptr, pic.frame, pic.pic, pic.pic_size, pic.vStart, pic.soundOffset, pic.lStart, pic.lEnd, pic.vEnd);

			if( pic.bFree ) free( pic.pic );

            if (pic.vEnd <= pic.frame)
            {
                av_log(ptr->oc, AV_LOG_ERROR, "encoded all frames\n");
                done = true;
            }
        } 
        LeaveCriticalSection(&ptr->queueLock);
        Sleep(1);
    } while(!done);
    return 0;
}
