/***********************************************************************
 * 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 "OGMOutput.h"
#include "../VideoSource.h"
#include "../Error.h"
#include <limits.h>

void AddComment(comment_list **comments, char *tag, char *comment);

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

OGMOutputFile::OGMOutputFile(comment_list *video_comments, comment_list *audio_comments, comment_list *audio2_comments, bool bDemux) {
	initialize();
	this->video_comments = video_comments;
	this->audio_comments = audio_comments;
	this->audio2_comments = audio2_comments;
	inputSubset = NULL;
	lStartMS = 0;
	lEndMS = _I32_MAX;
	this->bDemux = bDemux;
}

void OGMOutputFile::initialize(void) {
	fCaching = true;
	packetizers = NULL;
	file = NULL;
	hFile = NULL;
	fastIO = NULL;
	n_frame = 0;
	n_frames = 0;
	eos_packets_produced = false;
	video_serial = -1;
	audio_serial = -1;
	audio2_serial = -1;
	bDemux = false;
	intern_generator = new SerialGenerator(T_INCREASING, true);
	if(!intern_generator)
		throw MyMemoryError();
	s_generator = new SerialGenerator(T_INCREASING, true);
	if(!s_generator)
		throw MyMemoryError();
}

void OGMOutputFile::setSubset(FrameSubset *subset, long start_ms, long end_ms, long usPerFrame) {
	inputSubset = subset;
	lStartMS = start_ms;
	lEndMS = end_ms;
	this->usPerFrame = usPerFrame;
}

OGMOutputFile::~OGMOutputFile(void) {
	// Clean up
	if(intern_generator)
		delete intern_generator;

	packetizer_mesh *next_packetizer;
	while(packetizers) {
		next_packetizer = packetizers->next;
		delete packetizers;
		packetizers = next_packetizer;
	}

	// Delete the SerialGenerator after the Packetizers
	// because they call the generator
	if(s_generator)
		delete s_generator;

	if(hFile) {
		SetEndOfFile(hFile);
		CloseHandle(hFile);
	}

	if(file)
		free(file);

	delete fastIO;
}

comment_list *OGMOutputFile::translate_comments(comment_list *comments) {
	comment_list *new_comments = NULL;
	comment_list *comment = comments;
	while(comment) {
		if((!comment->tag)
		|| ((strlen(comment->tag)!=9) && (strlen(comment->tag)!=13))
		|| ((strlen(comment->tag)==9) && (strncmp(comment->tag, "CHAPTER", 7)))
		|| ((strlen(comment->tag)==13) && (strncmp(comment->tag, "CHAPTER", 7)||strncmp(comment->tag+9, "NAME", 4))) ) {
			// We don't have to modify this comment
			AddComment(&new_comments, comment->tag, comment->comment);
		}
		comment = comment->next;
	}
	translate_chapters(&new_comments, comments);
	return new_comments;
}

void OGMOutputFile::translate_chapters(comment_list **new_comments, comment_list *comments) {
	char tag[20];
	char time[20];
	int num_chapter_input = 1;
	int num_chapter_output = 1;
	comment_list *chapter_time = NULL;
	while(chapter_time = get_chapter_time(comments, num_chapter_input)) {
		bool keep = true;
		s64 new_time = 0;
		comment_list *chapter_name = get_chapter_name(comments, num_chapter_input);
		num_chapter_input++;
		int h, m, s, ms;
		if(sscanf(chapter_time->comment, "%d:%02d:%02d.%03d"
		, &h, &m, &s, &ms)!=4) {
			// Not a good time format, let as is
			sprintf(tag, "CHAPTER%02d", num_chapter_output);
			AddComment(new_comments, tag, chapter_time->comment);
			if(chapter_name) {
				sprintf(tag, "CHAPTER%02dNAME", num_chapter_output);
				AddComment(new_comments, tag, chapter_name->comment);
			}
			num_chapter_output++;
			continue;
		}

		s64 old_time = /*1000 * (*/
			ms
			+ 1000 * s
			+ 1000 * 60 * m
			+ 1000 * 60 * 60 * h
			/*)*/;

		s64 skipped_frames = 0;
		if(inputSubset) {
			keep = false;
			FrameSubsetNode *node = inputSubset->getFirstFrame();
			while(node) {
				// Normally for active subset there are nor maskes subsets ...
				if(node->bMask)
					skipped_frames += node->len;
				if( (node->start*usPerFrame/1000<=old_time)
				&& ((node->start+node->len)*usPerFrame/1000>=old_time) ) {
					// This node contains our chapter
					if(node->bMask)
						break;
					keep = true;
					old_time -= (skipped_frames * usPerFrame / 1000);
					break;
				}
				bool node_kept = !node->bMask;
				long old_end = node->start + node->len;
				node = inputSubset->getNextFrame(node);
				// If the current node is kept and the next one too
				// count frames between them (frames that are skipped)
				if(node && node_kept && !node->bMask)
					skipped_frames += node->start - old_end;
			}
		}

		if( (old_time < lStartMS/**1000*/) || (old_time>lEndMS/**1000*/) )
			keep = false;

		if(keep) {
			old_time -= (lStartMS/**1000*/);
			new_time = old_time/* / 1000*/;
			sprintf(tag, "CHAPTER%02d", num_chapter_output);
			int ms2 = new_time % 1000;
			int s2 = (new_time / 1000) % 60;
			int m2 = (new_time / 1000 / 60) %60;
			int h2 = (new_time / 1000 / 60 / 60);
			sprintf(time, "%02d:%02d:%02d.%03d", h2, m2, s2, ms2);
			AddComment(new_comments, tag, time);

			sprintf(tag, "CHAPTER%02dNAME", num_chapter_output);
			AddComment(new_comments, tag, chapter_name->comment);
			num_chapter_output++;
		}
	}
}

