//	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.

#define f_DUB_CPP

#include "VirtualDub.h"

#include <process.h>
#include <crtdbg.h>
#include <time.h>
#include <stdio.h>

#include <windows.h>
#include <commctrl.h>
#include <vfw.h>
#include <ddraw.h>

#include "resource.h"

#include "crash.h"
#include "tls.h"
#include "convert.h"
#include "filters.h"
#include "gui.h"
#include "ddrawsup.h"
#include "prefs.h"
#include "command.h"
#include "misc.h"

//#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)

#include "Error.h"
#include "VideoSequenceCompressor.h"
#include "AsyncBlitter.h"
#include "AVIOutputPreview.h"
#include "AVIOutput.h"
#include "Histogram.h"
#include "AudioSource.h"
#include "VideoSource.h"
#include "AVIPipe.h"
#include "VBitmap.h"
#include "FrameSubset.h"
#include "InputFile.h"
#include "VideoTelecineRemover.h"

#include "dub.h"
#include "dubstatus.h"

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

#define ASYNCHRONOUS_OVERLAY_SUPPORT

//#define STOP_SPEED_DEBUGGING

//#define RELEASE_MODE_DEBUGGING

#ifdef RELEASE_MODE_DEBUGGING
#define _XRPT0(y,x) OutputDebugString(x)
#else
#define _XRPT0(y,x) _RPT0(y,x)
#endif

#define DEBUG_SLEEP
//#define DEBUG_SLEEP Sleep(2000)

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

#ifdef STOP_SPEED_DEBUGGING

	static __int64 start_time, stop_time;

#endif

DubOptions g_dubOpts = {
	{
		0,				// no amp
		500,			// preload by 500ms
		1,				// every frame
		0,				// no new rate
		0,				// offset: 0ms
		false,			// period is in frames
		true,			// audio interleaving enabled
		true,			// yes, offset audio with video
		true,			// yes, clip audio to video length
		false,			// no integral change
		false,			// no high quality
		DubAudioOptions::P_NOCHANGE,		// no precision change
		DubAudioOptions::C_NOCHANGE,		// no channel change
		DubAudioOptions::M_NONE,
	},

// secondary audio
	{
		0,				// no amp
		500,			// preload by 500ms
		1,				// every frame
		0,				// no new rate
		0,				// offset: 0ms
		false,			// period is in frames
		true,			// audio interleaving enabled
		true,			// yes, offset audio with video
		true,			// yes, clip audio to video length
		false,			// no integral change
		false,			// no high quality
		DubAudioOptions::P_NOCHANGE,		// no precision change
		DubAudioOptions::C_NOCHANGE,		// no channel change
		DubAudioOptions::M_NONE,
	},

	{
		DubVideoOptions::D_24BIT,	// input: 24bit
		DubVideoOptions::D_24BIT,	// output: 24bit
		DubVideoOptions::M_FULL,	// mode: full
		TRUE,						// show input video
		TRUE,						// show output video
		FALSE,						// decompress output video before display
		FALSE,						// histograms
		TRUE,						// sync to audio
		1,							// no frame rate decimation
		0,							// no change in frame rate
		0,							// start offset: 0ms
		0,							// end offset: 0ms
		false,						// No inverse telecine
		false,						// (IVTC mode)
		-1,							// (IVTC offset)
		false,						// (IVTC polarity)
		0,							// progressive preview
	},

	{
		2097152,				// 2Mb AVI output buffer
		65536,					// 64K WAV input buffer
		32,						// 32 pipe buffers
		true,					// dynamic enable
		false,
		false,					// directdraw,
		true,					// drop frames
	},

	true,			// show status
	false,			// move slider
};

static const int g_iPriorities[][2]={

	// I/O							processor
	{ THREAD_PRIORITY_IDLE,			THREAD_PRIORITY_IDLE,			},
	{ THREAD_PRIORITY_LOWEST,		THREAD_PRIORITY_LOWEST,			},
	{ THREAD_PRIORITY_BELOW_NORMAL,	THREAD_PRIORITY_LOWEST,			},
	{ THREAD_PRIORITY_NORMAL,		THREAD_PRIORITY_BELOW_NORMAL,	},
	{ THREAD_PRIORITY_NORMAL,		THREAD_PRIORITY_NORMAL,			},
	{ THREAD_PRIORITY_ABOVE_NORMAL,	THREAD_PRIORITY_NORMAL,			},
	{ THREAD_PRIORITY_HIGHEST,		THREAD_PRIORITY_ABOVE_NORMAL,	},
	{ THREAD_PRIORITY_HIGHEST,		THREAD_PRIORITY_HIGHEST,		}
};

/////////////////////////////////////////////////
void AVISTREAMINFOtoAVIStreamHeader(AVIStreamHeader_fixed *dest, AVISTREAMINFO *src) {
	dest->fccType			= src->fccType;
	dest->fccHandler		= src->fccHandler;
	dest->dwFlags			= src->dwFlags;
	dest->wPriority			= src->wPriority;
	dest->wLanguage			= src->wLanguage;
	dest->dwInitialFrames	= src->dwInitialFrames;
	dest->dwStart			= src->dwStart;
	dest->dwScale			= src->dwScale;
	dest->dwRate			= src->dwRate;
	dest->dwLength			= src->dwLength;
	dest->dwSuggestedBufferSize = src->dwSuggestedBufferSize;
	dest->dwQuality			= src->dwQuality;
	dest->dwSampleSize		= src->dwSampleSize;
	dest->rcFrame.left		= (short)src->rcFrame.left;
	dest->rcFrame.top		= (short)src->rcFrame.top;
	dest->rcFrame.right		= (short)src->rcFrame.right;
	dest->rcFrame.bottom	= (short)src->rcFrame.bottom;

	_RPT3(0,"scale %ld, rate %ld, length %ld\n",src->dwScale,src->dwRate, src->dwLength);
}
/////////////////////////////////////////////////

extern const char g_szOutOfMemory[];
extern const char g_szError[];
extern BOOL g_syncroBlit, g_vertical;
extern HWND g_hWnd;
extern HINSTANCE g_hInst;
extern bool g_fWine;

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

//==============================//
//=====  MODIFICATION OGM  =====//
//=====    -= Cyrius =-    =====//
//BEGIN ========================//
// Moved Dubber class definition to Dub.h
//END ==========================//

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

IDubber::~IDubber() {
}

IDubber *CreateDubber(DubOptions *xopt) {
	return new Dubber(xopt);
}

Dubber::Dubber(DubOptions *xopt)
	: mFramesDelayedByCodec(0)
	, mFramesPushedToFlushCodec(0)
{
	opt				= xopt;
	aSrc			= NULL;
	a2Src			= NULL;
	vSrc			= NULL;
	pInput			= NULL;

	// clear the workin' variables...

	fError				= false;
	lStopCount			= 0;

	fAbort				= false;
	fUserAbort			= false;

	pVideoPacker		= NULL;
	pStatusHandler		= NULL;

	fADecompressionOk	= false;
	fA2DecompressionOk	= false;
	fVDecompressionOk	= false;
	fFiltersOk			= FALSE;
	timerInterval		= 0;
	timerID				= NULL;

	hEventAbortOk		= INVALID_HANDLE_VALUE;
	lThreadsActive		= 0;
	hThreadMain			= NULL;
	hThreadProcessor	= NULL;
	inputBuffer			= NULL;
	audioBuffer			= NULL;
	audio2Buffer		= NULL;
	inputBufferSize		= 0;
	audioBufferSize		= 0;
	audio2BufferSize		= 0;
//	hFileShared			= INVALID_HANDLE_VALUE;
//	tempBuffer			= NULL;
//	outputBuffer		= NULL;
	pipe				= NULL;
	blitter				= NULL;
	outputDecompressor	= NULL;
	hDDInput			= NULL;
	hDDOutput			= NULL;
	okToDraw			= 0;
	hwndStatus			= NULL;
	vInfo.total_size	= 0;
	aInfo.total_size	= 0;
	a2Info.total_size	= 0;
	vInfo.fAudioOnly	= false;

	hdcCompatInput = hdcCompatOutput = NULL;
	hbmInput = hbmOutput = NULL;
	pdsInput			= NULL;
	pdsOutput			= NULL;

//==============================//
//=====  MODIFICATION OGM  =====//
//=====    -= Cyrius =-    =====//
//BEGIN ========================//
	audioStream = NULL;
//END ==========================//
	audioStreamSource		= NULL;
	audioStreamConverter	= NULL;
	audioStreamResampler	= NULL;
	audioStreamAmplifier	= NULL;
	audioStatusStream		= NULL;
	audioSubsetFilter		= NULL;
	audioCompressor			= NULL;
	audioCorrector			= NULL;


//==============================//
//=====  MODIFICATION OGM  =====//
//=====    -= Cyrius =-    =====//
//BEGIN ========================//
	audio2Stream = NULL;
//END ==========================//
	audio2StreamSource		= NULL;
	audio2StreamConverter	= NULL;
	audio2StreamResampler	= NULL;
	audio2StreamAmplifier	= NULL;
	audio2StatusStream		= NULL;
	audio2SubsetFilter		= NULL;
	audio2Compressor		= NULL;
	audio2Corrector			= NULL;

	inputSubsetActive		= NULL;
	inputSubsetAlloc		= NULL;

	wfexAudioCompressionFormat = NULL;
	wfexAudio2CompressionFormat = NULL;


	inputHisto			= NULL;
	outputHisto			= NULL;

	fPhantom = false;

	pInvTelecine		= NULL;

	i64SegmentSize		= 0;
	i64SegmentCredit	= 0;
	AVIout				= NULL;
	fEnableSpill		= false;

	lSpillVideoPoint	= 0;
	lSpillAudioPoint	= 0;
	lSpillAudio2Point	= 0;
	lSegmentFrameLimit	= 0;
	lSegmentFrameStart	= 0;

	hicOutput = NULL;

	mDSCLastVideoFrame = -1;

	fSyncToAudioEvenClock = false;
	mbAudioFrozen = false;
	mbAudioFrozenValid = false;
}

Dubber::~Dubber() {
	_RPT0(0,"Dubber: destructor called.\n");

	Stop();
}

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

void Dubber::SetAudioCompression(WAVEFORMATEX *wf, LONG cb) {
	wfexAudioCompressionFormat = wf;
	cbAudioCompressionFormat = cb;
}

void Dubber::SetAudio2Compression(WAVEFORMATEX *wf, LONG cb) {
	wfexAudio2CompressionFormat = wf;
	cbAudio2CompressionFormat = cb;
}

void Dubber::SetPhantomVideoMode() {
	fPhantom = true;
	vInfo.fAudioOnly = true;
}

void Dubber::SetInputFile(InputFile *pInput) {
	this->pInput = pInput;
}

void Dubber::SetStatusHandler(IDubStatusHandler *pdsh) {
	pStatusHandler = pdsh;
}


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

struct DubberSetFrameRectangles {
	IDDrawSurface *pDD;
	RECT *pr;

	DubberSetFrameRectangles(IDDrawSurface *_pDD, RECT *_pr) : pDD(_pDD), pr(_pr) {}
};

void Dubber::SetFrameRectangles2(void *pv) {
	DubberSetFrameRectangles *pData = (DubberSetFrameRectangles *)pv;

	pData->pDD->SetOverlayPos(pData->pr);
}

void Dubber::SetFrameRectangles(RECT *prInput, RECT *prOutput) {
	rInputFrame = *prInput;
	rOutputFrame = *prOutput;

	if (g_vertical) {
		rInputHistogram.left	= rInputFrame.right + 6;
		rInputHistogram.top		= rInputFrame.top;
		rOutputHistogram.left	= rOutputFrame.right + 6;
		rOutputHistogram.top	= rOutputFrame.top;
	} else {
		rInputHistogram.left	= rInputFrame.left;
		rInputHistogram.top		= rInputFrame.bottom + 6;
		rOutputHistogram.left	= rOutputFrame.left;
		rOutputHistogram.top	= rOutputFrame.bottom + 6;
	}

	rInputHistogram.right	= rInputHistogram.left + 256;
	rInputHistogram.bottom	= rInputHistogram.top  + 128;

	rOutputHistogram.right	= rOutputHistogram.left + 256;
	rOutputHistogram.bottom	= rOutputHistogram.top  + 128;

	if (pdsInput) {
		RECT r = rInputFrame;

		r.left += x_client;
		r.right += x_client;
		r.top += y_client;
		r.bottom += y_client;

#ifdef ASYNCHRONOUS_OVERLAY_SUPPORT
		blitter->postAFC(0x80000000, SetFrameRectangles2, &DubberSetFrameRectangles(pdsInput, &r));
#else
		pdsInput->SetOverlayPos(&r);
#endif
	}
}

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

struct DubberSetClientRectOffset {
	IDDrawSurface *pDD;
	long x,y;

	DubberSetClientRectOffset(IDDrawSurface *_pDD, long _x, long _y) : pDD(_pDD), x(_x), y(_y) {}
};


void Dubber::SetClientRectOffset2(void *pv) {
	DubberSetClientRectOffset *pData = (DubberSetClientRectOffset *)pv;

	pData->pDD->MoveOverlay(pData->x, pData->y);
}

void Dubber::SetClientRectOffset(int x, int y) {
	x_client = x;
	y_client = y;

	if (pdsInput)
#ifdef ASYNCHRONOUS_OVERLAY_SUPPORT
		blitter->postAFC(0x80000000, SetClientRectOffset2, &DubberSetClientRectOffset(pdsInput, x + rInputFrame.left, y + rInputFrame.top));
#else
		pdsInput->MoveOverlay(x + rInputFrame.left, y + rInputFrame.top);
#endif
}

void Dubber::EnableSpill(const char *pszPrefix, __int64 segsize, long framecnt) {
	pszSegmentPrefix = pszPrefix;
	fEnableSpill = true;
	nSpillSegment = 1;
	i64SegmentThreshold = segsize;
	lSegmentFrameLimit = framecnt;
}

//void InitStreamValuesStatic(DubVideoStreamInfo& vInfo, DubAudioStreamInfo& aInfo, VideoSource *video, AudioSource *audio, DubOptions *opt, FrameSubset *pfs) {
void InitStreamValuesStatic(DubVideoStreamInfo& vInfo, DubAudioStreamInfo& aInfo, DubAudioStreamInfo& a2Info, VideoSource *video, AudioSource *audio, AudioSource *audio2, DubOptions *opt, FrameSubset *pfs) {
	if (!pfs)
		pfs = inputSubset;

	if (video) {

		if (pfs) {
			vInfo.start_src		= 0;
			vInfo.end_src		= pfs->getTotalFrames();
		} else {
			vInfo.start_src		= video->lSampleFirst;
			vInfo.end_src		= video->lSampleLast;
		}
	} else {
		vInfo.start_src		= vInfo.start_dst	= 0;
		vInfo.end_src		= vInfo.end_dst		= 0;
	}

	if (audio) {
		aInfo.start_src		= audio->lSampleFirst;
		aInfo.end_src		= audio->lSampleLast;
	} else {
		aInfo.start_src		= aInfo.start_dst	= 0;
		aInfo.end_src		= aInfo.end_dst		= 0;
	}

	if (audio2) {
		a2Info.start_src	= audio2->lSampleFirst;
		a2Info.end_src		= audio2->lSampleLast;
	} else {
		a2Info.start_src	= a2Info.start_dst	= 0;
		a2Info.end_src		= a2Info.end_dst	= 0;
	}

	vInfo.cur_src			= vInfo.start_src;
	aInfo.cur_src			= aInfo.start_src;
	a2Info.cur_src			= a2Info.start_src;


	if (video) {
		// compute new frame rate

		vInfo.usPerFrame	= MulDivUnsigned(video->streamInfo.dwScale,1000000U,video->streamInfo.dwRate);

		if (opt->video.frameRateNewMicroSecs == DubVideoOptions::FR_SAMELENGTH) {
			if (audio && audio->streamInfo.dwLength)
				vInfo.usPerFrame	= MulDiv(audio->samplesToMs(audio->streamInfo.dwLength),1000,video->streamInfo.dwLength);
		} else if (opt->video.frameRateNewMicroSecs)
			vInfo.usPerFrame	= opt->video.frameRateNewMicroSecs;

		// are we supposed to offset the video?

		if (opt->video.lStartOffsetMS) {
			vInfo.start_src += video->msToSamples(opt->video.lStartOffsetMS); 
		}

		if (opt->video.lEndOffsetMS)
			vInfo.end_src -= video->msToSamples(opt->video.lEndOffsetMS);

		vInfo.usPerFrameIn	= vInfo.usPerFrame;
		vInfo.usPerFrame	*= opt->video.frameRateDecimation;

		// make sure we start reading on a key frame

		if (opt->video.mode == DubVideoOptions::M_NONE)
			vInfo.start_src	= video->nearestKey(vInfo.start_src);

		vInfo.cur_src		= vInfo.start_src;
		vInfo.cur_dst		= vInfo.start_dst;
	}

	if (audio) {
		long lStartOffsetMS = -opt->audio.offset;

		// offset the start of the audio appropriately...

		if (opt->audio.fStartAudio && video && opt->video.lStartOffsetMS) {
			if (!pfs)
				lStartOffsetMS += MulDiv(vInfo.usPerFrame, vInfo.start_src - video->lSampleFirst, 1000*opt->video.frameRateDecimation);
		}

		aInfo.start_src += audio->msToSamples(lStartOffsetMS);

		// clip the end of the audio if supposed to...

		if (opt->audio.fEndAudio) {
			long lMaxLength;

			lMaxLength = (long)((
							(
								(__int64)(vInfo.end_src - vInfo.start_src)
								*
								audio->streamInfo.dwRate
							)
							* vInfo.usPerFrame
						) / ((__int64)audio->streamInfo.dwScale * 1000000 * opt->video.frameRateDecimation));

			if (aInfo.end_src - aInfo.start_src > lMaxLength)
				aInfo.end_src = aInfo.start_src + lMaxLength;
		}

		// resampling audio?

		aInfo.resampling = false;
		aInfo.converting = false;

		if (opt->audio.mode > DubAudioOptions::M_NONE) {
			if (opt->audio.new_rate) {
				aInfo.resampling = true;
			}

			if (opt->audio.newPrecision != DubAudioOptions::P_NOCHANGE || opt->audio.newChannels != DubAudioOptions::C_NOCHANGE) {
				aInfo.converting = true;

				aInfo.is_16bit = opt->audio.newPrecision==DubAudioOptions::P_16BIT
								|| (opt->audio.newPrecision==DubAudioOptions::P_NOCHANGE && audio->getWaveFormat()->wBitsPerSample>8);
				aInfo.is_stereo = opt->audio.newChannels==DubAudioOptions::C_STEREO
								|| (opt->audio.newChannels==DubAudioOptions::C_NOCHANGE && audio->getWaveFormat()->nChannels>1);
				aInfo.is_right = (opt->audio.newChannels==DubAudioOptions::C_MONORIGHT);
				aInfo.single_channel = (opt->audio.newChannels==DubAudioOptions::C_MONOLEFT || opt->audio.newChannels==DubAudioOptions::C_MONORIGHT);
				aInfo.bytesPerSample = (aInfo.is_16bit ? 2 : 1) * (aInfo.is_stereo ? 2 : 1);

			}
		}

		aInfo.cur_src		= audio->nearestKey(aInfo.start_src);
		aInfo.cur_dst		= aInfo.start_dst;
	}


	if (audio2) {
		long lStartOffsetMS = -opt->audio2.offset;

		// offset the start of the audio appropriately...

		if (opt->audio2.fStartAudio && video && opt->video.lStartOffsetMS) {
			if (!pfs)
				lStartOffsetMS += MulDiv(vInfo.usPerFrame, vInfo.start_src - video->lSampleFirst, 1000*opt->video.frameRateDecimation);
		}

		a2Info.start_src += audio2->msToSamples(lStartOffsetMS);

		// clip the end of the audio if supposed to...

		if (opt->audio2.fEndAudio) {
			long lMaxLength;

			lMaxLength = (long)((
							(
								(__int64)(vInfo.end_src - vInfo.start_src)
								*
								audio2->streamInfo.dwRate
							)
							* vInfo.usPerFrame
						) / ((__int64)audio2->streamInfo.dwScale * 1000000 * opt->video.frameRateDecimation));

			if (a2Info.end_src - a2Info.start_src > lMaxLength)
				a2Info.end_src = a2Info.start_src + lMaxLength;
		}

		// resampling audio?

		a2Info.resampling = false;
		a2Info.converting = false;

		if (opt->audio2.mode > DubAudioOptions::M_NONE) {
			if (opt->audio2.new_rate) {
				a2Info.resampling = true;
			}

			if (opt->audio2.newPrecision != DubAudioOptions::P_NOCHANGE || opt->audio2.newChannels != DubAudioOptions::C_NOCHANGE) {
				a2Info.converting = true;

				a2Info.is_16bit = opt->audio2.newPrecision==DubAudioOptions::P_16BIT
								|| (opt->audio2.newPrecision==DubAudioOptions::P_NOCHANGE && audio2->getWaveFormat()->wBitsPerSample>8);
				a2Info.is_stereo = opt->audio2.newChannels==DubAudioOptions::C_STEREO
								|| (opt->audio2.newChannels==DubAudioOptions::C_NOCHANGE && audio2->getWaveFormat()->nChannels>1);
				a2Info.is_right = (opt->audio2.newChannels==DubAudioOptions::C_MONORIGHT);
				a2Info.single_channel = (opt->audio2.newChannels==DubAudioOptions::C_MONOLEFT || opt->audio2.newChannels==DubAudioOptions::C_MONORIGHT);
				a2Info.bytesPerSample = (a2Info.is_16bit ? 2 : 1) * (a2Info.is_stereo ? 2 : 1);

			}
		}

		a2Info.cur_src		= audio2->nearestKey(a2Info.start_src);
		a2Info.cur_dst		= a2Info.start_dst;
	}

	vInfo.cur_proc_src = vInfo.cur_src;
	aInfo.cur_proc_src = aInfo.cur_src;
	a2Info.cur_proc_src = a2Info.cur_src;

	_RPT3(0,"Dub: Audio is from (%ld,%ld) starting at %ld\n", aInfo.start_src, aInfo.end_src, aInfo.cur_src);
	_RPT3(0,"Dub: Audio2 is from (%ld,%ld) starting at %ld\n", a2Info.start_src, a2Info.end_src, a2Info.cur_src);
	_RPT3(0,"Dub: Video is from (%ld,%ld) starting at %ld\n", vInfo.start_src, vInfo.end_src, vInfo.cur_src);
}

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

