/***********************************************************************
 * VirtualDub Modification for OGM
 *
 * Copyright (C) 2002 Cyrius
 *
 * 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 (see the file COPYING); if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * or visit http://www.gnu.org/copyleft/gpl.html
 *
 ***********************************************************************
 *
 *
 *
 */

#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 "OGMOutput.h"
#include "../Histogram.h"
#include "OGMAudioSource.h"
#include "OGMAudio.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 "OGMDub.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

int BYTES_FOR(__int64 n) {
	if(n<2)
		return 0;

	int i=1;
	while(n>>=8)
		i++;

	return i;
}

// Dubber class is fine but we need some changes for OGM :)
class OGMDubber : public Dubber {
private:
	long lStartOffsetMS;
	long lStartOffset2MS;
	double nSamplesPerBlock;
	double nSamplesPerBlock2;
	ogm_stream *ogm_streams;
	void InitOGMStreams(void);
	void MainAddAudioFrame(int lag);
	void MainAddAudio2Frame(int lag);
	void MainAddAudioFrame(ogm_stream *stream, int lag);
	long ReadAudio(long& lAStreamPos, long samples);
	long ReadAudio2(long& lAStreamPos, long samples);
	long ReadAudio(ogm_stream *stream, long& lAStreamPos, long samples);
	void MainThread();
	void ProcessingThread();
	void WriteOGMAudio(int serial, void *buffer, long lActualBytes, long lActualSamples);
	BOOL AudioEnd(void);
public:
	OGMDubber(DubOptions *, ogm_stream *ogm_streams);
	void InitAudioConversionChain();
	void InitAudio2ConversionChain();
	//void Init(VideoSource *video, AudioSource *audio, AVIOutput *out, char *szFile, HDC hDC, COMPVARS *videoCompVars);
	virtual void Init(VideoSource *video, AudioSource *audio, AudioSource *audio2, AVIOutput *out, char *szFile, HDC hDC, COMPVARS *videoCompVars);
};


IDubber *CreateOGMDubber(DubOptions *xopt, ogm_stream *ogm_streams) {
	return new OGMDubber(xopt, ogm_streams);
}

OGMDubber::OGMDubber(DubOptions *xopt, ogm_stream *ogm_streams)
: Dubber(xopt) {
	this->ogm_streams = ogm_streams;
	lStartOffsetMS = 0;
	lStartOffset2MS = 0;
	nSamplesPerBlock = 1;
	nSamplesPerBlock2 = 1;
}

void OGMDubber::InitOGMStreams(void) {
	if(!ogm_streams)
		return;

	ogm_stream *stream = ogm_streams;
	while(stream) {
		memset(&stream->aInfo, 0, sizeof(stream->aInfo));
		stream->serial = -1;

		stream->aInfo.start_src	= stream->audio->lSampleFirst;
		stream->aInfo.end_src		= stream->audio->lSampleLast;

		stream->aInfo.cur_src		= stream->aInfo.start_src;

		long lStartOffsetMS = -stream->offset;

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

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

		stream->lStartOffsetMS = lStartOffsetMS;
		if(lStartOffsetMS < 0)
			lStartOffsetMS = 0;

		stream->aInfo.start_src += stream->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)
								*
								stream->audio->streamInfo.dwRate
							)
							* vInfo.usPerFrame
						) / ((__int64)stream->audio->streamInfo.dwScale * 1000000 * opt->video.frameRateDecimation));

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

			// resampling audio?

			stream->aInfo.resampling = false;
			stream->aInfo.converting = false;

			// No resampling or converting
			/*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);

				}
			}*/

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

		stream->aInfo.cur_proc_src = stream->aInfo.cur_src;

		stream->audio->streamBegin(fPreview);

		// Maybe we already worked with the stream, so delete it before dubbing
		if(stream->audioStream)
			delete stream->audioStream;
		stream->audioStream = NULL;

		// If no audio stream defined, create it
		if(stream->use && !(stream->audioStream = new AudioStreamSource(stream->audio, stream->aInfo.start_src, stream->audio->lSampleLast - stream->aInfo.start_src, opt->audio.mode > DubAudioOptions::M_NONE)))
			throw MyError("Dub: Unable to create audio stream source");

		if(stream->use && inputSubsetActive) {
			AudioStream *new_stream = NULL;
			if(!(new_stream = new OGMAudioSubset(stream->audioStream, inputSubsetActive, vInfo.usPerFrameIn,
					opt->audio.fStartAudio ? MulDiv(vInfo.usPerFrameIn, vInfo.start_src, 1000) : 0, FALSE)
					))
			throw MyError("Dub: Unable to create audio subset filter");

			stream->audioStream = new_stream;
		}

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

		if(vSrc && stream->use && opt->audio.fEndAudio)
			stream->audioStream->SetLimit((long)((
				((__int64)(vInfo.end_src - vInfo.start_src) * stream->audioStream->GetFormat()->nSamplesPerSec * (__int64)vInfo.usPerFrameIn)) / ((__int64)1000000)
				));
		stream = stream->next;
	}
}