void delete_comments(comment_list **p_comments) {
	if(!p_comments)
		return;
	comment_list *comment = NULL;
	while(*p_comments) {
		comment = (*p_comments)->next;
		delete *p_comments;
		*p_comments = comment;
	}
}

comment_list *get_chapter_time(comment_list *comments, int num_chapter) {
	char tag[20];
	sprintf(tag, "CHAPTER%02d", num_chapter);
	comment_list *comment = comments;
	while(comment) {
		if(comment->tag
			&& (!strcmp(comment->tag, tag)))
			break;
		comment = comment->next;
	}
	return comment;
}

comment_list *get_chapter_name(comment_list *comments, int num_chapter) {
	char tag[20];
	sprintf(tag, "CHAPTER%02dNAME", num_chapter);
	comment_list *comment = comments;
	while(comment) {
		if(comment->tag
			&& (!strcmp(comment->tag, tag)))
			break;
		comment = comment->next;
	}
	return comment;
}

void OGMOutputFile::sendComments(Packetizer *pktz, comment_list *comments) {
	if(!pktz)
		return;
	if(!comments) {
		pktz->produce_comment_packet(comments, true);
		return;
	}
	comment_list *new_comments = translate_comments(comments);
	pktz->produce_comment_packet(new_comments, true);
	delete_comments(&new_comments);
}

void OGMOutputFile::setComments(OGMReadStream *stream, Packetizer *pktz, comment_list *comments) {
	if(!stream)
		return;
	if(!comments) {
		stream->setComments(comments);
		stream->sendHeaders(pktz);
		return;
	}
	comment_list *new_comments = translate_comments(comments);
	stream->setComments(new_comments);
	stream->sendHeaders(pktz);
	delete_comments(&new_comments);
}

/*
 * Add a new packetizer to handle stream num_stream from the input file
 */