// may be called at any time in Init() after streams setup

/*void Dubber::InitAudioConversionChain() {

	// ready the audio stream for streaming operation

	aSrc->streamBegin(fPreview);
	fADecompressionOk = true;

	// Initialize audio conversion chain
	//
	// First, create a source.

	if (!(audioStreamSource = new AudioStreamSource(aSrc, aInfo.start_src, aSrc->lSampleLast - aInfo.start_src, opt->audio.mode > DubAudioOptions::M_NONE)))
		throw MyError("Dub: Unable to create audio stream source");

	audioStream = audioStreamSource;

	// Attach a converter if we need to...

	if (aInfo.converting) {
		if (aInfo.single_channel)
			audioStreamConverter = new AudioStreamConverter(audioStream, aInfo.is_16bit, aInfo.is_right, true);
		else
			audioStreamConverter = new AudioStreamConverter(audioStream, aInfo.is_16bit, aInfo.is_stereo, false);

		if (!audioStreamConverter)
			throw MyError("Dub: Unable to create audio stream converter");

		audioStream = audioStreamConverter;
	}

	// Attach a converter if we need to...

	if (aInfo.resampling) {
		if (!(audioStreamResampler = new AudioStreamResampler(audioStream, opt->audio.new_rate ? opt->audio.new_rate : aSrc->getWaveFormat()->nSamplesPerSec, opt->audio.integral_rate, opt->audio.fHighQuality)))
			throw MyError("Dub: Unable to create audio stream resampler");

		audioStream = audioStreamResampler;
	}

	// Attach an amplifier if needed...

	if (opt->audio.mode > DubAudioOptions::M_NONE && opt->audio.volume) {
		if (!(audioStreamAmplifier = new AudioStreamAmplifier(audioStream, opt->audio.volume)))
			throw MyError("Dub: Unable to create audio stream amplifier");

		audioStream = audioStreamAmplifier;
	}

	audioTimingStream = audioStream;

	// Tack on a subset filter as well...

	if (inputSubsetActive) {
		if (!(audioSubsetFilter = new AudioSubset(audioStream, inputSubsetActive, vInfo.usPerFrameIn,
					opt->audio.fStartAudio ? MulDiv(vInfo.usPerFrameIn, vInfo.start_src, 1000) : 0
				)))
			throw MyError("Dub: Unable to create audio subset filter");

		audioStream = audioSubsetFilter;
	}

	// Make sure we only get what we want...

	if (vSrc && opt->audio.fEndAudio)
		audioStream->SetLimit((long)((
			((__int64)(vInfo.end_src - vInfo.start_src) * audioStream->GetFormat()->nAvgBytesPerSec * (__int64)vInfo.usPerFrameIn)) / ((__int64)1000000 * audioStream->GetFormat()->nBlockAlign)
			));

	audioStatusStream = audioStream;

	// Tack on a compressor if we want...

	if (opt->audio.mode > DubAudioOptions::M_NONE && wfexAudioCompressionFormat) {
		if (!(audioCompressor = new AudioCompressor(audioStream, wfexAudioCompressionFormat, cbAudioCompressionFormat)))
			throw MyError("Dub: Unable to create audio compressor");

		audioStream = audioCompressor;
	}

	// Check the output format, and if we're compressing to
	// MPEG Layer III, compensate for the lag and create a bitrate corrector

	if (!fEnableSpill && !g_prefs.fNoCorrectLayer3 && audioCompressor && audioCompressor->GetFormat()->wFormatTag == WAVE_FORMAT_MPEGLAYER3) {

		audioCompressor->CompensateForMP3();

		if (!(audioCorrector = new AudioL3Corrector()))
			throw MyError("Dub: Unable to create audio corrector");
	}

}
*/
void Dubber::InitAudioConversionChain() {

	// ready the audio stream for streaming operation

	aSrc->streamBegin(fPreview);
	fADecompressionOk = true;

	// Initialize audio conversion chain
	//
	// First, create a source.

	if (!(audioStreamSource = new AudioStreamSource(aSrc, aInfo.start_src, aSrc->lSampleLast - aInfo.start_src, opt->audio.mode > DubAudioOptions::M_NONE)))
		throw MyError("Dub: Unable to create audio stream source");

	audioStream = audioStreamSource;

	saveAudioInputMode = audioInputMode;
	if( aSrc->streamInfo.dwScale==1152 ) audioInputMode = AUDIOIN_MP3;
	if( aSrc->streamInfo.dwScale==600 ) audioInputMode = AUDIOIN_OGG;

	if( (audioInputMode==AUDIOIN_MP3) || (audioInputMode==AUDIOIN_OGG) )
	{

		audioTimingStream = audioStream;

		// Tack on a subset filter as well...

		if (inputSubsetActive) {
			if (!(audioSubsetFilter = new AudioSubset(audioStream, inputSubsetActive, vInfo.usPerFrameIn,
						opt->audio.fStartAudio ? MulDiv(vInfo.usPerFrameIn, vInfo.start_src, 1000) : 0
					)))
				throw MyError("Dub: Unable to create audio subset filter");

			audioStream = audioSubsetFilter;
		}

		if (vSrc && opt->audio.fEndAudio)
			audioStream->SetLimit((long)((
					((__int64)audioStream->GetFormat()->nSamplesPerSec * (__int64)vInfo.usPerFrameNoTelecine * (__int64)(vInfo.end_src - vInfo.start_src))
					/
					((__int64)1000000 * (__int64)(aSrc->streamInfo.dwScale))
				)));

		audioStatusStream = audioStream;
	}
	else
	{
		// Attach a converter if we need to...

		if (aInfo.converting) {
			if (aInfo.single_channel)
				audioStreamConverter = new AudioStreamConverter(audioStream, aInfo.is_16bit, aInfo.is_right, true);
			else
				audioStreamConverter = new AudioStreamConverter(audioStream, aInfo.is_16bit, aInfo.is_stereo, false);

			if (!audioStreamConverter)
				throw MyError("Dub: Unable to create audio stream converter");

			audioStream = audioStreamConverter;
		}

		// Attach a converter if we need to...

		if (aInfo.resampling) {
			if (!(audioStreamResampler = new AudioStreamResampler(audioStream, opt->audio.new_rate ? opt->audio.new_rate : aSrc->getWaveFormat()->nSamplesPerSec, opt->audio.integral_rate, opt->audio.fHighQuality)))
				throw MyError("Dub: Unable to create audio stream resampler");

			audioStream = audioStreamResampler;
		}

		// Attach an amplifier if needed...

		if (opt->audio.mode > DubAudioOptions::M_NONE && opt->audio.volume) {
			if (!(audioStreamAmplifier = new AudioStreamAmplifier(audioStream, opt->audio.volume)))
				throw MyError("Dub: Unable to create audio stream amplifier");

			audioStream = audioStreamAmplifier;
		}

		audioTimingStream = audioStream;

		// Tack on a subset filter as well...

		if (inputSubsetActive) {
			if (!(audioSubsetFilter = new AudioSubset(audioStream, inputSubsetActive, vInfo.usPerFrameIn,
						opt->audio.fStartAudio ? MulDiv(vInfo.usPerFrameIn, vInfo.start_src, 1000) : 0
					)))
				throw MyError("Dub: Unable to create audio subset filter");

			audioStream = audioSubsetFilter;
		}

		// Make sure we only get what we want...

		if (vSrc && opt->audio.fEndAudio)
			audioStream->SetLimit((long)((
				((__int64)(vInfo.end_src - vInfo.start_src) * audioStream->GetFormat()->nAvgBytesPerSec * (__int64)vInfo.usPerFrameIn)) / ((__int64)1000000 * audioStream->GetFormat()->nBlockAlign)
				));

		audioStatusStream = audioStream;

		// Tack on a compressor if we want...

		if (opt->audio.mode > DubAudioOptions::M_NONE && wfexAudioCompressionFormat) {
			if (!(audioCompressor = new AudioCompressor(audioStream, wfexAudioCompressionFormat, cbAudioCompressionFormat)))
				throw MyError("Dub: Unable to create audio compressor");

			audioStream = audioCompressor;
		}

		// Check the output format, and if we're compressing to
		// MPEG Layer III, compensate for the lag and create a bitrate corrector

		if (!fEnableSpill && !g_prefs.fNoCorrectLayer3 && audioCompressor && audioCompressor->GetFormat()->wFormatTag == WAVE_FORMAT_MPEGLAYER3) {

			audioCompressor->CompensateForMP3();

			if (!(audioCorrector = new AudioL3Corrector()))
				throw MyError("Dub: Unable to create audio corrector");
		}
	}
}



void Dubber::InitAudio2ConversionChain() {

	// ready the audio stream for streaming operation

	a2Src->streamBegin(fPreview);
	fA2DecompressionOk = true;

	// Initialize audio conversion chain
	//
	// First, create a source.

	if (!(audio2StreamSource = new AudioStreamSource(a2Src, a2Info.start_src, a2Src->lSampleLast - a2Info.start_src, opt->audio2.mode > DubAudioOptions::M_NONE)))
		throw MyError("Dub: Unable to create audio stream source");

	audio2Stream = audio2StreamSource;

	saveAudio2InputMode = audio2InputMode;
	if( a2Src->streamInfo.dwScale==1152 ) audio2InputMode = AUDIOIN_MP3;
	if( a2Src->streamInfo.dwScale==600 ) audio2InputMode = AUDIOIN_OGG;

	if( (audio2InputMode==AUDIOIN_MP3) || (audio2InputMode==AUDIOIN_OGG) )
	{

		audio2TimingStream = audio2Stream;

		// Tack on a subset filter as well...

		if (inputSubsetActive) {
			if (!(audio2SubsetFilter = new AudioSubset(audio2Stream, inputSubsetActive, vInfo.usPerFrameIn,
						opt->audio2.fStartAudio ? MulDiv(vInfo.usPerFrameIn, vInfo.start_src, 1000) : 0
					)))
				throw MyError("Dub: Unable to create audio subset filter");

			audio2Stream = audio2SubsetFilter;
		}

		if (vSrc && opt->audio2.fEndAudio)
			audio2Stream->SetLimit((long)((
					((__int64)audio2Stream->GetFormat()->nSamplesPerSec * (__int64)vInfo.usPerFrameNoTelecine * (__int64)(vInfo.end_src - vInfo.start_src))
					/
					((__int64)1000000 * (__int64)(a2Src->streamInfo.dwScale))
				)));

		audio2StatusStream = audio2Stream;
	}
	else
	{
		// Attach a converter if we need to...

		if (a2Info.converting) {
			if (a2Info.single_channel)
				audio2StreamConverter = new AudioStreamConverter(audio2Stream, a2Info.is_16bit, a2Info.is_right, true);
			else
				audio2StreamConverter = new AudioStreamConverter(audio2Stream, a2Info.is_16bit, a2Info.is_stereo, false);

			if (!audio2StreamConverter)
				throw MyError("Dub: Unable to create audio stream converter");

			audio2Stream = audio2StreamConverter;
		}

		// Attach a converter if we need to...

		if (a2Info.resampling) {
			if (!(audio2StreamResampler = new AudioStreamResampler(audio2Stream, opt->audio2.new_rate ? opt->audio2.new_rate : a2Src->getWaveFormat()->nSamplesPerSec, opt->audio2.integral_rate, opt->audio2.fHighQuality)))
				throw MyError("Dub: Unable to create audio stream resampler");

			audio2Stream = audio2StreamResampler;
		}

		// Attach an amplifier if needed...

		if (opt->audio2.mode > DubAudioOptions::M_NONE && opt->audio2.volume) {
			if (!(audio2StreamAmplifier = new AudioStreamAmplifier(audio2Stream, opt->audio2.volume)))
				throw MyError("Dub: Unable to create audio stream amplifier");

			audio2Stream = audio2StreamAmplifier;
		}

		audio2TimingStream = audio2Stream;

		// Tack on a subset filter as well...

		if (inputSubsetActive) {
			if (!(audio2SubsetFilter = new AudioSubset(audio2Stream, inputSubsetActive, vInfo.usPerFrameIn,
						opt->audio2.fStartAudio ? MulDiv(vInfo.usPerFrameIn, vInfo.start_src, 1000) : 0
					)))
				throw MyError("Dub: Unable to create audio subset filter");

			audio2Stream = audio2SubsetFilter;
		}

		// Make sure we only get what we want...

		if (vSrc && opt->audio2.fEndAudio)
			audio2Stream->SetLimit((long)((
				((__int64)(vInfo.end_src - vInfo.start_src) * audio2Stream->GetFormat()->nAvgBytesPerSec * (__int64)vInfo.usPerFrameIn)) / ((__int64)1000000 * audio2Stream->GetFormat()->nBlockAlign)
				));

		audio2StatusStream = audio2Stream;

		// Tack on a compressor if we want...

		if (opt->audio2.mode > DubAudioOptions::M_NONE && wfexAudio2CompressionFormat) {
			if (!(audio2Compressor = new AudioCompressor(audio2Stream, wfexAudio2CompressionFormat, cbAudio2CompressionFormat)))
				throw MyError("Dub: Unable to create audio compressor");

			audio2Stream = audio2Compressor;
		}

		// Check the output format, and if we're compressing to
		// MPEG Layer III, compensate for the lag and create a bitrate corrector

		if (!fEnableSpill && !g_prefs.fNoCorrectLayer3 && audio2Compressor && audio2Compressor->GetFormat()->wFormatTag == WAVE_FORMAT_MPEGLAYER3) {

			audio2Compressor->CompensateForMP3();

			if (!(audio2Corrector = new AudioL3Corrector()))
				throw MyError("Dub: Unable to create audio corrector");
		}
	}
}



