//	VirtualDub - Video processing and capture application
//	Copyright (C) 1998-2001 Avery Lee
//
//	This program is free software; you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation; either version 2 of the License, or
//	(at your option) any later version.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program; if not, write to the Free Software
//	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

#include "VirtualDub.h"

#include "AudioSource.h"
#include "VideoSource.h"
#include "AVIIndex.h"
#include "FastWriteStream.h"

#include "wrappedMMIO.h"
#include "Error.h"
#include "AVIOutput.h"
#include "oshelper.h"
#include "misc.h"

///////////////////////////////////////////

extern "C" unsigned long version_num;

///////////////////////////////////////////

typedef __int64 QUADWORD;

// The following comes from the OpenDML 1.0 spec for extended AVI files

// bIndexType codes
//
#define AVI_INDEX_OF_INDEXES 0x00	// when each entry in aIndex
									// array points to an index chunk

#define AVI_INDEX_OF_CHUNKS 0x01	// when each entry in aIndex
									// array points to a chunk in the file

#define AVI_INDEX_IS_DATA	0x80	// when each entry is aIndex is
									// really the data

// bIndexSubtype codes for INDEX_OF_CHUNKS

#define AVI_INDEX_2FIELD	0x01	// when fields within frames
									// are also indexed
	struct _avisuperindex_entry {
		QUADWORD qwOffset;		// absolute file offset, offset 0 is
								// unused entry??
		DWORD dwSize;			// size of index chunk at this offset
		DWORD dwDuration;		// time span in stream ticks
	};
	struct _avistdindex_entry {
		DWORD dwOffset;			// qwBaseOffset + this is absolute file offset
		DWORD dwSize;			// bit 31 is set if this is NOT a keyframe
	};

#pragma pack(push)
#pragma pack(2)

typedef struct _avisuperindex_chunk {
	FOURCC fcc;					// ’ix##’
	DWORD cb;					// size of this structure
	WORD wLongsPerEntry;		// must be 4 (size of each entry in aIndex array)
	BYTE bIndexSubType;			// must be 0 or AVI_INDEX_2FIELD
	BYTE bIndexType;			// must be AVI_INDEX_OF_INDEXES
	DWORD nEntriesInUse;		// number of entries in aIndex array that
								// are used
	DWORD dwChunkId;			// ’##dc’ or ’##db’ or ’##wb’, etc
	DWORD dwReserved[3];		// must be 0
	struct _avisuperindex_entry aIndex[];
} AVISUPERINDEX, * PAVISUPERINDEX;

typedef struct _avistdindex_chunk {
	FOURCC fcc;					// ’ix##’
	DWORD cb;
	WORD wLongsPerEntry;		// must be sizeof(aIndex[0])/sizeof(DWORD)
	BYTE bIndexSubType;			// must be 0
	BYTE bIndexType;			// must be AVI_INDEX_OF_CHUNKS
	DWORD nEntriesInUse;		//
	DWORD dwChunkId;			// ’##dc’ or ’##db’ or ’##wb’ etc..
	QUADWORD qwBaseOffset;		// all dwOffsets in aIndex array are
								// relative to this
	DWORD dwReserved3;			// must be 0
	struct _avistdindex_entry aIndex[];
} AVISTDINDEX, * PAVISTDINDEX;

#pragma pack(pop)

///////////////////////////////////////////

AVIOutputStream::AVIOutputStream(class AVIOutput *output) {
	this->output = output;
	format = NULL;
}

AVIOutputStream::~AVIOutputStream() {
	delete format;
}

BOOL AVIOutputStream::_write(FOURCC ckid, LONG dwIndexFlags, LPVOID lpBuffer, LONG cbBuffer) {
	output->writeIndexedChunk(ckid, dwIndexFlags, lpBuffer, cbBuffer);
//	output->writeIndexedChunk('bd00', dwIndexFlags, lpBuffer, cbBuffer);
	return TRUE;
}

BOOL AVIOutputStream::finalize() {
	_RPT0(0,"AVIOutputStream: finalize()\n");
	return TRUE;
}

////////////////////////////////////

AVIAudioOutputStream::AVIAudioOutputStream(class AVIOutput *out) : AVIOutputStream(out) {
	lTotalSamplesWritten = 0;
	// 13/11/2002, Cyrius : set the track as primary audio by default (the caller will change
	// it if necessary)
	setPrimary(TRUE);
}

BOOL AVIAudioOutputStream::write(LONG dwIndexFlags, LPVOID lpBuffer, LONG cbBuffer, LONG lSamples) {
	BOOL success;

	// 13/11/2002, Cyrius : use the correct ID for the track (otherwise we will write all audio
	// data in the same track :( )
	success = _write(/*mmioFOURCC('0','1','w','b')*/ id, dwIndexFlags, lpBuffer, cbBuffer);

	if (success) lTotalSamplesWritten += lSamples;

	return success;
}

BOOL AVIAudioOutputStream::finalize() {
	_RPT0(0,"AVIAudioOutputStream: finalize()\n");

	if (!lTotalSamplesWritten)
		streamInfo.dwLength = 1;
	else
		streamInfo.dwLength = lTotalSamplesWritten;
	return TRUE;
}

BOOL AVIAudioOutputStream::flush() {
	return TRUE;
}

////////////////////////////////////

AVIVideoOutputStream::AVIVideoOutputStream(class AVIOutput *out) : AVIOutputStream(out) {
	lTotalSamplesWritten = 0;
}

BOOL AVIVideoOutputStream::write(LONG dwIndexFlags, LPVOID lpBuffer, LONG cbBuffer, LONG lSamples) {
	BOOL success;

	success = _write(id, dwIndexFlags, lpBuffer, cbBuffer);

	if (success) lTotalSamplesWritten += lSamples;

	return success;
}

