//---------------------------------------------------------------------------
//	AVFile - library for audio and video files processing
//	2002 - 2007 Stanislav Sumec <sumec@fit.vutbr.cz>
//
//	fffile.cpp
//	ffmpeg file reader and writer
//
//	13.3.2004
//		- first release
//---------------------------------------------------------------------------

#ifdef AVFILE_DEBUG
#include <iostream>
#endif
#include "fffile.h"
#include "fffilei.h"

//---------------------------------------------------------------------------
int TFFFile::Count = 0;
bool TFFFile::Registered = false;

//---------------------------------------------------------------------------
void TFFFile::Register()
{
	if (!Registered)
	{
		av_register_all();
		Registered = true;
	}
	Count++;
}

//---------------------------------------------------------------------------
void TFFFile::UnRegister()
{
	Count--;
}

//---------------------------------------------------------------------------
TFFFile::TFFFile()
{
	RegisterFileType("avi");
	RegisterFileType("mpg");
	RegisterFileType("mpeg");
	RegisterFileType("wav");
	RegisterFileType("mp3");
	Description = "FFmpeg file input and output";
	Implemented = iGetFrameB;
	Capabilities = cDependentStreams;
	FrameBuffer = NULL;
	OutputBuffer = NULL;
	AudioOutputBuffer = NULL;
	AudioInputFrame = NULL;
	Internal = new TFFFileI();
	Register();
}

//---------------------------------------------------------------------------
TFFFile::~TFFFile()
{
	Close();
	delete Internal;
	UnRegister();
}