void Dubber::InitOutputFile(char *szFile) {

	// Do audio.
/*
	if (aSrc && AVIout->audioOut) {
		WAVEFORMATEX *outputAudioFormat;
		LONG outputAudioFormatSize;

		// initialize AVI parameters...

		AVISTREAMINFOtoAVIStreamHeader(&AVIout->audioOut->streamInfo, &aSrc->streamInfo);
		AVIout->audioOut->streamInfo.dwStart			= 0;
		AVIout->audioOut->streamInfo.dwInitialFrames	= opt->audio.preload ? 1 : 0;

		if (!(outputAudioFormat = (WAVEFORMATEX *)AVIout->audioOut->allocFormat(outputAudioFormatSize = audioStream->GetFormatLen())))
			throw MyMemoryError();

		memcpy(outputAudioFormat, audioStream->GetFormat(), audioStream->GetFormatLen());

		if (opt->audio.mode > DubAudioOptions::M_NONE) {
			AVIout->audioOut->streamInfo.dwSampleSize = outputAudioFormat->nBlockAlign;
			AVIout->audioOut->streamInfo.dwRate		= outputAudioFormat->nAvgBytesPerSec;
			AVIout->audioOut->streamInfo.dwScale	= outputAudioFormat->nBlockAlign;
			AVIout->audioOut->streamInfo.dwLength	= MulDiv(AVIout->audioOut->streamInfo.dwLength, outputAudioFormat->nSamplesPerSec, aSrc->getWaveFormat()->nSamplesPerSec);
		}
	}
*/
	if (aSrc && AVIout->audioOut) {
		WAVEFORMATEX *outputAudioFormat;
		LONG outputAudioFormatSize;

		if (!(outputAudioFormat = (WAVEFORMATEX *)AVIout->audioOut->allocFormat(outputAudioFormatSize = audioStream->GetFormatLen())))
			throw MyMemoryError();

		memcpy(outputAudioFormat, audioStream->GetFormat(), audioStream->GetFormatLen());

		if( (outputAudioFormat->nBlockAlign==1152)||(outputAudioFormat->nBlockAlign==600) ) {

			// please don't fuck with VBR MP3 or Ogg Vorbis WAVE headers

			AVISTREAMINFOtoAVIStreamHeader(&AVIout->audioOut->streamInfo, &aSrc->streamInfo);
			AVIout->audioOut->streamInfo.dwInitialFrames	= opt->audio.preload ? 1 : 0;

		} else {
			// initialize AVI parameters...

			AVISTREAMINFOtoAVIStreamHeader(&AVIout->audioOut->streamInfo, &aSrc->streamInfo);
			AVIout->audioOut->streamInfo.dwStart			= 0;
			AVIout->audioOut->streamInfo.dwInitialFrames	= opt->audio.preload ? 1 : 0;


			if (opt->audio.mode > DubAudioOptions::M_NONE) {
				AVIout->audioOut->streamInfo.dwSampleSize = outputAudioFormat->nBlockAlign;
				AVIout->audioOut->streamInfo.dwRate		= outputAudioFormat->nAvgBytesPerSec;
				AVIout->audioOut->streamInfo.dwScale	= outputAudioFormat->nBlockAlign;
				AVIout->audioOut->streamInfo.dwLength	= MulDiv(AVIout->audioOut->streamInfo.dwLength, outputAudioFormat->nSamplesPerSec, aSrc->getWaveFormat()->nSamplesPerSec);
			}
		}
	}

	if (a2Src && AVIout->audio2Out) {
		WAVEFORMATEX *outputAudio2Format;
		LONG outputAudio2FormatSize;

		if (!(outputAudio2Format = (WAVEFORMATEX *)AVIout->audio2Out->allocFormat(outputAudio2FormatSize = audio2Stream->GetFormatLen())))
			throw MyMemoryError();

		memcpy(outputAudio2Format, audio2Stream->GetFormat(), audio2Stream->GetFormatLen());

		if( (outputAudio2Format->nBlockAlign==1152)||(outputAudio2Format->nBlockAlign==600) ) {

			// please don't fuck with VBR MP3 or Ogg Vorbis WAVE headers

			AVISTREAMINFOtoAVIStreamHeader(&AVIout->audio2Out->streamInfo, &a2Src->streamInfo);
			AVIout->audio2Out->streamInfo.dwInitialFrames	= opt->audio2.preload ? 1 : 0;

		} else {
			// initialize AVI parameters...

			AVISTREAMINFOtoAVIStreamHeader(&AVIout->audio2Out->streamInfo, &a2Src->streamInfo);
			AVIout->audio2Out->streamInfo.dwStart			= 0;
			AVIout->audio2Out->streamInfo.dwInitialFrames	= opt->audio2.preload ? 1 : 0;


			if (opt->audio2.mode > DubAudioOptions::M_NONE) {
				AVIout->audio2Out->streamInfo.dwSampleSize = outputAudio2Format->nBlockAlign;
				AVIout->audio2Out->streamInfo.dwRate		= outputAudio2Format->nAvgBytesPerSec;
				AVIout->audio2Out->streamInfo.dwScale	= outputAudio2Format->nBlockAlign;
				AVIout->audio2Out->streamInfo.dwLength	= MulDiv(AVIout->audio2Out->streamInfo.dwLength, outputAudio2Format->nSamplesPerSec, a2Src->getWaveFormat()->nSamplesPerSec);
			}
		}
	}

	// Do video.

	if (vSrc && AVIout->videoOut) {
		VBitmap *outputBitmap;
		
		if (opt->video.mode >= DubVideoOptions::M_FULL)
			outputBitmap = filters.OutputBitmap();
		else
			outputBitmap = filters.InputBitmap();

		AVISTREAMINFOtoAVIStreamHeader(&AVIout->videoOut->streamInfo, &vSrc->streamInfo);
		AVIout->videoOut->streamInfo.dwSampleSize = 0;

		if (opt->video.mode > DubVideoOptions::M_NONE) {
			if (fUseVideoCompression) {
				AVIout->videoOut->streamInfo.fccHandler	= compVars->fccHandler;
				AVIout->videoOut->streamInfo.dwQuality	= compVars->lQ;
			} else {
				AVIout->videoOut->streamInfo.fccHandler	= mmioFOURCC('D','I','B',' ');
			}
		}
		if (opt->video.frameRateNewMicroSecs || pInvTelecine) {
			AVIout->videoOut->streamInfo.dwRate		= 1000000U; // / opt->video.frameRateDecimation;
			AVIout->videoOut->streamInfo.dwScale	= vInfo.usPerFrame; //opt->video.frameRateNewMicroSecs;
		} else {

			// Dividing dwRate isn't good if we get a fraction like 10/1!

			if (AVIout->videoOut->streamInfo.dwScale > 0x7FFFFFFFUL / opt->video.frameRateDecimation)
				AVIout->videoOut->streamInfo.dwRate		/= opt->video.frameRateDecimation;
			else
				AVIout->videoOut->streamInfo.dwScale	*= opt->video.frameRateDecimation;
		}
		AVIout->videoOut->streamInfo.dwLength		= vInfo.end_src - vInfo.start_src;
		AVIout->videoOut->streamInfo.dwLength		/= opt->video.frameRateDecimation;

		AVIout->videoOut->streamInfo.rcFrame.left	= 0;
		AVIout->videoOut->streamInfo.rcFrame.top	= 0;
		AVIout->videoOut->streamInfo.rcFrame.right	= (short)outputBitmap->w;
		AVIout->videoOut->streamInfo.rcFrame.bottom	= (short)outputBitmap->h;

		AVIout->videoOut->setCompressed(TRUE);

		// initialize compression

		if (opt->video.mode >= DubVideoOptions::M_FASTREPACK) {
			if (opt->video.mode <= DubVideoOptions::M_SLOWREPACK)
				compressorVideoFormat = (BITMAPINFO *)vSrc->getDecompressedFormat();
			else {
				memset(&compressorVideoDIBFormat, 0, sizeof compressorVideoDIBFormat);
				compressorVideoDIBFormat.bmiHeader.biSize			= sizeof(BITMAPINFOHEADER);
				compressorVideoDIBFormat.bmiHeader.biWidth			= outputBitmap->w;
				compressorVideoDIBFormat.bmiHeader.biHeight			= outputBitmap->h;
				compressorVideoDIBFormat.bmiHeader.biPlanes			= 1;
				compressorVideoDIBFormat.bmiHeader.biBitCount		= iOutputDepth;
				compressorVideoDIBFormat.bmiHeader.biCompression	= BI_RGB;
				compressorVideoDIBFormat.bmiHeader.biSizeImage		= outputBitmap->pitch * outputBitmap->h;

				compressorVideoFormat = &compressorVideoDIBFormat;
			}
		} else {
			compressorVideoFormat = (BITMAPINFO *)vSrc->getImageFormat();
		}

		// Initialize output compressor.

		_RPT0(0,"Dub: Initializing output compressor.\n");

		if (fUseVideoCompression) {
			LONG formatSize;
			DWORD icErr;

			formatSize = ICCompressGetFormatSize(compVars->hic, compressorVideoFormat);
			if (formatSize < ICERR_OK)
				throw "Error getting compressor output format size.";

			_RPT1(0,"Video compression format size: %ld\n",formatSize);

			if (!AVIout->videoOut->allocFormat(formatSize))
				throw MyError("Out of memory");

			memset(AVIout->videoOut->getFormat(), 0, formatSize);

			if (ICERR_OK != (icErr = ICCompressGetFormat(compVars->hic,
								(BITMAPINFOHEADER *)compressorVideoFormat,
								AVIout->videoOut->getImageFormat())))
				throw MyICError("Output compressor",icErr);
				//throw "Error getting compressor output format.";

			if (!(pVideoPacker = new VideoSequenceCompressor()))
				throw MyMemoryError();

			pVideoPacker->init(compVars->hic, compressorVideoFormat, (BITMAPINFO *)AVIout->videoOut->getImageFormat(), compVars->lQ, compVars->lKey);
			pVideoPacker->setDataRate(compVars->lDataRate*1024, vInfo.usPerFrame, vInfo.end_src - vInfo.start_src);
			pVideoPacker->start();

			lVideoSizeEstimate = pVideoPacker->getMaxSize();

			// attempt to open output decompressor

			if (opt->video.mode <= DubVideoOptions::M_FASTREPACK)
				fShowDecompressedFrame = false;
			else if (fShowDecompressedFrame = !!opt->video.fShowDecompressedFrame) {
				DWORD err;

				if (!(outputDecompressor = ICLocate(
							'CDIV',
							AVIout->videoOut->streamInfo.fccHandler,
							AVIout->videoOut->getImageFormat(),
							&compressorVideoFormat->bmiHeader, ICMODE_DECOMPRESS))) {

					MyError("Output video warning: Could not locate output decompressor.").post(NULL,g_szError);

				} else if (ICERR_OK != (err = ICDecompressBegin(
						outputDecompressor,
						AVIout->videoOut->getImageFormat(),
						&compressorVideoFormat->bmiHeader))) {

					MyICError("Output video warning", err).post(NULL,g_szError);

					ICClose(outputDecompressor);
					outputDecompressor = NULL;

					fShowDecompressedFrame = false;
				}
			}

		} else {
			BITMAPINFOHEADER *outputVideoFormat;

			if (opt->video.mode < DubVideoOptions::M_FASTREPACK) {

				if (vSrc->getImageFormat()->biCompression == 0xFFFFFFFF)
					throw MyError("The source video stream uses a compression algorithm which is not compatible with AVI files. "
								"Direct stream copy cannot be used with this video stream.");

				AVIout->videoOut->setCompressed(TRUE);
				if (!(outputVideoFormat = (BITMAPINFOHEADER *)AVIout->videoOut->allocFormat(vSrc->getFormatLen())))
					throw MyMemoryError();

				memcpy(outputVideoFormat, vSrc->getImageFormat(), vSrc->getFormatLen());
			} else {
				if (!(outputVideoFormat = (BITMAPINFOHEADER *)AVIout->videoOut->allocFormat(sizeof(BITMAPINFOHEADER))))
					throw MyMemoryError();

				memcpy(outputVideoFormat, vSrc->getDecompressedFormat(), sizeof(BITMAPINFOHEADER));

				if (opt->video.mode == DubVideoOptions::M_FULL) {
					outputVideoFormat->biCompression= BI_RGB;
					outputVideoFormat->biWidth		= outputBitmap->w;
					outputVideoFormat->biHeight		= outputBitmap->h;
					outputVideoFormat->biBitCount	= iOutputDepth;
					outputVideoFormat->biSizeImage	= outputBitmap->pitch * outputBitmap->h;
				}
				AVIout->videoOut->setCompressed(TRUE);

				lVideoSizeEstimate = outputVideoFormat->biSizeImage;
				lVideoSizeEstimate = (lVideoSizeEstimate+1) & -2;
			}

		}
	}

	_RPT0(0,"Dub: Creating output file.\n");

	if (!AVIout->init(
				szFile,
				AVIout->videoOut ? vSrc ? compressorVideoFormat->bmiHeader.biWidth : 320 : 0, //filters.OutputBitmap()->w,
				AVIout->videoOut ? vSrc ? compressorVideoFormat->bmiHeader.biHeight : 240 : 0, //filters.OutputBitmap()->h,
				!!vSrc,
				!!aSrc,
				!!a2Src,
				opt->perf.outputBufferSize,
				opt->audio.enabled))
		throw MyError("Problem initializing AVI output.");
}

bool Dubber::AttemptInputOverlay(BITMAPINFOHEADER *pbih) {
	if (vSrc->setDecompressedFormat(pbih)) {
		DDSURFACEDESC ddsdOverlay;
		DDPIXELFORMAT ddpf;
		IDirectDrawSurface *lpddsOverlay;
		HRESULT res;

		memset(&ddpf, 0, sizeof ddpf);
		ddpf.dwSize			= sizeof ddpf;
		ddpf.dwFlags		= DDPF_FOURCC;
		ddpf.dwFourCC		= pbih->biCompression;
		ddpf.dwYUVBitCount	= pbih->biBitCount;

		memset(&ddsdOverlay, 0, sizeof ddsdOverlay);
		ddsdOverlay.dwSize = sizeof(ddsdOverlay);
		ddsdOverlay.dwFlags= DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
		ddsdOverlay.ddsCaps.dwCaps = DDSCAPS_OVERLAY | DDSCAPS_VIDEOMEMORY;
		ddsdOverlay.dwWidth  = vSrc->getImageFormat()->biWidth;
		ddsdOverlay.dwHeight = vSrc->getImageFormat()->biHeight;
		ddsdOverlay.ddpfPixelFormat = ddpf;
		
		res = DDrawObtainInterface()->CreateSurface(&ddsdOverlay, &lpddsOverlay, NULL);

		if (DD_OK == res) {
			if (!(pdsInput = CreateDDrawSurface(lpddsOverlay))) {
				lpddsOverlay->Release();
				throw MyMemoryError();
			}

			RECT r = rInputFrame;

			pdsInput->SetOverlayPos(&r);

			return true;
		}
	}

	return false;
}

void Dubber::AttemptInputOverlays() {
	if (DDrawInitialize(g_hWnd)) {
		BITMAPINFOHEADER bih;

		memcpy(&bih, vSrc->getImageFormat(), sizeof(BITMAPINFOHEADER));

		bih.biSize			= sizeof(BITMAPINFOHEADER);
		bih.biPlanes		= 1;
		bih.biBitCount		= 16;
		bih.biSizeImage		= (bih.biWidth+(bih.biWidth&1))*2*bih.biHeight;
		bih.biXPelsPerMeter	= 0;
		bih.biYPelsPerMeter	= 0;
		bih.biClrUsed		= 0;
		bih.biClrImportant	= 0;

		do {
			//---- begin 16-bit YUV negotiation ----

			// Attempt CYUV (YUV 4:2:2, Y?Y? ordering)

			bih.biCompression = 'VUYC';

			if (AttemptInputOverlay(&bih))
				break;

			// Attempt UYVY (YUV 4:2:2)

			bih.biCompression = 'YVYU';

			if (AttemptInputOverlay(&bih))
				break;

			// Attempt YUYV (YUV 4:2:2)

			bih.biCompression = 'VYUY';

			if (AttemptInputOverlay(&bih))
				break;

			// Attempt YUY2 (YUV 4:2:2, YUYV ordering)

			bih.biCompression = '2YUY';

			if (AttemptInputOverlay(&bih))
				break;

			//---- begin 12-bit YUV negotiation ----
#if 0
			// Attempt YV12 (YUV 4:2:0)

			bih.biCompression = '21VY';
			bih.biSizeImage		= (bih.biWidth/2)*(bih.biHeight/2)*6;
			bih.biBitCount		= 12;

			if (AttemptInputOverlay(&bih))
				break;
#endif

			DDrawDeinitialize();
		} while(0);

	}
}

void Dubber::AttemptInputOverlays2(void *pThis) {
	((Dubber *)pThis)->AttemptInputOverlays();
}

bool Dubber::AttemptOutputOverlay() {

	if (!DDrawInitialize(g_hWnd))
		return false;

	// Try and get the pixel format for the primary surface.

	DDPIXELFORMAT ddpf;

	memset(&ddpf, 0, sizeof ddpf);
	ddpf.dwSize		= sizeof ddpf;

	if (DD_OK != DDrawObtainPrimary()->GetPixelFormat(&ddpf))
		return false;

	// Check output pixel format; we can support:
	//
	//	15-bit RGB	00007c00	000003e0	0000001f
	//	16-bit RGB	0000f800	000007e0	0000001f
	//	24-bit RGB	00ff0000	0000ff00	000000ff
	//	32-bit RGB	00ff0000	0000ff00	000000ff

	if (!(ddpf.dwFlags & DDPF_RGB))
		return false;

	const VBitmap *outputBitmap = filters.OutputBitmap();

	memset(&compressorVideoDIBFormat, 0, sizeof compressorVideoDIBFormat);
	bihDisplayFormat.bV4Size			= sizeof(BITMAPINFOHEADER);
	bihDisplayFormat.bV4Width			= outputBitmap->w;
	bihDisplayFormat.bV4Height			= outputBitmap->h;
	bihDisplayFormat.bV4Planes			= 1;
	bihDisplayFormat.bV4BitCount		= (WORD)ddpf.dwRGBBitCount;
	bihDisplayFormat.bV4V4Compression	= BI_RGB;
	bihDisplayFormat.bV4SizeImage		= outputBitmap->pitch * outputBitmap->h;

	switch(ddpf.dwRGBBitCount) {
	case 16:
		if (ddpf.dwRBitMask == 0xf800 && ddpf.dwGBitMask == 0x07e0 && ddpf.dwBBitMask == 0x001f) {
			bihDisplayFormat.bV4Size			= sizeof(BITMAPV4HEADER);
			bihDisplayFormat.bV4V4Compression	= BI_BITFIELDS;
			bihDisplayFormat.bV4RedMask			= 0xf800;
			bihDisplayFormat.bV4GreenMask		= 0x07e0;
			bihDisplayFormat.bV4BlueMask		= 0x001f;
			bihDisplayFormat.bV4AlphaMask		= 0x0000;
			bihDisplayFormat.bV4CSType			= 0;
			fDisplay565 = true;
		} else if (ddpf.dwRBitMask != 0x7c00 || ddpf.dwGBitMask != 0x03e0 || ddpf.dwBBitMask != 0x001f)
			return false;

		break;

	case 24:
	case 32:
		if (ddpf.dwRBitMask != 0x00FF0000) return false;
		if (ddpf.dwGBitMask != 0x0000FF00) return false;
		if (ddpf.dwBBitMask != 0x000000FF) return false;
		break;
	}

	// Create off-screen surface.

	DDSURFACEDESC ddsdOverlay;
	IDirectDrawSurface *lpddsOutput;

	memset(&ddsdOverlay, 0, sizeof ddsdOverlay);
	ddsdOverlay.dwSize			= sizeof(ddsdOverlay);
	ddsdOverlay.dwFlags			= DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
	ddsdOverlay.ddsCaps.dwCaps	= DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY;
	ddsdOverlay.dwWidth			= bihDisplayFormat.bV4Width;
	ddsdOverlay.dwHeight		= bihDisplayFormat.bV4Height;
	ddsdOverlay.ddpfPixelFormat = ddpf;

	if (DD_OK != DDrawObtainInterface()->CreateSurface(&ddsdOverlay, &lpddsOutput, NULL))
		return false;

	if (!(pdsOutput = CreateDDrawSurface(lpddsOutput))) {
		lpddsOutput->Release();
		throw MyMemoryError();
	}

	return true;
}

void Dubber::InitDirectDraw() {

	if (!opt->perf.useDirectDraw)
		return;

	// Should we try and establish a DirectDraw overlay?

#ifdef ASYNCHRONOUS_OVERLAY_SUPPORT
	if (opt->video.mode == DubVideoOptions::M_SLOWREPACK) {
		blitter->postAFC(0x80000000, AttemptInputOverlays2, this);
	}
#else
	AttemptInputOverlays();
#endif

	// How about DirectShow output acceleration?

	if (opt->video.mode == DubVideoOptions::M_FULL)
		AttemptOutputOverlay();

	if (pdsInput || pdsOutput)
		SetClientRectOffset(x_client, y_client);
}