packetizer_mesh *OGMOutputFile::add_packetizer(int num_stream     // an arbitrary stream number (used to retrieve the correct Packetizer when reading data from the input file)
														, Packetizer *packetizer       // the packetizer to add to the list
														, void *extra                  // Pointer to optional extra data
														, bool will_have_few_packets) {// flag to say if the stream will have few packets or not
	if(_state == E_INVALID)
		throw MyError("Fatal error has previously occured with the Reader handling file [%s], cannot proceed\n",  file);

	packetizer_mesh *mesh = NULL;

	if(!packetizers) {
		packetizers = new packetizer_mesh;
		if(!packetizers) {
			_state = E_INVALID;
			throw MyMemoryError();
		}
		mesh = packetizers;
	} else {
		mesh = packetizers;
		if(mesh->num_stream == num_stream) {
			_RPT2(_CRT_WARN, "There is already a Packetizer handling stream %d in file %s\n", num_stream, file);
			return mesh;
		}
		while(mesh->next) {
			mesh = mesh->next;
			if(mesh->num_stream == num_stream) {
				_RPT2(_CRT_WARN, "There is already a Packetizer handling stream %d in file %s\n", num_stream, file);
				return mesh;
			}
		}

		mesh->next = new packetizer_mesh;
		if(!mesh->next) {
			_state = E_INVALID;
			throw MyMemoryError();
		}

		mesh = mesh->next;
	}

	mesh->num_stream      = num_stream;
	mesh->extra           = extra;
	mesh->has_few_packets = will_have_few_packets;
	mesh->packetizer      = packetizer;
	return mesh;
}

/*
 * Retrieve the packetizer associated to stream num_stream
 */
Packetizer *OGMOutputFile::get_packetizer(int num_stream, bool *few_packets) {
	Packetizer *packetizer = NULL;

	if(few_packets)
		*few_packets = false;

	packetizer_mesh *mesh = packetizers;
	while(mesh) {
		if(mesh->num_stream == num_stream) {
			if(few_packets)
				*few_packets = mesh->has_few_packets;
			return mesh->packetizer;
		}
		else
			mesh = mesh->next;
	}

	return packetizer;
}

/*
 * Retrieve the packetizer_mesh associated to stream num_stream
 */
packetizer_mesh *OGMOutputFile::get_packetizer_mesh(int num_stream) {
	packetizer_mesh *mesh = packetizers;
	while(mesh) {
		if(mesh->num_stream == num_stream)
			return mesh;
		else
			mesh = mesh->next;
	}

	return NULL;
}

/*
 * Tell if there are any pending header page in packetizers used
 */
bool OGMOutputFile::pending_header(void) {
	packetizer_mesh *mesh = packetizers;

	while(mesh) {
		if(mesh->packetizer->pending_header())
			return true;
		mesh = mesh->next;
	}

	return false;
}

/*
 * Retrieve the first pending header over all the streams
 */
muxer_page *OGMOutputFile::get_header(void) {
	packetizer_mesh *mesh = packetizers;

	// Get the max priority
	int max_priority = _I32_MIN;
	while(mesh) {
		if(mesh->packetizer->pending_header())
			if(mesh->packetizer->pending_mpage()->m_page->priority > max_priority)
				max_priority = mesh->packetizer->pending_mpage()->m_page->priority;
		mesh = mesh->next;
	}
	// Get the first one with this priority
	mesh = packetizers;
	while(mesh) {
		if(mesh->packetizer->pending_header())
			if(mesh->packetizer->pending_mpage()->m_page->priority == max_priority)
				return mesh->packetizer->get_header();
		mesh = mesh->next;
	}

	return NULL;
}

/*
 * Produce an EOS packet (thus finishing) for each handled stream
 */
void OGMOutputFile::produce_eos_packets(void) {
	if(eos_packets_produced)
		return;

	if(_state == E_INVALID)
		throw MyError("Fatal error has previously occured with the Reader handling file [%s], cannot proceed\n",  file);

#ifdef _DEBUG
	_RPT1(0, "Asking Packetizers of file [%s] to produce EOS Packet if not yet reached\n", file);
#endif

	packetizer_mesh *mesh = packetizers;

	while(mesh) {
		if( (!mesh->packetizer->eos())
			&& (mesh->packetizer->state()!=E_INVALID)
			&& (mesh->packetizer->state()!=STATE_EOS))
			mesh->packetizer->produce_eos_packet();
		mesh = mesh->next;
	}
	eos_packets_produced = true;;
	_state = STATE_EOS;
}