BOOL AVIVideoOutputStream::finalize() {
	_RPT0(0,"AVIVideoOutputStream: finalize()\n");

	if (!lTotalSamplesWritten)
		streamInfo.dwLength = 1;
	else
		streamInfo.dwLength = lTotalSamplesWritten;
	return TRUE;
}

////////////////////////////////////

char AVIOutput::szME[]="AVIOutput";

AVIOutput::AVIOutput() {
	audioOut			= NULL;
	audio2Out			= NULL;
	videoOut			= NULL;
}

AVIOutput::~AVIOutput() {
	_RPT0(0,"AVIOutput::~AVIOutput()\n");
	delete audioOut;
	delete audio2Out;
	delete videoOut;
}

AVIOutputFile::AVIOutputFile() {
	hFile				= NULL;
	fastIO				= NULL;
	index				= NULL;
	index_audio			= NULL;
	index_audio2		= NULL;
	index_video			= NULL;
	fCaching			= TRUE;
	i64FilePosition		= 0;
	i64XBufferLevel		= 0;
	xblock				= 0;
	fExtendedAVI		= true;
	lAVILimit			= 0x7F000000L;
	fCaptureMode		= false;
	iPadOffset			= 0;
	pSegmentHint		= NULL;
	cbSegmentHint		= 0;
	fInitComplete		= false;

	pHeaderBlock		= (char *)allocmem(65536);
	nHeaderLen			= 0;
	i64FarthestWritePoint	= 0;
	lLargestIndexDelta[0]	= 0;
	lLargestIndexDelta[1]	= 0;
	lLargestIndexDelta[2]	= 0;
	i64FirstIndexedChunk[0] = 0;
	i64FirstIndexedChunk[1] = 0;
	i64FirstIndexedChunk[2] = 0;
	i64LastIndexedChunk[0] = 0;
	i64LastIndexedChunk[1] = 0;
	i64LastIndexedChunk[2] = 0;
	lIndexedChunkCount[0]	= 0;
	lIndexedChunkCount[1]	= 0;
	lIndexedChunkCount[2]	= 0;
	lIndexSize			= 0;
	fPreemptiveExtendFailed = false;
}

AVIOutputFile::~AVIOutputFile() {
	delete index;
	delete index_audio;
	delete index_audio2;
	delete index_video;
	delete pSegmentHint;
	freemem(pHeaderBlock);

	_RPT0(0,"AVIOutputFile: destructor called\n");

	if (hFile) {
		LONG lHi = (LONG)(i64FarthestWritePoint>>32);
		DWORD dwError;

		if (0xFFFFFFFF != SetFilePointer(hFile, (LONG)i64FarthestWritePoint, &lHi, FILE_BEGIN)
			|| (dwError = GetLastError()) != NO_ERROR) {

			SetEndOfFile(hFile);
		}
	}

	delete fastIO;

	if (hFile)
		CloseHandle(hFile);
}

//////////////////////////////////

BOOL AVIOutputFile::initOutputStreams() {
	if (!(audio2Out = new AVIAudioOutputStream(this))) return FALSE;
	audio2Out->setPrimary(FALSE);
	if (!(audioOut = new AVIAudioOutputStream(this))) return FALSE;
	if (!(videoOut = new AVIVideoOutputStream(this))) return FALSE;

	return TRUE;
}

void AVIOutputFile::disable_os_caching() {
	fCaching = FALSE;
	lChunkSize = 0;
}

void AVIOutputFile::disable_extended_avi() {
	fExtendedAVI = false;
}

void AVIOutputFile::set_1Gb_limit() {
	lAVILimit = 0x3F000000L;
}

void AVIOutputFile::set_chunk_size(long l) {
	lChunkSize = l;
}

void AVIOutputFile::set_capture_mode(bool b) {
	fCaptureMode = b;
}

void AVIOutputFile::setSegmentHintBlock(bool fIsFinal, const char *pszNextPath, int cbBlock) {
	if (!pSegmentHint)
		if (!(pSegmentHint = new char[cbBlock]))
			throw MyMemoryError();

	cbSegmentHint = cbBlock;

	memset(pSegmentHint, 0, cbBlock);

	pSegmentHint[0] = !fIsFinal;
	if (pszNextPath)
		strcpy(pSegmentHint+1, pszNextPath);
}

// I don't like to bitch about other programs (well, okay, so I do), but
// Windows Media Player deserves special attention here.  The ActiveMovie
// implementation of OpenDML hierarchial indexing >2Gb *SUCKS*.  It can't
// cope with a JUNK chunk at the end of the hdrl chunk (even though
// the Microsoft documentation in AVIRIFF.H shows one), requires that
// all standard indexes be the same size except for the last one, and
// requires buffer size information for streams.  NONE of this is required
// by ActiveMovie when an extended index is absent (easily verified by
// changing the 'indx' chunks to JUNK chunks).  While diagnosing these
// problems I got an interesting array of error messages from WMP,
// including:
//
//	o Downloading codec from activex.microsoft.com
//		(Because of an extended index!?)
//	o "Cannot allocate memory because no size has been set"
//		???
//	o "The file format is invalid."
//		Detail: "The file format is invalid. (Error=8004022F)"
//		Gee, that clears everything up.
//	o My personal favorite: recursion of the above until the screen
//		has 100+ dialogs and WMP crashes with a stack fault.
//
// Basically, supporting WMP (or as I like to call it, WiMP) was an
// absolute 100% pain in the ass.

BOOL AVIOutputFile::init(const char *szFile, LONG xSize, LONG ySize, BOOL videoIn, BOOL audioIn, BOOL audio2In, LONG bufferSize, BOOL is_interleaved) {
	return _init(szFile, xSize, ySize, videoIn, audioIn, audio2In, bufferSize, is_interleaved, true);
}