void Dubber::InitDisplay() {
	_RPT0(0,"Dub: Initializing input window display.\n");


	// Check color depth of output device.  If it is 8-bit, we're
	// stuck with DrawDibDraw().  If it's at least 15 bits, then
	// we should create a DIBSection, select it into a memory
	// context, and BltBlt() to the screen instead.  It's about
	// 5-10% faster under Win95 and about 400x (!) faster under WINE.
	//
	// Okay, never mind... WINE still doesn't support DIBSection
	// windows. :(

	int bitsPerPel;

	bitsPerPel = GetDeviceCaps(hDCWindow, BITSPIXEL);

	if (!pdsInput && opt->video.mode > DubVideoOptions::M_FASTREPACK && !g_fWine) {
		if (bitsPerPel < 15 || vSrc->getDecompressedFormat()->biBitCount < 15
				|| !vSrc->getFrameBufferObject()) {

			if (hDDInput = DrawDibOpen()) {
				if (!DrawDibBegin(hDDInput, hDCWindow, vSrc->getDecompressedFormat()->biWidth, vSrc->getDecompressedFormat()->biHeight, vSrc->getDecompressedFormat(), vSrc->getDecompressedFormat()->biWidth, vSrc->getDecompressedFormat()->biHeight, 0)) {
					DrawDibClose(hDDInput);
					hDDInput = NULL;
					_RPT0(0,"Dub WARNING: could not init input video window!\n");
				}
			}
		} else {
			if (!(hdcCompatInput = CreateCompatibleDC(hDCWindow)))
				throw MyError("Couldn't create compatible display context for input window");

			if (!(hbmInput = CreateDIBSection(
					hdcCompatInput,
					(LPBITMAPINFO)vSrc->getDecompressedFormat(),
					DIB_RGB_COLORS,
					&lpvInput,
					vSrc->getFrameBufferObject(),
					vSrc->getFrameBufferOffset()
				)))
				throw MyError("Couldn't create DIB section for input window");

			hbmInputOld = (HBITMAP)SelectObject(hdcCompatInput, hbmInput);
		}
	}

	_RPT0(0,"Dub: Initializing output window display.\n");
	if (opt->video.mode == DubVideoOptions::M_FULL) {
		if (!pdsOutput && !g_fWine) {
			if (bitsPerPel < 15) {
				if (hDDOutput = DrawDibOpen()) {
					if (!DrawDibBegin(
								hDDOutput,
								hDCWindow,
								compressorVideoFormat->bmiHeader.biWidth,
								compressorVideoFormat->bmiHeader.biHeight,
								&compressorVideoFormat->bmiHeader,
								compressorVideoFormat->bmiHeader.biWidth,
								compressorVideoFormat->bmiHeader.biHeight,
								0)) {

						DrawDibClose(hDDOutput);
						hDDOutput = NULL;
					}
				}
			} else {
				if (!(hdcCompatOutput = CreateCompatibleDC(hDCWindow)))
					throw MyError("Couldn't create compatible display context for output window");

				// check to see if DC is 565 16-bit, the only mode that does not support a line
				// of grays... hmm... is 15 possible for bitsPerPel?

				COLORREF crTmp;

				fDisplay565 = false;

				if (bitsPerPel==15 || bitsPerPel==16) {
					crTmp = GetPixel(hDCWindow, 0,0);
					SetPixel(hDCWindow,0,0,RGB(0x80, 0x88, 0x80));

					if (GetPixel(hDCWindow,0,0) == RGB(0x80, 0x88, 0x80)) {
						fDisplay565 = true;

						_RPT0(0,"Display is 5-6-5 16-bit\n");
					}
					SetPixel(hDCWindow, 0, 0, crTmp);
				}

				memcpy(&bihDisplayFormat, compressorVideoFormat, sizeof(BITMAPINFOHEADER));
				if (fDisplay565 && fPreview && bihDisplayFormat.bV4BitCount == 16) {
					bihDisplayFormat.bV4Size			= sizeof(BITMAPV4HEADER);
					bihDisplayFormat.bV4V4Compression	= BI_BITFIELDS;
					bihDisplayFormat.bV4RedMask			= 0xf800;
					bihDisplayFormat.bV4GreenMask		= 0x07e0;
					bihDisplayFormat.bV4BlueMask		= 0x001f;
					bihDisplayFormat.bV4AlphaMask		= 0x0000;
					bihDisplayFormat.bV4CSType			= 0;
				}

				HANDLE hMapObject;
				LONG lMapOffset;

				filters.getOutputMappingParams(hMapObject, lMapOffset);

				if (!(hbmOutput = CreateDIBSection(
						hdcCompatOutput,
						(LPBITMAPINFO)&bihDisplayFormat,
						DIB_RGB_COLORS,
						&lpvOutput,
						hMapObject,
						lMapOffset
					)))
					throw MyError("Couldn't create DIB section for output window");

				hbmOutputOld = (HBITMAP)SelectObject(hdcCompatOutput, hbmOutput);
			}

			// attempt to open output decompressor
#if 0
			ICINFO info;

			for(int i=0; ICInfo(ICTYPE_VIDEO, i, &info); i++) {
				char szName[256];

				hicOutput = ICOpen(ICTYPE_VIDEO, info.fccHandler, ICMODE_DRAW);
				if (!hicOutput)
					continue;

				ICGetInfo(hicOutput, &info, sizeof info);

				WideCharToMultiByte(CP_ACP, 0, info.szDescription, -1, szName, sizeof szName, NULL, NULL);

				if (strstr(szName, "miroVIDEO")) {

					if (hicOutput) {
						ICDrawBegin(hicOutput, ICDRAW_HDC, NULL, g_hWnd, hDCWindow, 0, 0,
								compressorVideoFormat->bmiHeader.biWidth,
								compressorVideoFormat->bmiHeader.biHeight,
								AVIout->videoOut->getImageFormat(),
								0,
								0,
								compressorVideoFormat->bmiHeader.biWidth,
								compressorVideoFormat->bmiHeader.biHeight,
								1000000,
								vInfo.usPerFrame);

						ICDrawStart(hicOutput);
					}

					break;
				}

				ICClose(hicOutput);
				hicOutput = NULL;
			}
#endif
		}

		if (opt->video.fHistogram) {
			inputHisto = new Histogram(hDCWindow, 128);
			outputHisto = new Histogram(hDCWindow, 128);
		}
	}

}

bool Dubber::NegotiateFastFormat(BITMAPINFOHEADER *pbih) {
	return vSrc->setDecompressedFormat(pbih) &&
			ICERR_OK == ICCompressQuery(compVars->hic, pbih, NULL);
}

bool Dubber::NegotiateFastFormat(int depth) {
	return vSrc->setDecompressedFormat(depth) &&
			ICERR_OK == ICCompressQuery(compVars->hic, vSrc->getDecompressedFormat(), NULL);
}

void Dubber::InitSelectInputFormat() {
	//	DIRECT:			Don't care.
	//	FASTREPACK:		Negotiate with output compressor.
	//	SLOWREPACK:		[Dub]		Use selected format.
	//					[Preview]	Negotiate with display driver.
	//	FULL:			Use selected format.

	if (opt->video.mode == DubVideoOptions::M_NONE)
		return;

	if (opt->video.mode == DubVideoOptions::M_FASTREPACK && fUseVideoCompression) {
		BITMAPINFOHEADER bih;

		// Begin decompressor to compressor format negotiation.
		//

		// Attempt IF09.

#if 0
		int blocks;

		memcpy(&bih, vSrc->getImageFormat(), sizeof(BITMAPINFOHEADER));

		blocks = ((bih.biWidth+3)/4)*((bih.biHeight+3)/4);

		bih.biSize			= sizeof(BITMAPINFOHEADER);
		bih.biPlanes		= 3;
		bih.biBitCount		= 9;		// does it matter?
		bih.biCompression	= '90FI';
		bih.biSizeImage		= blocks*(16 + 2) + ((blocks+31)/32)*4;
		bih.biXPelsPerMeter	= 0;
		bih.biYPelsPerMeter	= 0;
		bih.biClrUsed		= 0;
		bih.biClrImportant	= 0;

		if (NegotiateFastFormat(&bih))
			return;
#endif

		memcpy(&bih, vSrc->getImageFormat(), sizeof(BITMAPINFOHEADER));

        bih.biSize          = sizeof(BITMAPINFOHEADER);
        bih.biPlanes        = 1;
        bih.biSizeImage     = ((bih.biWidth+3)&(~3))*bih.biHeight;
		bih.biSizeImage += bih.biSizeImage>>1;
        bih.biXPelsPerMeter = 0;
        bih.biYPelsPerMeter = 0;
        bih.biClrUsed       = 0;
        bih.biClrImportant  = 0;

        // Attempt YV12.
        
        bih.biCompression   = '21VY';
        bih.biBitCount      = 12;
        bih.biSizeImage     = ((bih.biWidth+(bih.biWidth&1))*(bih.biHeight+(bih.biHeight&1))*3)/2;

        if (NegotiateFastFormat(&bih))
            return;

        // Attempt UYVY.
        bih.biBitCount      = 16;
        bih.biCompression   = 'YVYU';
        bih.biSizeImage     = (bih.biWidth+(bih.biWidth&1))*2*bih.biHeight;
    
    if (NegotiateFastFormat(&bih))
            return;

        // Attempt YUYV.


		bih.biCompression	= 'VYUY';

		if (NegotiateFastFormat(&bih))
			return;


		// Attempt YV12.
		
		/*bih.biCompression	= '21VY';

		if (NegotiateFastFormat(&bih))
			return;*/


		// Attempt YUY2.
		
		bih.biCompression	= '2YUY';

		if (NegotiateFastFormat(&bih))
			return;

		// Attempt RGB format negotiation.

		if (NegotiateFastFormat(16+8*opt->video.inputDepth))
			return;

		if (NegotiateFastFormat(24))
			return;
		if (NegotiateFastFormat(32))
			return;
		if (NegotiateFastFormat(16))
			return;
		if (NegotiateFastFormat(8))
			return;

		throw MyError("Video format negotiation failed: use slow-repack or full mode.");
	}

	// Negotiate RGB format.

	if (!vSrc->setDecompressedFormat(16+8*opt->video.inputDepth))
		if (!vSrc->setDecompressedFormat(32))
			if (!vSrc->setDecompressedFormat(24))
				if (!vSrc->setDecompressedFormat(16))
					if (!vSrc->setDecompressedFormat(8))
						throw MyError("The decompression codec cannot decompress to an RGB format. This is very unusual. Check that any \"Force YUY2\" options are not enabled in the codec's properties.");
}

//void Dubber::Init(VideoSource *video, AudioSource *audio, AVIOutput *out, char *szFile, HDC hDC, COMPVARS *videoCompVars) {
void Dubber::Init(VideoSource *video, AudioSource *audio, AudioSource *audio2, AVIOutput *out, char *szFile, HDC hDC, COMPVARS *videoCompVars) {

	aSrc				= audio;
	a2Src				= audio2;
	vSrc				= video;
	AVIout				= out;

	fPreview			= !!AVIout->isPreview();

	compVars			= videoCompVars;
	hDCWindow			= hDC;
	fUseVideoCompression = !fPreview && opt->video.mode>DubVideoOptions::M_NONE && compVars && (compVars->dwFlags & ICMF_COMPVARS_VALID) && compVars->hic;
//	fUseVideoCompression = opt->video.mode>DubVideoOptions::M_NONE && compVars && (compVars->dwFlags & ICMF_COMPVARS_VALID) && compVars->hic;

	// check the mode; if we're using DirectStreamCopy mode, we'll need to
	// align the subset to keyframe boundaries!

	if (vSrc && inputSubset) {
		inputSubsetActive = inputSubset;

		if (opt->video.mode == DubVideoOptions::M_NONE) {
			FrameSubsetNode *pfsn;

			if (!(inputSubsetActive = inputSubsetAlloc = new FrameSubset()))
				throw MyMemoryError();

			pfsn = inputSubset->getFirstFrame();
			while(pfsn) {
				long end = pfsn->start + pfsn->len;
				long start = vSrc->nearestKey(pfsn->start + vSrc->lSampleFirst) - vSrc->lSampleFirst;

				_RPT3(0,"   subset: %5d[%5d]-%-5d\n", pfsn->start, start, pfsn->start+pfsn->len-1);
				inputSubsetActive->addRange(pfsn->start, pfsn->len, pfsn->bMask);

				// Mask ranges never need to be extended backwards, because they don't hold any
				// data of their own.  If an include range needs to be extended backwards, though,
				// it may need to extend into a previous merge range.  To avoid this problem,
				// we do a delete of the range before adding the tail.

				if (!pfsn->bMask) {
					if (start < pfsn->start) {
						inputSubsetActive->deleteInputRange(start, pfsn->start-start);
						inputSubsetActive->addRangeMerge(start, pfsn->start-start, false);
					}
				}

				pfsn = inputSubset->getNextFrame(pfsn);
			}

#ifdef _DEBUG
			pfsn = inputSubsetActive->getFirstFrame();

			while(pfsn) {
				_RPT2(0,"   padded subset: %8d-%-8d\n", pfsn->start, pfsn->start+pfsn->len-1);
				pfsn = inputSubsetActive->getNextFrame(pfsn);
			}
#endif
		}
	}

	// initialize stream values

	//InitStreamValuesStatic(vInfo, aInfo, video, audio, opt, inputSubsetActive);
	InitStreamValuesStatic(vInfo, aInfo, a2Info, video, audio, audio2, opt, inputSubsetActive);

	vInfo.nLag = 0;
	vInfo.usPerFrameNoTelecine = vInfo.usPerFrame;
	if (opt->video.mode >= DubVideoOptions::M_FULL && opt->video.fInvTelecine) {
		vInfo.usPerFrame = MulDiv(vInfo.usPerFrame, 30, 24);
	}

	lSpillVideoOk = vInfo.cur_src;
	lSpillAudioOk = aInfo.cur_src;
	lSpillAudio2Ok = a2Info.cur_src;

	_RPT0(0,"Dub: Initializing AVI output.\n");

	if (!(AVIout->initOutputStreams())) throw MyError("Out of memory");

	_RPT0(0,"Dub: Creating blitter.\n");

	if (g_syncroBlit || !fPreview)
		blitter = new AsyncBlitter();
	else
		blitter = new AsyncBlitter(8);

	if (!blitter) throw MyError("Couldn't create AsyncBlitter");

	blitter->pulse();

	// Select an appropriate input format.  This is really tricky...

	vInfo.fAudioOnly = true;
	if (vSrc && AVIout->videoOut) {
		InitSelectInputFormat();
		vInfo.fAudioOnly = false;
	}

	iOutputDepth = 16+8*opt->video.outputDepth;

	// Initialize filter system.

	nVideoLag = nVideoLagNoTelecine = 0;

	if (vSrc) {
		BITMAPINFOHEADER *bmih = vSrc->getDecompressedFormat();

		filters.initLinearChain(&g_listFA, (Pixel *)(bmih+1), bmih->biWidth, bmih->biHeight, 32 /*bmih->biBitCount*/, iOutputDepth);

		fsi.lMicrosecsPerFrame		= vInfo.usPerFrame;
		fsi.lMicrosecsPerSrcFrame	= vInfo.usPerFrameIn;
		fsi.lCurrentFrame			= 0;

		if (filters.ReadyFilters(&fsi))
			throw "Error readying filters.";

		fFiltersOk = TRUE;

		nVideoLagNoTelecine = nVideoLag = filters.getFrameLag();

		// Inverse telecine?

		if (opt->video.mode >= DubVideoOptions::M_FULL && opt->video.fInvTelecine) {
			if (!(pInvTelecine = CreateVideoTelecineRemover(filters.InputBitmap(), !opt->video.fIVTCMode, opt->video.nIVTCOffset, opt->video.fIVTCPolarity)))
				throw MyMemoryError();

			nVideoLag += 10;
		}
	}

	nVideoLagPreload = nVideoLagNoTelecine;
	vInfo.nLag = nVideoLag;

	// initialize directdraw display if in preview

	if (fPreview)
		InitDirectDraw();

	// initialize input decompressor

	if (vSrc && AVIout->videoOut) {

		_RPT0(0,"Dub: Initializing input decompressor.\n");

		vSrc->streamBegin(fPreview);
		fVDecompressionOk = TRUE;

	}

	// Initialize audio.

	_RPT0(0,"Dub: Initializing audio.\n");

	if (aSrc)
		InitAudioConversionChain();
	if (a2Src)
		InitAudio2ConversionChain();
	else
		audio2Stream = NULL;

	// Initialize output file.

	InitOutputFile(szFile);

	// Initialize input window display.

	if (vSrc && AVIout->videoOut)
		InitDisplay();

	// Allocate input buffer.

	if (!(inputBuffer = allocmem(inputBufferSize = 65536)))
		throw MyMemoryError();

	// Create a pipe.

	_RPT0(0,"Dub: Creating data pipe.\n");

	if (!(pipe = new AVIPipe(opt->perf.pipeBufferCount, 16384)) || !pipe->isOkay())
		throw MyError("Couldn't create pipe");

	// Create events.

	_RPT0(0,"Dub: Creating events.\n");

	if (!(hEventAbortOk = CreateEvent(NULL,FALSE,FALSE,NULL)))
		throw MyError("Couldn't create abort event");
}

void Dubber::Go(int iPriority) {
	OSVERSIONINFO ovi;

	// check the version.  if NT, don't touch the processing priority!

	ovi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

	fNoProcessingPriority = GetVersionEx(&ovi) && ovi.dwPlatformId == VER_PLATFORM_WIN32_NT;

	if (!iPriority)
		iPriority = fNoProcessingPriority || !AVIout->isPreview() ? 5 : 6;

	this->iPriority = iPriority;

	// Reset timer.

	_XRPT0(0,"Dub: Starting multimedia timer.\n");

	if (fPreview) {
//		timerInterval = MulDiv(AVIout->videoOut->streamInfo.dwScale, 1000L, AVIout->videoOut->streamInfo.dwRate);
		timerInterval = vInfo.usPerFrame / 1000;

		if (opt->video.fSyncToAudio || opt->video.nPreviewFieldMode) {
//			blitter->setPulseCallback(PulseCallbackProc, this);

			timerInterval /= 2;
		}

//		timerInterval /= 2;

		if (TIMERR_NOERROR != timeBeginPeriod(timerInterval)) {
			timerInterval = 0;
			throw MyError("Couldn't initialize timer!");
		}

		timer_counter = 0;
		timer_period = timerInterval;

		if (!(timerID = timeSetEvent(timerInterval, timerInterval, DubFrameTimerProc, (DWORD)this, TIME_PERIODIC)))
			throw MyError("Couldn't start timer!");

	}

	// Initialize threads.

	_XRPT0(0,"Dub: Kickstarting threads.\n");

	if (!(hThreadProcessor = (HANDLE)_beginthread(ProcessingThreadKickstart, 0, (void *)this)))
		throw MyError("Couldn't create processing thread");

//	if (fPreview && !fNoProcessingPriority)
	SetThreadPriority(hThreadProcessor, g_iPriorities[iPriority-1][0]);

	// Wait for the thread count to increment to 1.  Otherwise, it's possible for
	// one of the later initialization events to fail, and the end() function to
	// be called before the first thread is even scheduled.  If this happens,
	// the thread will be left dangling.

	while(!lThreadsActive) Sleep(100);

	// Continue with other threads.

	if (!(hThreadMain = (HANDLE)_beginthread(MainThreadKickstart, 0, (void *)this)))
		throw "Couldn't create main dubbing thread";

	SetThreadPriority(hThreadMain, g_iPriorities[iPriority-1][1]);

	// Create status window during the dub.

	_XRPT0(0,"Dub: Creating status window.\n");

	pStatusHandler->InitLinks(&aInfo, &a2Info, &vInfo, aSrc, a2Src, vSrc, pInput, audioStatusStream, audio2StatusStream, this, opt);

	if (hwndStatus = pStatusHandler->Display(NULL, iPriority)) {
		MSG msg;

		// NOTE: WM_QUIT messages seem to get blocked if the window is dragging/sizing
		//		 or has a menu.

		DEFINE_SP(sp);

		while (!fAbort && GetMessage(&msg, (HWND) NULL, 0, 0)) { 

			CHECK_STACK(sp);

			if (guiCheckDialogs(&msg)) continue;
			if (!IsWindow(hwndStatus) || !IsDialogMessage(hwndStatus, &msg)) { 
				TranslateMessage(&msg); 
			    DispatchMessage(&msg); 
			}
	    }

	}

	Stop();

	if (fError)
		throw err;

	pStatusHandler->SetLastPosition(vInfo.cur_proc_src);
//	if (positionCallback)
//		positionCallback(vInfo.start_src, vInfo.cur_proc_src < vInfo.start_src ? vInfo.start_src : vInfo.cur_proc_src > vInfo.end_src ? vInfo.end_src : vInfo.cur_proc_src, vInfo.end_src);

	_RPT0(0,"Dub: exit.\n");
}

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

static void DestroyIDDrawSurface(void *pv) {
	delete (IDDrawSurface *)pv;
	DDrawDeinitialize();
}