long OGMDubber::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;
				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, nSamplesPerBlock * 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, nSamplesPerBlock * ltActualSamples, 0,-1,0, handle);

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

			} while(samples && ltActualSamples);

			return lActualBytes;

		}
		// 16/11/2002, Cyrius : Better handle AC3 here too ^^'
		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, nSamplesPerBlock * lActualSamples,0, -1,0, handle);

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

	return lActualBytes;
}

long OGMDubber::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, nSamplesPerBlock2 * ltActualSamples,0, -2,0, handle);

				a2Info.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 = audio2Stream->Read(destBuffer, samples, &ltActualBytes);

				lActualBytes += ltActualBytes;
				lActualSamples += ltActualSamples;

				samples -= ltActualSamples;
				lAStreamPos += ltActualSamples;

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

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

			} while(samples && ltActualSamples);

			return lActualBytes;

		}
		// 16/11/2002, Cyrius : Better handle AC3 here too ^^'
		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, nSamplesPerBlock2 * lActualSamples,0, -2,0, handle);

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

	return lActualBytes;
}

void OGMDubber::MainAddAudioFrame(int lag) {
	long lAvgBytesPerSec = audioTimingStream->GetFormat()->nAvgBytesPerSec;
	// nAvgBytesPerSec / nBlockAlign = nSamplesPerSec / samples_per_block
	long lSamplesPerSec = audioTimingStream->GetFormat()->nSamplesPerSec;
	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;

		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);

	// For OGM while reading audio samples the audio stream may go
	// beyond video
	//_ASSERT(aInfo.cur_src <= lAudioPoint);
}

void OGMDubber::MainAddAudio2Frame(int lag) {
	long lAvgBytesPerSec = audio2TimingStream->GetFormat()->nAvgBytesPerSec;
	// nAvgBytesPerSec / nBlockAlign = nSamplesPerSec / samples_per_block
	long lSamplesPerSec = audioTimingStream->GetFormat()->nSamplesPerSec;
	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->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;

		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);

	// For OGM while reading audio samples the audio stream may go
	// beyond video
	//_ASSERT(aInfo.cur_src <= lAudioPoint);
}