FastWriteStream *AVIOutputFile::initCapture(const char *szFile, LONG xSize, LONG ySize, BOOL videoIn, BOOL audioIn, LONG bufferSize, BOOL is_interleaved) {
	return _init(szFile, xSize, ySize, videoIn, audioIn, FALSE, bufferSize, is_interleaved, false)
		? fastIO : NULL;
}

BOOL AVIOutputFile::_init(const char *szFile, LONG xSize, LONG ySize, BOOL videoIn, BOOL audioIn, BOOL audio2In, LONG bufferSize, BOOL is_interleaved, bool fThreaded) {
	AVISUPERINDEX asi={0};
	struct _avisuperindex_entry asie_dumb[MAX_SUPERINDEX_ENTRIES];

	fLimitTo4Gb = IsFilenameOnFATVolume(szFile);

	if (audio2In) {
		if (!audio2Out) return FALSE;
	} else {
		delete audio2Out;
		audio2Out = NULL;
	}

	if (audioIn) {
		if (!audioOut) return FALSE;
	} else {
		delete audioOut;
		audioOut = NULL;
	}

	if (!videoOut) return FALSE;

	// Allocate indexes

	if (!(index = new AVIIndex())) return FALSE;

	if (fExtendedAVI) {
		if (!(index_audio2 = new AVIIndex())) return FALSE;
		if (!(index_audio = new AVIIndex())) return FALSE;
		if (!(index_video = new AVIIndex())) return FALSE;
	}

	// Initialize main AVI header (avih)

	memset(&avihdr, 0, sizeof avihdr);
	avihdr.dwMicroSecPerFrame		= MulDivUnsigned(videoOut->streamInfo.dwScale, 1000000U, videoOut->streamInfo.dwRate);
	avihdr.dwMaxBytesPerSec			= 0;
	avihdr.dwPaddingGranularity		= 0;
	avihdr.dwFlags					= AVIF_HASINDEX | (is_interleaved ? AVIF_ISINTERLEAVED : 0);
	avihdr.dwTotalFrames			= videoOut->streamInfo.dwLength;
	avihdr.dwInitialFrames			= 0;
	avihdr.dwStreams				= audio2In ? 3: (audioIn ? 2 : 1);
	avihdr.dwSuggestedBufferSize	= 0;
	avihdr.dwWidth					= xSize;
	avihdr.dwHeight					= ySize;

	// Initialize file

	if (!fCaching) {

		hFile = CreateFile(szFile, GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);

		if (INVALID_HANDLE_VALUE == hFile)
			throw MyWin32Error("%s: %%s", GetLastError(), szME);

		if (!(fastIO = new FastWriteStream(szFile, bufferSize, lChunkSize ? lChunkSize : bufferSize/4, fThreaded)))
			throw MyMemoryError();
	} else {
		hFile = CreateFile(szFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_WRITE_THROUGH, NULL);

		if (INVALID_HANDLE_VALUE == hFile)
			throw MyWin32Error("%s: %%s", GetLastError(), szME);
	}

	i64FilePosition = 0;

	////////// Initialize the first 'AVI ' chunk //////////

	__int64 hdrl_pos;
	__int64 odml_pos;

	DWORD dw[64];

	// start RIFF chunk

	dw[0]	= FOURCC_RIFF;
	dw[1]	= 0;
	dw[2]	= formtypeAVI;

	_writeHdr(dw, 12);

	// start header chunk

	hdrl_pos = _beginList(listtypeAVIHEADER);

	// write out main AVI header

	main_hdr_pos = _writeHdrChunk(ckidAVIMAINHDR, &avihdr, sizeof avihdr);

	_RPT1(0,"Main header is at %08lx\n", main_hdr_pos);


	// start video stream headers

	strl_pos = _beginList(listtypeSTREAMHEADER);

	// write out video stream header and format

	video_hdr_pos	= _writeHdrChunk(ckidSTREAMHEADER, &videoOut->streamInfo, sizeof videoOut->streamInfo);
	_writeHdrChunk(ckidSTREAMFORMAT, videoOut->getFormat(), videoOut->getFormatLen());

	_RPT1(0,"Video header is at %08lx\n", video_hdr_pos);

	// write out video superindex (but make it a JUNK chunk for now).

	if (fExtendedAVI) {
		memset(asie_dumb, 0, sizeof asie_dumb);
		video_indx_pos = _getPosition();
		asi.fcc = ckidAVIPADDING;
		asi.cb = (sizeof asi)-8 + MAX_SUPERINDEX_ENTRIES*sizeof(_avisuperindex_entry);
		_writeHdr(&asi, sizeof asi);
		_writeHdr(asie_dumb, MAX_SUPERINDEX_ENTRIES*sizeof(_avisuperindex_entry));
	}

	// finish video stream header

	_closeList(strl_pos);

	videoOut->streamInfo.dwSuggestedBufferSize = 0;

	// if there is audio...

	if (audioIn) {
		// start audio stream headers

		strl_pos = _beginList(listtypeSTREAMHEADER);

		// write out audio stream header and format

		audio_hdr_pos	= _writeHdrChunk(ckidSTREAMHEADER, &audioOut->streamInfo, sizeof audioOut->streamInfo);
		audio_format_pos = _writeHdrChunk(ckidSTREAMFORMAT, audioOut->getFormat(), audioOut->getFormatLen());

		_RPT1(0,"Audio header is at %08lx\n", audio_hdr_pos);

		// write out audio superindex (but make it a JUNK chunk for now).

		if (fExtendedAVI) {
			audio_indx_pos = _getPosition();
			asi.fcc = ckidAVIPADDING;
			asi.cb = (sizeof asi)-8 + MAX_SUPERINDEX_ENTRIES*sizeof(_avisuperindex_entry);
			_writeHdr(&asi, sizeof asi);
			_writeHdr(asie_dumb, MAX_SUPERINDEX_ENTRIES*sizeof(_avisuperindex_entry));
		}

		// finish audio stream header

		_closeList(strl_pos);

		audioOut->streamInfo.dwSuggestedBufferSize = 0;
	}

// if there is a second audio...

	if (audio2In) {
		// start audio stream headers

		strl_pos = _beginList(listtypeSTREAMHEADER);

		// write out audio stream header and format

		audio2_hdr_pos	= _writeHdrChunk(ckidSTREAMHEADER, &audio2Out->streamInfo, sizeof audio2Out->streamInfo);
		audio2_format_pos = _writeHdrChunk(ckidSTREAMFORMAT, audio2Out->getFormat(), audio2Out->getFormatLen());

		_RPT1(0,"Audio2 header is at %08lx\n", audio2_hdr_pos);

		// write out audio superindex (but make it a JUNK chunk for now).

		if (fExtendedAVI) {
			audio2_indx_pos = _getPosition();
			asi.fcc = ckidAVIPADDING;
			asi.cb = (sizeof asi)-8 + MAX_SUPERINDEX_ENTRIES*sizeof(_avisuperindex_entry);
			_writeHdr(&asi, sizeof asi);
			_writeHdr(asie_dumb, MAX_SUPERINDEX_ENTRIES*sizeof(_avisuperindex_entry));
		}

		// finish audio stream header

		_closeList(strl_pos);

		audio2Out->streamInfo.dwSuggestedBufferSize = 0;
	}


	// write out dmlh header (indicates real # of frames)

	if (fExtendedAVI) {
		odml_pos = _beginList('lmdo');

		memset(dw, 0, sizeof dw);
		dmlh_pos = _writeHdrChunk('hlmd', dw, 62*4);

		_closeList(odml_pos);
	}

	// write out segment hint block

	if (pSegmentHint)
		seghint_pos = _writeHdrChunk('mges', pSegmentHint, cbSegmentHint);

	_closeList(hdrl_pos);
//	_flushHdr();

	// pad out to a multiple of 2048 bytes
	//
	// WARNING: ActiveMovie/WMP can't handle a trailing JUNK chunk in hdrl
	//			if an extended index is in use.  It says the file format
	//			is invalid!

	{
		char *s;
		long pad;

		pad = (2048 - ((_getPosition()+8)&2047))&2047;

		if (pad) {
			if (!(s = (char *)allocmem(pad)))
				return FALSE;

			memset(s,0,pad);

			if (pad > 80)
				sprintf(s, "VirtualDub build %d/%s", version_num,
#ifdef _DEBUG
		"debug"
#else
		"release"
#endif
				);

			_writeHdrChunk(ckidAVIPADDING, s, pad);

			freemem(s);
		}

//		// If we are using a fast path, sync the fast path to the slow path

//		if (fastIO)
//			fastIO->Seek(i64FilePosition);
	}

	if (fastIO)
//		fastIO->FlushStart();
		fastIO->Put(pHeaderBlock, nHeaderLen);
	else
		_flushHdr();

	// If we're using the fast path, we're aligned to a sector boundary.
	// Write out the 12 header bytes.

	_openXblock();


	{
		DWORD dwLo, dwHi;

		dwLo = GetFileSize(hFile, &dwHi);

		if (dwLo != 0xFFFFFFFF || GetLastError()==NO_ERROR)
			i64EndOfFile = dwLo | ((__int64)dwHi << 32);
		else
			i64EndOfFile = 0;
	}

	fInitComplete = true;

	return TRUE;
}