void Dubber::Stop() {
	bool fSkipDXShutdown = false;

	if (InterlockedExchange(&lStopCount, 1))
		return;

	_XRPT0(0,"Dub: Beginning stop process.\n");

	if (pipe)
		pipe->abort();

	if (blitter)
		blitter->flush();

	_XRPT0(0,"Dub: Killing threads.\n"); DEBUG_SLEEP;

	fAbort = TRUE;
	while(lThreadsActive) {
		DWORD dwRes;

		dwRes = MsgWaitForMultipleObjects(1, &hEventAbortOk, FALSE, 10000, QS_ALLINPUT);

		if (WAIT_OBJECT_0+1 == dwRes)
			guiDlgMessageLoop(hwndStatus);
		else if (WAIT_TIMEOUT == dwRes) {
			MessageBox(g_hWnd, "Thread deadlock detected attempting to abort - killing process.", "VirtualDub Internal Error", MB_ICONEXCLAMATION|MB_OK);
			ExitProcess(0);
		}

_XRPT0(0,"\tDub: Threads still active\n");

		_RPT1(0,"\tDub: %ld threads active\n", lThreadsActive);

#ifdef _DEBUG
		if (blitter) _RPT1(0,"\t\tBlitter locks active: %08lx\n", blitter->lock_state);
#endif
	}

	if (blitter)
		blitter->flush();

	_XRPT0(0,"Dub: Freezing status handler.\n"); DEBUG_SLEEP;

	if (pStatusHandler)
		pStatusHandler->Freeze();

	_XRPT0(0,"Dub: Killing timers.\n"); DEBUG_SLEEP;

	if (timerID)		{ timeKillEvent(timerID);		timerID = NULL; }
	if (timerInterval)	{ timeEndPeriod(timerInterval);	timerInterval = NULL; }

	if (hicOutput) {
		ICDrawStop(hicOutput);
		ICDrawFlush(hicOutput);
		ICDrawEnd(hicOutput);
		ICClose(hicOutput);
		hicOutput = NULL;
	}

	if (pVideoPacker) {
		_RPT0(0,"Dub: Ending frame compression.\n");

		delete pVideoPacker;

		pVideoPacker = NULL;
	}

	if (pdsInput) {
		_XRPT0(0,"Dub: Destroying input overlay.\n");

#ifdef ASYNCHRONOUS_OVERLAY_SUPPORT
		blitter->postAFC(0x80000000, DestroyIDDrawSurface, (void *)pdsInput);
		pdsInput = NULL;
		fSkipDXShutdown = true;
#else
		delete pdsInput;
		pdsInput = NULL;
#endif
	}

	_XRPT0(0,"Dub: Deallocating resources.\n"); DEBUG_SLEEP;

	if (pipe)			{ delete pipe; pipe = NULL; }
	if (blitter)		{ delete blitter; blitter=NULL; }

	GdiFlush();

	filters.DeinitFilters();

	if (fVDecompressionOk)	{ vSrc->streamEnd(); }
	if (fADecompressionOk)	{ aSrc->streamEnd(); }
	if (fA2DecompressionOk)	{ a2Src->streamEnd(); }

	if (hEventAbortOk)	{ CloseHandle(hEventAbortOk); hEventAbortOk = NULL; }
	if (inputBuffer)	{ freemem(inputBuffer); inputBuffer = NULL; }
	if (audioBuffer)	{ freemem(audioBuffer); audioBuffer = NULL; }
	if (audio2Buffer)	{ freemem(audio2Buffer); audio2Buffer = NULL; }

	if (audioCorrector)			{ delete audioCorrector; audioCorrector = NULL; }
	if (audioCompressor)		{ delete audioCompressor; audioCompressor = NULL; }
	if (audioSubsetFilter)		{ delete audioSubsetFilter; audioSubsetFilter = NULL; }
	if (audioStreamAmplifier)	{ delete audioStreamAmplifier; audioStreamAmplifier = NULL; }
	if (audioStreamResampler)	{ delete audioStreamResampler; audioStreamResampler = NULL; }
	if (audioStreamConverter)	{ delete audioStreamConverter; audioStreamConverter = NULL; }
	if (audioStreamSource)		{ delete audioStreamSource; audioStreamSource = NULL; }

	if (audio2Corrector)		{ delete audio2Corrector; audio2Corrector = NULL; }
	if (audio2Compressor)		{ delete audio2Compressor; audio2Compressor = NULL; }
	if (audio2SubsetFilter)		{ delete audio2SubsetFilter; audio2SubsetFilter = NULL; }
	if (audio2StreamAmplifier)	{ delete audio2StreamAmplifier; audio2StreamAmplifier = NULL; }
	if (audio2StreamResampler)	{ delete audio2StreamResampler; audio2StreamResampler = NULL; }
	if (audio2StreamConverter)	{ delete audio2StreamConverter; audio2StreamConverter = NULL; }
	if (audio2StreamSource)		{ delete audio2StreamSource; audio2StreamSource = NULL; }

	if (inputSubsetAlloc)		{ delete inputSubsetAlloc; inputSubsetAlloc = NULL; }

	_XRPT0(0,"Dub: Releasing display elements.\n"); DEBUG_SLEEP;

	if (inputHisto)				{ delete inputHisto; inputHisto = NULL; }
	if (outputHisto)			{ delete outputHisto; outputHisto = NULL; }

	if (hDDInput)				{ DrawDibClose(hDDInput); hDDInput = NULL; }
	if (hDDOutput)				{ DrawDibClose(hDDOutput); hDDOutput = NULL; }

	// deinitialize DirectDraw

	_XRPT0(0,"Dub: Deinitializing DirectDraw.\n");

	if (pdsOutput)	delete pdsOutput;	pdsOutput = NULL;

	if (!fSkipDXShutdown)	DDrawDeinitialize();

	// A pile of **** to Microsoft for a buggy CreateDIBSection().
	//
	// Seems that if you provide a handle to a file mapping section with
	// an offset >=64K under NT4, it fails to unmap the view, resulting
	// in a memory leak if you don't do it yourself...
	//
	// This bug was fixed in Windows 98 and NT5.

	if (hbmInput) {
		SelectObject(hdcCompatInput, hbmInputOld);
		DeleteObject(hbmInput);
		UnmapViewOfFile(lpvInput);
		hbmInput = NULL;
	}
	if (hbmOutput) {
		SelectObject(hdcCompatOutput, hbmOutputOld);
		DeleteObject(hbmOutput);
		UnmapViewOfFile(lpvOutput);
		hbmOutput = NULL;
	}

	if (hdcCompatInput)			{ DeleteDC(hdcCompatInput); hdcCompatInput = NULL; }
	if (hdcCompatOutput)		{ DeleteDC(hdcCompatOutput); hdcCompatOutput = NULL; }

	filters.DeallocateBuffers();
	
	delete pInvTelecine;	pInvTelecine = NULL;

	if (outputDecompressor)	{
		ICDecompressEnd(outputDecompressor);
		ICClose(outputDecompressor);
		outputDecompressor = NULL;
	}

	if (AVIout) {
		delete AVIout;
		AVIout = NULL;
	}

	if( aSrc ) audioInputMode = saveAudioInputMode;
	if( a2Src ) audio2InputMode = saveAudio2InputMode;


	//_RPT0(0,"Dub: Stop complete.\n");
//	OutputDebugString("Dub: Stop complete.\n");

#ifdef STOP_SPEED_DEBUGGING
	__asm {
		rdtsc
		mov dword ptr stop_time+0,eax
		mov dword ptr stop_time+4,edx
	}

	{
		char buf[128];

		wsprintf(buf, "braking time: %d ms\n", (int)((stop_time - start_time)/300000000));
		OutputDebugString(buf);
	}
#endif
}

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

static long g_lPulseClock;

void CALLBACK Dubber::DubFrameTimerProc(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2) {
	Dubber *thisPtr = (Dubber *)dwUser;

	if (thisPtr->opt->video.fSyncToAudio) {
		long lActualPoint;
		AVIAudioPreviewOutputStream *pAudioOut = (AVIAudioPreviewOutputStream *)thisPtr->AVIout->audioOut;

		lActualPoint = pAudioOut->getPosition();

		if (!pAudioOut->isFrozen()) {
			if (thisPtr->opt->video.nPreviewFieldMode) {
				g_lPulseClock = MulDiv(lActualPoint, 2000, thisPtr->vInfo.usPerFrame);

				g_lPulseClock += thisPtr->nVideoLagNoTelecine*2;
			} else {
				g_lPulseClock = MulDiv(lActualPoint, 1000, thisPtr->vInfo.usPerFrame);

				g_lPulseClock += thisPtr->nVideoLagNoTelecine;
			}

			if (g_lPulseClock<0)
				g_lPulseClock = 0;

			if (lActualPoint != -1) {
				thisPtr->blitter->setPulseClock(g_lPulseClock);
				thisPtr->fSyncToAudioEvenClock = false;
				thisPtr->mbAudioFrozen = false;
				return;
			}
		}

		// Hmm... we have no clock!

		thisPtr->mbAudioFrozen = true;

		if (thisPtr->fSyncToAudioEvenClock || thisPtr->opt->video.nPreviewFieldMode) {
			if (thisPtr->blitter) {
				thisPtr->blitter->pulse();
			}
		}

		thisPtr->fSyncToAudioEvenClock = !thisPtr->fSyncToAudioEvenClock;

		return;
	}


	if (thisPtr->blitter) thisPtr->blitter->pulse();

#if 0
	if (thisPtr->blitter) {
		if (timeGetTime() - thisPtr->timer_counter >= thisPtr->timer_period) {
			thisPtr->timer_counter += thisPtr->timer_period;
			thisPtr->blitter->pulse();
		}
	}
#endif
}

int Dubber::PulseCallbackProc(void *_thisPtr, DWORD framenum) {
#if 1
	return AsyncBlitter::PCR_OKAY;
#else
	Dubber *thisPtr = (Dubber *)_thisPtr;
	long lAudioPoint1, lAudioPoint2, lActualPoint;

	lAudioPoint1 = MulDiv(framenum, thisPtr->vInfo.usPerFrame, 1000);
	lAudioPoint2 = MulDiv(framenum+3, thisPtr->vInfo.usPerFrame, 1000);

	lActualPoint = ((AVIAudioPreviewOutputStream *)thisPtr->AVIout->audioOut)->getPosition();

	if (lActualPoint <= 0) return AsyncBlitter::PCR_OKAY;

//	_RPT3(0,"%d %d %d\n", lAudioPoint1, lActualPoint, lAudioPoint2);

	if      (lActualPoint <  lAudioPoint1)	return AsyncBlitter::PCR_WAIT;
//	else if (lActualPoint >= lAudioPoint2)	return AsyncBlitter::PCR_NOBLIT;
	else									return AsyncBlitter::PCR_OKAY;
#endif
}

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

void Dubber::ResizeInputBuffer(long bufsize) {
	inputBufferSize = (bufsize+65535) & 0xFFFF0000L;

	if (!(inputBuffer = reallocmem(inputBuffer, inputBufferSize)))
		throw "Error reallocating input buffer.\n";
}

void Dubber::ResizeAudioBuffer(long bufsize) {
	audioBufferSize = (bufsize+65535) & 0xFFFF0000L;

	if (!(audioBuffer = reallocmem(audioBuffer, audioBufferSize)))
		throw "Error reallocating audio buffer.\n";
}

void Dubber::ResizeAudio2Buffer(long bufsize) {
	audio2BufferSize = (bufsize+65535) & 0xFFFF0000L;

	if (!(audio2Buffer = reallocmem(audio2Buffer, audio2BufferSize)))
		throw "Error reallocating audio buffer.\n";
}

void Dubber::ReadVideoFrame(long stream_frame, long display_frame, BOOL preload) {
	_RPT2(_CRT_WARN,"entering Dubber::ReadVideoFrame streamframe %d displayframe %d\n",stream_frame,display_frame);
	LONG lActualBytes;
	int hr;

	void *buffer;
	int handle;

	LONG lSize;

	if (fPhantom) {
		buffer = pipe->getWriteBuffer(0, &handle, INFINITE);
		if (!buffer) return;	// hmm, aborted...

		bool bIsKey = !!vSrc->isKey(display_frame);

		pipe->postBuffer(0, stream_frame, display_frame,
			(bIsKey ? 0 : 1)
			+(preload ? 2 : 0),
			0,
			handle);

		return;
	}

//	_RPT2(0,"Reading frame %ld (%s)\n", lVStreamPos, preload ? "preload" : "process");

	hr = vSrc->read(stream_frame, 1, NULL, 0x7FFFFFFF, &lSize, NULL);
	if (hr) {
		if (hr == AVIERR_FILEREAD)
			throw MyError("Video frame %d could not be read from the source. The file may be corrupt.", stream_frame);
		else
			throw MyAVIError("Dub/IO-Video", hr);
	}

	// Add 4 bytes -- otherwise, we can get crashes with uncompressed video because
	// the bitmap routines expect to be able to read 4 bytes out.

	buffer = pipe->getWriteBuffer(lSize+4, &handle, INFINITE);
	if (!buffer) return;	// hmm, aborted...

	hr = vSrc->read(stream_frame, 1, buffer, lSize,	&lActualBytes,NULL); 
	if (hr) {
		if (hr == AVIERR_FILEREAD)
			throw MyError("Video frame %d could not be read from the source. The file may be corrupt.", stream_frame);
		else
			throw MyAVIError("Dub/IO-Video", hr);
	}

	if (opt->video.mode > DubVideoOptions::M_NONE)
		i64SegmentSize += lVideoSizeEstimate;
	else
		i64SegmentSize += lActualBytes + (lActualBytes&1);

	i64SegmentSize += 24;

	display_frame = vSrc->streamToDisplayOrder(stream_frame);

	pipe->postBuffer(lActualBytes, stream_frame, display_frame,
		(vSrc->isKey(display_frame) ? 0 : 1)
		+(preload ? 2 : 0),
//		+((lVStreamPos % opt->video.frameRateDecimation) || preload ? 2 : 0),
		vSrc->getDropType(display_frame),
		handle);


}

void Dubber::ReadNullVideoFrame(long display_frame) {
	void *buffer;
	int handle;

	buffer = pipe->getWriteBuffer(1, &handle, INFINITE);
	if (!buffer) return;	// hmm, aborted...

	if (display_frame >= 0) {
		pipe->postBuffer(0, vSrc->displayToStreamOrder(display_frame), display_frame,
			(vSrc->isKey(display_frame) ? 0 : 1),
//			+((lVStreamPos % opt->video.frameRateDecimation)? 2 : 0),
			vSrc->getDropType(display_frame),
			handle);
	} else {
		pipe->postBuffer(0, display_frame, display_frame,
			0,
			AVIPipe::kDroppable,
			handle);
	}

	i64SegmentSize += 24 + lVideoSizeEstimate;

//	_RPT0(0,"posted.\n");
}
/*
long Dubber::ReadAudio(long& lAStreamPos, long samples) {
	LONG lActualBytes=0;
	LONG lActualSamples=0;

	void *buffer;
	int handle;
	long len;

	LONG ltActualBytes, ltActualSamples;
	char *destBuffer;

	if (audioCompressor) {
		void *holdBuffer;
		LONG lSrcSamples;

		holdBuffer = audioCompressor->Compress(samples, &lSrcSamples, &lActualBytes, &lActualSamples);

		if (audioCorrector)
			audioCorrector->Process(holdBuffer, lActualBytes);

		buffer = pipe->getWriteBuffer(lActualBytes, &handle, INFINITE);
		if (!buffer) return 0;

		memcpy(buffer, holdBuffer, lActualBytes);

		lAStreamPos += lSrcSamples;
	} else {
		len = samples * audioStream->GetFormat()->nBlockAlign;
		buffer = pipe->getWriteBuffer(len, &handle, INFINITE);
		if (!buffer) return 0; // aborted

		destBuffer = (char *)buffer;

		do {
			ltActualSamples = audioStream->Read(destBuffer, samples, &ltActualBytes);

			lActualBytes += ltActualBytes;
			lActualSamples += ltActualSamples;

			samples -= ltActualSamples;
			destBuffer += ltActualBytes;
			len -= ltActualBytes;
			lAStreamPos += ltActualSamples;
		} while(samples && ltActualBytes);
	}

	pipe->postBuffer(lActualBytes, lActualSamples, 0, -1, 0, handle);

	aInfo.total_size += lActualBytes + 24;
	i64SegmentSize += lActualBytes + 24;

	return lActualBytes;
}
*/

long Dubber::ReadAudio(long& lAStreamPos, long samples) {
	LONG lActualBytes=0;
	LONG lActualSamples=0;

	void *buffer;
	int handle;
	long len;

	LONG ltActualBytes, ltActualSamples;
	char *destBuffer;

	if (audioCompressor) {
		void *holdBuffer;
		LONG lSrcSamples;

		holdBuffer = audioCompressor->Compress(samples, &lSrcSamples, &lActualBytes, &lActualSamples);

		if (audioCorrector)
			audioCorrector->Process(holdBuffer, lActualBytes);

		buffer = pipe->getWriteBuffer(lActualBytes, &handle, INFINITE);
		if (!buffer) return 0;

		memcpy(buffer, holdBuffer, lActualBytes);

		lAStreamPos += lSrcSamples;
	} else {

		if( audioInputMode == AUDIOIN_MP3 ) {
			do {
				len = samples*1152*4;// pulco : *4
				buffer = pipe->getWriteBuffer(len, &handle, INFINITE);
				if (!buffer) return 0; // aborted

				destBuffer = (char *)buffer;

				ltActualSamples = audioStream->Read(destBuffer, samples, &ltActualBytes);

				lActualBytes += ltActualBytes;
				lActualSamples += ltActualSamples;

				samples -= ltActualSamples;
				lAStreamPos += ltActualSamples;

				pipe->postBuffer(ltActualBytes, ltActualSamples,0, -1,0, handle);

				aInfo.total_size += ltActualBytes + 24;
				i64SegmentSize += ltActualBytes + 24;

			} while(samples && ltActualBytes);

			return lActualBytes;

		}
		else if( audioInputMode == AUDIOIN_OGG ) {
			do {
				len = samples*600;
				buffer = pipe->getWriteBuffer(len, &handle, INFINITE);
				if (!buffer) return 0; // aborted

				destBuffer = (char *)buffer;

				ltActualSamples = audioStream->Read(destBuffer, samples, &ltActualBytes);

				lActualBytes += ltActualBytes;
				lActualSamples += ltActualSamples;

				samples -= ltActualSamples;
				lAStreamPos += ltActualSamples;

				pipe->postBuffer(ltActualBytes, ltActualSamples, 0,-1,0, handle);

				aInfo.total_size += ltActualBytes + 24;
				i64SegmentSize += ltActualBytes + 24;

			} while(samples && ltActualSamples);

			return lActualBytes;

		}
		// 12/11/2002, Cyrius : handle AC3 here (combine advantages of WAV handling - next else -
		// and of AC3 frames (thanks to nBlockAlign and the fact we post multiple buffers if necessary)
		else if( audioInputMode == AUDIOIN_AC3 ) {
			do {
				len = samples * audioStream->GetFormat()->nBlockAlign;
				buffer = pipe->getWriteBuffer(len, &handle, INFINITE);
				if (!buffer) return 0; // aborted

				destBuffer = (char *)buffer;

				ltActualSamples = audioStream->Read(destBuffer, samples, &ltActualBytes);

				lActualBytes += ltActualBytes;
				lActualSamples += ltActualSamples;

				samples -= ltActualSamples;
				lAStreamPos += ltActualSamples;

				pipe->postBuffer(ltActualBytes, ltActualSamples,0, -1,0, handle);

				aInfo.total_size += ltActualBytes + 24;
				i64SegmentSize += ltActualBytes + 24;

			} while(samples && ltActualBytes);

			return lActualBytes;

		}
		else
		{
			len = samples * audioStream->GetFormat()->nBlockAlign;
			buffer = pipe->getWriteBuffer(len, &handle, INFINITE);
			if (!buffer) return 0; // aborted

			destBuffer = (char *)buffer;

			do {
				ltActualSamples = audioStream->Read(destBuffer, samples, &ltActualBytes);

				lActualBytes += ltActualBytes;
				lActualSamples += ltActualSamples;

				samples -= ltActualSamples;
				destBuffer += ltActualBytes;
				len -= ltActualBytes;
				lAStreamPos += ltActualSamples;
			} while(samples && ltActualBytes);
		}
	}


	pipe->postBuffer(lActualBytes, lActualSamples,0, -1,0, handle);

	aInfo.total_size += lActualBytes + 24;
	i64SegmentSize += lActualBytes + 24;

	return lActualBytes;
}