long OGMDubber::ReadAudio(ogm_stream *stream, long& lAStreamPos, long samples) {
	LONG lActualBytes=0;
	LONG lActualSamples=0;

	void *buffer;
	int handle;
	long len;

	LONG ltActualBytes, ltActualSamples;
	char *destBuffer;

	//len = samples * audioStream->GetFormat()->nBlockAlign;
	// There may be VBR streams, so get a big buffer in case
	len = (3 * samples * stream->audioStream->GetFormat()->nAvgBytesPerSec) / stream->audioStream->GetFormat()->nSamplesPerSec;
	if(len < 64*1024)
		len = 64*1024;
	// Do a loop to fill multiple buffers when necessary (it is the case
	// when the input audio is a Vorbis stream : we can read and write only a packet
	// at a time, i.e. a limited number of samples at a time
	do {
		buffer = pipe->getWriteBuffer(len, &handle, INFINITE);
		if (!buffer) return 0; // aborted

		destBuffer = (char *)buffer;

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

		//02-11-2002 (Cyrius) : workaround for subtitle cutting (on selection end)
		if(stream->audio->isText) {
			// For text streams, we verify we won't exceed the video stream duration
			if(stream->aInfo.cur_src + ltActualSamples > stream->aInfo.end_src)
				ltActualSamples = stream->aInfo.end_src - stream->aInfo.cur_src;
		}

		lActualBytes += ltActualBytes;
		lActualSamples += ltActualSamples;

		samples -= ltActualSamples;
		destBuffer += ltActualBytes;
		len -= ltActualBytes;
		lAStreamPos += ltActualSamples;

		// Beware not to send those data to the "normal" audio streams
		// handled by VirtualDub ;)
		// Little trick : When updating audio processing (info we see when we are dubbing)
		// VirtualDub test if exdata is equal to -1 (-1 == first audio) and -2 (second audio)
		// When sending data we bufferized (in AVIPipe) to the correct stream
		// exdata < 0 == audio
		// So set exdata = -3-stream->serial, so that VirtualDub will see it as
		// audio when treating bufferized data (but not as the main audio strem)
		// and we will be able to retrieve the stream serial when processing data
		// to our OGMOutput :)
		//02-11-2002 (Cyrius) : forgot there is a second audio stream now :) (-2 -> -3)
		pipe->postBuffer(ltActualBytes, ltActualSamples, 0, -3-stream->serial, 0, handle);

		stream->aInfo.total_size += ltActualBytes;
		i64SegmentSize += ltActualBytes;

	}while((samples>0) && (ltActualSamples>0));
	// Only test samples
	// Cannot use len calculation for knowing how many bytes were really needed
	//} while(samples && ltActualBytes);

	return lActualBytes;
}

void OGMDubber::MainAddAudioFrame(ogm_stream *stream, int lag) {
	long lAvgBytesPerSec = stream->audioStream->GetFormat()->nAvgBytesPerSec;
	// nAvgBytesPerSec / nBlockAlign = nSamplesPerSec
	long lSamplesPerSec = stream->audioStream->GetFormat()->nSamplesPerSec;
	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;

		/*lAudioPoint = (long)(aInfo.start_src + aInfo.lPreloadSamples +
			(
				((__int64)lAvgBytesPerSec*(__int64)vInfo.usPerFrameNoTelecine*lFrame)
				/
				((__int64)1000000*audioTimingStream->GetFormat()->nBlockAlign)
			));*/
		lAudioPoint = (long)(stream->aInfo.start_src + stream->aInfo.lPreloadSamples +
			(
				((__int64)lSamplesPerSec*(__int64)vInfo.usPerFrameNoTelecine*lFrame)
				/
				((__int64)1000000)
			));

	} 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 = stream->aInfo.start_src + stream->aInfo.lPreloadSamples +
				(LONG)((i64CurrentFrameMs * lSamplesPerSec) / (1000));

	}

	// Round lAudioPoint to next block size if preview

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

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

	if(lAudioPoint <= stream->aInfo.cur_src)
		return;

	/*if(fEnableSpill)
		CheckSpill(vInfo.cur_src, lAudioPoint);*/

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

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

	// For OGM while reading audio samples the audio stream may go
	// beyond video
	//_ASSERT(aInfo.cur_src <= lAudioPoint);
}

void OGMDubber::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");
		}
	}
	nSamplesPerBlock =
		(double)audioStream->GetFormat()->nSamplesPerSec /
		((double)audioStream->GetFormat()->nAvgBytesPerSec / (double)audioStream->GetFormat()->nBlockAlign);
}



void OGMDubber::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");
		}
	}
	nSamplesPerBlock2 =
		(double)audio2Stream->GetFormat()->nSamplesPerSec /
		((double)audio2Stream->GetFormat()->nAvgBytesPerSec / (double)audio2Stream->GetFormat()->nBlockAlign);
}