void OGMOutputFile::produce_eos_packet(int serial) {
	Packetizer *pkz = get_packetizer(serial);
	if(pkz && !pkz->eos())
		pkz->produce_eos_packet();
}

/*
 * Get a pending muxer_page
 */
muxer_page *OGMOutputFile::get_mpage(void) {
	if(_state == E_INVALID)
		throw MyError("Fatal error has previously occured with the Reader handling file [%s], cannot proceed\n",  file);

	packetizer_mesh *mesh = packetizers;
	while(mesh) {
		if(mesh->packetizer->pending_mpage())
			return mesh->packetizer->get_mpage();
		mesh = mesh->next;
	}

	_RPT1(_CRT_WARN, "No pending muxer_page available for file [%s]\n", file);

	return NULL;
}

/*
 * Get the pending muxer_page with the smallest timestamp
 */
muxer_page *OGMOutputFile::get_first_mpage(void) {
	if(_state == E_INVALID)
		throw MyError("Fatal error has previously occured with the Reader handling file [%s], cannot proceed\n",  file);

	packetizer_mesh *mesh         = packetizers;
	packetizer_mesh *winner_mesh  = packetizers;
	time_stamp smallest_timestamp = MAX_TIMESTAMP;
	time_stamp timestamp          = MAX_TIMESTAMP;
	while(mesh) {
		//if(mesh->packetizer->pending_mpage()
		//	&& ((timestamp=mesh->packetizer->get_smallest_timestamp())<smallest_timestamp) ) {
		if(mesh->packetizer->pending_mpage()
			&& ((timestamp=mesh->packetizer->get_smallest_last_timestamp())<smallest_timestamp) ) {
			winner_mesh = mesh;
			smallest_timestamp = timestamp;
		}
		mesh = mesh->next;
	}

	if(winner_mesh)
		return winner_mesh->packetizer->get_mpage();

	_RPT1(_CRT_WARN, "No pending muxer_page available for file [%s]\n", file); 

	return NULL;
}

/*
 * Write a muxer_page to a file
 */
void OGMOutputFile::write_mpage(muxer_page* m_page) {
	if(!m_page)
		throw MyError("Try to write a NULL muxer_page");
	ogg_page *opage = m_page->page;
	if(!opage)
		throw MyError("Try to write a NULL ogg_page from a muxer_page");
	if( (!opage->header) || (opage->header_len<=0) )
		throw MyError("Try to write an ogg_page with invalid header from a muxer_page");
#ifdef _DEBUG
	if( (!opage->body) || (opage->body_len<=0) )
		_RPT0(_CRT_WARN, "Writing an ogg_page with NULL body\n");
#endif

	if(fastIO) {
		fastIO->Put(opage->header, opage->header_len);
		fastIO->Put(opage->body, opage->body_len);
	} else {
		DWORD n_write = 0;
		if( (!WriteFile(hFile, opage->header, opage->header_len, &n_write, NULL)) || (n_write!=opage->header_len))
			throw MyWin32Error("%s: %%s", GetLastError(), szME);

		if( (!WriteFile(hFile, opage->body, opage->body_len, &n_write, NULL)) || (n_write!=opage->body_len))
			throw MyWin32Error("%s: %%s", GetLastError(), szME);
	}
}

/*
 * Tell if there are at least one pending muxer_page for each valid stream
 */
bool OGMOutputFile::pending_mpages(void) {
	packetizer_mesh *mesh = packetizers;

	// Some packetizers could be marked as handling few packets
	// Using prepare_mpages() in a normal way could result
	// in queueing a lot of Packets in the other Packetizers
	// before reaching the next available Packet
	// Hence the has_few_packets flag
	//
	// pending_mpages() will verify that all valid Packetizers that
	// haven't the has_few_packets flag have at least one muxer_page pending
	// But we must also verify that there is at least one Packetizer that
	// haven't the has_few_packets
	bool at_least_one_mpage_pending = false;
	while(mesh) {
		if((mesh->packetizer->state()!=E_INVALID)
			&& (mesh->packetizer->state()!=STATE_EOS)
			&& (!mesh->has_few_packets)
			&& (!mesh->packetizer->pending_mpage()))
			return false;
		if(mesh->packetizer->pending_mpage())
			at_least_one_mpage_pending = true;
		mesh = mesh->next;
	}

	// Reaching this point could mean :
	//1) There is one or more valid Packetizers without the has_few_packets
	//  flag, and they all have at least one muxer_page pending
	//2) All Packetizers have the has_few_packets flag
	//
	// Case 1 is OK and means at_least_one_mpage_pending is true, so return true;
	// Case 2 make us test the at_least_one_mpage_pending flag to know
	// if it's OK or not.
	return at_least_one_mpage_pending;
	//return true;
}