long Dubber::ReadAudio2(long& lAStreamPos, long samples) {
	LONG lActualBytes=0;
	LONG lActualSamples=0;

	void *buffer;
	int handle;
	long len;

	LONG ltActualBytes, ltActualSamples;
	char *destBuffer;

	if (audio2Compressor) {
		void *holdBuffer;
		LONG lSrcSamples;

		holdBuffer = audio2Compressor->Compress(samples, &lSrcSamples, &lActualBytes, &lActualSamples);

		if (audio2Corrector)
			audio2Corrector->Process(holdBuffer, lActualBytes);

		buffer = pipe->getWriteBuffer(lActualBytes, &handle, INFINITE);
		if (!buffer) return 0;

		memcpy(buffer, holdBuffer, lActualBytes);

		lAStreamPos += lSrcSamples;
	} else {

		if( audio2InputMode == AUDIOIN_MP3 ) {
			do {
				len = samples*1152;
				buffer = pipe->getWriteBuffer(len, &handle, INFINITE);
				if (!buffer) return 0; // aborted

				destBuffer = (char *)buffer;

				ltActualSamples = audio2Stream->Read(destBuffer, samples, &ltActualBytes);

				lActualBytes += ltActualBytes;
				lActualSamples += ltActualSamples;

				samples -= ltActualSamples;
				lAStreamPos += ltActualSamples;

				pipe->postBuffer(ltActualBytes, ltActualSamples,0, -2,0, handle);

				a2Info.total_size += ltActualBytes + 24;
				i64SegmentSize += ltActualBytes + 24;

			} while(samples && ltActualBytes);

			return lActualBytes;

		}
		else if( audio2InputMode == AUDIOIN_OGG ) {
			do {
				len = samples*600;
				buffer = pipe->getWriteBuffer(len, &handle, INFINITE);
				if (!buffer) return 0; // aborted

				destBuffer = (char *)buffer;

				ltActualSamples = audio2Stream->Read(destBuffer, samples, &ltActualBytes);

				lActualBytes += ltActualBytes;
				lActualSamples += ltActualSamples;

				samples -= ltActualSamples;
				lAStreamPos += ltActualSamples;

				pipe->postBuffer(ltActualBytes, ltActualSamples,0, -2,0, handle);

				a2Info.total_size += ltActualBytes + 24;
				i64SegmentSize += ltActualBytes + 24;

			} while(samples && ltActualSamples);

			return lActualBytes;

		}
		// 12/11/2002, Cyrius : handle AC3 here (combine advantages of WAV handling - next else -
		// and of AC3 frames (thanks to nBlockAlign and the fact we post multiple buffers if necessary)
		else if( audio2InputMode == AUDIOIN_AC3 ) {
			do {
				len = samples * audio2Stream->GetFormat()->nBlockAlign;
				buffer = pipe->getWriteBuffer(len, &handle, INFINITE);
				if (!buffer) return 0; // aborted

				destBuffer = (char *)buffer;

				ltActualSamples = audio2Stream->Read(destBuffer, samples, &ltActualBytes);

				lActualBytes += ltActualBytes;
				lActualSamples += ltActualSamples;

				samples -= ltActualSamples;
				lAStreamPos += ltActualSamples;

				pipe->postBuffer(ltActualBytes, ltActualSamples,0, -2,0, handle);

				a2Info.total_size += ltActualBytes + 24;
				i64SegmentSize += ltActualBytes + 24;

			} while(samples && ltActualBytes);

			return lActualBytes;

		}
		else
		{
			len = samples * audio2Stream->GetFormat()->nBlockAlign;
			buffer = pipe->getWriteBuffer(len, &handle, INFINITE);
			if (!buffer) return 0; // aborted

			destBuffer = (char *)buffer;

			do {
				ltActualSamples = audio2Stream->Read(destBuffer, samples, &ltActualBytes);

				lActualBytes += ltActualBytes;
				lActualSamples += ltActualSamples;

				samples -= ltActualSamples;
				destBuffer += ltActualBytes;
				len -= ltActualBytes;
				lAStreamPos += ltActualSamples;
			} while(samples && ltActualBytes);
		}
	}


	pipe->postBuffer(lActualBytes, lActualSamples,0, -2,0, handle);

	a2Info.total_size += lActualBytes + 24;
	i64SegmentSize += lActualBytes + 24;

	return lActualBytes;
}




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

void Dubber::NextSegment() {
	char szFile[MAX_PATH];
	bool fVideo;
	bool fAudio;
	bool fAudio2;
	AVIOutputFile *AVIout_new;

	pipe->sync();

	fVideo = !!AVIout->videoOut;
	fAudio = !!AVIout->audioOut;
	fAudio2 = !!AVIout->audio2Out;

	((AVIOutputFile *)AVIout)->setSegmentHintBlock(false, NULL, 1);

	if (!AVIout->finalize())
		throw MyError("Error finalizing avi segment");

	AVIout_new = new AVIOutputFile();
	if (!AVIout_new)
		throw MyMemoryError();

	try {
		AVIout_new->disable_extended_avi();
		AVIout_new->disable_os_caching();

		AVIout_new->setSegmentHintBlock(true, NULL, 1);

		sprintf(szFile, "%s.%02d.avi", pszSegmentPrefix, nSpillSegment++);

		if (!AVIout_new->initOutputStreams())
			throw MyMemoryError();

		if (fVideo && vSrc) {
			int l;

			AVIout_new->videoOut->setCompressed(TRUE);
			memcpy(&AVIout_new->videoOut->streamInfo, &AVIout->videoOut->streamInfo, sizeof AVIout->videoOut->streamInfo);
			if (!(AVIout_new->videoOut->allocFormat(l = AVIout->videoOut->getFormatLen())))
				throw MyMemoryError();

			memcpy(AVIout_new->videoOut->getFormat(), AVIout->videoOut->getFormat(), l);
		}
		if (fAudio && aSrc) {
			int l;

			memcpy(&AVIout_new->audioOut->streamInfo, &AVIout->audioOut->streamInfo, sizeof AVIout->audioOut->streamInfo);
			if (!(AVIout_new->audioOut->allocFormat(l = AVIout->audioOut->getFormatLen())))
				throw MyMemoryError();

			memcpy(AVIout_new->audioOut->getFormat(), AVIout->audioOut->getFormat(), l);
		}
		if (fAudio2 && a2Src) {
			int l;

			memcpy(&AVIout_new->audio2Out->streamInfo, &AVIout->audio2Out->streamInfo, sizeof AVIout->audio2Out->streamInfo);
			if (!(AVIout_new->audio2Out->allocFormat(l = AVIout->audio2Out->getFormatLen())))
				throw MyMemoryError();

			memcpy(AVIout_new->audio2Out->getFormat(), AVIout->audio2Out->getFormat(), l);
		}

		if (!AVIout_new->init(szFile, 
					fVideo ? vSrc ? compressorVideoFormat->bmiHeader.biWidth : 320 : 0, //filters.OutputBitmap()->w,
					fVideo ? vSrc ? compressorVideoFormat->bmiHeader.biHeight : 240 : 0, //filters.OutputBitmap()->h,
					!!vSrc,
					!!aSrc,
					!!a2Src,
					opt->perf.outputBufferSize,
					opt->audio.enabled))
			throw MyError("Problem initializing AVI output.");
	} catch(const MyError&) {
		delete AVIout_new;
		throw;
	}

	if (AVIout)
		delete AVIout;

	AVIout = AVIout_new;

	lSegmentFrameStart = lSpillVideoPoint;
	i64SegmentSize = 0;
	i64SegmentCredit = 0;
	lSpillVideoPoint = lSpillAudioPoint = lSpillAudio2Point = 0;
	lSpillVideoOk = lSpillAudioOk = lSpillAudio2Ok = 0;
}

void Dubber::CheckSpill(long videopt, long audiopt, long audio2pt) {
	long lFrame;
	long lFrame2;
	long lSample;
	long lSample2;

	// Are the new values still below the last computed 'safe' thresholds?

	if (videopt <= lSpillVideoOk && audiopt <= lSpillAudioOk && audio2pt <= lSpillAudio2Ok)
		return;

	// Find out how many sync'ed video frames we'd be pushing ahead.

	__int64 nAdditionalBytes;

	lFrame = ((videopt-vInfo.start_src) / opt->video.frameRateDecimation);

	if (aSrc) {
		__int64 nBlockAlignMillion;
		__int64 nAvgBytesPerSecSpeed;

		if( (audioInputMode==AUDIOIN_MP3) || (audioInputMode==AUDIOIN_OGG) ) {

			// in VBR mode, it's much more simple
			lFrame2 = MulDiv((audiopt-aInfo.start_src), aSrc->streamInfo.dwScale*1000000, aSrc->streamInfo.dwRate*vInfo.usPerFrameNoTelecine );

		} else {

			nBlockAlignMillion = audioTimingStream->GetFormat()->nBlockAlign * 1000000i64;
			nAvgBytesPerSecSpeed = (__int64)vInfo.usPerFrameNoTelecine * audioTimingStream->GetFormat()->nAvgBytesPerSec;

			// <audio samples> * <audio bytes per sample> / <audio bytes per second> = <seconds>
			// <seconds> * 1000000 / <microseconds per frame> = <frames>
			// (<audio samples> * <audio bytes per sample> * 1000000) / (<audio bytes per second> * <microseconds per frame>) = <frames>

			lFrame2 = int64divroundup((__int64)(audiopt - aInfo.start_src) * nBlockAlignMillion,
						nAvgBytesPerSecSpeed);
		}

		if (pInvTelecine)
			lFrame2 += nVideoLag;

		if (lFrame2 > lFrame)
			lFrame = lFrame2;

		// Quantize to 5 frames in inverse telecine mode.

		lFrame2 = lFrame;
		if (pInvTelecine) {
			lFrame += 4;
			lFrame -= lFrame % 5;
			lFrame2 = lFrame - 10;
		}

		if( (audioInputMode==AUDIOIN_MP3) || (audioInputMode==AUDIOIN_OGG) ) {

			lSample = MulDiv(lFrame2, aSrc->streamInfo.dwRate*vInfo.usPerFrameNoTelecine, aSrc->streamInfo.dwScale*1000000 );

		} else {
			// Find equivalent audio point.
			//
			// (<audio samples> = (<frames> * <audio bytes per second> * <microseconds per frame>) / (<audio bytes per sample> * 1000000);

			lSample = int64divround((__int64)lFrame2 * nAvgBytesPerSecSpeed, nBlockAlignMillion);
		}

		if (a2Src) {
			__int64 nBlockAlignMillion;
			__int64 nAvgBytesPerSecSpeed;

			if( (audio2InputMode==AUDIOIN_MP3) || (audio2InputMode==AUDIOIN_OGG) ) {

				// in VBR mode, it's much more simple
				lFrame2 = MulDiv((audio2pt-a2Info.start_src), a2Src->streamInfo.dwScale*1000000, a2Src->streamInfo.dwRate*vInfo.usPerFrameNoTelecine );

			} else {

				nBlockAlignMillion = audio2TimingStream->GetFormat()->nBlockAlign * 1000000i64;
				nAvgBytesPerSecSpeed = (__int64)vInfo.usPerFrameNoTelecine * audio2TimingStream->GetFormat()->nAvgBytesPerSec;

				// <audio samples> * <audio bytes per sample> / <audio bytes per second> = <seconds>
				// <seconds> * 1000000 / <microseconds per frame> = <frames>
				// (<audio samples> * <audio bytes per sample> * 1000000) / (<audio bytes per second> * <microseconds per frame>) = <frames>

				lFrame2 = int64divroundup((__int64)(audio2pt - a2Info.start_src) * nBlockAlignMillion,
							nAvgBytesPerSecSpeed);
			}

			if (pInvTelecine)
				lFrame2 += nVideoLag;

			if (lFrame2 > lFrame)
				lFrame = lFrame2;

			// Quantize to 5 frames in inverse telecine mode.

			lFrame2 = lFrame;
			if (pInvTelecine) {
				lFrame += 4;
				lFrame -= lFrame % 5;
				lFrame2 = lFrame - 10;
			}

			if( (audio2InputMode==AUDIOIN_MP3) || (audio2InputMode==AUDIOIN_OGG) ) {

				lSample2 = MulDiv(lFrame2, a2Src->streamInfo.dwRate*vInfo.usPerFrameNoTelecine, a2Src->streamInfo.dwScale*1000000 );

			} else {
				// Find equivalent audio point.
				//
				// (<audio samples> = (<frames> * <audio bytes per second> * <microseconds per frame>) / (<audio bytes per sample> * 1000000);

				lSample2 = int64divround((__int64)lFrame2 * nAvgBytesPerSecSpeed, nBlockAlignMillion);
			}

			if( lSample2>lSample ) lSample = lSample2;

		} // if( a2Src )

	} else
		lFrame2 = lFrame;

	// Figure out how many more bytes it would be.

	if (opt->video.mode)
		nAdditionalBytes = (__int64)lVideoSizeEstimate * (vInfo.start_src + lFrame2 * opt->video.frameRateDecimation - vInfo.cur_src + nVideoLag);
	else {
		HRESULT hr;
		LONG lSize = 0;
		long lSamp = vInfo.cur_src;
		long lSampLimit = vInfo.start_src + lFrame2 * opt->video.frameRateDecimation + nVideoLag;

		nAdditionalBytes = 0;

		while(lSamp < lSampLimit) {
			hr = vSrc->read(lSamp, 1, NULL, 0x7FFFFFFF, &lSize, NULL);
			if (!hr)
				nAdditionalBytes += lSize;
			lSamp += opt->video.frameRateDecimation;
		}
	}

	if (aSrc && lSample > aInfo.cur_src) {
		if( (audioInputMode==AUDIOIN_MP3) || (audioInputMode==AUDIOIN_OGG) ) {
			
			nAdditionalBytes += MulDiv( lSample - aInfo.cur_src, audioTimingStream->GetFormat()->nAvgBytesPerSec*aSrc->streamInfo.dwScale, aSrc->streamInfo.dwRate );

		} else {

			nAdditionalBytes += (lSample - aInfo.cur_src) * audioTimingStream->GetFormat()->nBlockAlign;
		}
	}

	if (a2Src && lSample > a2Info.cur_src) {
		if( (audio2InputMode==AUDIOIN_MP3) || (audio2InputMode==AUDIOIN_OGG) ) {
			
			nAdditionalBytes += MulDiv( lSample - a2Info.cur_src, audio2TimingStream->GetFormat()->nAvgBytesPerSec*a2Src->streamInfo.dwScale, a2Src->streamInfo.dwRate );

		} else {

			nAdditionalBytes += (lSample - a2Info.cur_src) * audio2TimingStream->GetFormat()->nBlockAlign;
		}
	}

	if (nAdditionalBytes + (i64SegmentSize - i64SegmentCredit) < i64SegmentThreshold && (!lSegmentFrameLimit || lFrame-lSegmentFrameStart<=lSegmentFrameLimit)) {

		// We're fine.  Mark down the new thresholds so we don't have to recompute them.

		lFrame = lFrame * opt->video.frameRateDecimation + vInfo.start_src;

		_RPT3(0,"Pushing threshold to %ld, %ld, %ld: ", 
			lFrame, lSample+aInfo.start_src,lSample+a2Info.start_src);
		_RPT3(0,"current position %ld, %ld, %ld\n", 
			vInfo.cur_src, aInfo.cur_src, a2Info.cur_src);

		lSpillVideoOk = lFrame;

		if (aSrc)
			lSpillAudioOk = lSample+aInfo.start_src;

		if (a2Src)
			lSpillAudio2Ok = lSample+a2Info.start_src;

		return;
	}

	// Doh!  Force a split at the current thresholds.

	lSpillVideoPoint = lSpillVideoOk;

	if (aSrc)
		lSpillAudioPoint = lSpillAudioOk;

	if (a2Src)
		lSpillAudio2Point = lSpillAudio2Ok;

	_RPT3(0,"Forcing split at %ld, %ld, %ld: ", 
		lSpillVideoPoint, lSpillAudioPoint, lSpillAudio2Point);
	_RPT3(0,"current position %ld, %ld, %ld\n", 
		vInfo.cur_src, aInfo.cur_src, a2Info.cur_src);

	lFrame = lFrame * opt->video.frameRateDecimation + vInfo.start_src;
	lSpillVideoOk = lFrame;

	if (aSrc) {
		lSpillAudioOk = lSample + aInfo.start_src;
	}
	if (a2Src) {
		lSpillAudio2Ok = lSample + a2Info.start_src;
	}

	// Are we exactly at the right point?

	if (vInfo.cur_src == lSpillVideoPoint 
		&& (!aSrc || aInfo.cur_src == lSpillAudioPoint)
		&& (!a2Src || a2Info.cur_src == lSpillAudio2Point))
		NextSegment();
}

void Dubber::MainAddVideoFrame() {
	_RPT0(_CRT_WARN,"entering Dubber::MainAddVideoFrame\n");
	
	long f;
	BOOL is_preroll;

	if (vInfo.cur_src < vInfo.end_src + nVideoLag) {
		BOOL fRead = FALSE;
		long lFrame = vInfo.cur_src;

		// If we're doing segment spilling but don't have an audio stream,
		// break if we can't fit the next frame.

		if (fEnableSpill && !audioStream)
			if (i64SegmentSize - i64SegmentCredit >= i64SegmentThreshold)
				NextSegment();

		// If we're using an input subset, translate the frame.

		if (vInfo.cur_src >= vInfo.end_src)
			lFrame = -1;		// force null read
		else if (inputSubsetActive)
			lFrame = inputSubsetActive->lookupFrame(vInfo.cur_src) + vSrc->lSampleFirst;

		if (lFrame >= vSrc->lSampleFirst && lFrame < vSrc->lSampleLast) {
			if (opt->video.mode != DubVideoOptions::M_NONE) {
				long lSize;
				int nFrames;

				vSrc->streamSetDesiredFrame(lFrame);

				nFrames = vSrc->streamGetRequiredCount(&lSize);

				if (fEnableSpill)
					CheckSpill(vInfo.cur_src + opt->video.frameRateDecimation, aInfo.cur_src, a2Info.cur_src);

				if (!lSpillVideoPoint || vInfo.cur_src < lSpillVideoPoint) {
					while(-1 != (f = vSrc->streamGetNextRequiredFrame(&is_preroll))) {
						ReadVideoFrame(f, lFrame, is_preroll && opt->video.mode>=DubVideoOptions::M_FASTREPACK);

						fRead = TRUE;
					}

					if (!fRead)
						ReadNullVideoFrame(lFrame);
				}
			} else {

				if (fEnableSpill)
					CheckSpill(vInfo.cur_src + opt->video.frameRateDecimation, aInfo.cur_src, a2Info.cur_src);

				if (!lSpillVideoPoint || vInfo.cur_src < lSpillVideoPoint) {

					// If we just copied the last frame, drop in a null frame.

					if (mDSCLastVideoFrame == lFrame)
						ReadNullVideoFrame(lFrame);
					else {
						// In DSC mode, we may not be able to copy the desired frames due to
						// frame dependencies.  In that case, copy sequential frames from the
						// last keyframe.

						if (opt->video.frameRateDecimation > 1) {
							long lKey = vSrc->nearestKey(lFrame);

							if (lKey > mDSCLastVideoFrame)
								mDSCLastVideoFrame = lKey;
							else
								++mDSCLastVideoFrame;

							lFrame = mDSCLastVideoFrame;
						}

						ReadVideoFrame(lFrame, lFrame, FALSE);
						mDSCLastVideoFrame = lFrame;
					}
				}
			}
		} else {
			// Flushing out the lag -- read a null frame.

			ReadNullVideoFrame(lFrame);
		}
	} else if (mFramesPushedToFlushCodec < mFramesDelayedByCodec) {
		ReadNullVideoFrame(-1);
		++mFramesPushedToFlushCodec;
	}

	vInfo.cur_src += opt->video.frameRateDecimation;
}

