#include <stdafx.h>
#include "FFmpegPlayer.h"



// iOrange - I/O funcs
int FFmpegPlayer::ReadPacket(void *opaque, uint8_t *buf, int buf_size)
{
	FILE* f = reinterpret_cast<FILE*>(opaque);
	return fread(buf, 1, buf_size, f);
}

int64_t FFmpegPlayer::Seek(void *opaque, int64_t offset, int whence)
{
	FILE* f = reinterpret_cast<FILE*>(opaque);

	/**
	 * Oring this flag as into the "whence" parameter to a seek function causes it to
	 * seek by any means (like reopening and linear reading) or other normally unreasonble
	 * means that can be extreemly slow.
	 * This may be ignored by the seek code.
	 */
	if (AVSEEK_FORCE == whence)
		return 0;
	// iOrange - if we receive AVSEEK_SIZE as whence, just need to report file size
	else if (AVSEEK_SIZE == whence)
	{
		long pos = ftell(f);
		fseek(f, 0, SEEK_END);
		long fileSize = ftell(f);
		fseek(f, pos, SEEK_SET);
		return fileSize;
	}

	return fseek(f, offset, whence);
}



FFmpegPlayer::FFmpegPlayer()
	: m_VideoOpened(false)
	, m_File(0)
	, m_VideoTimer(0)
	, m_LastVideoFrame(~0)
	, m_FrameWidth(0)
	, m_FrameHeight(0)
	, m_FramesPerSecond(0)
	, m_Stopped(true)
// FFmpeg stuff
	, m_IOBuffer(0)
	, m_InputContext(0)
	, m_Frame(0)
	, m_VideoStreamIdx(-1)
	, m_VideoStream(0)
	, m_OutputFormat(0)
{
}
FFmpegPlayer::~FFmpegPlayer()
{
	// iOrange - free our decode frame
	if (m_Frame)
		av_free(m_Frame);

	// iOrange - free our codec
	avcodec_close(m_VideoStream->codec);

	// iOrange - free input context
	avformat_close_input(&m_InputContext);
}


// iOrange - opens video from file using "fileName", return true if all is ok, false if not
bool FFmpegPlayer::OpenVideoFile(const std::string& fileName)
{
	m_VideoOpened = false;

	// iOrange - initialize libavformat
	av_register_all();

	// iOrange - try to open file
	if (this->OpenFile(fileName))
	{
		// iOrange - initialize input context
		m_InputContext = avformat_alloc_context();

		// iOrange - alloc I/O context with our custom I/O funcs and buffer
		const int k_IOBufferSize = 8192;
		m_IOBuffer = reinterpret_cast<uint8*>(av_malloc(k_IOBufferSize));
		m_InputContext->pb = avio_alloc_context(m_IOBuffer, k_IOBufferSize, 0, m_File, FFmpegPlayer::ReadPacket, 0, FFmpegPlayer::Seek);

		// iOrange - try to open video file
		int err = avformat_open_input(&m_InputContext, fileName.c_str(), 0, 0);
		if (0 == err)
		{
			// iOrange - try to find video stream
			if (this->OpenVideoStream())
			{
				// iOrange - alloc decode frame
				m_Frame = avcodec_alloc_frame();

				// iOrange - start video decoding
				av_read_play(m_InputContext);

				// iOrange - to know some info about frame - we need to decode it first
				m_LastVideoFrame = 0;
				m_Stopped = false;
				this->DecodeVideoFrame();

				// iOrange - if decoded ok
				if (!this->IsStopped())
				{
					m_FrameWidth = m_Frame->width;
					m_FrameHeight = m_Frame->height;
					m_FramesPerSecond = static_cast<float>(m_VideoStream->time_base.den) / static_cast<float>(m_VideoStream->time_base.num);

					m_VideoOpened = true;
				}
			}
		}
	}

	return m_VideoOpened;
}

// iOrange - return video frame width
int FFmpegPlayer::GetFrameWidth(void) const
{
	return m_FrameWidth;
}

// iOrange - return video frame height
int FFmpegPlayer::GetFrameHeight(void) const
{
	return m_FrameHeight;
}

// iOrange - return video frequency in frames-per-second (FPS)
float FFmpegPlayer::GetFramesPerSecond(void) const
{
	return m_FramesPerSecond;
}