BOOL OGMOutputFile::process_data(int serial, bool key, LPVOID buffer, LONG length, LONG nSamples) {
	packetizer_mesh *mesh = get_packetizer_mesh(serial);
	if(mesh) {
		if(((extra_data *)mesh->extra)->is_vorbis) {
			mesh->packetizer->process_frame((unsigned char *)buffer, F_VORBIS, length, nSamples, key, false);
		} else {
			bool is_end = false;
			if(((extra_data *)mesh->extra)->is_text)
				is_end = ((extra_data *)mesh->extra)->audio->isEOS();
			mesh->packetizer->process_frame((unsigned char *)buffer, F_DATA, length, nSamples, key, is_end, ((extra_data *)mesh->extra)->is_text);
		}
		process_pending_mpages();
		return TRUE;
	} else
		return FALSE;
}

void OGMOutputFile::process_pending_mpages() {
	muxer_page *m_page = NULL;

	// Write in file only if we have enough data pending
	while(pending_mpages()) {
		m_page = get_first_mpage();
		write_mpage(m_page);
		if(m_page)
			delete m_page;
	}
}

void OGMOutputFile::flush_packetizers() {
	packetizer_mesh *mesh = packetizers;
	while(mesh) {
		mesh->packetizer->flush_pages();
		mesh = mesh->next;
	}
}

void OGMOutputFile::disable_os_caching() {
	fCaching = FALSE;
}

long OGMOutputFile::prepareStream(int serial, __int64 start) {
	ogg_int64_t lag = 0;
	packetizer_mesh *mesh = get_packetizer_mesh(serial);
	if(mesh) {
		if(mesh->extra && ((extra_data *)mesh->extra)->audio)
			lag = ((extra_data *)mesh->extra)->audio->samplesLag(start);
		mesh->packetizer->update_granulepos(lag);
	}
	return lag;
}

void OGMOutputFile::update_granulepos(int serial, long time_offset_ms) {
	Packetizer *pkz = get_packetizer(serial);
	if(pkz)
		pkz->update_granulepos((time_stamp)time_offset_ms);
}

///////////////////////////////////////////////////////////////////////////////
// Compatiblity with AVIOutput :

BOOL OGMOutputFile::initOutputStreams() {
	// Create the "fake" output streams
	if(!bDemux) {
		if (!(audioOut = new OGMAudioOutputStream(this))) return FALSE;
		if (!(audio2Out = new OGMAudioOutputStream(this))) return FALSE;
		if (!(videoOut = new OGMVideoOutputStream(this))) return FALSE;
	}

	// Give those streams their serial
	// Too bad it seems this function is caled before those stream serilas are defined
	// Cut and Paste into init()
	if(!bDemux) {
		((OGMAudioOutputStream *)audioOut)->setSerial(audio_serial);
		((OGMAudioOutputStream *)audio2Out)->setSerial(audio2_serial);
		((OGMVideoOutputStream *)videoOut)->setSerial(video_serial);
	}

	return TRUE;
}