//---------------------------------------------------------------------------
void TFFFile::OpenA(const char *FileName, int Mode, int Type, bool ThrowExceptions)
{
	int ret;
	Close();

	if (Mode == omRead)
	{
		if ((Type & otVideo) || (Type & otAudio))
		{
			char *Error = NULL;
			AVFormatParameters params;
			memset(&params, 0, sizeof(params));

			ret = av_open_input_file(&Internal->ic, FileName, NULL, 0, &params);
			if (ret < 0)
			{
				Error = "Could not open file!";
			}

			if (!Error)
			{
				ret = av_find_stream_info(Internal->ic);
				if (ret < 0)
				{
					Error = "Could not find info!";
				}
			}

			if (!Error)
			{
				Internal->VideoIndex = -1;
				Internal->AudioIndex = -1;
				for (unsigned int i = 0; i < Internal->ic->nb_streams; i++)
				{
					AVCodecContext *enc = Internal->ic->streams[i]->codec;
					if ((Type & otVideo) && (enc->codec_type == CODEC_TYPE_VIDEO))
					{
						if (Internal->VideoIndex < 0)
						{
							Internal->VideoIndex = i;
						}
					}
					else if ((Type & otAudio) && (enc->codec_type == CODEC_TYPE_AUDIO))
					{
						if (Internal->AudioIndex < 0)
						{
							Internal->AudioIndex = i;
						}
					}
				}
			}

			if (!Error)
			{
				if (Type & otVideo)
				{
					if (Internal->VideoIndex < 0)
					{
						Error = "Could not find video stream!";
					}

					if (!Error)
					{
						AVCodecContext *enc = Internal->ic->streams[Internal->VideoIndex]->codec;
						if (!Internal->OpenCodec(enc))
						{
							Error = "Could not open video decoder!";
						}
					}

					if (!Error)
					{
						FrameInBuffer = ~0;
						VideoPos = 0;

						AVStream *video_st = Internal->ic->streams[Internal->VideoIndex];
						VideoInfo.Width = video_st->codec->width;
						VideoInfo.Height = video_st->codec->height;
#pragma message Sometimes incorrect values, e.g. if fps = 23.976
						VideoInfo.FrameRate.Rate =	video_st->r_frame_rate.num;
						VideoInfo.FrameRate.Scale = video_st->r_frame_rate.den;
						Length = (video_st->r_frame_rate.num * video_st->time_base.num * (video_st->duration)) / (video_st->time_base.den * video_st->r_frame_rate.den);

						FrameBuffer = (BYTE*)GetMem(VideoInfo.Width * VideoInfo.Height * 4);
						if (!FrameBuffer)
						{
							Error = "Could not allocate frame buffer!";
						}
					}

					if (Error)
					{
						Internal->VideoIndex = -1;
						if (ThrowExceptions)
						{
							Close();
							throw AVFileErrorOpenVideo(Error);
						}
						Error = NULL;
					}
					else
					{
						OpenMode = Mode;
						OpenType |= otVideo;
						if (ReadVideoFrame(false))
						{
							FrameInBuffer = 0;
						}
					}
				}

				if (Type & otAudio)
				{
					if (Internal->AudioIndex < 0)
					{
						Error = "Could not find audio stream!";
					}

					if (!Error)
					{
						AVCodecContext *enc = Internal->ic->streams[Internal->AudioIndex]->codec;
						if (!Internal->OpenCodec(enc))
						{
							Error = "Could not open audio decoder!";
						}
					}

					if (!Error)
					{
						AudioInputFrameSize = (AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2;
						AudioInputFrame = (BYTE*)GetMem(AudioInputFrameSize);
						if (!AudioInputFrameSize)
						{
							Error = "Could not allocate audio buffer!";
						}
					}

					if (!Error)
					{
						AudioPos = 0;
						AVStream *audio_st = Internal->ic->streams[Internal->AudioIndex];
						AudioInfo.Channels = audio_st->codec->channels;
						AudioInfo.SamplesPerSec = audio_st->codec->sample_rate;
						AudioSampleSize = 2 * AudioInfo.Channels;
						AudioLength = (int64_t(audio_st->codec->sample_rate) * audio_st->time_base.num * audio_st->duration) / audio_st->time_base.den;
#ifdef AVFILE_DEBUG
						std::cerr<<"AudioLength:"<<AudioLength<<std::endl;
#endif
					}
					if (Error)
					{
						Internal->AudioIndex = -1;
						if (ThrowExceptions)
						{
							Close();
							throw AVFileErrorOpenAudio(Error);
						}
					}
					else
					{
						OpenMode = Mode;
						OpenType |= otAudio;
					}
				}
			}
			else
			{
				if (ThrowExceptions)
				{
					Close();
					throw AVFileErrorOpenVideo(Error);
				}
			}
		}
	}
	else if (Mode == omWrite)
	{
		char *Error = NULL;
		bool VideoStreamOpened, AudioStreamOpened;
		VideoStreamOpened = AudioStreamOpened = false;

		 /* auto detect the output format from the name. default is mpeg. */
		AVOutputFormat *fmt = guess_format(NULL, FileName, NULL);
		if (!fmt)
		{
			fmt = guess_format("mpeg", NULL, NULL);
		}
		if (!fmt)
		{
			Error = "Could not find suitable output format!";
		}

		/* allocate the output media context */
		if (!Error)
		{
			Internal->oc = av_alloc_format_context();
			if (!Internal->oc)
			{
				Error = "Memory error!";
			}
			else
			{
				Internal->oc->oformat = fmt;
			}
		}

		/* add the audio and video streams using the default format codecs and initialize the codecs */
		Internal->video_st = NULL;
		Internal->audio_st = NULL;
		if (!Error)
		{
			if ((fmt->video_codec != CODEC_ID_NONE) && (Type & otVideo))
			{
				AVCodec *p = NULL;
				if (Parameters.Codec != "")
				{
					p = first_avcodec;
					while (p)
					{
						if (!strcmp(p->name, Parameters.Codec.c_str()) && p->type == CODEC_TYPE_VIDEO)
						{
							break;
						}
						p = p->next;
					}
				}
				Internal->video_st = Internal->AddVideoStream(Internal->oc, (p ? p->id : fmt->video_codec), VideoInfo.Width, VideoInfo.Height, VideoInfo.FrameRate.Rate, VideoInfo.FrameRate.Scale, Parameters.KeyFrame, Parameters.BitRate);
			}

			if ((fmt->audio_codec != CODEC_ID_NONE) && (Type & otAudio))
			{
				AVCodec *p = NULL;
				if (Parameters.AudioCodec != "")
				{
					p = first_avcodec;
					while (p)
					{
						if (!strcmp(p->name, Parameters.AudioCodec.c_str()) && p->type == CODEC_TYPE_AUDIO)
						{
							break;
						}
						p = p->next;
					}
				}
				Internal->audio_st = Internal->AddAudioStream(Internal->oc, (p ? p->id : fmt->audio_codec), AudioInfo.SamplesPerSec, AudioInfo.Channels, Parameters.AudioBitRate);
			}
		}

		if (!Error)
		{
			if (av_set_parameters(Internal->oc, NULL) < 0)
			{
				Error = "Invalid output format parameters!";
			}
		}

		if (!Error)
		{
			if (Type & otVideo)
			{
				VideoStreamOpened = Internal->video_st && Internal->OpenVideo(Internal->oc, Internal->video_st);
				if (!VideoStreamOpened)
				{
					Internal->video_st = NULL;
				}
			}

			if (Type & otAudio)
			{
				AudioStreamOpened = Internal->audio_st && Internal->OpenAudio(Internal->oc, Internal->audio_st);
				if (!AudioStreamOpened)
				{
					Internal->audio_st = NULL;
				}
			}
		}

		if (!Error)
		{
			if (!(fmt->flags & AVFMT_NOFILE))
			{
				if (url_fopen(&Internal->oc->pb, FileName, URL_WRONLY) < 0)
				{
					Error = "Could not open file!";
				}
			}
		}

		if (!Error && (VideoStreamOpened || AudioStreamOpened))
		{
			av_write_header(Internal->oc);
		}

		if (!Error)
		{
			OutputBufferSize = 1024 * 1024;
			{
				OutputBuffer = (BYTE*)GetMem(OutputBufferSize);
			}
			if (!OutputBuffer)
			{
				Error = "Could not allocate output buffer!";
			}
		}

		if (!Error && AudioStreamOpened)
		{
			AudioOutputBufferSize = 4 * AVCODEC_MAX_AUDIO_FRAME_SIZE;
			AudioOutputBuffer = (BYTE*)GetMem(AudioOutputBufferSize);
			if (!AudioOutputBuffer)
			{
				Error = "Could not allocate audio output buffer!";
			}
			if (!Error)
			{
				AVCodecContext *c = Internal->audio_st->codec;
#ifdef AVFILE_DEBUG
				std::cerr<<c->frame_size<<std::endl;
#endif
				if (c->frame_size <= 1)
				{
					AudioInputFrameSize = AudioOutputBufferSize / c->channels;
					switch(c->codec_id)
					{
					case CODEC_ID_PCM_S16LE:
					case CODEC_ID_PCM_S16BE:
					case CODEC_ID_PCM_U16LE:
					case CODEC_ID_PCM_U16BE:
						AudioInputFrameSize >>= 1;
						break;
					default:
						break;
					}
				}
				else
				{
					AudioInputFrameSize = c->frame_size;
				}
				AudioSampleSize = 2 * c->channels;
				AudioInputFramePos = 0;
				AudioInputFrameSize = AudioInputFrameSize * 2 * c->channels;
				AudioInputFrame = (BYTE*)GetMem(AudioInputFrameSize);
				if (!AudioInputFrame)
				{
					Error = "Could not allocate audio input buffer!";
				}
			}
		}

		if (Error)
		{
			Close();
			if (ThrowExceptions)
			{
				throw AVFileErrorOpenVideo(Error);
			}
		}
		else
		{
			VideoPos = 0;
			AudioPos = 0;
			OpenMode = Mode;
			OpenType = (VideoStreamOpened?otVideo:otNone) | (AudioStreamOpened?otAudio:otNone);
		}
	}

	if (ThrowExceptions)
	{
		if (Mode != OpenMode)
		{
			Close();
			throw AVFileErrorOpenInvalidMode();
		}
		if ((Type & otVideo) && !(OpenType & otVideo))
		{
			Close();
			throw AVFileErrorOpenVideo();
		}
		if ((Type & otAudio) && !(OpenType & otAudio))
		{
			Close();
			throw AVFileErrorOpenAudio();
		}
	}
}

//---------------------------------------------------------------------------
void TFFFile::Close()
{
	if (Internal->ic != NULL)
	{
		if (Internal->VideoIndex >= 0 && Internal->ic->streams[Internal->VideoIndex]->codec->codec)
		{
			avcodec_close(Internal->ic->streams[Internal->VideoIndex]->codec);
		}
		if (Internal->AudioIndex >= 0 && Internal->ic->streams[Internal->AudioIndex]->codec->codec)
		{
			avcodec_close(Internal->ic->streams[Internal->AudioIndex]->codec);
		}
		av_close_input_file(Internal->ic);
		Internal->ic = NULL;
		Internal->VideoIndex = -1;
		Internal->AudioIndex = -1;
	}

	if (Internal->oc != NULL)
	{
		if (Internal->video_st)
		{
			avcodec_close(Internal->video_st->codec);
		}

		if (Internal->audio_st)
		{
			avcodec_close(Internal->audio_st->codec);
		}

		av_write_trailer(Internal->oc);

		for (unsigned int i = 0; i < Internal->oc->nb_streams; i++)
		{
			av_freep(&Internal->oc->streams[i]);
		}

		if (!(Internal->oc->oformat->flags & AVFMT_NOFILE))
		{
			url_fclose(&Internal->oc->pb);
		}

		av_free(Internal->oc);

		Internal->video_st = NULL;
		Internal->audio_st = NULL;
		Internal->oc = NULL;
	}

	if (FrameBuffer != NULL)
	{
		FreeMem(FrameBuffer);
		FrameBuffer = NULL;
	}

	if (OutputBuffer != NULL)
	{
		FreeMem(OutputBuffer);
		OutputBuffer = NULL;
	}

	if (AudioOutputBuffer != NULL)
	{
		FreeMem(AudioOutputBuffer);
		AudioOutputBuffer = NULL;
	}

	if (AudioInputFrame != NULL)
	{
		FreeMem(AudioInputFrame);
		AudioInputFrame = NULL;
	}

	Internal->FlushQueues();
	Internal->VideoStreamStart = -1;

	OpenMode = omClosed;
	OpenType = otNone;
}


//---------------------------------------------------------------------------
bool TFFFile::ReadVideoFrame(bool Skip)
{
	int ret;
	bool stop = false, error = false;
	AVStream *video_st = Internal->ic->streams[Internal->VideoIndex];

	while (!stop && !error)
	{
		AVPacket *pkt;
		if (Internal->VideoPackets.empty())
		{
			pkt = new AVPacket;
			ret = av_read_frame(Internal->ic, pkt);
			if (ret < 0)
			{
				delete pkt;
				error = true;
				break;
			}
		}
		else
		{
			pkt = Internal->VideoPackets.front();
			Internal->VideoPackets.pop();
		}
#ifdef AVFILE_DEBUG
		std::cerr << "pkt->pts:" << pkt->pts << std::endl;
		std::cerr << "pkt->dts:" << pkt->dts << std::endl;
#endif
		if (pkt->stream_index == Internal->VideoIndex)
		{
/*
			FILE *fff = fopen("c:\\test.log", "a");
			fprintf(fff, "A %d %d %d\n", (int)pkt->dts, (int)pkt->pts, pkt->flags);
			fclose(fff);
*/
			AVFrame frame;
			unsigned char *ptr;
			ptr = pkt->data;
			int len, len1, got_picture;
			AVCodecContext *codec = video_st->codec;
			if (codec->codec_id == CODEC_ID_RAWVIDEO)
			{
				if (Internal->VideoStreamStart < 0)
				{
					Internal->VideoStreamStart = pkt->dts;
				}

				if (!Skip)
				{
					avpicture_fill((AVPicture *)&frame, ptr, codec->pix_fmt, codec->width, codec->height);

					if (Parameters.Deinterlace)
					{
						avpicture_deinterlace((AVPicture *)&frame, (AVPicture *)&frame, codec->pix_fmt, VideoInfo.Width, VideoInfo.Height);
					}

					AVPicture pict;
					int dst_pix_fmt = PIX_FMT_RGBA32;
					memset(&pict, 0, sizeof(pict));
					pict.data[0] = FrameBuffer;
					pict.linesize[0] = VideoInfo.Width * 4;
					ret = img_convert(&pict, dst_pix_fmt, (AVPicture *)&frame, codec->pix_fmt, codec->width, codec->height);
					if (ret < 0)
					{
						error = true;
					}
					else
					{
						stop = true;
					}
				}
				else
				{
					stop = true;
				}
			}
			else
			{
				len = pkt->size;
				while (len > 0)
				{
					if (Skip)
					{
						codec->hurry_up = 1;
					}
					len1 = avcodec_decode_video(codec, &frame, &got_picture, ptr, len);
					if (Skip)
					{
						codec->hurry_up = 0;
					}
					if (len1 < 0)
					{
						error = true;
						break;
					}

					if (got_picture)
					{
						if (Internal->VideoStreamStart < 0)
						{
							Internal->VideoStreamStart = pkt->dts;
						}
/*
						FILE *fff = fopen("c:\\test.log", "a");
						fprintf(fff, "B %d %d\n", (int)pkt->dts, (int)pkt->pts);
						fclose(fff);
*/
						if (!Skip)
						{
							if (Parameters.Deinterlace)
							{
								avpicture_deinterlace((AVPicture *)&frame, (AVPicture *)&frame, codec->pix_fmt, VideoInfo.Width, VideoInfo.Height);
							}

							AVPicture pict;
							int dst_pix_fmt = PIX_FMT_RGBA32;
							memset(&pict, 0, sizeof(pict));
							pict.data[0] = FrameBuffer;
							pict.linesize[0] = VideoInfo.Width * 4;
							ret = img_convert(&pict, dst_pix_fmt, (AVPicture *)&frame, codec->pix_fmt, codec->width, codec->height);
							ret = 0;
							if (ret < 0)
							{
								error = true;
							}
							else
							{
								stop = true;
							}
						}
						else
						{
							stop = true;
						}
						break;
					}
					ptr += len1;
					len -= len1;
				}
			}

			av_free_packet(pkt);
			delete pkt;
		}
		else if (pkt->stream_index == Internal->AudioIndex)
		{
#ifdef AVFILE_DEBUG
			std::cerr << "pushing audio packet" << std::endl;
#endif
			if (av_dup_packet(pkt) != 0)
			{
				av_free_packet(pkt);
				delete pkt;
				throw AVFileErrorMemory();
			}
			Internal->AudioPackets.push(pkt);
		}
		else
		{
			av_free_packet(pkt);
			delete pkt;
		}
	}

	return !error && stop;
}

//---------------------------------------------------------------------------
bool TFFFile::FindVideoFrame()
{
	int ret;
	bool stop = false, error = false;

	while (!stop && !error)
	{
		AVPacket *pkt;
		if (Internal->VideoPackets.empty())
		{
			pkt = new AVPacket;
			ret = av_read_frame(Internal->ic, pkt);
			if (ret < 0)
			{
				delete pkt;
				error = true;
				break;
			}
		}
		else
		{
			stop = true;
			break;
		}
		if (pkt->stream_index == Internal->VideoIndex)
		{
			if (av_dup_packet(pkt) != 0)
			{
				av_free_packet(pkt);
				delete pkt;
				throw AVFileErrorMemory();
			}
			Internal->VideoPackets.push(pkt);
			stop = true;
			break;
		}
		else if (pkt->stream_index == Internal->AudioIndex)
		{
			if (av_dup_packet(pkt) != 0)
			{
				av_free_packet(pkt);
				delete pkt;
				throw AVFileErrorMemory();
			}
			Internal->AudioPackets.push(pkt);
		}
		else
		{
			av_free_packet(pkt);
			delete pkt;
		}
	}

	return !error && stop;
}

//---------------------------------------------------------------------------
BYTE* TFFFile::GetFrameB(unsigned int Position)
{
	CheckRead();
	CheckVideoStream();

	if (Position != VideoPos)
	{
		if (Position == FrameInBuffer)
		{
			VideoPos = Position;
		}
		else
		{
			SeekVideo(Position);
		}
	}

	if (VideoPos != FrameInBuffer)
	{
		if (!ReadVideoFrame(false))
		{
			if (VideoPos < GetVideoLength())
			{
				/* hack for incorrect video stream length (longer) */
				memset(FrameBuffer, 0, VideoInfo.Width * VideoInfo.Height * 4);
			}
			else
			{
				throw AVFileErrorGetVideo();
			}
		}
		else
		{
			if (VideoPos == (GetVideoLength() - 1))
			{
				/* hack for incorrect video stream length (shorter) */
				if (FindVideoFrame())
				{
					Length++;
				}
			}
		}
		FrameInBuffer = VideoPos;
	}

	VideoPos++;
	return FrameBuffer;
}

//---------------------------------------------------------------------------
void TFFFile::SkipFrames(unsigned int Frames)
{
	CheckRead();
	CheckVideoStream();

	if (Frames == 0)
	{
		return;
	}

	if ((VideoPos + Frames) >= Length)
	{
		throw AVFileErrorGetVideo();
	}

	while (Frames > 0)
	{
		if (ReadVideoFrame(true))
		{
			if (VideoPos < GetVideoLength())
			{
				/* hack for incorrect video stream length */
			}
			else
			{
				throw AVFileErrorGetVideo();
			}
		}

		Frames--;
		VideoPos++;
	}
}

//---------------------------------------------------------------------------
unsigned int TFFFile::GetVideoLength()
{
	CheckOpened();
	CheckVideoStream();
	return Length;
}

//---------------------------------------------------------------------------
void TFFFile::Seek(unsigned int Frame)
{
	CheckOpened();

	if (OpenMode == omRead)
	{
		Internal->FlushQueues();
		int64_t pts = (int64_t(VideoInfo.FrameRate.Scale) * AV_TIME_BASE * (int64_t(Frame))) / (int64_t(VideoInfo.FrameRate.Rate));

		// posun o sekundu zpet
		int64_t rts = pts - AV_TIME_BASE;
		if (Internal->ic->start_time != AV_NOPTS_VALUE)
		{
			rts += Internal->ic->start_time;
		}
		if (rts < 0)
		{
			rts = 0;
		}

		int ret = av_seek_frame(Internal->ic, -1, rts, AVSEEK_FLAG_BACKWARD);
		if (ret < 0)
		{
			throw AVFileErrorRead("Could not seek!");
		}

		// uklid bufferu kodeku
		if (Internal->VideoIndex >= 0)
		{
			avcodec_flush_buffers(Internal->ic->streams[Internal->VideoIndex]->codec);
		}
		if (Internal->AudioIndex >= 0)
		{
			avcodec_flush_buffers(Internal->ic->streams[Internal->AudioIndex]->codec);
		}

		// reinicializace video kodeku
		if (Internal->VideoIndex >= 0)
		{
			AVCodecContext *enc = Internal->ic->streams[Internal->VideoIndex]->codec;
			if (enc && !Internal->OpenCodec(enc))
			{
				throw AVFileErrorRead("Could not open video decoder!");
			}
		}

		// nalezeni prvniho packetu v pozadovanem casu
		bool stop = false, error = false, key_frame = false;
		while (!stop && !error)
		{
			AVPacket *pkt;
			pkt = new AVPacket;
			ret = av_read_frame(Internal->ic, pkt);
			if (ret < 0)
			{
				delete pkt;
				error = true;
				break;
			}

			// vkladat do fronty
			if (pkt->stream_index == Internal->VideoIndex)
			{
				// video vzdy
				if (av_dup_packet(pkt) != 0)
				{
					av_free_packet(pkt);
					delete pkt;
					throw AVFileErrorMemory();
				}
				Internal->VideoPackets.push(pkt);
			}
			else if (pkt->stream_index == Internal->AudioIndex)
			{
				// audio az od pozadovaneho casu
				int64_t packet_time = pkt->dts;
				if (Internal->ic->streams[pkt->stream_index]->start_time != AV_NOPTS_VALUE)
				{
					packet_time -= Internal->ic->streams[pkt->stream_index]->start_time;
				}
				packet_time = AV_TIME_BASE * packet_time * Internal->ic->streams[pkt->stream_index]->time_base.num / Internal->ic->streams[pkt->stream_index]->time_base.den;
				if (packet_time >= pts)
				{
					if (av_dup_packet(pkt) != 0)
					{
						av_free_packet(pkt);
						delete pkt;
						throw AVFileErrorMemory();
					}
					Internal->AudioPackets.push(pkt);
				}
				else
				{
					av_free_packet(pkt);
					delete pkt;
				}
			}
			else
			{
				av_free_packet(pkt);
				delete pkt;
			}

			// ridici stream video
			if (Internal->VideoIndex >= 0)
			{
				if (pkt->stream_index == Internal->VideoIndex)
				{
					int64_t packet_time = pkt->dts;
					if (Internal->VideoStreamStart > 0)
					{
						packet_time -= Internal->VideoStreamStart;
					}
					packet_time = AV_TIME_BASE * packet_time * Internal->ic->streams[pkt->stream_index]->time_base.num / Internal->ic->streams[pkt->stream_index]->time_base.den;
					int64_t packet_duration = AV_TIME_BASE * (int64_t)pkt->duration * Internal->ic->streams[pkt->stream_index]->time_base.num / Internal->ic->streams[pkt->stream_index]->time_base.den;

					if (pkt->flags & PKT_FLAG_KEY)
					{
						key_frame = true;
					}

					if (key_frame && (packet_time + packet_duration / 2) >= pts)
					{
						stop = true;
						break;
					}
				}
			}
			else
			{
				// delalat ridici stream audio
				throw AVFileErrorRead("Could not seek!");
			}
		}

		if (!error && stop)
		{
			// dekodovani vsech snimku az k pozadovanemu
			if (Internal->VideoIndex >= 0)
			{
//				int count = 0;
				while (!Internal->VideoPackets.empty())
				{
					stop = ReadVideoFrame(false);
//					count++;
				}
//				count = count;
			}
		}

		if (error || !stop)
		{
			if (Frame < GetVideoLength())
			{
				/* hack for incorrect video stream length */
				memset(FrameBuffer, 0, VideoInfo.Width * VideoInfo.Height * 4);
			}
			else
			{
				throw AVFileErrorRead("Could not seek!");
			}
		}

		if (Internal->VideoIndex >= 0)
		{
			FrameInBuffer = Frame;
			VideoPos = Frame;
		}
		if (Internal->AudioIndex >= 0)
		{
			AudioPos = (int64_t(AudioInfo.SamplesPerSec) * pts) / AV_TIME_BASE;
		}
	}
}

//---------------------------------------------------------------------------
void TFFFile::SeekVideo(unsigned int Frame)
{
	CheckOpened();
	CheckVideoStream();
	Seek(Frame);
}

//---------------------------------------------------------------------------
void TFFFile::SeekAudio(unsigned int Sample)
{
	CheckOpened();
	CheckAudioStream();
	Seek((int64_t(Sample) * int64_t(VideoInfo.FrameRate.Rate)) / (int64_t(AudioInfo.SamplesPerSec) * int64_t(VideoInfo.FrameRate.Scale)));
}

//---------------------------------------------------------------------------
void TFFFile::PutFrame(BYTE *Buffer)
{
	CheckWrite();
	CheckVideoStream();

	int out_size, ret;
	AVCodecContext *c;
	c = Internal->video_st->codec;
	AVFrame *Src = Internal->AllocPicture(PIX_FMT_RGBA32, VideoInfo.Width, VideoInfo.Height, Buffer), *Temp = NULL, *Frame;
	if (!Src)
	{
		 throw AVFileErrorMemory();
	}

	if (c->pix_fmt != PIX_FMT_RGBA32)
	{
		Temp = Internal->AllocPicture(c->pix_fmt, VideoInfo.Width, VideoInfo.Height);
		if (!Temp)
		{
			throw AVFileErrorMemory();
		}
		img_convert((AVPicture *)Temp, c->pix_fmt, (AVPicture *)Src, PIX_FMT_RGBA32, c->width, c->height);
		Frame = Temp;
	}
	else
	{
		Frame = Src;
	}

	if (Internal->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 = Internal->video_st->index;
		pkt.data = (uint8_t *)Frame;
		pkt.size = sizeof(AVPicture);

		ret = av_write_frame(Internal->oc, &pkt);
	}
	else
	{
		/* encode the image */
		out_size = avcodec_encode_video(c, OutputBuffer, OutputBufferSize, Frame);
		/* if zero size, it means the image was buffered */
		if (out_size != 0)
		{
			AVPacket pkt;
			av_init_packet(&pkt);

			pkt.pts = c->coded_frame->pts;
			if (c->coded_frame->key_frame)
			{
				pkt.flags |= PKT_FLAG_KEY;
			}
			pkt.stream_index= Internal->video_st->index;
			pkt.data = OutputBuffer;
			pkt.size = out_size;

			/* write the compressed frame in the media file */
			ret = av_write_frame(Internal->oc, &pkt);
		}
		else
		{
			ret = 0;
		}
	}

	av_free(Src);
	if (Temp)
	{
		av_free(Temp->data[0]);
		av_free(Temp);
	}

	if (ret != 0)
	{
		throw AVFileErrorPutVideo();
	}

	VideoPos++;
	if (VideoPos >= Length)
	{
		Length = VideoPos;
	}
}

//---------------------------------------------------------------------------
void TFFFile::PutAudio(BYTE *Buffer, unsigned int Size)
{
	CheckWrite();
	CheckAudioStream();
#ifdef AVFILE_DEBUG
	std::cerr << "Size: " << Size << std::endl;
#endif
	int ret;
	unsigned int Pos = 0;
	while (Size > 0)
	{
		unsigned int Num = AudioInputFrameSize - AudioInputFramePos;
#ifdef AVFILE_DEBUG
		std::cerr << "Num: " << Num << std::endl;
#endif
		if (Num > 0)
		{
			if (Size < Num)
			{
				Num = Size;
			}
			memcpy(AudioInputFrame + AudioInputFramePos, Buffer + Pos, Num);
			Pos += Num;
			Size -= Num;
			AudioInputFramePos += Num;
		}
#ifdef AVFILE_DEBUG
		std::cerr << "AudioInputFramePos: " << AudioInputFramePos << std::endl;
#endif
		if (AudioInputFramePos >= AudioInputFrameSize)
		{
			AudioInputFramePos = 0;
			AVCodecContext *c = Internal->audio_st->codec;
			int out_size = avcodec_encode_audio(c, AudioOutputBuffer, AudioOutputBufferSize, (short*)AudioInputFrame);
#ifdef AVFILE_DEBUG
			std::cerr << "out_size: " << out_size << std::endl;
#endif
			AVPacket pkt;
			av_init_packet(&pkt);
			pkt.size = out_size;
			pkt.pts = c->coded_frame->pts;
			pkt.flags |= PKT_FLAG_KEY;
			pkt.stream_index = Internal->audio_st->index;
			pkt.data = AudioOutputBuffer;

			ret = av_write_frame(Internal->oc, &pkt);
			if (ret != 0)
			{
				throw AVFileErrorPutAudio("");
			}
		}
	}
	AudioPos += Size / AudioSampleSize;
}

//---------------------------------------------------------------------------
BYTE* TFFFile::GetAudio(unsigned int &Size)
{
	CheckRead();
	CheckAudioStream();

	AudioOutputBufferSize = 0;
	if (AudioOutputBuffer != NULL)
	{
		FreeMem(AudioOutputBuffer);
		AudioOutputBuffer = NULL;
	}

	int ret;
	bool error = false, EndOfFile = false;
	AVStream *audio_st = Internal->ic->streams[Internal->AudioIndex];

	while (!error && (AudioOutputBufferSize == 0 || !Internal->AudioPackets.empty()))
	{
		AVPacket *pkt;
		if (Internal->AudioPackets.empty())
		{
			pkt = new AVPacket;
			ret = av_read_frame(Internal->ic, pkt);
			if (ret < 0)
			{
				delete pkt;
				error = true;
				EndOfFile = true;
				break;
			}
		}
		else
		{
			pkt = Internal->AudioPackets.front();
			Internal->AudioPackets.pop();
		}
#ifdef AVFILE_DEBUG
		std::cerr << pkt->pts << std::endl;
#endif
		if (pkt->stream_index == Internal->AudioIndex)
		{
#ifdef AVFILE_DEBUG
			std::cerr << "audio packet" << std::endl;
#endif
			unsigned char *audio_pkt_data;
			unsigned int audio_pkt_size;

			audio_pkt_data = pkt->data;
			audio_pkt_size = pkt->size;
			int len1, data_size;
			AVCodecContext *codec = audio_st->codec;
			while (audio_pkt_size > 0)
			{
				len1 = avcodec_decode_audio(codec, (int16_t *)AudioInputFrame, &data_size, audio_pkt_data, audio_pkt_size);
#ifdef AVFILE_DEBUG
				std::cerr<<"audio_pkt_size: "<<audio_pkt_size<<std::endl;
				std::cerr<<"len1: "<<len1<<std::endl;
				std::cerr<<"data_size: "<<data_size<<std::endl;
#endif
				if (len1 < 0)
				{
					error = true;
					break;
				}

				audio_pkt_data += len1;
				audio_pkt_size -= len1;

				if (data_size <= 0)
				{
					continue;
				}

				unsigned int BufferSize = AudioOutputBufferSize + data_size;
				BYTE *Buffer = (BYTE*)GetMem(BufferSize);
				if (Buffer == NULL)
				{
					av_free_packet(pkt);
					delete pkt;
					throw AVFileErrorMemory();
				}
				if (AudioOutputBuffer != NULL)
				{
					memcpy(Buffer, AudioOutputBuffer, AudioOutputBufferSize);
				}
				memcpy(Buffer + AudioOutputBufferSize, AudioInputFrame, data_size);
				AudioOutputBufferSize = BufferSize;

				if (AudioOutputBuffer != NULL)
				{
					FreeMem(AudioOutputBuffer);
				}
				AudioOutputBuffer = Buffer;
			}
#ifdef AVFILE_DEBUG
			std::cerr << "audio_pkt_size: " << audio_pkt_size << std::endl;
#endif
			av_free_packet(pkt);
			delete pkt;
		}
		else if (pkt->stream_index == Internal->VideoIndex)
		{
#ifdef AVFILE_DEBUG
			std::cerr << "pushing video packet" << std::endl;
#endif
			if (av_dup_packet(pkt) != 0)
			{
				av_free_packet(pkt);
				delete pkt;
				throw AVFileErrorMemory();
			}
			Internal->VideoPackets.push(pkt);
		}
		else
		{
			av_free_packet(pkt);
			delete pkt;
		}
	}

	if (error)
	{
		/* hack for incorrect audio stream length */
		if (EndOfFile && AudioPos < GetAudioLength())
		{
			unsigned int Rest = (GetAudioLength() - AudioPos) * AudioSampleSize;
			unsigned int BufferSize = AudioOutputBufferSize + Rest;
			BYTE *Buffer = (BYTE*)GetMem(BufferSize);
			if (Buffer == NULL)
			{
				throw AVFileErrorMemory();
			}
			if (AudioOutputBuffer != NULL)
			{
				memcpy(Buffer, AudioOutputBuffer, AudioOutputBufferSize);
			}
			memset(Buffer + AudioOutputBufferSize, 0, Rest);
			AudioOutputBufferSize = BufferSize;
			if (AudioOutputBuffer != NULL)
			{
				FreeMem(AudioOutputBuffer);
			}
			AudioOutputBuffer = Buffer;
		}
		else
		{
			throw AVFileErrorGetAudio();
		}
	}

	Size = AudioOutputBufferSize;
	AudioPos += Size / AudioSampleSize;
	return AudioOutputBuffer;
}

//---------------------------------------------------------------------------
void TFFFile::SkipAudio()
{
	CheckRead();
	CheckAudioStream();

	int ret;
	bool error = false;
	AVStream *audio_st = Internal->ic->streams[Internal->AudioIndex];

	while (!error && !Internal->AudioPackets.empty())
	{
		AVPacket *pkt;
		pkt = Internal->AudioPackets.front();
		Internal->AudioPackets.pop();
#ifdef AVFILE_DEBUG
		std::cerr << pkt->pts << std::endl;
#endif
		if (pkt->stream_index == Internal->AudioIndex)
		{
#ifdef AVFILE_DEBUG
			std::cerr << "audio packet" << std::endl;
#endif
			unsigned char *audio_pkt_data;
			unsigned int audio_pkt_size;

			audio_pkt_data = pkt->data;
			audio_pkt_size = pkt->size;
			int len1, data_size;
			AVCodecContext *codec = audio_st->codec;
			while (audio_pkt_size > 0)
			{
				codec->hurry_up = 1;
				len1 = avcodec_decode_audio(codec, (int16_t *)AudioInputFrame, &data_size, audio_pkt_data, audio_pkt_size);
				codec->hurry_up = 0;
#ifdef AVFILE_DEBUG
				std::cerr << "audio_pkt_size: " << audio_pkt_size << std::endl;
				std::cerr << "len1: " << len1 << std::endl;
				std::cerr << "data_size: " << data_size << std::endl;
#endif
				if (len1 < 0)
				{
					error = true;
					break;
				}

				audio_pkt_data += len1;
				audio_pkt_size -= len1;

				if (data_size <= 0)
				{
					continue;
				}

				AudioPos += data_size / AudioSampleSize;
			}
#ifdef AVFILE_DEBUG
			std::cerr << "audio_pkt_size: " << audio_pkt_size << std::endl;
#endif
			av_free_packet(pkt);
			delete pkt;
		}
		else if (pkt->stream_index == Internal->VideoIndex)
		{
#ifdef AVFILE_DEBUG
			std::cerr << "pushing video packet" << std::endl;
#endif
			if (av_dup_packet(pkt) != 0)
			{
				av_free_packet(pkt);
				delete pkt;
				throw AVFileErrorMemory();
			}
			Internal->VideoPackets.push(pkt);
		}
		else
		{
			av_free_packet(pkt);
			delete pkt;
		}
	}

	if (error)
	{
		throw AVFileErrorGetAudio();
	}
}

//---------------------------------------------------------------------------
unsigned int TFFFile::GetAudioLength()
{
	CheckOpened();
	CheckAudioStream();
	return AudioLength;
}

//---------------------------------------------------------------------------
TFFFileI::TFFFileI()
{
	ic = oc = NULL;
//	/*enc =*/ aenc = NULL;
	audio_st = video_st = NULL;
	AudioIndex = VideoIndex = -1;
}

//---------------------------------------------------------------------------
AVStream *TFFFileI::AddVideoStream(AVFormatContext *oc, int Codec, int Width, int Height, int Rate, int Scale, int KeyFrame, int BitRate)
{
	AVCodecContext *c;
	AVStream *st;

	st = av_new_stream(oc, 0);
	if (!st)
	{
		return NULL;
	}
	c = st->codec;
	c->codec_id = (CodecID)Codec;
	c->codec_type = CODEC_TYPE_VIDEO;
	if (c->codec_id == CODEC_ID_MPEG4)
	{
		memcpy(&c->codec_tag, "DIVX", 4);
	}

	/* put sample parameters */
	c->bit_rate = BitRate;
	/* resolution must be a multiple of two */
	c->width = Width;
	c->height = Height;
	/* frames per second */
	c->time_base.den = Rate;
	c->time_base.num = Scale;
	c->pix_fmt = PIX_FMT_YUV420P;
//	int xxx = sizeof(c->pix_fmt);
//	printf("----------\n%d\n----------\n", xxx);
	c->gop_size = KeyFrame; /* emit one intra frame every twelve frames at most */
	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;
}

//---------------------------------------------------------------------------
bool TFFFileI::OpenVideo(AVFormatContext *oc, AVStream *Stream)
{
	AVCodec *codec;
	AVCodecContext *c;
	c = Stream->codec;
	/* find the video encoder */
	codec = avcodec_find_encoder(c->codec_id);
	if (!codec)
	{
		return false;
	}

	/* open the codec */
	if (avcodec_open(c, codec) < 0)
	{
		return false;
	}

	return true;
}

//---------------------------------------------------------------------------
AVFrame* TFFFileI::AllocPicture(int Format, int Width, int Height, uint8_t *Buffer)
{
	AVFrame *picture;
	uint8_t *picture_buf;
	int size;

	picture = avcodec_alloc_frame();
	if (!picture)
	{
		return NULL;
	}

	if (Buffer)
	{
		picture_buf = Buffer;
	}
	else
	{
		size = avpicture_get_size(Format, Width, Height);
		picture_buf = (uint8_t*)av_malloc(size);
		if (!picture_buf)
		{
			av_free(picture);
			return NULL;
		}
	}

	avpicture_fill((AVPicture *)picture, picture_buf, Format, Width, Height);
	return picture;
}

//---------------------------------------------------------------------------
AVStream *TFFFileI::AddAudioStream(AVFormatContext *oc, int Codec, int Rate, int Channels, int BitRate)
{
	AVCodecContext *c;
	AVStream *st;

	st = av_new_stream(oc, 1);
	if (!st)
	{
		return NULL;
	}
	c = st->codec;
	c->codec_id = (CodecID)Codec;
	c->codec_type = CODEC_TYPE_AUDIO;

	/* put sample parameters */
	c->bit_rate = BitRate;
	c->sample_rate = Rate;
	c->channels = Channels;
	return st;
}

//---------------------------------------------------------------------------
bool TFFFileI::OpenAudio(AVFormatContext *oc, AVStream *Stream)
{
	AVCodec *codec;
	AVCodecContext *c;
	c = Stream->codec;
	/* find the audio encoder */
	codec = avcodec_find_encoder(c->codec_id);
	if (!codec)
	{
		return false;
	}

	/* open the codec */
	if (avcodec_open(c, codec) < 0)
	{
		return false;
	}

	return true;
}

//---------------------------------------------------------------------------
void TFFFileI::FlushQueues()
{
	while (!AudioPackets.empty())
	{
		AVPacket *pkt = AudioPackets.front();
		av_free_packet(pkt);
		delete pkt;
		AudioPackets.pop();
#ifdef AVFILE_DEBUG
		std::cerr << "audio queue pop" << std::endl;
#endif
	}

	while (!VideoPackets.empty())
	{
		AVPacket *pkt = VideoPackets.front();
		av_free_packet(pkt);
		delete pkt;
		VideoPackets.pop();
#ifdef AVFILE_DEBUG
		std::cerr << "video queue pop" << std::endl;
#endif
	}
}

//---------------------------------------------------------------------------
AVCodec* TFFFileI::OpenCodec(AVCodecContext *enc)
{
	if (enc->codec)
	{
		avcodec_close(enc);
	}

	AVCodec *codec = avcodec_find_decoder(enc->codec_id);
	if (!codec || avcodec_open(enc, codec) < 0)
	{
		return NULL;
	}
	else
	{
		return codec;
	}
}

//---------------------------------------------------------------------------
int TFFFile::GetAudioSkew()
{
	CheckOpened();
	CheckRead();

	if (Internal->VideoIndex < 0 || Internal->AudioIndex < 0)
	{
		return 0;
	}

	AVStream *video_st = Internal->ic->streams[Internal->VideoIndex];
	AVStream *audio_st = Internal->ic->streams[Internal->AudioIndex];

	int64_t VideoStart = (1000 * video_st->time_base.num * (video_st->start_time)) / (video_st->time_base.den);
	int64_t AudioStart = (1000 * audio_st->time_base.num * (audio_st->start_time)) / (audio_st->time_base.den);

	return AudioStart - VideoStart;
}