void OGMDubber::MainThread() {

	///////////
	// First init our other streams (if any)
	InitOGMStreams();
	///////////

	_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) {
			// Do not do a preload : seems too much samples at a time is fatal for OggDS
			// presently
			aInfo.lPreloadSamples = 0;

			if(lStartOffsetMS < 0)
				((OGMAudioOutputStream *)AVIout->audioOut)->update_granulepos(-lStartOffsetMS);
		}

		if (a2Src) {
			// Do not do a preload : seems too much samples at a time is fatal for OggDS
			// presently
			a2Info.lPreloadSamples = 0;

			if(lStartOffset2MS < 0)
				((OGMAudioOutputStream *)AVIout->audio2Out)->update_granulepos(-lStartOffset2MS);
		}

		// Tel the output we have other streams :)
		ogm_stream *stream = ogm_streams;
		while(stream) {
			if(stream->use &&
				(!vInfo.fAudioOnly || (vInfo.fAudioOnly && stream->to_demux))) {
				int serial = ((OGMOutputFile *)AVIout)->add_stream(stream->audio, stream->comments);
				stream->serial = serial;
				stream->aInfo.lPreloadSamples = 0;
				long lag = ((OGMOutputFile *)AVIout)->prepareStream(serial, stream->aInfo.cur_src);
				stream->aInfo.cur_src += lag;
				// If the first frame of data we want is beyond the beginning of
				// the stream, no problem. But it is not that easy in the contrary case
				// VirtualDub fill data with 0x00 or 0x80 concerning the samples
				// that must be created due to the gap between when we want
				// to start, and when really start the stream.
				// Unfortunately this is not good for a subtitle stream (unless you
				// like to see a "...€€€€..." subtitle at the beginning of the clip ;)
				// Moreover it make WMPlayer crash with audio :(
				if(stream->lStartOffsetMS < 0)
					((OGMOutputFile *)AVIout)->update_granulepos(stream->serial, -stream->lStartOffsetMS);
			}
			stream = stream->next;
		}

		// Ask output to write all headers in the output file
		((OGMOutputFile *)AVIout)->write_headers();

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

		// Do it!!!

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

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

				while(!fAbort && (vInfo.cur_src<vInfo.end_src+nVideoLag || !AudioEnd() || 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);
					}

					// Do our other streams
					stream = ogm_streams;
					while(stream) {
						if(/*stream->audioStream &&*/ stream->use && !stream->audioStream->isEnd()) {
							MainAddAudioFrame(stream, nVideoLag + mFramesDelayedByCodec);
						}
						// Warning : even if it is the end, don't
						// ask the output to produce EOS for the stream
						// because we may not have processed all data yet (there is
						// a thread working in background to send the data !!!)
						stream = stream->next;
					}

					if (lSpillVideoPoint && vInfo.cur_src == lSpillVideoPoint && aInfo.cur_src == lSpillAudioPoint)
						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);
					}

				// Do our other streams
				stream = ogm_streams;
				while(stream) {
					while(!fAbort && stream->use && (!vInfo.fAudioOnly || (vInfo.fAudioOnly && stream->to_demux)) && !stream->audioStream->isEnd()) {
						ReadAudio(stream, stream->aInfo.cur_src, 1024); //8192);
					}
					stream = stream->next;
				}

				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");
}

BOOL OGMDubber::AudioEnd(void) {
	if( (audioStream && !audioStream->isEnd()) || (audio2Stream && !audio2Stream->isEnd()) )
		return FALSE;

	ogm_stream *stream = ogm_streams;
	while(stream) {
		if(stream->use && !stream->audioStream->isEnd())
			return FALSE;
		stream = stream->next;
	}

	return TRUE;
}