// iOrange - advance video playing by "dt" milliseconds
uint32 FFmpegPlayer::Advance(uint32 dt)
{
	if (m_Stopped)
		return m_LastVideoFrame;

	// iOrange - advance video timer by delta time in milliseconds (dt)
	m_VideoTimer += dt;

	// iOrange - calculate current frame
	const uint32 curFrame = static_cast<uint32>(floor(static_cast<float>(m_VideoTimer) * m_FramesPerSecond * 0.001f));
	if (m_LastVideoFrame != curFrame)
	{
		m_LastVideoFrame = curFrame;
		this->DecodeVideoFrame();
	}

	return curFrame;
}

// iOrange - return true if video is not playing
bool FFmpegPlayer::IsStopped(void) const
{
	return m_Stopped;
}

// iOrange: clips t to range [0; 255]
inline int clip(int t)
{
	return ((t < 0) ? (0) : ((t > 255) ? 255 : t));
}

// iOrange - extract current frame to RGB color space using CPU for yuv-rgb conversion (integer optimization)
void FFmpegPlayer::GetFrameRGB(char* outFrame, int pitch)
{
	for (int y = 0; y < m_FrameHeight; ++y)
	{
		for (int x = 0; x < m_FrameWidth; ++x)
		{
			const int off = x + y * pitch;
			const int xx = x >> 1;
			const int yy = y >> 1;

			const int Y = static_cast<int>(m_Frame->data[0][x  +  y * m_Frame->linesize[0]]) - 16;
			const int U = static_cast<int>(m_Frame->data[1][xx + yy * m_Frame->linesize[1]]) - 128;
			const int V = static_cast<int>(m_Frame->data[2][xx + yy * m_Frame->linesize[2]]) - 128;

			outFrame[off * 3 + 0] = clip((298 * Y           + 409 * V + 128) >> 8);
			outFrame[off * 3 + 1] = clip((298 * Y - 100 * U - 208 * V + 128) >> 8);
			outFrame[off * 3 + 2] = clip((298 * Y + 516 * U           + 128) >> 8);
		}
	}
}

// iOrange - extract current frame to planar YUV444 (de-interlaced yuv) to perform yuv-rgb conversion on GPU
void FFmpegPlayer::GetFrameYUV444(char* outFrame, int pitch)
{
	for (int y = 0; y < m_FrameHeight; ++y)
	{
		for (int x = 0; x < m_FrameWidth; ++x)
		{
			const int off = x + y * pitch;
			const int xx = x >> 1;
			const int yy = y >> 1;

			outFrame[off * 3 + 0] = m_Frame->data[0][x  +  y * m_Frame->linesize[0]];
			outFrame[off * 3 + 1] = m_Frame->data[1][xx + yy * m_Frame->linesize[1]];
			outFrame[off * 3 + 2] = m_Frame->data[2][xx + yy * m_Frame->linesize[2]];
		}
	}
}


// iOrange - open file routine
bool FFmpegPlayer::OpenFile(const std::string& fileName)
{
#if _MSC_VER >= 1300
	if (0 != fopen_s(&m_File, fileName.c_str(), "rb"))
		m_File = 0;
#else
	m_File = fopen(fileName.c_str(), "rb");
#endif

	return 0 != m_File;
}

// iOrange - open video stream routine
bool FFmpegPlayer::OpenVideoStream(void)
{
	// iOrange - first of all - try to find video stream
	m_VideoStreamIdx = av_find_best_stream(m_InputContext, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
	if (m_VideoStreamIdx < 0 || m_VideoStreamIdx >= static_cast<int>(m_InputContext->nb_streams))
		return false;

	// iOrange - get the pointer to video stream
	m_VideoStream = m_InputContext->streams[m_VideoStreamIdx];

	// iOrange - and now we need to initialize 
	AVCodecContext* avctx = m_VideoStream->codec;
	AVCodec* codec = avcodec_find_decoder(avctx->codec_id);
	AVDictionary* opts = 0;
	if (!codec || avcodec_open2(avctx, codec, &opts) < 0)
		return false;

	// iOrange - if everything is ok - return true
	return true;
}

// iOrange - buffer some data from file into Ogg stream
int FFmpegPlayer::BufferData(void)
{
	return 0;
}

// iOrange - decodes current frame from Theora to YUV
void FFmpegPlayer::DecodeVideoFrame(void)
{
	AVPacket packet;
	int gotPicture = 0;

	while (!gotPicture)
	{
		int err = av_read_frame(m_InputContext, &packet);
		if (err >= 0 && packet.stream_index == m_VideoStreamIdx)
			err = avcodec_decode_video2(m_VideoStream->codec, m_Frame, &gotPicture, &packet);

		if (0 > err)
		{
			m_Stopped = true;
			break;
		}
	}

	av_free_packet(&packet);
}