BOOL AVIOutputFile::finalize() {
	AVISUPERINDEX asi_video;
	AVISUPERINDEX asi_audio;
	AVISUPERINDEX asi_audio2;
	struct _avisuperindex_entry asie_video[MAX_SUPERINDEX_ENTRIES];
	struct _avisuperindex_entry asie_audio[MAX_SUPERINDEX_ENTRIES];
	struct _avisuperindex_entry asie_audio2[MAX_SUPERINDEX_ENTRIES];
	DWORD dw;
	int i;

	if (!fInitComplete)
		return TRUE;

	if (videoOut) if (!videoOut->finalize()) return FALSE;
	if (audioOut) if (!audioOut->finalize()) return FALSE;
	if (audio2Out) if (!audio2Out->finalize()) return FALSE;

	// fast path: clean it up and resync slow path.

	// create extended indices

	if (fExtendedAVI && xblock != 0) {
		_createNewIndices(index_video, &asi_video, asie_video, false);
		if (audioOut)
			_createNewIndices(index_audio, &asi_audio, asie_audio, audioOut);
		if (audio2Out)
			_createNewIndices(index_audio2, &asi_audio2, asie_audio2, audio2Out);
	}

	// finish last Xblock

	_closeXblock();

	if (fastIO) {
		char pad[2048+8];

		// pad to next boundary

		_RPT1(0,"AVIOutputFile: starting pad at position %08I64x\n", i64FilePosition);

		*(long *)(pad + 0) = 'KNUJ';
		*(long *)(pad + 4) = (2048 - ((i64FilePosition+8)&2047))&2047;
		memset(pad+8, 0, 2048);
		_write(pad, *(long *)(pad+4) + 8);

		// flush fast path, get disk position

		fastIO->Flush1();
		fastIO->Flush2(hFile);

		// seek slow path up

		_seekDirect(i64FilePosition);

	}	

	// truncate file

	SetEndOfFile(hFile);

	_RPT0(0,"AVIOutputFile: Writing main AVI header...\n");
	_seekHdr(main_hdr_pos+8);
	_writeHdr(&avihdr, sizeof avihdr);

	_RPT0(0,"AVIOutputFile: Writing video header...\n");
	_seekHdr(video_hdr_pos+8);
	_writeHdr(&videoOut->streamInfo, sizeof(AVIStreamHeader_fixed));

	if (audioOut) {
		_RPT0(0,"AVIOutputFile: Writing audio header...\n");
		_seekHdr(audio_hdr_pos+8);
		_writeHdr(&audioOut->streamInfo, sizeof(AVIStreamHeader_fixed));

		// we have to rewrite the audio format, in case someone
		// fixed fields in the format afterward (MPEG-1/L3)

		_RPT0(0,"AVIOutputFile: Writing audio format...\n");

		_seekHdr(audio_format_pos+8);
		_writeHdr(audioOut->getFormat(), audioOut->getFormatLen());
	}

	if (audio2Out) {
		_RPT0(0,"AVIOutputFile: Writing audio2 header...\n");
		_seekHdr(audio2_hdr_pos+8);
		_writeHdr(&audio2Out->streamInfo, sizeof(AVIStreamHeader_fixed));

		// we have to rewrite the audio format, in case someone
		// fixed fields in the format afterward (MPEG-1/L3)

		_RPT0(0,"AVIOutputFile: Writing audio2 format...\n");

		_seekHdr(audio2_format_pos+8);
		_writeHdr(audio2Out->getFormat(), audio2Out->getFormatLen());
	}

	if (fExtendedAVI) {
		_RPT0(0,"AVIOutputFile: writing dmlh header...\n");
		_seekHdr(dmlh_pos+8);
		dw = videoOut->streamInfo.dwLength;
		_writeHdr(&dw, 4);

		if (xblock > 1) {
			_RPT0(0,"AVIOutputFile: writing video superindex...\n");

			_seekHdr(video_indx_pos);
			_writeHdr(&asi_video, sizeof asi_video);
			_writeHdr(asie_video, sizeof(_avisuperindex_entry)*MAX_SUPERINDEX_ENTRIES);

			if (audioOut) {
				_seekHdr(audio_indx_pos);
				_writeHdr(&asi_audio, sizeof asi_audio);
				_writeHdr(asie_audio, sizeof(_avisuperindex_entry)*MAX_SUPERINDEX_ENTRIES);
			}
			if (audio2Out) {
				_seekHdr(audio2_indx_pos);
				_writeHdr(&asi_audio2, sizeof asi_audio2);
				_writeHdr(asie_audio2, sizeof(_avisuperindex_entry)*MAX_SUPERINDEX_ENTRIES);
			}
		}
	}

	if (pSegmentHint) {
		_seekHdr(seghint_pos+8);
		_writeHdr(pSegmentHint, cbSegmentHint);
	}

	_flushHdr();

	_RPT0(0,"AVIOutputFile: closing RIFF and movi chunks...\n");

	for(i=0; i<xblock; i++) {
		DWORD dwLen;
		
		dwLen = (DWORD)avi_riff_len[i];
		_seekDirect(avi_riff_pos[i]+4);
		_writeDirect(&dwLen, 4);

		dwLen = (DWORD)avi_movi_len[i];
		_seekDirect(avi_movi_pos[i]+4);
		_writeDirect(&dwLen, 4);
	}

//	if (!FlushFileBuffers(hFile))
//		throw MyWin32Error("%s: %%s", GetLastError(), szME);

	// What do you do when a close fails?

	if (!CloseHandle(hFile)) {
		hFile = NULL;
		throw MyWin32Error("%s: %%s", GetLastError(), szME);
	}

	hFile = NULL;

	return TRUE;
}