BOOL OGMOutputFile::init(const char *szFile, LONG xSize, LONG ySize, BOOL videoIn, BOOL audioIn, BOOL audio2In, LONG bufferSize, BOOL is_interleaved) {
	file = strdup(szFile);

	if (!fCaching) {

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

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

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

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

	if(bDemux)
		return TRUE;

	// Video
	if(videoIn) {
		extra_data *extra = new extra_data;
		if(!extra)
			throw MyMemoryError();
		// Take the infos from the output stream (the Dubber fixed it according to
		// what the user wanted : compression, new width and new height, ...)
		//AVISTREAMINFO infos = input->videoSrc->streamInfo;
		AVIStreamHeader_fixed infos = videoOut->streamInfo;
		//BITMAPINFOHEADER *bi = (BITMAPINFOHEADER *)input->videoSrc->getFormat();
		BITMAPINFOHEADER *bi = ((OGMVideoOutputStream *)videoOut)->getImageFormat();

		HRESULT hr;
		// Get the framerate
		double fps = (double)infos.dwRate / (double)infos.dwScale;
		long max_frame_size = infos.dwSuggestedBufferSize;
		// Alright normally I should verify what should be the suggested size of the
		// generated Ogg stream's buffer and for our Packetizer's buffer
		// But it is of no use here : Maybe the user asked for a compressed stream
		// Moreover I don't have the input stream itself :p
		// So the solution : get the suggested buffer size
		// If the value is 0, then take 32k (should be enough)
		// Anyway if the buffer is too small at a moment the Packetizer realloc a new one :)

		if(max_frame_size<=0)
			max_frame_size = 32 * 1024;

		stream_header sh;
		memset(&sh, 0, sizeof(stream_header));

		// Fill the stream_header structure
		strcpy(sh.streamtype, "video");
		strcpy(sh.subtype, (char *)&bi->biCompression);
		sh.size = sizeof(stream_header);
		sh.time_unit = 10000000 / fps;
		sh.samples_per_unit = 1;
		sh.default_len = 1;
		sh.buffersize = max_frame_size + OVERHEAD;
		sh.bits_per_sample = bi->biBitCount;
		sh.sh.video.width = bi->biWidth;
		sh.sh.video.height = bi->biHeight;

		video_serial = intern_generator->create_unique_serial(0);
		intern_generator->register_serial(video_serial);

		int nb_headers = 1;
		if(video_comments)
			nb_headers = 2;

		Packetizer *avi_video_packetizer = new Packetizer(video_serial, s_generator, sh, 0, true, true, 0, nb_headers, false, false);

		if(!avi_video_packetizer)
			throw MyMemoryError();

		if(video_comments)
			//avi_video_packetizer->produce_comment_packet(video_comments, true);
			sendComments(avi_video_packetizer, video_comments);
		// OK the Packetizer has been created, and it automatically created the Header Packet
		_RPT1(0, "new Packetizer created (handling AVI video stream of file [%s])\n", file);

		add_packetizer(video_serial, avi_video_packetizer, extra, false);
	}
	// Audio
	if(audioIn) {
		extra_data *extra = new extra_data;
		if(!extra)
			throw MyMemoryError();
		AVIStreamHeader_fixed infos = audioOut->streamInfo;
		WAVEFORMATEX *wfe = ((OGMAudioOutputStream *)audioOut)->getWaveFormat();

		HRESULT hr;
		long max_frame_size = infos.dwSuggestedBufferSize;

		if(max_frame_size<=0)
			max_frame_size = 32 * 1024;

		stream_header sh;
		memset(&sh, 0, sizeof(stream_header));

		// Fill the stream_header structure
		strcpy(sh.streamtype, "audio");
		sprintf(sh.subtype, "%04X", wfe->wFormatTag);
		sh.size = sizeof(stream_header);
		sh.time_unit = 10000000;
		sh.samples_per_unit = wfe->nSamplesPerSec;
		sh.default_len = 1;
		sh.buffersize = max_frame_size + OVERHEAD;
		sh.bits_per_sample = wfe->wBitsPerSample;
		sh.sh.audio.avgbytespersec = wfe->nAvgBytesPerSec;
		sh.sh.audio.blockalign = wfe->nBlockAlign;
		sh.sh.audio.channels = wfe->nChannels;

		audio_serial = intern_generator->create_unique_serial(1);
		intern_generator->register_serial(audio_serial);

		extra->is_vorbis = false;

		int nb_headers = 1;
		if(audio_comments)
			nb_headers = 2;

		Packetizer *avi_audio_packetizer = new Packetizer(audio_serial, s_generator, sh, 0, true, true, 1, nb_headers, extra->is_vorbis, extra->is_vorbis);
		if(audio_comments) {
			sendComments(avi_audio_packetizer, audio_comments);
			//avi_audio_packetizer->produce_comment_packet(audio_comments, true);
		}

		if(!avi_audio_packetizer)
			throw MyMemoryError();

		// OK the Packetizer has been created, and it automatically created the Header Packet
		_RPT1(_CRT_WARN, "new Packetizer created (handling AVI audio stream of file [%s])\n", file);

		add_packetizer(audio_serial, avi_audio_packetizer, extra, false);
	}
	if(audio2In) {
		extra_data *extra = new extra_data;
		if(!extra)
			throw MyMemoryError();
		AVIStreamHeader_fixed infos = audio2Out->streamInfo;
		WAVEFORMATEX *wfe = ((OGMAudioOutputStream *)audio2Out)->getWaveFormat();

		HRESULT hr;
		long max_frame_size = infos.dwSuggestedBufferSize;

		if(max_frame_size<=0)
			max_frame_size = 32 * 1024;

		stream_header sh;
		memset(&sh, 0, sizeof(stream_header));

		// Fill the stream_header structure
		strcpy(sh.streamtype, "audio");
		sprintf(sh.subtype, "%04X", wfe->wFormatTag);
		sh.size = sizeof(stream_header);
		sh.time_unit = 10000000;
		sh.samples_per_unit = wfe->nSamplesPerSec;
		sh.default_len = 1;
		sh.buffersize = max_frame_size + OVERHEAD;
		sh.bits_per_sample = wfe->wBitsPerSample;
		sh.sh.audio.avgbytespersec = wfe->nAvgBytesPerSec;
		sh.sh.audio.blockalign = wfe->nBlockAlign;
		sh.sh.audio.channels = wfe->nChannels;

		audio2_serial = intern_generator->create_unique_serial(1);
		intern_generator->register_serial(audio2_serial);

		extra->is_vorbis = false;

		int nb_headers = 1;
		if(audio2_comments)
			nb_headers = 2;

		Packetizer *avi_audio_packetizer = new Packetizer(audio2_serial, s_generator, sh, 0, true, true, 1, nb_headers, extra->is_vorbis, extra->is_vorbis);
		if(audio_comments) {
			sendComments(avi_audio_packetizer, audio2_comments);
			//avi_audio_packetizer->produce_comment_packet(audio2_comments, true);
		}

		if(!avi_audio_packetizer)
			throw MyMemoryError();

		// OK the Packetizer has been created, and it automatically created the Header Packet
		_RPT1(_CRT_WARN, "new Packetizer created (handling AVI audio stream of file [%s])\n", file);

		add_packetizer(audio2_serial, avi_audio_packetizer, extra, false);
	}

	// Give the "fake" output streams their serials
	((OGMAudioOutputStream *)audioOut)->setSerial(audio_serial);
	((OGMAudioOutputStream *)audio2Out)->setSerial(audio2_serial);
	((OGMVideoOutputStream *)videoOut)->setSerial(video_serial);

	flush_packetizers();
	/*muxer_page *m_page = NULL;
	while(pending_header()) {
		m_page = get_header();
		write_mpage(m_page);
		if(m_page)
			delete m_page;
	}*/

	return TRUE;
}

int OGMOutputFile::add_stream(OGMAudioSource *audioSource, comment_list *comments) {
	if(!audioSource)
		return -1;

	extra_data *extra = new extra_data;
	if(!extra)
		throw MyMemoryError();
	AVISTREAMINFO infos = audioSource->streamInfo;
	WAVEFORMATEX *wfe = audioSource->getWaveFormat();

	HRESULT hr;
	long max_frame_size = infos.dwSuggestedBufferSize;

	if(max_frame_size<=0)
		max_frame_size = 32 * 1024;

	stream_header sh;
	memset(&sh, 0, sizeof(stream_header));

	// Fill the stream_header structure
	if(!audioSource->isText) {
		strcpy(sh.streamtype, "audio");
		sprintf(sh.subtype, "%04X", wfe->wFormatTag);
		sh.size = sizeof(stream_header);
		sh.time_unit = 10000000;
		sh.samples_per_unit = wfe->nSamplesPerSec;
		sh.default_len = 1;
		sh.buffersize = max_frame_size + OVERHEAD;
		sh.bits_per_sample = wfe->wBitsPerSample;
		sh.sh.audio.avgbytespersec = wfe->nAvgBytesPerSec;
		sh.sh.audio.blockalign = wfe->nBlockAlign;
		sh.sh.audio.channels = wfe->nChannels;
	} else {
		strcpy(sh.streamtype, "text");
		sh.size = sizeof(stream_header);
		sh.time_unit = 10000;
		sh.samples_per_unit = 1;
		sh.default_len = 1;
		sh.buffersize = 16*1024;
	}

	int new_serial = intern_generator->create_unique_serial(0);
	intern_generator->register_serial(new_serial);

	extra->is_vorbis = (audioSource->isOGM ? ((OGMAudioReadStream *)((OGMAudioSourceOGM *)audioSource)->getStream())->getType() == S_VORBIS : false);
	extra->is_text = audioSource->isText;

	int nb_headers = 1;
	if(audioSource->isOGM) {
		nb_headers = ((OGMAudioReadStream *)((OGMAudioSourceOGM *)audioSource)->getStream())->nbHeaders();
		if((nb_headers == 1) && (comments))
			nb_headers = 2;
	} else if (comments)
		nb_headers++;

	Packetizer *audio_packetizer = new Packetizer(new_serial, s_generator, sh, 0, !audioSource->isOGM, !audioSource->isText, audioSource->isText ? 0 : 1, nb_headers, extra->is_vorbis, extra->is_vorbis);

	if(!audio_packetizer)
		throw MyMemoryError();

	if(audioSource->isOGM) {
		OGMReadStream *read_stream = (OGMReadStream *)((OGMAudioSourceOGM *)audioSource)->getStream();
		setComments(read_stream, audio_packetizer, comments);
		//read_stream->setComments(comments);
		//read_stream->sendHeaders(audio_packetizer);
	} else if(comments) {
		sendComments(audio_packetizer, comments);
		//audio_packetizer->produce_comment_packet(comments, true);
	}

	// OK the Packetizer has been created, and it automatically created the Header Packet
	_RPT0(0, "new Packetizer created (handling audio stream)\n");

	extra->audio = audioSource;

	add_packetizer(new_serial, audio_packetizer, extra, extra->is_text);

	audio_packetizer->flush_pages();
	/*muxer_page *m_page = NULL;
	while(pending_header()) {
		m_page = get_header();
		write_mpage(m_page);
		if(m_page)
			delete m_page;
	}*/

	return new_serial;
}

void OGMOutputFile::write_headers(void) {
	muxer_page *m_page = NULL;
	while(pending_header()) {
		m_page = get_header();
		write_mpage(m_page);
		if(m_page)
			delete m_page;
	}
}

BOOL OGMOutputFile::finalize() {
	produce_eos_packets();
	flush_packetizers();
	process_pending_mpages();

	if(fastIO) {
		// This one is for flushing remaining data in the pipeline
		fastIO->Flush1();
		// This one is for update state of the file (update filesize)
		// Didn't understand well what is going on in this FastWriteStream
		// But if you don't call this function it seems that the size of the file is
		// not updated :/
		fastIO->Flush2(hFile);
	}

	SetEndOfFile(hFile);

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

	hFile = NULL;

	return TRUE;
}

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

void OGMOutputFile::writeIndexedChunk(FOURCC ckid, LONG dwIndexFlags, LPVOID lpBuffer, LONG cbBuffer) {
	// Hmm there are no chunks in OGM but Packets and Pages :p
	// I don't want any function to use this one (it doesn't provide the number of samples), so :
	throw MyError("Attempt to use OGMOutputFile::writeIndexedChunk");
}