void OGMDubber::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;

			while(!fAbort && (buf = pipe->getReadBuffer(&len, &samples, &dframe, &exdata, &droptype, &handle, 1000))) {

				CHECK_STACK(sp);

				if(exdata<0) {
					// Packets are cut in segments of 255 bytes max
					// Each segment cost 1 bytes in the Page header (to say its length)
					// add 1 byte for Packet header + ? bytes for coding number of samples
					i64SegmentSize += len / 255 + 2 + BYTES_FOR(samples);
					// There is :
					if(exdata == -1) {
						aInfo.total_size += len / 255 + 2 + BYTES_FOR(samples);
						// The main audio stream handled by VirtualDub ...
						WriteAudio(buf, len, samples);
						if (firstPacket && fPreview) {
							AVIout->audioOut->flush();
							blitter->enablePulsing(TRUE);
							firstPacket = FALSE;
							mbAudioFrozen = false;

							if (hicOutput)
								ICDrawStart(hicOutput);
						}
					} else if(exdata == -2) {
						a2Info.total_size += len / 255 + 2 + BYTES_FOR(samples);
						// The second audio stream handled by VirtualDub ...
						WriteAudio2(buf, len, samples);
					} else {
						// ... and our own streams for OGM
						// Write those data to the correct output :)
						//02-11-2002 (Cyrius) : forgot there is a second audio stream now :) (-2 -> -3)
						WriteOGMAudio(-3-exdata, buf, len, samples);
					}
				} else {
					if (firstPacket && fPreview && !aSrc && !ogm_streams) {
						blitter->enablePulsing(TRUE);
						firstPacket = FALSE;
					}
					// VirtualDub will add 24 bytes (for AVI chunk header ?)
					// add 1 byte for Packet header
					// Don't use len : it doesn't correspond to the size of compressed frame (when using compression)
					vInfo.total_size -= 23;
					i64SegmentSize -= 23;
					WriteVideoFrame(buf, exdata, droptype, len, samples, dframe);

					if (fPreview && aSrc) {
						((AVIAudioPreviewOutputStream *)AVIout->audioOut)->start();
						mbAudioFrozenValid = true;
					}
				}
				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;
	}

	pipe->isFinalized();

	// if preview mode, choke the audio

	if (AVIout->audioOut && AVIout->isPreview())
		((AVIAudioPreviewOutputStream *)AVIout->audioOut)->stop();

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

	hThreadProcessor = NULL;

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

void OGMDubber::WriteOGMAudio(int serial, void *buffer, long lActualBytes, long lActualSamples) {
	if(!lActualBytes) return;

	ogm_stream *stream = ogm_streams;
	while(stream) {
		if(stream->serial == serial)
			break;
		stream = stream->next;
	}

	if(!stream)
		return;

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

	stream->aInfo.total_size += lActualBytes / 255 + 2 + BYTES_FOR(lActualSamples);
	stream->aInfo.cur_proc_src += lActualBytes;
}

//void InitOGMStreamValuesStatic(DubVideoStreamInfo& vInfo, DubAudioStreamInfo& aInfo, VideoSource *video, AudioSource *audio, DubOptions *opt, FrameSubset *pfs, long *plStartOffsetMS) {
void InitOGMStreamValuesStatic(DubVideoStreamInfo& vInfo, DubAudioStreamInfo& aInfo, DubAudioStreamInfo& a2Info, VideoSource *video, AudioSource *audio, AudioSource *audio2, DubOptions *opt, FrameSubset *pfs, long *plStartOffsetMS, long *plStartOffset2MS) {
	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);
		}

		if(plStartOffsetMS) {
			*plStartOffsetMS = lStartOffsetMS;
			if(lStartOffsetMS < 0)
				lStartOffsetMS = 0;
		}

		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);
		}

		if(plStartOffset2MS) {
			*plStartOffset2MS = lStartOffsetMS;
			if(lStartOffsetMS < 0)
				lStartOffsetMS = 0;
		}

		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);
}

//void OGMDubber::Init(VideoSource *video, AudioSource *audio, AVIOutput *out, char *szFile, HDC hDC, COMPVARS *videoCompVars) {
void OGMDubber::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

	//InitOGMStreamValuesStatic(vInfo, aInfo, video, audio, opt, inputSubsetActive, &lStartOffsetMS);
	InitOGMStreamValuesStatic(vInfo, aInfo, a2Info, video, audio, audio2, opt, inputSubsetActive, &lStartOffsetMS, &lStartOffset2MS);

	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;

	// Tell our output what is our subset so that it can change
	// chapter times
	((OGMOutputFile *)AVIout)->setSubset(
		inputSubsetActive
		, opt->video.lStartOffsetMS
		, vSrc->samplesToMs(
		  inputSubsetActive ? inputSubsetActive->getTotalFrames()
			                  : vSrc->lSampleLast - vSrc->lSampleFirst)
			- opt->video.lEndOffsetMS
		, vInfo.usPerFrame);

	// 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");
}