BOOL AVIOutputFile::isPreview() { return FALSE; }

long AVIOutputFile::bufferStatus(long *lplBufferSize) {
	if (fastIO) {
		return fastIO->getBufferStatus(lplBufferSize);
	} else {
		return 0;
	}
}

////////////////////////////

__int64 AVIOutputFile::_writeHdr(void *data, long len) {
	if (nHeaderLen < (long)i64FilePosition + len)
		nHeaderLen = (long)i64FilePosition + len;

	memcpy(pHeaderBlock + (long)i64FilePosition, data, len);

	i64FilePosition += len;

	return i64FilePosition - len;
}

__int64 AVIOutputFile::_beginList(FOURCC ckid) {
	DWORD dw[3];

	dw[0] = FOURCC_LIST;
	dw[1] = 0;
	dw[2] = ckid;

	return _writeHdr(dw, 12);
}

__int64 AVIOutputFile::_writeHdrChunk(FOURCC ckid, void *data, long len) {
	DWORD dw[2];
	__int64 pos;

	dw[0] = ckid;
	dw[1] = len;

	pos = _writeHdr(dw, 8);

	_writeHdr(data, len);

	if (len & 1) {
		dw[0] = 0;

		_writeHdr(dw, 1);
	}

	return pos;
}

void AVIOutputFile::_closeList(__int64 pos) {
	DWORD dw;
	__int64 i64FPSave = i64FilePosition;
	
	dw = i64FilePosition - (pos+8);

	_seekHdr(pos+4);
	_writeHdr(&dw, 4);
	_seekHdr(i64FPSave);
}

void AVIOutputFile::_flushHdr() {
	DWORD dwActual;
	DWORD dwError;

	if (0xFFFFFFFF == SetFilePointer(hFile, 0, NULL, FILE_BEGIN))
		if ((dwError = GetLastError()) != NO_ERROR)
			throw MyWin32Error("%s: %%s", dwError, szME);

	i64FilePosition = 0;

	if (!WriteFile(hFile, pHeaderBlock, nHeaderLen, &dwActual, NULL)
		|| dwActual != nHeaderLen)

		throw MyWin32Error("%s: %%s", GetLastError(), szME);

	i64FilePosition = nHeaderLen;

	if (i64FilePosition > i64FarthestWritePoint)
		i64FarthestWritePoint = i64FilePosition;
}

__int64 AVIOutputFile::_getPosition() {
	return i64FilePosition;
}