void Dubber::MainAddAudioFrame(int lag) {
	long lAvgBytesPerSec = audioTimingStream->GetFormat()->nAvgBytesPerSec;
	long lBlockSize;
	LONG lAudioPoint;
	LONG lFrame = ((vInfo.cur_src-vInfo.start_src-lag) / opt->video.frameRateDecimation);

	// If IVTC is active, round up to a multiple of five.

	if (pInvTelecine) {
		lFrame += 4;
		lFrame -= lFrame % 5;
	}

	// Per-frame interleaving?

	if (!opt->audio.is_ms || opt->audio.interval<=1) {
		if (opt->audio.interval > 1)
			lFrame = ((lFrame+opt->audio.interval-1)/opt->audio.interval)*opt->audio.interval;

		if( audioInputMode == AUDIOIN_MP3 )
		{
			lAudioPoint = (long)(aInfo.start_src + aInfo.lPreloadSamples +
				(
					(
						(__int64)audioTimingStream->GetFormat()->nSamplesPerSec
						*
						(__int64)vInfo.usPerFrameNoTelecine
						*
						lFrame
					)
					/
					(
						(__int64)1000000
						*
						(__int64)1152
					)
				));
		} else if( audioInputMode == AUDIOIN_OGG )
		{
			lAudioPoint = (long)(aInfo.start_src + aInfo.lPreloadSamples +
				(
					(
						(__int64)audioTimingStream->GetFormat()->nSamplesPerSec
						*
						(__int64)vInfo.usPerFrameNoTelecine
						*
						lFrame
					)
					/
					(
						(__int64)1000000
						*
						(__int64)600
					)
				));
		} else {
			lAudioPoint = (long)(aInfo.start_src + aInfo.lPreloadSamples +
				(
					((__int64)lAvgBytesPerSec*(__int64)vInfo.usPerFrameNoTelecine*lFrame)
					/
					((__int64)1000000*audioTimingStream->GetFormat()->nBlockAlign)
				));
		}
	} else {						// Per n-ms interleaving

		__int64 i64CurrentFrameMs;

		i64CurrentFrameMs = ((__int64)vInfo.usPerFrameNoTelecine * lFrame)/1000;

		// Round up lCurrentFrameMs to next interval

		i64CurrentFrameMs = ((i64CurrentFrameMs+opt->audio.interval-1)/opt->audio.interval)*opt->audio.interval;

		// nAvgBytesPerSec/nBlockAlign = samples per second

		lAudioPoint = aInfo.start_src + aInfo.lPreloadSamples +
				(LONG)((i64CurrentFrameMs * lAvgBytesPerSec) / (audioTimingStream->GetFormat()->nBlockAlign*1000));

	}

	// Round lAudioPoint to next block size if preview

	if (fPreview) {
		lBlockSize = (lAvgBytesPerSec / audioTimingStream->GetFormat()->nBlockAlign+4)/5;

		lAudioPoint += lBlockSize - 1;
		lAudioPoint -= lAudioPoint % lBlockSize;
	}

	if (lAudioPoint <= aInfo.cur_src)
		return;

	if (fEnableSpill)
		CheckSpill(vInfo.cur_src, lAudioPoint, a2Info.cur_src);

	if (lSpillAudioPoint && lAudioPoint > lSpillAudioPoint)
		lAudioPoint = lSpillAudioPoint;

	if (lAudioPoint > aInfo.cur_src)
		ReadAudio(aInfo.cur_src,lAudioPoint - aInfo.cur_src);

	_ASSERT(aInfo.cur_src <= lAudioPoint);
}

void Dubber::MainAddAudio2Frame(int lag) {
	long lAvgBytesPerSec = audio2TimingStream->GetFormat()->nAvgBytesPerSec;
	long lBlockSize;
	LONG lAudioPoint;
	LONG lFrame = ((vInfo.cur_src-vInfo.start_src-lag) / opt->video.frameRateDecimation);

	// Per-frame interleaving?

	if (!opt->audio2.is_ms || opt->audio2.interval<=1) {
		if (opt->audio2.interval > 1)
			lFrame = ((lFrame+opt->audio2.interval-1)/opt->audio2.interval)*opt->audio2.interval;

		if( audio2InputMode == AUDIOIN_MP3 )
		{
			lAudioPoint = (long)(a2Info.start_src + a2Info.lPreloadSamples +
				(
					(
						(__int64)audio2TimingStream->GetFormat()->nSamplesPerSec
						*
						(__int64)vInfo.usPerFrameNoTelecine
						*
						lFrame
					)
					/
					(
						(__int64)1000000
						*
						(__int64)1152
					)
				));
		} else if( audio2InputMode == AUDIOIN_OGG )
		{
			lAudioPoint = (long)(a2Info.start_src + a2Info.lPreloadSamples +
				(
					(
						(__int64)audio2TimingStream->GetFormat()->nSamplesPerSec
						*
						(__int64)vInfo.usPerFrameNoTelecine
						*
						lFrame
					)
					/
					(
						(__int64)1000000
						*
						(__int64)600
					)
				));
		} else {
			lAudioPoint = (long)(a2Info.start_src + a2Info.lPreloadSamples +
				(
					((__int64)lAvgBytesPerSec*(__int64)vInfo.usPerFrameNoTelecine*lFrame)
					/
					((__int64)1000000*audio2TimingStream->GetFormat()->nBlockAlign)
				));
		}
	} else {						// Per n-ms interleaving

		__int64 i64CurrentFrameMs;

		i64CurrentFrameMs = ((__int64)vInfo.usPerFrameNoTelecine * lFrame)/1000;

		// Round up lCurrentFrameMs to next interval

		i64CurrentFrameMs = ((i64CurrentFrameMs+opt->audio2.interval-1)/opt->audio2.interval)*opt->audio2.interval;

		// nAvgBytesPerSec/nBlockAlign = samples per second

		lAudioPoint = a2Info.start_src + a2Info.lPreloadSamples +
				(LONG)((i64CurrentFrameMs * lAvgBytesPerSec) / (audio2TimingStream->GetFormat()->nBlockAlign*1000));

	}

	// Round lAudioPoint to next block size if preview

	if (fPreview) {
		lBlockSize = (lAvgBytesPerSec / audio2TimingStream->GetFormat()->nBlockAlign+4)/5;

		lAudioPoint += lBlockSize - 1;
		lAudioPoint -= lAudioPoint % lBlockSize;
	}

	if (lAudioPoint <= a2Info.cur_src)
		return;

	if (fEnableSpill)
		CheckSpill(vInfo.cur_src, aInfo.cur_src, lAudioPoint);

	if (lSpillAudio2Point && lAudioPoint > lSpillAudio2Point)
		lAudioPoint = lSpillAudio2Point;

	if (lAudioPoint > a2Info.cur_src)
		ReadAudio2(a2Info.cur_src,lAudioPoint - a2Info.cur_src);

	_ASSERT(a2Info.cur_src <= lAudioPoint);
}

void Dubber::MainThreadKickstart(void *thisPtr) {
	InitThreadData("I/O processing");
	((Dubber *)thisPtr)->MainThread();
	DeinitThreadData();
}

void Dubber::MainThread() {

	///////////

	_XRPT0(0,"Dub/Main: Start.\n");

	InterlockedIncrement((LONG *)&lThreadsActive);

	try {

		DEFINE_SP(sp);

		// Preload audio before the first video frame.

//		_RPT1(0,"Before preload: %ld\n", aInfo.cur_src);

		if (aSrc) {
			if( audioInputMode == AUDIOIN_MP3 ) {
				aInfo.lPreloadSamples	= (long) ((__int64)opt->audio.preload * (__int64)audioTimingStream->GetFormat()->nSamplesPerSec)/1152000 ;
			} else if( audioInputMode == AUDIOIN_OGG ) {
				aInfo.lPreloadSamples	= (long) ((__int64)opt->audio.preload * (__int64)audioTimingStream->GetFormat()->nSamplesPerSec)/600000 ;
			} else {
				aInfo.lPreloadSamples	= (long)(((__int64)opt->audio.preload * audioTimingStream->GetFormat()->nAvgBytesPerSec)/(1000 * audioTimingStream->GetFormat()->nBlockAlign));
			}

			if (aInfo.lPreloadSamples>0) {
				_RPT1(0,"Dub/Main: Prewriting %ld samples\n", aInfo.lPreloadSamples);
				ReadAudio(aInfo.cur_src, aInfo.lPreloadSamples);
			}
		}

		if (a2Src) {
			if( audio2InputMode == AUDIOIN_MP3 ) {
				a2Info.lPreloadSamples	= (long) ((__int64)opt->audio2.preload * (__int64)audio2TimingStream->GetFormat()->nSamplesPerSec)/1152000 ;
			} else if( audioInputMode == AUDIOIN_OGG ) {
				a2Info.lPreloadSamples	= (long) ((__int64)opt->audio2.preload * (__int64)audio2TimingStream->GetFormat()->nSamplesPerSec)/600000 ;
			} else {
				a2Info.lPreloadSamples	= (long)(((__int64)opt->audio2.preload * audio2TimingStream->GetFormat()->nAvgBytesPerSec)/(1000 * audio2TimingStream->GetFormat()->nBlockAlign));
			}

			if (a2Info.lPreloadSamples>0) {
				_RPT1(0,"Dub/Main: Prewriting %ld samples\n", a2Info.lPreloadSamples);
				ReadAudio2(a2Info.cur_src, a2Info.lPreloadSamples);
			}
		}

//		_RPT1(0,"After preload: %ld\n", aInfo.cur_src);

		// Do it!!!

		try {
			if (opt->audio.enabled && aSrc && vSrc && AVIout->videoOut) {
				LONG lStreamCounter = 0;

				_RPT0(0,"Dub/Main: Taking the **Interleaved** path.\n");

				while(!fAbort && (vInfo.cur_src<vInfo.end_src+nVideoLag || !audioStream->isEnd() || mFramesPushedToFlushCodec < mFramesDelayedByCodec)) { 
					BOOL doAudio = TRUE;

					CHECK_STACK(sp);

					if (!lSpillVideoPoint || vInfo.cur_src < lSpillVideoPoint)
						MainAddVideoFrame();

					if ((!lSpillAudioPoint || aInfo.cur_src < lSpillAudioPoint) && audioStream && !audioStream->isEnd()) {
						MainAddAudioFrame(nVideoLag + mFramesDelayedByCodec);
					}
					if ((!lSpillAudio2Point || a2Info.cur_src < lSpillAudio2Point) && audio2Stream && !audio2Stream->isEnd()) {
						MainAddAudio2Frame(nVideoLag + mFramesDelayedByCodec);
					}

					if (lSpillVideoPoint && vInfo.cur_src == lSpillVideoPoint && aInfo.cur_src == lSpillAudioPoint && a2Info.cur_src == lSpillAudio2Point )
						NextSegment();

//					_RPT3(0,"segment size: %I64d - %I64d = %I64d\n", i64SegmentSize, i64SegmentCredit, i64SegmentSize - i64SegmentCredit);
				}

			} else {
				_RPT0(0,"Dub/Main: Taking the **Non-Interleaved** path.\n");

				if (aSrc)
					while(!fAbort && !audioStream->isEnd()) {
						ReadAudio(aInfo.cur_src, 1024); //8192);
					}
				if (a2Src)
					while(!fAbort && !audio2Stream->isEnd()) {
						ReadAudio2(a2Info.cur_src, 1024); //8192);
					}

				if (vSrc && AVIout->videoOut)
					while(!fAbort && (vInfo.cur_src < vInfo.end_src+nVideoLag || mFramesPushedToFlushCodec < mFramesDelayedByCodec)) { 
						BOOL fRead = FALSE;
						long lFrame = vInfo.cur_src;

						CHECK_STACK(sp);

						if (!lSpillVideoPoint || vInfo.cur_src < lSpillVideoPoint)
							MainAddVideoFrame();

						if (lSpillVideoPoint && vInfo.cur_src == lSpillVideoPoint)
							NextSegment();

					}
			}
		} catch(MyError& e) {
			if (!fError) {
				err.TransferFrom(e);
				fError = true;
			}
//			e.post(NULL, "Dub Error (will attempt to finalize)");
		}

		// wait for the pipeline to clear...

		if (!fAbort) pipe->finalize();

		// finalize the output.. if it's not a preview...

		if (!AVIout->isPreview()) {
			while(lThreadsActive>1) {
//			_RPT1(0,"\tDub/Main: %ld threads active\n", lThreadsActive);

				WaitForSingleObject(hEventAbortOk, 200);
			}

			_RPT0(0,"Dub/Main: finalizing...\n");

			// update audio rate...

			if (audioCorrector) {
				WAVEFORMATEX *wfex = AVIout->audioOut->getWaveFormat();
				
				wfex->nAvgBytesPerSec = audioCorrector->ComputeByterate(wfex->nSamplesPerSec);

				AVIout->audioOut->streamInfo.dwRate = wfex->nAvgBytesPerSec
					* AVIout->audioOut->streamInfo.dwScale;
			}
			if (audio2Corrector) {
				WAVEFORMATEX *wfex = AVIout->audio2Out->getWaveFormat();
				
				wfex->nAvgBytesPerSec = audio2Corrector->ComputeByterate(wfex->nSamplesPerSec);

				AVIout->audio2Out->streamInfo.dwRate = wfex->nAvgBytesPerSec
					* AVIout->audio2Out->streamInfo.dwScale;
			}

			// finalize avi

			if (!AVIout->finalize()) throw MyError("Error finalizing AVI!");
			_RPT0(0,"Dub/Main: finalized.\n");
		}

		// kill everyone else...

		fAbort = true;

	} catch(MyError& e) {
//		e.post(NULL,"Dub Error");

		if (!fError) {
			err.TransferFrom(e);
			fError = true;
		}
		fAbort = TRUE;
	} catch(int) {
		;	// do nothing
	}

	// All done, time to get the pooper-scooper and clean up...

	hThreadMain = NULL;

	InterlockedDecrement((LONG *)&lThreadsActive);
	SetEvent(hEventAbortOk);

#ifdef _DEBUG
	_CrtCheckMemory();
#endif

	_XRPT0(0,"Dub/Main: End.\n");
}

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

#define BUFFERID_INPUT (1)
#define BUFFERID_OUTPUT (2)
#define BUFFERID_PACKED (4)

