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

#include <ctype.h>
#pragma hdrstop
#include "avifile.h"

//---------------------------------------------------------------------------
TAVIFile::TAVIFile()
{
	RegisterFileType("avi");
	RegisterFileType("wav");
	Description = "Video for Windows file input and output";
	Implemented = iGetFrameA + iGetFrameA2 + iDIBGetFrameA;
	AVIFileInit();
	AVIFile = NULL;
	has = NULL;
	AudioStream = NULL;
	pget = NULL;
	VideoStream = NULL;
	FrameBuffer = NULL;
	CompressedVideoStream = NULL;

	ash.cbStruct = sizeof(ash);
	ash.fdwStatus = 0;
	Close();
}

//---------------------------------------------------------------------------
TAVIFile::~TAVIFile()
{
	Close();
	AVIFileExit();
}

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

//MessageBox(NULL, FileName, "MB", MB_OK);

	if (Mode == omRead)
	{
		hr = AVIFileOpen(&AVIFile, FileName, OF_READ, 0L);
		if (hr != 0)
		{
			if (ThrowExceptions)
			{
				throw AVFileErrorOpen();
			}
		}
		else
		{
			if (Type & otVideo)
			{
				hr = AVIFileGetStream(AVIFile, &VideoStream, streamtypeVIDEO, 0);
				if (hr == AVIERR_OK)
				{
					bool Error = true;
					AVISTREAMINFO si;
					if (AVIStreamInfo(VideoStream, &si, sizeof(si)) == 0)
					{
						VideoPos = si.dwStart;
						VideoInfo.FrameRate.Rate = si.dwRate;
						VideoInfo.FrameRate.Scale = si.dwScale;
						LONG s;
						if (AVIStreamReadFormat(VideoStream, VideoPos, NULL, &s) == 0)
						{
							BYTE *wf = (BYTE*)GetMem(s);
							if (wf != NULL)
							{
								if (AVIStreamReadFormat(VideoStream, VideoPos, wf, &s) == 0)
								{
									// internal format
									BITMAPINFOHEADER *b = (BITMAPINFOHEADER*)wf;
									VideoInfo.Width = b->biWidth;
									VideoInfo.Height = b->biHeight;
									// expected format
									BITMAPINFOHEADER d;
									memset(&d, 0, sizeof(BITMAPINFOHEADER));
									d.biSize = sizeof(BITMAPINFOHEADER);
									d.biWidth = VideoInfo.Width;
									d.biHeight = VideoInfo.Height;
									d.biPlanes = 1;
									d.biBitCount = 32;
									d.biCompression = BI_RGB;
									// open decompressor
									pget = AVIStreamGetFrameOpen(VideoStream, &d);
									if (pget == NULL)
									{
										pget = AVIStreamGetFrameOpen(VideoStream, NULL);
									}
									if (pget == NULL)
									{
										pget = AVIStreamGetFrameOpen(VideoStream, (BITMAPINFOHEADER*)AVIGETFRAMEF_BESTDISPLAYFMT);
									}
									if (pget != NULL)
									{
										ScanLineWidth = VideoInfo.Width * 4;
										AlignedScanLineWidth = DIB_SCAN_LINE(ScanLineWidth);
										FrameBuffer = (BYTE*)GetMem(ScanLineWidth * VideoInfo.Height);
										Error = FrameBuffer == NULL;
									}
								}
								FreeMem(wf);
							}
						}
					}
					if (Error)
					{
						if (VideoStream != NULL)
						{
							AVIStreamRelease(VideoStream);
							VideoStream = NULL;
						}
					}
					else
					{
						OpenMode = Mode;
						OpenType |= otVideo;
					}
				}
			}

			if (Type & otAudio)
			{
				hr = AVIFileGetStream(AVIFile, &AudioStream, streamtypeAUDIO, 0);
				if (hr == AVIERR_OK)
				{
					bool Error = true;
					AudioPos = AVIStreamStart(AudioStream);
					LONG s;
					hr = AVIStreamReadFormat(AudioStream, AudioPos, NULL, &s);
					if (hr == 0)
					{
						BYTE *wf = (BYTE*)GetMem(s);
						if (wf != NULL)
						{
							hr = AVIStreamReadFormat(AudioStream, AudioPos, wf, &s);
							if (hr == 0)
							{
								WAVEFORMATEX *pwf = (LPWAVEFORMATEX)wf;
								WAVEFORMATEX dwf;
								dwf.wFormatTag = WAVE_FORMAT_PCM;
								dwf.nChannels = pwf->nChannels;
								dwf.nSamplesPerSec = pwf->nSamplesPerSec;
								dwf.wBitsPerSample = 16;
								dwf.nBlockAlign = dwf.nChannels * (dwf.wBitsPerSample / 8);
								dwf.nAvgBytesPerSec = dwf.nBlockAlign * dwf.nSamplesPerSec;
								dwf.cbSize = 0;

								AudioInfo.Channels = dwf.nChannels;
								AudioInfo.SamplesPerSec = dwf.nSamplesPerSec;
								ascale = ((LPWAVEFORMATEX)wf)->nBlockAlign;

								hr = acmStreamOpen(&has, NULL, (LPWAVEFORMATEX)wf, &dwf, NULL, 0, 0, ACM_STREAMOPENF_NONREALTIME);
								if (hr == 0)
								{
									ash.cbDstLength = 44100;
									hr = acmStreamSize(has, ash.cbDstLength, &ash.cbSrcLength, ACM_STREAMSIZEF_DESTINATION);
									if (hr == 0)
									{
										ash.pbSrc = (BYTE*)GetMem(ash.cbSrcLength);
										ash.pbDst = (BYTE*)GetMem(ash.cbDstLength);
										if (ash.pbSrc != NULL && ash.pbDst != NULL)
										{
											hr = acmStreamPrepareHeader(has, &ash, 0);
											Error = hr != 0;
											if (Error)
											{
												FreeMem(ash.pbSrc);
												FreeMem(ash.pbDst);
											}
										}
										else
										{
											if (ash.pbSrc != NULL)
											{
												FreeMem(ash.pbSrc);
											}
											if (ash.pbDst != NULL)
											{
												FreeMem(ash.pbDst);
											}
										}
									}
								}
							}
						}
						FreeMem(wf);
					}
					if (Error)
					{
						if (has != NULL)
						{
							acmStreamClose(has, 0);
							has = NULL;
						}
						if (AudioStream != NULL)
						{
							AVIStreamRelease(AudioStream);
							AudioStream = NULL;
						}
					}
					else
					{
						OpenMode = Mode;
						OpenType |= otAudio;
					}
				}
			}
		}
	}
	else if (Mode == omWrite)
	{
		hr = AVIFileOpen(&AVIFile, FileName, OF_CREATE, 0L);
		if (hr != 0)
		{
			throw AVFileErrorOpen();
		}

		if (Type & otVideo)
		{
			bmp.biSize = sizeof(BITMAPINFOHEADER);
			bmp.biWidth = VideoInfo.Width;
			bmp.biHeight = VideoInfo.Height;

			bmp.biPlanes = 1;
			bmp.biBitCount = 32;
			bmp.biCompression = BI_RGB;
			ScanLineWidth = VideoInfo.Width * 4;
			AlignedScanLineWidth = DIB_SCAN_LINE(ScanLineWidth);
			bmp.biSizeImage = AlignedScanLineWidth * bmp.biHeight;
			bmp.biXPelsPerMeter = 0;
			bmp.biYPelsPerMeter = 0;
			bmp.biClrUsed = 0;
			bmp.biClrImportant = 0;

			AVISTREAMINFO asi;
			memset(&asi, 0, sizeof(asi));
			asi.fccType = streamtypeVIDEO;
			asi.fccHandler = 0;
			asi.dwScale = VideoInfo.FrameRate.Scale;
			asi.dwRate = VideoInfo.FrameRate.Rate;
			asi.dwSuggestedBufferSize  = bmp.biSizeImage;
			SetRect(&asi.rcFrame, 0, 0, bmp.biWidth, bmp.biHeight);

			bool Error = true;
			hr = AVIFileCreateStream(AVIFile, &VideoStream, &asi);
			if (hr == 0)
			{
				AVICOMPRESSOPTIONS opts;
				AVICOMPRESSOPTIONS FAR * aopts[1] = {&opts};
				memset(&opts, 0, sizeof(opts));
				opts.dwFlags=AVICOMPRESSF_VALID;
				opts.dwQuality=7500;
				if (AVISaveOptions(NULL, ICMF_CHOOSE_KEYFRAME | ICMF_CHOOSE_DATARATE, 1, &VideoStream, (LPAVICOMPRESSOPTIONS FAR *) &aopts))
				{
					int cx, cy;
					GetCompressorAlign(opts.fccHandler, cx, cy);
					if ((VideoInfo.Width % cx ) || (VideoInfo.Height % cy))
					{
						AVIStreamRelease(VideoStream);
						VideoStream = NULL;

						AVIFileRelease(AVIFile);
						AVIFile = NULL;

						hr = AVIFileOpen(&AVIFile, FileName, OF_CREATE, 0L);
						if (hr == 0)
						{
							bmp.biSize = sizeof(BITMAPINFOHEADER);
							bmp.biWidth = (VideoInfo.Width / cx) * cx;
							bmp.biHeight = (VideoInfo.Height / cy) * cy;
							bmp.biPlanes = 1;
							bmp.biBitCount = 32;
							bmp.biCompression = BI_RGB;
							ScanLineWidth = VideoInfo.Width * 4;
							AlignedScanLineWidth = DIB_SCAN_LINE(bmp.biWidth * 4);
							bmp.biSizeImage = AlignedScanLineWidth * bmp.biHeight;
							bmp.biXPelsPerMeter = 0;
							bmp.biYPelsPerMeter = 0;
							bmp.biClrUsed = 0;
							bmp.biClrImportant = 0;

							AVISTREAMINFO asi;
							memset(&asi, 0, sizeof(asi));
							asi.fccType = streamtypeVIDEO;
							asi.fccHandler = 0;
							asi.dwScale = VideoInfo.FrameRate.Scale;
							asi.dwRate = VideoInfo.FrameRate.Rate;
							asi.dwSuggestedBufferSize  = bmp.biSizeImage;
							SetRect(&asi.rcFrame, 0, 0, bmp.biWidth, bmp.biHeight);
							hr = AVIFileCreateStream(AVIFile, &VideoStream, &asi);
							if (hr != NULL)
							{
								VideoStream = NULL;
							}
						}
					}
					if (VideoStream != NULL)
					{
						hr = AVIMakeCompressedStream(&CompressedVideoStream, VideoStream, &opts, NULL);
						if (hr == 0)
						{
							LPBITMAPINFOHEADER lpbmp = &bmp;
							hr = AVIStreamSetFormat(CompressedVideoStream, 0, lpbmp, lpbmp->biSize + lpbmp->biClrUsed * sizeof(RGBQUAD));
							if (hr == AVIERR_OK)
							{
								FrameBuffer = (BYTE*)GetMem(bmp.biSizeImage);
								Error = FrameBuffer == NULL;
							}
						}
					}
					AVISaveOptionsFree(1, (LPAVICOMPRESSOPTIONS FAR *) &aopts);
				}
			}
			if (Error)
			{
				if (CompressedVideoStream != NULL)
				{
					AVIStreamRelease(CompressedVideoStream);
					CompressedVideoStream = NULL;
				}
				if (VideoStream != NULL)
				{
					AVIStreamRelease(VideoStream);
					VideoStream = NULL;
				}
			}
			else
			{
				OpenMode = Mode;
				OpenType |= otVideo;
				VideoPos = 0;
			}
		}

		if (Type & otAudio)
		{
			bool Error = true;
			WAVEFORMATEX wf;
			wf.cbSize = 0;
			wf.wFormatTag = WAVE_FORMAT_PCM;
			wf.nChannels = AudioInfo.Channels;
			wf.nSamplesPerSec = AudioInfo.SamplesPerSec;
			wf.wBitsPerSample = 16;
			wf.nBlockAlign = wf.nChannels * (wf.wBitsPerSample / 8);
			wf.nAvgBytesPerSec = wf.nBlockAlign * wf.nSamplesPerSec;

			DWORD pdwf_size = 0;
			if ((acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT, &pdwf_size) != 0) || pdwf_size < sizeof(wf))
			{
				pdwf_size = sizeof(wf);
			}

			WAVEFORMATEX *pdwf = (WAVEFORMATEX*)GetMem(pdwf_size);
			if (pdwf != NULL)
			{
				memset(pdwf, 0, pdwf_size);
				memcpy(pdwf, &wf, sizeof(wf));

				ACMFORMATCHOOSE acmfmc;
				memset(&acmfmc, 0, sizeof(acmfmc));
				acmfmc.cbStruct = sizeof(acmfmc);
				acmfmc.fdwStyle = ACMFORMATCHOOSE_STYLEF_INITTOWFXSTRUCT;
				acmfmc.pwfx = (LPWAVEFORMATEX)pdwf;
				acmfmc.cbwfx = pdwf_size;
				acmfmc.fdwEnum = ACM_FORMATENUMF_CONVERT | ACM_FORMATENUMF_NCHANNELS;
				acmfmc.pwfxEnum = &wf;
				hr = acmFormatChoose(&acmfmc);
				if (hr == MMSYSERR_NOERROR)
				{
					hr = acmStreamOpen(&has, NULL, (LPWAVEFORMATEX)&wf, pdwf, NULL, 0, 0, ACM_STREAMOPENF_NONREALTIME);
					if (hr == 0)
					{
						ash.cbSrcLength = 44100;
						hr = acmStreamSize(has, ash.cbSrcLength, &ash.cbDstLength, ACM_STREAMSIZEF_SOURCE);
						if (hr == 0)
						{
							ash.pbSrc = (BYTE*)GetMem(ash.cbSrcLength);
							ash.pbDst = (BYTE*)GetMem(ash.cbDstLength);
							ash.cbSrcLengthUsed = 0;
							if (ash.pbSrc != NULL && ash.pbDst != NULL)
							{
								hr = acmStreamPrepareHeader(has, &ash, 0);
								if (hr == 0)
								{
									memset(&AudioStreamInfo, 0, sizeof(AudioStreamInfo));
									AudioStreamInfo.fccType = streamtypeAUDIO;
									AudioStreamInfo.fccHandler = 0;
									AudioStreamInfo.dwScale = pdwf->nBlockAlign;
									AudioStreamInfo.dwRate = pdwf->nAvgBytesPerSec;
									AudioStreamInfo.dwSuggestedBufferSize = ash.cbDstLength;
									SetRect(&AudioStreamInfo.rcFrame, 0, 0, VideoInfo.Width, VideoInfo.Height);
									hr = AVIFileCreateStream(AVIFile, &AudioStream, &AudioStreamInfo);
									if (hr == 0)
									{
										hr = AVIStreamSetFormat(AudioStream, 0, pdwf, sizeof(WAVEFORMATEX) + pdwf->cbSize);
										Error = hr != AVIERR_OK;
									}
								}
								else
								{
									FreeMem(ash.pbSrc);
									FreeMem(ash.pbDst);
								}
							}
							else
							{
								if (ash.pbSrc != NULL)
								{
									FreeMem(ash.pbSrc);
								}
								if (ash.pbDst != NULL)
								{
									FreeMem(ash.pbDst);
								}
							}
						}
					}
				}
			}
			FreeMem(pdwf);
			if (Error)
			{
				if (has != NULL)
				{
					acmStreamClose(has, 0);
					has = NULL;
				}
				if (AudioStream != NULL)
				{
					AVIStreamRelease(AudioStream);
					AudioStream = NULL;
				}
			}
			else
			{
				OpenMode = Mode;
				OpenType |= otAudio;
				AudioPos = 0;
			}
		}
	}

	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 TAVIFile::Close()
{
	if (ash.fdwStatus != 0)
	{
		acmStreamUnprepareHeader(has, &ash, 0);
		FreeMem(ash.pbSrc);
		FreeMem(ash.pbDst);
	}

	if (has != NULL)
	{
		acmStreamClose(has, 0);
	}

	if (AudioStream != NULL)
	{
		AVIStreamRelease(AudioStream);
	}

	if (pget != NULL)
	{
		AVIStreamGetFrameClose(pget);
	}

	if (CompressedVideoStream != NULL)
	{
		AVIStreamRelease(CompressedVideoStream);
	}

	if (VideoStream != NULL)
	{
		AVIStreamRelease(VideoStream);
	}

	if (AVIFile != NULL)
	{
		 AVIFileRelease(AVIFile);
	}

	ash.fdwStatus = 0;
	has = NULL;
	AudioStream = NULL;
	pget = NULL;
	VideoStream = NULL;
	CompressedVideoStream = NULL;
	AVIFile = NULL;

	OpenMode = omClosed;
	OpenType = otNone;

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

//---------------------------------------------------------------------------
BITMAPINFO* TAVIFile::DIBGetFrameA()
{
	CheckRead();
	if (pget == NULL)
	{
		throw AVFileErrorGetVideo("Decompressor is not initialized!");
	}
	LPBITMAPINFO bmp = (PBITMAPINFO)AVIStreamGetFrame(pget, VideoPos);
	if (bmp == NULL)
	{
		throw AVFileErrorGetVideo();
	}
	VideoPos++;
	return bmp;
}

//---------------------------------------------------------------------------
BYTE* TAVIFile::GetFrameA()
{
	BITMAPINFO *Bitmap = DIBGetFrameA();
	if (Bitmap->bmiHeader.biCompression == BI_RGB && Bitmap->bmiHeader.biBitCount == 32)
	{
		BYTE *Src = (BYTE*)Bitmap + Bitmap->bmiHeader.biSize + Bitmap->bmiHeader.biSizeImage;
		BYTE *Dst = FrameBuffer;
		for (int i = 0; i < VideoInfo.Height; i++)
		{
			Src -= AlignedScanLineWidth;
			memcpy(Dst, Src, ScanLineWidth);
			Dst += ScanLineWidth;
		}
	}
	else
	{
		DWORD dwSourceBitsSize = Bitmap->bmiHeader.biHeight * DIB_SCAN_LINE((Bitmap->bmiHeader.biWidth * Bitmap->bmiHeader.biPlanes * Bitmap->bmiHeader.biBitCount) / 8);

		LPBITMAPINFO lpbmi = NULL;
		LPBYTE lpSourceBits, lpTargetBits, lpResult;
		HDC hDC = NULL, hSourceDC, hTargetDC;
		HBITMAP hSourceBitmap, hTargetBitmap, hOldTargetBitmap, hOldSourceBitmap;
		DWORD dwTargetBitsSize, dwTargetHeaderSize;

		dwTargetHeaderSize = sizeof(BITMAPINFO);
		lpbmi = (LPBITMAPINFO)GetMem(dwTargetHeaderSize);
		if (lpbmi == NULL)
		{
			VideoPos--;
			throw AVFileErrorMemory();
		}
		lpbmi->bmiHeader.biSize = sizeof( BITMAPINFOHEADER );
		lpbmi->bmiHeader.biWidth = Bitmap->bmiHeader.biWidth;
		lpbmi->bmiHeader.biHeight = Bitmap->bmiHeader.biHeight;
		lpbmi->bmiHeader.biPlanes = 1;
		lpbmi->bmiHeader.biBitCount = 32;
		lpbmi->bmiHeader.biCompression = BI_RGB;
		lpbmi->bmiHeader.biSizeImage = 0;
		lpbmi->bmiHeader.biXPelsPerMeter = 0;
		lpbmi->bmiHeader.biYPelsPerMeter = 0;
		lpbmi->bmiHeader.biClrUsed = 0;
		lpbmi->bmiHeader.biClrImportant = 0;

		hDC = GetDC(NULL);
		void *Temp;
		hTargetBitmap = CreateDIBSection(hDC, lpbmi, DIB_RGB_COLORS, &Temp, NULL, 0);
		lpTargetBits = (LPBYTE)Temp;
		hSourceBitmap = CreateDIBSection(hDC, Bitmap, DIB_RGB_COLORS, &Temp, NULL, 0);
		lpSourceBits = (LPBYTE)Temp;
//		hTargetBitmap = CreateDIBSection(hDC, lpbmi, DIB_RGB_COLORS, &(void*)lpTargetBits, NULL, 0);
//		hSourceBitmap = CreateDIBSection(hDC, Bitmap, DIB_RGB_COLORS, &(void*)lpSourceBits, NULL, 0);
		hSourceDC = CreateCompatibleDC(hDC);
		hTargetDC = CreateCompatibleDC(hDC);

		memcpy(lpSourceBits, (BYTE*)Bitmap + Bitmap->bmiHeader.biSize + Bitmap->bmiHeader.biClrUsed * sizeof(RGBQUAD), dwSourceBitsSize);
		hOldSourceBitmap = (HBITMAP)SelectObject(hSourceDC, hSourceBitmap);
		hOldTargetBitmap = (HBITMAP)SelectObject(hTargetDC, hTargetBitmap);

		BitBlt(hTargetDC, 0, 0, lpbmi->bmiHeader.biWidth, lpbmi->bmiHeader.biHeight, hSourceDC, 0, 0, SRCCOPY);

		SelectObject(hSourceDC, hOldSourceBitmap);
		SelectObject(hSourceDC, hOldTargetBitmap);
		DeleteDC(hSourceDC);
		DeleteDC(hTargetDC);
		ReleaseDC(NULL, hDC);
		GdiFlush();

		BYTE *Src = lpTargetBits + DIB_SCAN_LINE((lpbmi->bmiHeader.biWidth * lpbmi->bmiHeader.biPlanes * lpbmi->bmiHeader.biBitCount) / 8) * Bitmap->bmiHeader.biHeight;
		BYTE *Dst = FrameBuffer;
		for (int i = 0; i < VideoInfo.Height; i++)
		{
			Src -= AlignedScanLineWidth;
			memcpy(Dst, Src, ScanLineWidth);
			Dst += ScanLineWidth;
		}

		DeleteObject(hTargetBitmap);
		DeleteObject(hSourceBitmap);
		FreeMem(lpbmi);
	}
	return FrameBuffer;
}

//---------------------------------------------------------------------------
void TAVIFile::GetFrameA2(BYTE *Buffer)
{
	BITMAPINFO *Bitmap = DIBGetFrameA();
	if (Bitmap->bmiHeader.biCompression == BI_RGB && Bitmap->bmiHeader.biBitCount == 32)
	{
		BYTE *Src = (BYTE*)Bitmap + Bitmap->bmiHeader.biSize + Bitmap->bmiHeader.biSizeImage;
		BYTE *Dst = Buffer;
		for (int i = 0; i < VideoInfo.Height; i++)
		{
			Src -= AlignedScanLineWidth;
			memcpy(Dst, Src, ScanLineWidth);
			Dst += ScanLineWidth;
		}
	}
	else
	{
		DWORD dwSourceBitsSize = Bitmap->bmiHeader.biHeight * DIB_SCAN_LINE((Bitmap->bmiHeader.biWidth * Bitmap->bmiHeader.biPlanes * Bitmap->bmiHeader.biBitCount) / 8);

		LPBITMAPINFO lpbmi = NULL;
		LPBYTE lpSourceBits, lpTargetBits, lpResult;
		HDC hDC = NULL, hSourceDC, hTargetDC;
		HBITMAP hSourceBitmap, hTargetBitmap, hOldTargetBitmap, hOldSourceBitmap;
		DWORD dwTargetBitsSize, dwTargetHeaderSize;

		dwTargetHeaderSize = sizeof(BITMAPINFO);
		lpbmi = (LPBITMAPINFO)GetMem(dwTargetHeaderSize);
		if (lpbmi == NULL)
		{
			VideoPos--;
			throw AVFileErrorMemory();
		}
		lpbmi->bmiHeader.biSize = sizeof( BITMAPINFOHEADER );
		lpbmi->bmiHeader.biWidth = Bitmap->bmiHeader.biWidth;
		lpbmi->bmiHeader.biHeight = Bitmap->bmiHeader.biHeight;
		lpbmi->bmiHeader.biPlanes = 1;
		lpbmi->bmiHeader.biBitCount = 32;
		lpbmi->bmiHeader.biCompression = BI_RGB;
		lpbmi->bmiHeader.biSizeImage = 0;
		lpbmi->bmiHeader.biXPelsPerMeter = 0;
		lpbmi->bmiHeader.biYPelsPerMeter = 0;
		lpbmi->bmiHeader.biClrUsed = 0;
		lpbmi->bmiHeader.biClrImportant = 0;

		hDC = GetDC(NULL);
		void *Temp;
		hTargetBitmap = CreateDIBSection(hDC, lpbmi, DIB_RGB_COLORS, &Temp, NULL, 0);
		lpTargetBits = (LPBYTE)Temp;
		hSourceBitmap = CreateDIBSection(hDC, Bitmap, DIB_RGB_COLORS, &Temp, NULL, 0);
		lpSourceBits = (LPBYTE)Temp;

//		hTargetBitmap = CreateDIBSection(hDC, lpbmi, DIB_RGB_COLORS, &(void*)lpTargetBits, NULL, 0);
//		hSourceBitmap = CreateDIBSection(hDC, Bitmap, DIB_RGB_COLORS, &(void*)lpSourceBits, NULL, 0);
		hSourceDC = CreateCompatibleDC(hDC);
		hTargetDC = CreateCompatibleDC(hDC);

		memcpy(lpSourceBits, (BYTE*)Bitmap + Bitmap->bmiHeader.biSize + Bitmap->bmiHeader.biClrUsed * sizeof(RGBQUAD), dwSourceBitsSize);
		hOldSourceBitmap = (HBITMAP)SelectObject(hSourceDC, hSourceBitmap);
		hOldTargetBitmap = (HBITMAP)SelectObject(hTargetDC, hTargetBitmap);

		BitBlt(hTargetDC, 0, 0, lpbmi->bmiHeader.biWidth, lpbmi->bmiHeader.biHeight, hSourceDC, 0, 0, SRCCOPY);

		SelectObject(hSourceDC, hOldSourceBitmap);
		SelectObject(hSourceDC, hOldTargetBitmap);
		DeleteDC(hSourceDC);
		DeleteDC(hTargetDC);
		ReleaseDC(NULL, hDC);
		GdiFlush();

		BYTE *Src = lpTargetBits + DIB_SCAN_LINE((lpbmi->bmiHeader.biWidth * lpbmi->bmiHeader.biPlanes * lpbmi->bmiHeader.biBitCount) / 8) * Bitmap->bmiHeader.biHeight;
		BYTE *Dst = Buffer;
		for (int i = 0; i < VideoInfo.Height; i++)
		{
			Src -= AlignedScanLineWidth;
			memcpy(Dst, Src, ScanLineWidth);
			Dst += ScanLineWidth;
		}

		DeleteObject(hTargetBitmap);
		DeleteObject(hSourceBitmap);
		FreeMem(lpbmi);
	}
}

//---------------------------------------------------------------------------
unsigned int TAVIFile::GetVideoLength()
{
	if (VideoStream == NULL)
	{
		throw AVFileErrorVideoNotOpened();
	}
	return AVIStreamLength(VideoStream);
}

//---------------------------------------------------------------------------
unsigned int TAVIFile::GetAudioLength()
{
	if (AudioStream == NULL)
	{
		throw AVFileErrorAudioNotOpened();
	}
	return AVIStreamLength(AudioStream);
}

//---------------------------------------------------------------------------
BYTE* TAVIFile::GetAudio(unsigned int &Size)
{
	CheckRead();
	if (AudioStream == NULL || has == NULL)
	{
		throw AVFileErrorAudioNotOpened();
	}

	LONG rs, rb;
	if (AVIStreamRead(AudioStream, AudioPos, ash.cbSrcLength / ascale, ash.pbSrc, ash.cbSrcLength, &rb, &rs) != 0)
	{
		throw AVFileErrorGetAudio();
	}

	DWORD bs = ash.cbSrcLength;
	ash.cbSrcLength = rb;
	if (acmStreamConvert(has, &ash, 0))
	{
		ash.cbSrcLength = bs;
		throw AVFileErrorGetAudio("Could not decompress audio!");
	}
	ash.cbSrcLength = bs;

	AudioPos += rs;
	Size = ash.cbDstLengthUsed;
	return ash.pbDst;
}

//---------------------------------------------------------------------------
void TAVIFile::Seek(unsigned int Frame)
{
	CheckRead();
	if (VideoStream != NULL)
	{
		VideoPos = Frame;
	}
	if (AudioStream != NULL)
	{
		if (VideoStream != NULL)
		{
			AudioPos = AVIStreamSampleToSample(AudioStream, VideoStream, Frame);
		}
		else
		{
			LONG time = Round(double(Frame) * 1000.0 * double(VideoInfo.FrameRate.Scale) / double(VideoInfo.FrameRate.Rate));
			AudioPos = AVIStreamNearestSample(AudioStream, AVIStreamTimeToSample(AudioStream, time));
		}
	}
}

//---------------------------------------------------------------------------
void TAVIFile::PutFrame(BYTE *Buffer)
{
	CheckWrite();
	if (CompressedVideoStream == NULL)
	{
		throw AVFileErrorVideoNotOpened();
	}

	BYTE *Src = Buffer;
	BYTE *Dst = FrameBuffer + bmp.biSizeImage;

	for (int i = 0; i < bmp.biHeight; i++)
	{
		Dst -= AlignedScanLineWidth;
		if (AlignedScanLineWidth > ScanLineWidth)
		{
			memcpy(Dst, Src, ScanLineWidth);
			memset(Dst + ScanLineWidth, 0, AlignedScanLineWidth - ScanLineWidth);
		}
		else
		{
			memcpy(Dst, Src, AlignedScanLineWidth);
		}
		Src += ScanLineWidth;
	}

	if (AVIStreamWrite(CompressedVideoStream, VideoPos, 1, LPBYTE(FrameBuffer), bmp.biSizeImage, AVIIF_KEYFRAME, NULL, NULL) != AVIERR_OK)
	{
		throw AVFileErrorPutVideo();
	}
	VideoPos++;
}

//---------------------------------------------------------------------------
void TAVIFile::PutAudio(BYTE *Buffer, unsigned int Size)
{
	CheckWrite();
	if (AudioStream == NULL)
	{
		throw AVFileErrorAudioNotOpened();
	}

	unsigned int Converted = 0;
	while (Converted < Size)
	{
		unsigned int Bytes = Size - Converted;
		if (Bytes > ash.cbSrcLength)
		{
			Bytes = ash.cbSrcLength;
		}
		memcpy(ash.pbSrc, Buffer + Converted, Bytes);

		DWORD bs = ash.cbSrcLength;
		ash.cbSrcLength = Bytes;
		if (acmStreamConvert(has, &ash, 0))
		{
			ash.cbSrcLength = bs;
			throw AVFileErrorPutAudio("Could not compress audio!");
		}

		LONG sw, bw;
		if (AVIStreamWrite(AudioStream, AudioPos, ash.cbDstLengthUsed / AudioStreamInfo.dwScale, LPBYTE(ash.pbDst), ash.cbDstLengthUsed, 0, &sw, &bw) != AVIERR_OK)
		{
			throw AVFileErrorPutVideo();
		}
		AudioPos+=sw;

		ash.cbSrcLength = bs;
		Converted += Bytes;
	}
}

//---------------------------------------------------------------------------
void TAVIFile::GetCompressorAlign(DWORD fccHandler, int &x, int &y)
{
	typedef struct
	{
		char ID[4];
		int x, y;
	}
	TCompressorAlign;

	TCompressorAlign Aligns[] =
	{
		{'m', 'p', 'g', '4', 4, 2},
		{'m', 'p', '4', '2', 4, 2},
		{'m', 'p', '4', '3', 4, 2},
		{'d', 'i', 'v', '3', 4, 2},
		{'d', 'i', 'v', 'x', 4, 2}
	};
	int CompressorsCount = sizeof(Aligns) / sizeof(TCompressorAlign);

	x = 1;
	y = 1;
	char *ID = (char*)&fccHandler;
	for (int i = 0; i < CompressorsCount; i++)
	{
		int j = 0;
		while (j < 4)
		{
			if (toupper(Aligns[i].ID[j]) == toupper(ID[j]))
			{
				j++;
			}
			else
			{
				break;
			}
		}
		if (j == 4)
		{
			x = Aligns[i].x;
			y = Aligns[i].y;
			return;
		}
	}
}