void AVIOutputFile::_seekHdr(__int64 i64NewPos) {
	i64FilePosition = i64NewPos;
}

void AVIOutputFile::_seekDirect(__int64 i64NewPos) {
	LONG lHi = (LONG)(i64NewPos>>32);
	DWORD dwError;

//	_RPT1(0,"Seeking to %I64d\n", i64NewPos);

	if (0xFFFFFFFF == SetFilePointer(hFile, (LONG)i64NewPos, &lHi, FILE_BEGIN))
		if ((dwError = GetLastError()) != NO_ERROR)
			throw MyWin32Error("%s: %%s", dwError, szME);

	i64FilePosition = i64NewPos;
}

__int64 AVIOutputFile::_writeDirect(void *data, long len) {
	DWORD dwActual;

	if (!WriteFile(hFile, data, len, &dwActual, NULL)
		|| dwActual != len)

		throw MyWin32Error("%s: %%s", GetLastError(), szME);

	i64FilePosition += len;

	if (i64FilePosition > i64FarthestWritePoint)
		i64FarthestWritePoint = i64FilePosition;

	return i64FilePosition - len;
}

bool AVIOutputFile::_extendFile(__int64 i64NewPoint) {
	bool fSuccess;

	// Have we already extended the file past that point?

	if (i64NewPoint < i64EndOfFile)
		return true;

	// Attempt to extend the file.

	__int64 i64Save = i64FilePosition;

	_seekDirect(i64NewPoint);
	fSuccess = !!SetEndOfFile(hFile);
	_seekDirect(i64Save);

	if (fSuccess) {
		i64EndOfFile = i64NewPoint;
//		_RPT1(0,"Successfully extended file to %I64d bytes\n", i64EndOfFile);
	} else {
//		_RPT1(0,"Failed to extend file to %I64d bytes\n", i64NewPoint);
	}

	return fSuccess;
}

void AVIOutputFile::_write(void *data, int len) {

	if (!fPreemptiveExtendFailed && i64FilePosition + len + lIndexSize > i64EndOfFile - 8388608) {
		fPreemptiveExtendFailed = !_extendFile((i64FilePosition + len + lIndexSize + 16777215) & -8388608);
	}

	if (fastIO) {
		fastIO->Put(data,len);
		i64FilePosition += len;
		if (i64FilePosition > i64FarthestWritePoint)
			i64FarthestWritePoint = i64FilePosition;
	} else
//		_writeHdr(data,len);
		_writeDirect(data, len);
}