void Dubber::WriteVideoFrame(void *buffer, int exdata, int droptype, LONG lastSize, long sample_num, long display_num) {
	LONG dwBytes;
	bool isKey;
	void *frameBuffer;
	LPVOID lpCompressedData;

	_RPT0(0,"Dubber::WriteVideoFrame\n");
	// Preview fast drop -- if there is another keyframe in the pipe, we can drop
	// all the frames to it without even decoding them!
	//
	// Anime song played during development of this feature: "Trust" from the
	// Vandread OST.

	if (fPreview && opt->perf.fDropFrames) {

		// If audio is frozen, force frames to be dropped.

		bool bDrop = true;

		if (!(exdata & 4))
			bDrop = !vSrc->isDecodable(sample_num);

		if (mbAudioFrozen && mbAudioFrozenValid) {
			lDropFrames = 1;
		}

		if (lDropFrames && !bDrop) {

			// Attempt to drop a frame before the decoder.  Droppable frames (zero-byte
			// or B-frames) can be dropped without any problem without question.  Dependant
			// (P-frames or delta frames) and independent frames (I-frames or keyframes)
			// should only be dropped if there is a reasonable expectation that another
			// independent frame will arrive around the time that we want to stop dropping
			// frames, since we'll basically kill decoding until then.

			if (droptype == AVIPipe::kDroppable) {
				bDrop = true;
			} else {
				int total, indep;

				pipe->getDropDistances(total, indep);

				// Do a blind drop if we know a keyframe will arrive within two frames.

				if (indep == 0x3FFFFFFF && vSrc->nearestKey(display_num + opt->video.frameRateDecimation*2) > display_num)
					indep = 0;

				if (indep < lDropFrames) {
					bDrop = true;
				}
			}
		}

		if (bDrop) {
//			_RPT3(0, "Dropping: sample %d / display %d [%c]\n", sample_num, display_num, vSrc->getFrameTypeChar(display_num));

			if (!(exdata&2)) {
				blitter->nextFrame(opt->video.nPreviewFieldMode ? 2 : 1);
				vInfo.cur_proc_src += opt->video.frameRateDecimation;
			}
			++fsi.lCurrentFrame;
			if (lDropFrames)
				--lDropFrames;

			pStatusHandler->NotifyNewFrame(0);

			return;
		}

//		_RPT3(0, "Displaying: sample %d / display %d [%c]\n", sample_num, display_num, vSrc->getFrameTypeChar(display_num));
	}

	// With Direct mode, write video data directly to output.

	if (opt->video.mode == DubVideoOptions::M_NONE || fPhantom) {

//		_RPT2(0,"Processing frame %ld (#%ld)\n", sample_num, vInfo.cur_proc_src+1);

		if (!AVIout->videoOut->write((exdata & 1) ? 0 : AVIIF_KEYFRAME, (char *)buffer, lastSize, 1))
			throw MyError("Error writing video frame.");

		vInfo.total_size += lastSize + 24;
		++vInfo.cur_proc_src;
		++vInfo.processed;

		pStatusHandler->NotifyNewFrame(lastSize | (exdata&1 ? 0x80000000 : 0));

		return;
	}

	// Fast Repack: Decompress data and send to compressor (possibly non-RGB).
	// Slow Repack: Decompress data and send to compressor.
	// Full:		Decompress, process, filter, convert, send to compressor.

	blitter->lock(BUFFERID_INPUT);

//	_RPT2(0,"Sample %ld (keyframe: %d)\n", sample_num, !(exdata &1));

	// I promise these stupid exdata flags will be gone in 1.5.0.

	if (!(exdata & 4)) {
		VDCHECKPOINT;
		CHECK_FPU_STACK
		vSrc->streamGetFrame(buffer, lastSize, !(exdata&1), FALSE, sample_num);
		CHECK_FPU_STACK
	}
	VDCHECKPOINT;

//	guiSetStatus("Pulse clock: %ld, Delta: %ld\n", g_lPulseClock, blitter->getFrameDelta());

	if (exdata & 2) {
		blitter->unlock(BUFFERID_INPUT);
//		++vInfo.cur_proc_src;
		return;
	}

	if (lDropFrames && fPreview) {
		blitter->unlock(BUFFERID_INPUT);
		blitter->nextFrame(opt->video.nPreviewFieldMode ? 2 : 1);
		vInfo.cur_proc_src += opt->video.frameRateDecimation;
		++fsi.lCurrentFrame;
		--lDropFrames;

		pStatusHandler->NotifyNewFrame(0);

		return;
	}

	// Process frame to backbuffer for Full video mode.  Do not process if we are
	// running in Repack mode only!
	if (opt->video.mode == DubVideoOptions::M_FULL) {
		VBitmap *initialBitmap = filters.InputBitmap();
		VBitmap *lastBitmap = filters.LastBitmap();
		VBitmap *outputBitmap = filters.OutputBitmap();
		VBitmap destbm;
		long lInputFrameNum, lInputFrameNum2;

		lInputFrameNum = display_num - vSrc->lSampleFirst;

		if (pInvTelecine) {
			lInputFrameNum2 = pInvTelecine->ProcessOut(initialBitmap);
			pInvTelecine->ProcessIn(&VBitmap(vSrc->getFrameBuffer(), vSrc->getDecompressedFormat()), lInputFrameNum);

			lInputFrameNum = lInputFrameNum2;

			if (lInputFrameNum < 0) {
				blitter->unlock(BUFFERID_INPUT);
				vInfo.cur_proc_src += opt->video.frameRateDecimation;
				return;
			}
		} else
			initialBitmap->BitBlt(0, 0, &VBitmap(vSrc->getFrameBuffer(), vSrc->getDecompressedFormat()), 0, 0, -1, -1);


		if (inputHisto) {
			inputHisto->Zero();
			inputHisto->Process(filters.InputBitmap());
		}

		// process frame

//		fsi.lCurrentFrame		= (sample_num - vInfo.start_src) / opt->video.frameRateDecimation;

		fsi.lCurrentSourceFrame	= lInputFrameNum;
		fsi.lSourceFrameMS		= MulDiv(fsi.lCurrentSourceFrame, fsi.lMicrosecsPerSrcFrame, 1000);
		fsi.lDestFrameMS		= MulDiv(fsi.lCurrentFrame, fsi.lMicrosecsPerFrame, 1000);

		CHECK_FPU_STACK

		filters.RunFilters();

		CHECK_FPU_STACK

		++fsi.lCurrentFrame;

		if (nVideoLagPreload>0) {
			--nVideoLagPreload;
			blitter->unlock(BUFFERID_INPUT);
			vInfo.cur_proc_src += opt->video.frameRateDecimation;
			return;
		}


		blitter->lock(BUFFERID_OUTPUT);

//		if (!outputDecompressor)
//			outputBitmap.data = outputBuffer;

		do {
			if (pdsOutput) {
				if (!pdsOutput->LockInverted(&destbm))
					break;

				outputBitmap = &destbm;
			}

			if (fPreview && g_prefs.fDisplay & Preferences::DISPF_DITHER16)
				outputBitmap->BitBltDither(0, 0, lastBitmap, 0, 0, -1, -1, fDisplay565);
			else if (bihDisplayFormat.bV4V4Compression == BI_BITFIELDS)
				outputBitmap->BitBlt565(0, 0, lastBitmap, 0, 0, -1, -1);
			else
				outputBitmap->BitBlt(0, 0, lastBitmap, 0, 0, -1, -1);

			if (pdsOutput)
				pdsOutput->Unlock();

		} while(false);
	}

	// write it to the file
	
	frameBuffer = 		/*(opt->video.mode == DubVideoOptions::M_FASTREPACK ? buffer : */
						opt->video.mode <= DubVideoOptions::M_SLOWREPACK ? vSrc->getFrameBuffer()
						:filters.OutputBitmap()->data;


	if (fUseVideoCompression) {
		if (hicOutput)
			blitter->lock(BUFFERID_PACKED);

		CHECK_FPU_STACK
		lpCompressedData = pVideoPacker->packFrame(frameBuffer, &isKey, &dwBytes);
		CHECK_FPU_STACK

		// Check if codec buffered a frame.

		if (!lpCompressedData) {
			if (hicOutput)
				blitter->unlock(BUFFERID_PACKED);

			++mFramesDelayedByCodec;
			return;
		}

		if (fShowDecompressedFrame && outputDecompressor && dwBytes) {
			DWORD err;
			VBitmap *outputBitmap = filters.OutputBitmap();
			Pixel *outputBuffer = outputBitmap->data;

//			memset(outputBuffer, 0, outputBitmap->size);

			CHECK_FPU_STACK

			DWORD dwSize = compressorVideoFormat->bmiHeader.biSizeImage;

			compressorVideoFormat->bmiHeader.biSizeImage = dwBytes;

			VDCHECKPOINT;
			if (ICERR_OK != (err = ICDecompress(outputDecompressor,
				isKey ? 0 : ICDECOMPRESS_NOTKEYFRAME,
				AVIout->videoOut->getImageFormat(),
				lpCompressedData,
				&compressorVideoFormat->bmiHeader,
				outputBuffer
				)))

//					throw "Error decompressing output video frame.\n";
//					throw MyICError("Dub/Processor-Video (output)", err);

				fShowDecompressedFrame = false;
			VDCHECKPOINT;

			compressorVideoFormat->bmiHeader.biSizeImage = dwSize;

			CHECK_FPU_STACK
		}

		if (!AVIout->videoOut->write(isKey ? AVIIF_KEYFRAME : 0, (char *)lpCompressedData, dwBytes, 1))
			throw "Error writing video frame.";

	} else {

/*		if (!AVIout->videoOut->write(AVIIF_KEYFRAME, (char *)frameBuffer, filters.OutputBitmap()->size, 1))
			throw MyError("Error writing video frame.");

		dwBytes = filters.OutputBitmap()->size;*/

		VDCHECKPOINT;
		if (!AVIout->videoOut->write(AVIIF_KEYFRAME, (char *)frameBuffer, AVIout->videoOut->getImageFormat()->biSizeImage, 1))
			throw MyError("Error writing video frame.");
		VDCHECKPOINT;

		dwBytes = AVIout->videoOut->getImageFormat()->biSizeImage;
		isKey = true;
	}

	vInfo.total_size += dwBytes + 24;

	VDCHECKPOINT;

	if (okToDraw || fPreview) {
		if (opt->video.fShowInputFrame) {
			if (inputHisto) {
				inputHisto->Draw(hDCWindow, &rInputHistogram);
			}

			if (pdsInput) {
				if (opt->video.nPreviewFieldMode)
					blitter->postDirectDrawCopyLaced(
							BUFFERID_INPUT,
							vSrc->getFrameBuffer(),
							vSrc->getDecompressedFormat(),
							pdsInput,
							opt->video.nPreviewFieldMode>=2
					);
				else
					blitter->postDirectDrawCopy(
							BUFFERID_INPUT,
							vSrc->getFrameBuffer(),
							vSrc->getDecompressedFormat(),
							pdsInput
					);
			} else if (hbmInput) {
				if (opt->video.nPreviewFieldMode)
					blitter->postBitBltLaced(
							BUFFERID_INPUT,
							hDCWindow,
							rInputFrame.left, rInputFrame.top,
							rInputFrame.right-rInputFrame.left, rInputFrame.bottom-rInputFrame.top,
							hdcCompatInput,
							0,0,
							opt->video.nPreviewFieldMode>=2
					);
				else
					blitter->postStretchBlt(
							BUFFERID_INPUT,
							hDCWindow,
							rInputFrame.left, rInputFrame.top,
							rInputFrame.right-rInputFrame.left, rInputFrame.bottom-rInputFrame.top,
							hdcCompatInput,
							0,0,
							vSrc->getDecompressedFormat()->biWidth,vSrc->getDecompressedFormat()->biHeight
					);
			} else if (hDDInput)
				blitter->post(
						BUFFERID_INPUT,
						hDDInput,
						hDCWindow,
						rInputFrame.left, rInputFrame.top,
						rInputFrame.right-rInputFrame.left, rInputFrame.bottom-rInputFrame.top,
						vSrc->getDecompressedFormat(),
						vSrc->getFrameBuffer(),
						0,0,
						vSrc->getDecompressedFormat()->biWidth,vSrc->getDecompressedFormat()->biHeight,
						DDF_SAME_HDC | DDF_SAME_DRAW
					);
			else if (g_fWine)
				blitter->postStretchDIBits(
						BUFFERID_INPUT,
						hDCWindow,
						rInputFrame.left, rInputFrame.top,
						rInputFrame.right-rInputFrame.left, rInputFrame.bottom-rInputFrame.top,
						0,0,
						vSrc->getDecompressedFormat()->biWidth,vSrc->getDecompressedFormat()->biHeight,
						vSrc->getFrameBuffer(),
						(LPBITMAPINFO)vSrc->getDecompressedFormat(),
						DIB_RGB_COLORS,
						SRCCOPY
					);
			else
				blitter->unlock(BUFFERID_INPUT);
		} else
			blitter->unlock(BUFFERID_INPUT);

		if (hicOutput && lpCompressedData)
			blitter->postICDraw(BUFFERID_PACKED, hicOutput, isKey ? 0 : ICDRAW_NOTKEYFRAME, AVIout->videoOut->getImageFormat(), lpCompressedData, dwBytes, vInfo.processed);

		if (opt->video.mode == DubVideoOptions::M_FULL && opt->video.fShowOutputFrame && (!outputDecompressor || dwBytes)) {
			if (outputHisto) {
				outputHisto->Zero();
				outputHisto->Process(filters.LastBitmap());

				outputHisto->Draw(hDCWindow, &rOutputHistogram);
			}

			if (pdsOutput) {
				if (opt->video.nPreviewFieldMode)
					blitter->postDirectDrawBlitLaced(
							BUFFERID_OUTPUT,
							DDrawObtainPrimary(),
							pdsOutput,
							rOutputFrame.left+x_client, rOutputFrame.top+y_client,
							rOutputFrame.right-rOutputFrame.left, rOutputFrame.bottom-rOutputFrame.top,
							opt->video.nPreviewFieldMode>=2
							);
				else
					blitter->postDirectDrawBlit(
							BUFFERID_OUTPUT,
							DDrawObtainPrimary(),
							pdsOutput,
							rOutputFrame.left+x_client, rOutputFrame.top+y_client,
							rOutputFrame.right-rOutputFrame.left, rOutputFrame.bottom-rOutputFrame.top);
			} else if (hbmOutput) {
				if (opt->video.nPreviewFieldMode)
					blitter->postBitBltLaced(
							BUFFERID_OUTPUT,
							hDCWindow,
							rOutputFrame.left, rOutputFrame.top,
							rOutputFrame.right-rOutputFrame.left, rOutputFrame.bottom-rOutputFrame.top,
							hdcCompatOutput,
							0,0,
							opt->video.nPreviewFieldMode>=2
					);
				else
					blitter->postStretchBlt(
							BUFFERID_OUTPUT,
							hDCWindow,
							rOutputFrame.left, rOutputFrame.top,
							rOutputFrame.right-rOutputFrame.left, rOutputFrame.bottom-rOutputFrame.top,
							hdcCompatOutput,
							0,0,
							filters.OutputBitmap()->w,
							filters.OutputBitmap()->h
					);
			} else if (hDDOutput)
				blitter->post(
						BUFFERID_OUTPUT,
						hDDOutput,
						hDCWindow,
						rOutputFrame.left, rOutputFrame.top,
						rOutputFrame.right-rOutputFrame.left, rOutputFrame.bottom-rOutputFrame.top,
						&compressorVideoFormat->bmiHeader,
						filters.OutputBitmap()->data, //outputBuffer,
						0,0,
						filters.OutputBitmap()->w,
						filters.OutputBitmap()->h,
						DDF_SAME_HDC | DDF_SAME_DRAW
				);
			else if (g_fWine)
				blitter->postStretchDIBits(
						BUFFERID_OUTPUT,
						hDCWindow,
						rOutputFrame.left, rOutputFrame.top,
						rOutputFrame.right-rOutputFrame.left, rOutputFrame.bottom-rOutputFrame.top,
						0,0,
						filters.OutputBitmap()->w,
						filters.OutputBitmap()->h,
						filters.OutputBitmap()->data, //outputBuffer,
						(LPBITMAPINFO)&compressorVideoFormat->bmiHeader,
						DIB_RGB_COLORS,
						SRCCOPY
					);
			else
				blitter->unlock(BUFFERID_OUTPUT);

		} else
			blitter->unlock(BUFFERID_OUTPUT);

		--okToDraw;
	} else {
		blitter->unlock(BUFFERID_OUTPUT);
		blitter->unlock(BUFFERID_INPUT);
		blitter->unlock(BUFFERID_PACKED);
	}

//	guiSetStatus("Pulse clock: %ld\n", g_lPulseClock);
	if (opt->perf.fDropFrames && fPreview) {
		long lFrameDelta;

		lFrameDelta = blitter->getFrameDelta();

		if (opt->video.nPreviewFieldMode)
			lFrameDelta >>= 1;

//		guiSetStatus("Pulse clock: %ld, Delta: %ld\n", 255, g_lPulseClock, lFrameDelta);

		if (lFrameDelta < 0) lFrameDelta = 0;
		
		if (lFrameDelta > 0) {
			lDropFrames = lFrameDelta;
		}

//		vInfo.cur_proc_src = opt->video.frameRateDecimation * lFrameDelta;
	}


	blitter->nextFrame(opt->video.nPreviewFieldMode ? 2 : 1);

	vInfo.cur_proc_src += opt->video.frameRateDecimation;
	++vInfo.processed;

	if (opt->video.mode)
		i64SegmentCredit += lVideoSizeEstimate - (dwBytes + (dwBytes&1));

	pStatusHandler->NotifyNewFrame(isKey ? dwBytes : dwBytes | 0x80000000);

	VDCHECKPOINT;

}

void Dubber::WriteAudio(void *buffer, long lActualBytes, long lActualSamples) {
	if (!lActualBytes) return;

	if (!AVIout->audioOut->write(AVIIF_KEYFRAME, (char *)buffer, lActualBytes, lActualSamples))
		throw MyError("Error writing audio data.");

	aInfo.cur_proc_src += lActualBytes;
}

void Dubber::WriteAudio2(void *buffer, long lActualBytes, long lActualSamples) {
	if (!lActualBytes) return;

	if (!AVIout->audio2Out->write(AVIIF_KEYFRAME, (char *)buffer, lActualBytes, lActualSamples))
		throw MyError("Error writing audio data.");

	a2Info.cur_proc_src += lActualBytes;
}

void Dubber::ProcessingThreadKickstart(void *thisPtr) {
	InitThreadData("Processing");
	((Dubber *)thisPtr)->ProcessingThread();
	DeinitThreadData();
}

void Dubber::ProcessingThread() {
	BOOL quit = FALSE;
	BOOL firstPacket = TRUE;
	BOOL stillAudio = TRUE;

	lDropFrames = 0;
	vInfo.processed = 0;

	_RPT0(0,"Dub/Processor: start\n");

	InterlockedIncrement((LONG *)&lThreadsActive);

	try {
		DEFINE_SP(sp);

		do {
			void *buf;
			long len;
			long samples;
			long dframe;
			int exdata;
			int handle;
			int droptype;
_RPT0(0,"Dub/Processor: do ...while\n");
			while(!fAbort && (buf = pipe->getReadBuffer(&len, &samples, &dframe, &exdata, &droptype, &handle, 1000))) {
	_RPT0(0,"Dub/Processor: while (!fAbort && (buf = ...\n");
				CHECK_STACK(sp);

				if (exdata<0) {
					
_RPT0(0,"Dub/Processor: exdata<0\n");
					if( exdata==-2 ) {
						WriteAudio2(buf, len, samples);
						if (firstPacket && fPreview) {
							AVIout->audio2Out->flush();
							blitter->enablePulsing(TRUE);
							firstPacket = FALSE;

							if(hicOutput)
								ICDrawStart(hicOutput);
						}
					} else {
						WriteAudio(buf, len, samples);
						if (firstPacket && fPreview) {
							AVIout->audioOut->flush();
							blitter->enablePulsing(TRUE);
							firstPacket = FALSE;
							mbAudioFrozen = false;

							if (hicOutput)
								ICDrawStart(hicOutput);
						}
					}
				} else {
_RPT0(0,"Dub/Processor: exdata>=0\n");
					if (firstPacket && fPreview && !aSrc) {
						blitter->enablePulsing(TRUE);
						firstPacket = FALSE;
					}
					WriteVideoFrame(buf, exdata, droptype, len, samples, dframe);

					if(fPreview) {
						if(aSrc) {
							((AVIAudioPreviewOutputStream *)AVIout->audioOut)->start();
							mbAudioFrozenValid = true;
						}
						if(a2Src) {
							((AVIAudioPreviewOutputStream *)AVIout->audio2Out)->start();
						}
					}
				}
				pipe->releaseBuffer(handle);

				if (stillAudio && pipe->isNoMoreAudio()) {
					// HACK!! if it's a preview, flush the audio

					if (AVIout->isPreview()) {
						_RPT0(0,"Dub/Processor: flushing audio...\n");
						AVIout->audioOut->flush();
						_RPT0(0,"Dub/Processor: flushing audio....\n");
					}

					stillAudio = FALSE;
				}

			}
		} while(!fAbort && !pipe->isFinalized());
	} catch(MyError& e) {
		if (!fError) {
			err.TransferFrom(e);
			fError = true;
		}
		pipe->abort();
		fAbort = TRUE;
	}

	//	pulco : nandub had additional code here (for SBC ?)

_RPT0(0,"Dub/Processor: finalizing\n");

	pipe->isFinalized();

	// if preview mode, choke the audio

	if(AVIout->isPreview()) {
		if(AVIout->audioOut)
			((AVIAudioPreviewOutputStream *)AVIout->audioOut)->stop();
		if(AVIout->audio2Out)
			((AVIAudioPreviewOutputStream *)AVIout->audio2Out)->stop();
	}

	_XRPT0(0,"Dub/Processor: end\n");

	hThreadProcessor = NULL;

	InterlockedDecrement((LONG *)&lThreadsActive);
	SetEvent(hEventAbortOk);
}

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

void Dubber::Abort() {
#ifdef STOP_SPEED_DEBUGGING
	__asm {
		rdtsc
		mov dword ptr start_time+0,eax
		mov dword ptr start_time+4,edx
	}
#endif

	fUserAbort = true;
	fAbort = true;
	PostMessage(g_hWnd, WM_USER, 0, 0);
}

bool Dubber::isAbortedByUser() {
	return fUserAbort;
}

void Dubber::Tag(int x, int y) {
	POINT p;

	p.x = x;
	p.y = y;

	if (PtInRect(&rInputFrame, p))
		opt->video.fShowInputFrame = !opt->video.fShowInputFrame;
	else if (PtInRect(&rOutputFrame, p))
		opt->video.fShowOutputFrame = !opt->video.fShowOutputFrame;
	else if (PtInRect(&rInputHistogram, p)) {
		if (inputHisto) inputHisto->SetMode(Histogram::MODE_NEXT);
	} else if (PtInRect(&rOutputHistogram, p)) {
		if (outputHisto) outputHisto->SetMode(Histogram::MODE_NEXT);
	}
}

void Dubber::RealizePalette() {
	if (hDDOutput)
		DrawDibRealize(hDDOutput, hDCWindow, FALSE);
}

void Dubber::SetPriority(int index) {
	SetThreadPriority(hThreadMain, g_iPriorities[index][0]);
	SetThreadPriority(hThreadProcessor, g_iPriorities[index][1]);
}

void Dubber::UpdateFrames() {
	++okToDraw;
}