void AVIOutputFile::writeIndexedChunk(FOURCC ckid, LONG dwIndexFlags, LPVOID lpBuffer, LONG cbBuffer) {
	AVIIndexEntry2 avie;
	long buf[5];
	static char zero = 0;
	long siz;
	bool fOpenNewBlock = false;
	int nStream = 0;

	if ((ckid&0xffff) > (int)'00')
		nStream = 1;

	// Determine if we need to open another RIFF block (xblock).

	siz = cbBuffer + (cbBuffer&1) + 16;

	// The original AVI format can't accommodate RIFF chunks >4Gb due to the
	// use of 32-bit size fields.  Most RIFF parsers don't handle >2Gb because
	// of inappropriate use of signed variables.  And to top it all off,
	// stupid mistakes in the MCI RIFF parser prevent processing beyond the
	// 1Gb mark.
	//
	// To be save, we keep the first RIFF AVI chunk below 1Gb, and subsequent
	// RIFF AVIX chunks below 2Gb.  We have to leave ourselves a little safety
	// margin (16Mb in this case) for index blocks.

	if (fExtendedAVI)
		if (i64XBufferLevel + siz > (xblock ? 0x7F000000 : lAVILimit))
			fOpenNewBlock = true;

	// Check available disk space.
	//
	// Take the largest separation between data blocks,

	__int64 chunkloc;
	int idxblocksize;
	int idxblocks;
	__int64 maxpoint;

	chunkloc = i64FilePosition;
	if (fOpenNewBlock)
		chunkloc += 24;

	if (!i64FirstIndexedChunk[nStream])
		i64FirstIndexedChunk[nStream] = chunkloc;

	if ((long)(chunkloc - i64LastIndexedChunk[nStream]) > lLargestIndexDelta[nStream])
		lLargestIndexDelta[nStream] = (long)(chunkloc - i64LastIndexedChunk[nStream]);

	++lIndexedChunkCount[nStream];

	// compute how much total space we need to close the file

	idxblocks = 0;

	if (lLargestIndexDelta[0]) {
		idxblocksize = (int)(0x100000000i64 / lLargestIndexDelta[0]);
		if (idxblocksize > MAX_INDEX_ENTRIES)
			idxblocksize = MAX_INDEX_ENTRIES;
		idxblocks = (lIndexedChunkCount[0] + idxblocksize - 1) / idxblocksize;
	}
	if (lLargestIndexDelta[1]) {
		idxblocksize = (int)(0x100000000i64 / lLargestIndexDelta[1]);
		if (idxblocksize > MAX_INDEX_ENTRIES)
			idxblocksize = MAX_INDEX_ENTRIES;
		idxblocks += (lIndexedChunkCount[1] + idxblocksize - 1) / idxblocksize;
	}
	if (lLargestIndexDelta[2]) {
		idxblocksize = (int)(0x100000000i64 / lLargestIndexDelta[2]);
		if (idxblocksize > MAX_INDEX_ENTRIES)
			idxblocksize = MAX_INDEX_ENTRIES;
		idxblocks += (lIndexedChunkCount[2] + idxblocksize - 1) / idxblocksize;
	}

	lIndexSize = 0;

	if (fExtendedAVI)
		lIndexSize = idxblocks*sizeof(AVISTDINDEX);

	lIndexSize += 8 + 16*(lIndexedChunkCount[0]+lIndexedChunkCount[1]);
	
	// Give ourselves ~4K of headroom...

	maxpoint = (chunkloc + cbBuffer + 1 + 8 + 14 + 2047 + lIndexSize + 4096) & -2048i64;

	if (fLimitTo4Gb && maxpoint >= 0x100000000i64) {
		_RPT1(0,"overflow detected!  maxpoint=%I64d\n", maxpoint);
		_RPT2(0,"lIndexSize = %08lx (%ld index blocks)\n", lIndexSize, idxblocks);
		_RPT2(0,"sample counts = %ld, %ld\n", lIndexedChunkCount[0], lIndexedChunkCount[1]);

		throw MyError("Out of file space: Files cannot exceed 4 gigabytes on a FAT32 partition.");
	}

	if (!_extendFile(maxpoint))
		throw MyError("Not enough disk space to write additional data.");

	i64LastIndexedChunk[nStream] = chunkloc;

	// If we need to open a new Xblock, do so.

	if (fOpenNewBlock) {
		_closeXblock();
		_openXblock();
	}

	// Write the chunk.

	avie.ckid	= ckid;
	avie.pos	= i64FilePosition - (avi_movi_pos[0]+8); //chunkMisc.dwDataOffset - 2064;
	avie.size	= cbBuffer;

	if (dwIndexFlags & AVIIF_KEYFRAME)
		avie.size |= 0x80000000L;

	buf[0] = ckid;
	buf[1] = cbBuffer;

	_write(buf, 8);

	i64XBufferLevel += siz;

	// ActiveMovie/WMP requires a non-zero dwSuggestedBufferSize for
	// hierarchial indexing (piece of sh*t player).  So we continually
	// bump it up to the largest chunk size;


	if ((unsigned short)ckid == '10') {
		if (cbBuffer > audioOut->streamInfo.dwSuggestedBufferSize) {
			audioOut->streamInfo.dwSuggestedBufferSize = cbBuffer;
		}

		if (fExtendedAVI)
			if (!index_audio->add(&avie)) throw MyError("%s error: couldn't add audio chunk to index",szME);
	} else if ((unsigned short)ckid == '20') {
		if (cbBuffer > audio2Out->streamInfo.dwSuggestedBufferSize) {
			audio2Out->streamInfo.dwSuggestedBufferSize = cbBuffer;
		}

		if (fExtendedAVI)
			if (!index_audio2->add(&avie)) throw MyError("%s error: couldn't add audio chunk to index",szME);
	} else {
		if (cbBuffer > videoOut->streamInfo.dwSuggestedBufferSize)
			videoOut->streamInfo.dwSuggestedBufferSize = cbBuffer;

		if (fExtendedAVI)
			if (!index_video->add(&avie)) throw MyError("%s error: couldn't add video chunk to index",szME);
	}

	if (index)
		if (!index->add(&avie)) throw MyError("%s error: couldn't add chunk to index",szME);

	_write(lpBuffer, cbBuffer);

	// Align to 8-byte boundary, not 2-byte, in capture mode.

	if (fCaptureMode) {
		char *pp;
		int offset = (cbBuffer + iPadOffset) & 7;

		// offset=0:	no action
		// offset=1/2:	[00] 'JUNK' 6 <6 bytes>
		// offset=3/4:	[00] 'JUNK' 4 <4 bytes>
		// offset=5/6:	[00] 'JUNK' 2 <2 bytes>
		// offset=7:	00

		if (offset) {
			buf[0]	= 0;
			buf[1]	= 'KNUJ';
			buf[2]	= (-offset) & 6;
			buf[3]	= 0;
			buf[4]	= 0;

			pp = (char *)&buf[1];

			if (offset & 1)
				--pp;

			_write(pp, (offset & 1) + (((offset+1)&7) ? 8+buf[2] : 0));
		}

		iPadOffset = 0;

	} else {

		// Standard AVI: use 2 bytes

		if (cbBuffer & 1)
			_write(&zero, 1);
	}

}

void AVIOutputFile::_closeXblock() {
	avi_movi_len[xblock] = i64FilePosition - (avi_movi_pos[xblock]+8);

	if (!xblock) {
		avihdr.dwTotalFrames = videoOut->lTotalSamplesWritten;
		_writeLegacyIndex(true);
	}

	avi_riff_len[xblock] = i64FilePosition - (avi_riff_pos[xblock]+8);

	++xblock;

	i64XBufferLevel = 0;
}

void AVIOutputFile::_openXblock() {
	DWORD dw[8];

	if (xblock >= MAX_AVIBLOCKS)
		throw MyError("%s: Exceeded maximum RIFF count (%d)", szME, MAX_AVIBLOCKS);

	// If we're in capture mode, keep this stuff aligned to 8-byte boundaries!

	if (xblock != 0) {

		avi_riff_pos[xblock] = i64FilePosition;

		dw[0] = FOURCC_RIFF;
		dw[1] = 0x7F000000;
		dw[2] = xblock ? 'XIVA' : ' IVA';
		dw[3] = FOURCC_LIST;
		dw[4] = 0x7F000000;
		dw[5] = 'ivom';	// movi
		_write(dw,24);

		avi_movi_pos[xblock] = i64FilePosition - 12;
	} else {
		avi_riff_pos[xblock] = 0;

		avi_movi_pos[xblock] = i64FilePosition;

		dw[0] = FOURCC_LIST;
		dw[1] = 0x7FFFFFFF;
		dw[2] = 'ivom';		// movi

		if (fCaptureMode)
			iPadOffset = 4;

		_write(dw, 12);
	}

	// WARNING: For AVIFile to parse the index correctly, it assumes that the
	// first chunk in an index starts right after the movi chunk!

//	dw[0] = ckidAVIPADDING;
//	dw[1] = 4;
//	dw[2] = 0;
//	_write(dw, 12);
}

void AVIOutputFile::_writeLegacyIndex(bool use_fastIO) {
	if (!index)
		return;

	if (!index->makeIndex())
		throw MyMemoryError();

//	if (use_fastIO && fastIO) {
		DWORD dw[2];

		dw[0] = ckidAVINEWINDEX;
		dw[1] = index->indexLen() * sizeof(AVIINDEXENTRY);
		_write(dw, 8);
		_write(index->indexPtr(), index->indexLen() * sizeof(AVIINDEXENTRY));
//	} else {
//		_writeHdrChunk(ckidAVINEWINDEX, index->indexPtr(), index->indexLen() * sizeof(AVIINDEXENTRY));

	delete index;
	index = NULL;
}

void AVIOutputFile::_createNewIndices(AVIIndex *index, AVISUPERINDEX *asi, _avisuperindex_entry *asie, AVIAudioOutputStream	*paaos) {
	AVIIndexEntry2 *asie2;
	int size;
	int actual;
	int indexnum=0;
	int blocksize;

	if (!index || !index->size())
		return;

	if (!index->makeIndex2())
		throw MyMemoryError();

	size = index->indexLen();
	asie2 = index->index2Ptr();

	memset(asie, 0, sizeof(_avisuperindex_entry)*MAX_SUPERINDEX_ENTRIES);

	// Now we run into a bit of a problem.  DirectShow's AVI2 parser requires
	// that all index blocks have the same # of entries (except the last),
	// which is a problem since we also have to guarantee that each block
	// has offsets <4Gb.

	// For now, use a O(n^2) algorithm to find the optimal size.  This isn't
	// really a problem because this routine won't ever be called in time
	// critical circumstances, like streaming video capture.

	blocksize = MAX_INDEX_ENTRIES;

	{
		while(blocksize > 1) {
			int i;
			int nextblock = 0;
			__int64 offset;

			for(i=0; i<size; i++) {
				if (i == nextblock) {
					nextblock += blocksize;
					offset = asie2[i].pos;
				}

				if (asie2[i].pos >= offset + 0x100000000i64)
					break;
			}

			if (i >= size)
				break;

			--blocksize;
		}
	}

	// Write out the actual index blocks.

	while(size > 0) {
		if (indexnum >= MAX_SUPERINDEX_ENTRIES)
			throw MyError("Maximum number of extended AVI indices exceeded (%d)\n", MAX_SUPERINDEX_ENTRIES);

		actual = _writeNewIndex(&asie[indexnum], asie2, min(size, blocksize),
			paaos ? (paaos->isPrimary()? '10xi' : '20xi') : '00xi', 
			paaos ? paaos->id : videoOut->id, 
			paaos ? paaos->streamInfo.dwSampleSize : videoOut->streamInfo.dwSampleSize);

		asie2 += actual;
		size -= actual;
		++indexnum;
	}

	memset(asi, 0, sizeof(AVISUPERINDEX));
	asi->fcc			= 'xdni';
	asi->cb				= sizeof(AVISUPERINDEX)-8 + sizeof(_avisuperindex_entry)*MAX_SUPERINDEX_ENTRIES;
	asi->wLongsPerEntry	= 4;
	asi->bIndexSubType	= 0;
	asi->bIndexType		= AVI_INDEX_OF_INDEXES;
	asi->nEntriesInUse	= indexnum;
	asi->dwChunkId		= paaos ? paaos->id : videoOut->id;//is_audio ? 'bw10' : videoOut->id;
}

int AVIOutputFile::_writeNewIndex(struct _avisuperindex_entry *asie, AVIIndexEntry2 *avie2, int size, FOURCC fcc, DWORD dwChunkId, DWORD dwSampleSize) {
	AVISTDINDEX asi;
	AVIIndexEntry3 asie3[64];
	__int64 offset = avie2->pos;
	int tc;
	int i;
	int size0;

	// Scan, ascertain how many we can handle without exceeding 4Gb offset

	for(i=0; i<size; i++) {
		if (avie2[i].pos - offset >= 0x100000000i64)
			break;
	}

	size0 = size = i;

	// Check to see if we need to open a new AVIX block

	if (i64XBufferLevel + sizeof(AVISTDINDEX) + size*sizeof(_avistdindex_entry) > (xblock ? 0x7F000000 : lAVILimit)) {
		_closeXblock();
		_openXblock();
	}

	// setup superindex entry

	asie->qwOffset	= i64FilePosition;
	asie->dwSize	= sizeof(AVISTDINDEX) + size*sizeof(_avistdindex_entry);

	if (dwSampleSize) {
		__int64 total_bytes = 0;

		for(int i=0; i<size; i++)
			total_bytes += avie2[i].size & 0x7FFFFFFF;

		asie->dwDuration = (DWORD)(total_bytes / audioOut->streamInfo.dwSampleSize);
	} else
		asie->dwDuration = size;

	asi.fcc				= ((dwChunkId & 0xFFFF)<<16) | 'xi';
	asi.cb				= asie->dwSize - 8;
	asi.wLongsPerEntry	= 2;
	asi.bIndexSubType	= 0;
	asi.bIndexType		= AVI_INDEX_OF_CHUNKS;
	asi.nEntriesInUse	= size;
	asi.dwChunkId		= dwChunkId;
	asi.qwBaseOffset	= offset;
	asi.dwReserved3		= 0;

	_write(&asi, sizeof asi);

	while(size > 0) {
		tc = size;
		if (tc>64) tc=64;

		for(i=0; i<tc; i++) {
			asie3[i].dwOffset	= (DWORD)(avie2->pos - offset + avi_movi_pos[0] + 16);
			asie3[i].dwSizeKeyframe		= avie2->size;
			++avie2;
		}

		_write(asie3, tc*sizeof(AVIIndexEntry3));

		size -= tc;
	}

	return size0;
}
