/*
 *			GPAC - MPEG-4 Systems C Development Kit
 *
 *			Copyright (c) Jean Le Feuvre 2000-2005 
 *					All rights reserved
 *
 *  This file is part of GPAC / Authoring Tools sub-project
 *
 *  GPAC 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, or (at your option)
 *  any later version.
 *   
 *  GPAC 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 GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *
 */


#include <gpac/intern/m4_author_dev.h>

#ifndef M4_READ_ONLY

#include <gpac/m4_bifs.h>
#include <gpac/m4_scenegraph.h>
#include <gpac/intern/avilib.h>
#include <gpac/m4_ogg.h>
#include <gpac/m4_decoder.h>

M4Err dump_message(M4TrackDumper *dumper, M4Err e, char *format, ...)
{
	va_list args;
	if (dumper->flags & M4TD_PROBE_ONLY) return e;

	va_start(args, format);
	if (dumper->dump_message) {
		char szMsg[1024];
		vsprintf(szMsg, format, args);
		dumper->dump_message(dumper, e, szMsg);
	} else {
		vfprintf(stdout,format,args);
		fprintf(stdout, "\n");
		if (e) fprintf(stderr, "Error: %s\n", M4ErrToString(e));
	}
	va_end(args);
	return e;
}

void dump_progress(M4TrackDumper *dumper, u32 cur_samp, u32 count)
{
	if (dumper->dump_progress) {
		dumper->dump_progress(dumper, cur_samp, count);
	} else {
//		fprintf(stdout, "Extracting sample %d / %d (%.2f)\r", cur_samp, count, ((Float)cur_samp)*100 / count);
//		if (cur_samp==count) fprintf(stderr, "\n");
		MP4T_OnProgress("Exporting", cur_samp, count);
	}
}

/*that's very very crude, we only support vorbis & theora in MP4 - this will need cleanup as soon as possible*/
static M4Err dump_to_ogg(M4TrackDumper *dumper, char *szName, u32 track)
{
	FILE *out;
	ogg_stream_state os;
	ogg_packet op;
	ogg_page og;
	u32 count, i, di, theora_kgs, nb_i, nb_p;
	BitStream *bs;
	M4Sample *samp;
	ESDescriptor *esd = M4_GetStreamDescriptor(dumper->file, track, 1);


	M4_RandInit(1);
	ogg_stream_init(&os, M4_Rand());

	op.granulepos = 0;
	op.packetno = 0;
	op.b_o_s = 1;
	op.e_o_s = 0;

	out = f64_open(szName, "wb");
	if (!out) return dump_message(dumper, M4IOErr, "Error opening %s for writing - check disk access & permissions", szName);

	theora_kgs = 0;
	bs = NewBitStream(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, BS_READ);
	while (BS_Available(bs)) {
		op.bytes = BS_ReadU16(bs);
		op.packet = malloc(sizeof(char) * op.bytes);
		BS_ReadData(bs, op.packet, op.bytes);
		ogg_stream_packetin(&os, &op);

		if (op.b_o_s) {
			ogg_stream_pageout(&os, &og);
			fwrite(og.header, 1, og.header_len, out);
			fwrite(og.body, 1, og.body_len, out);
			op.b_o_s = 0;

			if (esd->decoderConfig->objectTypeIndication==0xDF) {
				u32 kff;
				BitStream *vbs = NewBitStream(op.packet, op.bytes, BS_READ);
				BS_SkipBytes(vbs, 40);
				BS_ReadInt(vbs, 6); /* quality */
				kff = 1 << BS_ReadInt(vbs, 5);
				DeleteBitStream(vbs);

				theora_kgs = 0;
				kff--;
				while (kff) {
					theora_kgs ++;
					kff >>= 1;
				}
			}
		}
		free(op.packet);
		op.packetno ++;
	}
	DeleteBitStream(bs);
	OD_DeleteDescriptor((Descriptor **)&esd);

	while (ogg_stream_pageout(&os, &og)>0) {
		fwrite(og.header, 1, og.header_len, out);
		fwrite(og.body, 1, og.body_len, out);
	}
	
	op.granulepos = -1;

	count = M4_GetSampleCount(dumper->file, track);

	nb_i = nb_p = 0;
	samp = M4_GetSample(dumper->file, track, 1, &di);
	for (i=0; i<count; i++) {
		M4Sample *next_samp = M4_GetSample(dumper->file, track, i+2, &di);
		if (!samp) break;
		op.bytes = samp->dataLength;
		op.packet = samp->data;
		op.packetno ++;

		if (theora_kgs) {
			if (samp->IsRAP) {
				if (i) nb_i+=nb_p+1;
				nb_p = 0;
			} else {
				nb_p++;
			}
			op.granulepos = nb_i;
			op.granulepos <<= theora_kgs;
			op.granulepos |= nb_p;
		} else {
			if (next_samp) op.granulepos = next_samp->DTS;
		}
		if (!next_samp) op.e_o_s = 1;

		ogg_stream_packetin(&os, &op);

		M4_DeleteSample(&samp);
		samp = next_samp;
		next_samp = NULL;
		dump_progress(dumper, i+1, count);
		if (dumper->flags & M4TD_DO_ABORT) break;

		while (ogg_stream_pageout(&os, &og)>0) {
			fwrite(og.header, 1, og.header_len, out);
			fwrite(og.body, 1, og.body_len, out);
		}
	}
	if (samp) M4_DeleteSample(&samp);

	while (ogg_stream_flush(&os, &og)>0) {
		fwrite(og.header, 1, og.header_len, out);
		fwrite(og.body, 1, og.body_len, out);
	}
    ogg_stream_clear(&os);
	fclose(out);
	return M4OK;
}


M4Err MP4T_DumpRawSamples(M4TrackDumper *dumper)
{
	DecoderConfigDescriptor *dcfg;
	UnknownStreamDescription *udesc;
	char szName[1000], szEXT[10], szType[5], szNum[1000];
	FILE *out;
	BitStream *bs;
	u32 track, i, di, count, m_type, m_stype;

	track = M4_GetTrackByID(dumper->file, dumper->trackID);
	m_type = M4_GetMediaType(dumper->file, track);
	m_stype = M4_GetMediaSubType(dumper->file, track, 1);

	if (dumper->sample_num) sprintf(szNum, " %d", dumper->sample_num);
	else strcpy(szNum, "s");

	udesc = NULL;
	dcfg = NULL;
	if ((m_stype==M4_MPEG4_SubType) || (m_stype==M4_ENC_MPEG4_SubType)) 
		dcfg = M4_GetDecoderInformation(dumper->file, track, 1);

	strcpy(szName, dumper->out_name ? dumper->out_name : "");
	if (dcfg) {
		switch (dcfg->streamType) {
		case M4ST_VISUAL:
			switch (dcfg->objectTypeIndication) {
			case 0x20:
				strcpy(szEXT, ".cmp");
				dump_message(dumper, M4OK, "Dumping MPEG-4 Visual sample%s", szNum);
				break;
			case 0x21:
				strcpy(szEXT, ".h264");
				dump_message(dumper, M4OK, "Dumping MPEG-4 AVC-H264 Visual sample%s", szNum);
				break;
			case 0x6C:
				strcpy(szEXT, ".jpg");
				dump_message(dumper, M4OK, "Dumping JPEG image%s", szNum);
				break;
			case 0x6D:
				strcpy(szEXT, ".png");
				dump_message(dumper, M4OK, "Dumping PNG image%s", szNum);
				break;
			case GPAC_OGG_MEDIA_OTI:
				strcpy(szEXT, ".theo");
				dump_message(dumper, M4OK, "Dumping Theora video sample%s", szNum);
				break;
			default:
				strcpy(szEXT, ".raw");
				dump_message(dumper, M4OK, "Dumping Unknown video sample%s (OTI %d)", szNum, dcfg->objectTypeIndication);
				break;
			}
			break;
		case M4ST_AUDIO:
			switch (dcfg->objectTypeIndication) {
			case 0x66:
			case 0x67:
			case 0x68:
			case 0x40:
				strcpy(szEXT, ".aac");
				dump_message(dumper, M4OK, "Dumping MPEG-%d AAC sample%s", (dcfg->objectTypeIndication==0x40) ? 4 : 2, szNum);
				break;
			case 0x69:
			case 0x6B:
				strcpy(szEXT, ".mp3");
				dump_message(dumper, M4OK, "Dumping MPEG-1/2 Audio (MP3) sample%s", szNum);
				break;
			case GPAC_OGG_MEDIA_OTI:
				strcpy(szEXT, ".vorb");
				dump_message(dumper, M4OK, "Dumping Vorbis audio sample%s", szNum);
				break;
			default:
				strcpy(szEXT, ".raw");
				dump_message(dumper, M4OK, "Dumping Unknown audio sample%s (OTI %d)", szNum, dcfg->objectTypeIndication);
				break;
			}
			break;
		case M4ST_SCENE:
			strcpy(szEXT, ".bifs"); dump_message(dumper, M4OK, "Dumping BIFS sample%s", szNum); 
			break;
		case M4ST_OD:
			strcpy(szEXT, ".od"); dump_message(dumper, M4OK, "Dumping OD sample%s", szNum); 
			break;
		case M4ST_MPEGJ:
			strcpy(szEXT, ".mpj"); dump_message(dumper, M4OK, "Dumping MPEG-J sample%s", szNum); 
			break;
		case M4ST_OCI:
			strcpy(szEXT, ".oci"); dump_message(dumper, M4OK, "Dumping OCI sample%s", szNum); 
			break;
		case M4ST_MPEG7:
			strcpy(szEXT, ".mp7"); dump_message(dumper, M4OK, "Dumping MPEG7 sample%s", szNum); 
			break;
		case M4ST_IPMP:
			strcpy(szEXT, ".ipmp"); dump_message(dumper, M4OK, "Dumping IPMP sample%s", szNum); 
			break;
		case M4ST_TEXT:
			strcpy(szEXT, ".tx3g"); dump_message(dumper, M4OK, "Dumping 3GP Text sample%s", szNum); 
			break;
		default:
			OD_DeleteDescriptor((Descriptor **) &dcfg);
			return dump_message(dumper, M4NotSupported, "Cannot dump systems track ID %d sample%s - use NHNT", dumper->trackID, szNum);
		}
		OD_DeleteDescriptor((Descriptor **) &dcfg);
	} else if ((m_stype==M4_3GP_AMR_SubType) || (m_stype==M4_3GP_AMR_WB_SubType)) {
		strcpy(szEXT, ".amr");
		dump_message(dumper, M4OK, "Extracting AMR Audio sample%s", szNum);
	} else if (m_stype==M4_3GP_H263_SubType) {
		dump_message(dumper, M4OK, "Extracting H263 Video sample%s", szNum);
		strcpy(szEXT, ".263");
	} else if (m_stype==M4_AVC_H264_SubType) {
		strcpy(szEXT, ".h264");
		dump_message(dumper, M4OK, "Dumping MPEG-4 AVC-H264 Visual sample%s", szNum);
	} else if (m_type==M4_FlashMediaType) {
		dump_message(dumper, M4OK, "Extracting Macromedia Flash Movie sample%s", szNum);
		strcpy(szEXT, ".swf");
	} else {
		char szMType[5];
		FCC_TO_STR(m_stype, szMType);
		strcpy(szEXT, ".");
		strcat(szEXT, szMType);
		udesc = M4_GetUnknownStreamDescription(dumper->file, track, 1);
		switch (m_type) {
		case M4_VisualMediaType: dump_message(dumper, M4OK, "Extracting \'%s\' Video - Compressor %s", szEXT, udesc ? udesc->szCompressorName : "Unknown"); break;
		case M4_AudioMediaType: dump_message(dumper, M4OK, "Extracting \'%s\' Audio - Compressor %s", szEXT, udesc ? udesc->szCompressorName : "Unknown"); break;
		default:
			dump_message(dumper, M4OK, "Extracting \'%s\' Track (type '%s') - Compressor %s sample%s", szEXT, FCC_TO_STR(m_type, szType), udesc ? udesc->szCompressorName : "Unknown", szNum);
			break;
		}
		if (udesc) free(udesc);
	}
	if (dumper->flags & M4TD_PROBE_ONLY) return M4OK;

	if (dumper->sample_num) {
		M4Sample *samp = M4_GetSample(dumper->file, track, dumper->sample_num, &di);
		if (!samp) return M4BadParam;
		sprintf(szName, "%s_%d%s", dumper->out_name, dumper->sample_num, szEXT);
		out = fopen(szName, "wb");
		bs = NewBitStreamFromFile(out, BS_FILE_WRITE_ONLY);
		BS_WriteData(bs, samp->data, samp->dataLength);
		M4_DeleteSample(&samp);
		DeleteBitStream(bs);
		fclose(out);
		return M4OK;
	}

	count = M4_GetSampleCount(dumper->file, track);
	for (i=0; i<count; i++) {
		M4Sample *samp = M4_GetSample(dumper->file, track, i+1, &di);
		if (!samp) break;

		if (count>=1000) {
			sprintf(szName, "%s_%08d%s", dumper->out_name, i+1, szEXT);
		} else {
			sprintf(szName, "%s_%03d%s", dumper->out_name, i+1, szEXT);
		}
		out = fopen(szName, "wb");
		bs = NewBitStreamFromFile(out, BS_FILE_WRITE_ONLY);
		BS_WriteData(bs, samp->data, samp->dataLength);
		M4_DeleteSample(&samp);
		dump_progress(dumper, i+1, count);
		DeleteBitStream(bs);
		fclose(out);
		if (dumper->flags & M4TD_DO_ABORT) break;
	}
	return M4OK;
}

/*QCP codec GUIDs*/
static const unsigned char *QCP_QCELP_GUID_1 = "\x41\x6D\x7F\x5E\x15\xB1\xD0\x11\xBA\x91\x00\x80\x5F\xB4\xB9\x7E";
static const unsigned char *QCP_EVRC_GUID = "\x8D\xD4\x89\xE6\x76\x90\xB5\x46\x91\xEF\x73\x6A\x51\x00\xCE\xB4";
static const unsigned char *QCP_SMV_GUID = "\x75\x2B\x7C\x8D\x97\xA7\x46\xED\x98\x5E\xD5\x3C\x8C\xC7\x5F\x84";


M4Err MP4T_DumpTrackNative(M4TrackDumper *dumper)
{
	DecoderConfigDescriptor *dcfg;
	UnknownStreamDescription *udesc;
	char szName[1000], szEXT[5], szType[5], GUID[16];
	FILE *out;
	u32 *qcp_rates;
	AVCConfig *avccfg;
	M4ADecoderSpecificInfo a_cfg;
	BitStream *bs;
	u32 track, i, di, count, m_type, m_stype, dsi_size, qcp_type;
	Bool is_ogg, has_qcp_pad;
	u32 aac_type, is_aac;
	char *dsi;
	QCPRateTable rtable[8];
	u32 rt_cnt;

	dsi_size = 0;
	dsi = NULL;
	avccfg = NULL;
	track = M4_GetTrackByID(dumper->file, dumper->trackID);
	m_type = M4_GetMediaType(dumper->file, track);
	m_stype = M4_GetMediaSubType(dumper->file, track, 1);
	has_qcp_pad = 0;

	is_aac = aac_type = 0;
	qcp_type = 0;
	is_ogg = 0;
	udesc = NULL;
	dcfg = NULL;
	if ((m_stype==M4_MPEG4_SubType) || (m_stype==M4_ENC_MPEG4_SubType)) 
		dcfg = M4_GetDecoderInformation(dumper->file, track, 1);

	strcpy(szName, dumper->out_name ? dumper->out_name : "");
	if (dcfg) {
		switch (dcfg->streamType) {
		case M4ST_VISUAL:
			switch (dcfg->objectTypeIndication) {
			case 0x20:
				dsi = dcfg->decoderSpecificInfo->data;
				dcfg->decoderSpecificInfo->data = NULL;
				dsi_size = dcfg->decoderSpecificInfo->dataLength;
				strcat(szName, ".cmp");
				dump_message(dumper, M4OK, "Extracting MPEG-4 Visual stream to cmp");
				break;
			case 0x21:
				avccfg = M4_AVC_GetStreamConfig(dumper->file, track, 1);
				strcat(szName, ".h264");
				dump_message(dumper, M4OK, "Extracting MPEG-4 AVC-H264 stream to h264");
				break;
			case 0x6A:
				strcat(szName, ".m1v");
				dump_message(dumper, M4OK, "Extracting MPEG-1 Visual stream to h264");
				break;
			case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: 
				strcat(szName, ".m2v");
				dump_message(dumper, M4OK, "Extracting MPEG-2 Visual stream to h264");
				break;
			case 0x6C:
				strcat(szName, ".jpg");
				dump_message(dumper, M4OK, "Extracting JPEG image");
				break;
			case 0x6D:
				strcat(szName, ".png");
				dump_message(dumper, M4OK, "Extracting PNG image");
				break;
			case GPAC_OGG_MEDIA_OTI:
				strcat(szName, ".ogg");
				dump_message(dumper, M4OK, "Extracting Ogg video");
				is_ogg = 1;
				break;
			default:
				OD_DeleteDescriptor((Descriptor **) &dcfg);
				return dump_message(dumper, M4NotSupported, "Unknown media in track ID %d - use NHNT instead", dumper->trackID);
			}
			break;
		case M4ST_AUDIO:
			switch (dcfg->objectTypeIndication) {
			case 0x66:
			case 0x67:
			case 0x68:
				dsi = dcfg->decoderSpecificInfo->data;
				dcfg->decoderSpecificInfo->data = NULL;
				dsi_size = dcfg->decoderSpecificInfo->dataLength;
				strcat(szName, ".aac");
				is_aac = 1;
				aac_type = dcfg->objectTypeIndication - 0x66;
				dump_message(dumper, M4OK, "Extracting MPEG-2 AAC");
				break;
			case 0x40:
				dsi = dcfg->decoderSpecificInfo->data;
				dcfg->decoderSpecificInfo->data = NULL;
				dsi_size = dcfg->decoderSpecificInfo->dataLength;
				is_aac = 2;
				strcat(szName, ".aac");
				dump_message(dumper, M4OK, "Extracting MPEG-4 AAC");
				break;
			case 0x69:
			case 0x6B:
				strcat(szName, ".mp3");
				dump_message(dumper, M4OK, "Extracting MPEG-1/2 Audio (MP3)");
				break;
			case GPAC_OGG_MEDIA_OTI:
				strcat(szName, ".ogg");
				is_ogg = 1;
				dump_message(dumper, M4OK, "Extracting Ogg audio");
				break;
			case 0xE1:
				strcat(szName, ".qcp"); qcp_type = 1;
				memcpy(GUID, QCP_QCELP_GUID_1, sizeof(char)*16);
				dump_message(dumper, M4OK, "Extracting QCELP-13K (QCP file)");
				break;
			case 0xA0:
				memcpy(GUID, QCP_EVRC_GUID, sizeof(char)*16);
				qcp_type = 3;
				if (dumper->flags & M4TD_PROBE_ONLY) dumper->flags |= M4TD_USE_QCP;
				break;
			case 0xA1:
				qcp_type = 2;
				memcpy(GUID, QCP_SMV_GUID, sizeof(char)*16);
				if (dumper->flags & M4TD_PROBE_ONLY) dumper->flags |= M4TD_USE_QCP;
				break;
			case 0xD1: 
				if (dcfg->decoderSpecificInfo && (dcfg->decoderSpecificInfo->dataLength==8)
					&& !strnicmp(dcfg->decoderSpecificInfo->data, "pvmm", 4)) {
					qcp_type = 3;
					memcpy(GUID, QCP_EVRC_GUID, sizeof(char)*16);
					if (dumper->flags & M4TD_PROBE_ONLY) dumper->flags |= M4TD_USE_QCP;
					break;
				}
				/*fall through*/
			default:
				OD_DeleteDescriptor((Descriptor **) &dcfg);
				return dump_message(dumper, M4NotSupported, "Unknown audio in track ID %d - use NHNT", dumper->trackID);
			}
			break;
		default:
			OD_DeleteDescriptor((Descriptor **) &dcfg);
			return dump_message(dumper, M4NotSupported, "Cannot extract systems track ID %d in raw format - use NHNT", dumper->trackID);
		}
		OD_DeleteDescriptor((Descriptor **) &dcfg);
	} else {
		if (m_stype==M4_3GP_AMR_SubType) {
			strcat(szName, ".amr");
			dump_message(dumper, M4OK, "Extracting AMR Audio");
		} else if (m_stype==M4_3GP_AMR_WB_SubType) {
			strcat(szName, ".awb");
			dump_message(dumper, M4OK, "Extracting AMR WideBand Audio");
		} else if (m_stype==M4_3GP_QCELP_SubType) {
			strcat(szName, ".qcp"); qcp_type = 1;
			memcpy(GUID, QCP_QCELP_GUID_1, sizeof(char)*16);
			dump_message(dumper, M4OK, "Extracting QCELP-13K (QCP file)");
		} else if (m_stype==M4_3GP_EVRC_SubType) {
			qcp_type = 3;
			memcpy(GUID, QCP_EVRC_GUID, sizeof(char)*16);
			if (dumper->flags & M4TD_PROBE_ONLY) dumper->flags |= M4TD_USE_QCP;
		} else if (m_stype==M4_3GP_SMV_SubType) {
			qcp_type = 2;
			memcpy(GUID, QCP_SMV_GUID, sizeof(char)*16);
			if (dumper->flags & M4TD_PROBE_ONLY) dumper->flags |= M4TD_USE_QCP;
		} else if (m_stype==M4_3GP_H263_SubType) {
			dump_message(dumper, M4OK, "Extracting H263 Video");
			strcat(szName, ".263");
		} else if (m_stype==M4_AVC_H264_SubType) {
			avccfg = M4_AVC_GetStreamConfig(dumper->file, track, 1);
			strcat(szName, ".h264");
			dump_message(dumper, M4OK, "Extracting MPEG-4 AVC-H264 stream to h264");
		} else if (m_type==M4_FlashMediaType) {
			dump_message(dumper, M4OK, "Extracting Macromedia Flash Movie");
			strcat(szName, ".swf");
		} else {
			FCC_TO_STR(m_stype, szEXT);
			strcat(szName, ".");
			strcat(szName, szEXT);
			udesc = M4_GetUnknownStreamDescription(dumper->file, track, 1);
			if (udesc) {
				dsi = udesc->extension_buf; udesc->extension_buf = NULL;
				dsi_size = udesc->extension_buf_size;
			}
			switch (m_type) {
			case M4_VisualMediaType: dump_message(dumper, M4OK, "Extracting \'%s\' Video - Compressor %s", szEXT, udesc ? udesc->szCompressorName : "Unknown"); break;
			case M4_AudioMediaType: dump_message(dumper, M4OK, "Extracting \'%s\' Audio - Compressor %s", szEXT, udesc ? udesc->szCompressorName : "Unknown"); break;
			default:
				dump_message(dumper, M4OK, "Extracting \'%s\' Track (type '%s') - Compressor %s", szEXT, FCC_TO_STR(m_type, szType), udesc ? udesc->szCompressorName : "Unknown");
				break;
			}
			if (udesc) free(udesc);
		}
	}
	if (dumper->flags & M4TD_PROBE_ONLY) {
		if (dsi) free(dsi);
		if (avccfg) AVC_DeleteConfig(avccfg);
		return M4OK;
	}

	if (is_ogg) return dump_to_ogg(dumper, szName, track);

	if (qcp_type>1) {
		if (dumper->flags & M4TD_USE_QCP) {
			strcat(szName, ".qcp");
			dump_message(dumper, M4OK, "Extracting %s audio (QCP file)", (qcp_type==2) ? "SMV" : "EVRC");
		} else if (qcp_type==2) {
			strcat(szName, ".smv");
			dump_message(dumper, M4OK, "Extracting SMV audio");
		} else {
			strcat(szName, ".evc");
			dump_message(dumper, M4OK, "Extracting EVRC audio");
		}
	}
	out = fopen(szName, "wb");
	if (!out) {
		if (dsi) free(dsi);
		if (avccfg) AVC_DeleteConfig(avccfg);
		return dump_message(dumper, M4IOErr, "Error opening %s for writing - check disk access & permissions", szName);
	}
	bs = NewBitStreamFromFile(out, BS_FILE_WRITE_ONLY);

	if (is_aac) {
		M4A_GetConfig(dsi, dsi_size, &a_cfg);
		if (is_aac==2) aac_type = a_cfg.base_object_type - 1;
		free(dsi);
		dsi = NULL;
	}
	if (dsi) {
		BS_WriteData(bs, dsi, dsi_size);
		free(dsi);
	}
	if (avccfg) {
		count = ChainGetCount(avccfg->sequenceParameterSets);
		for (i=0;i<count;i++) {
			AVCConfigSlot *sl = ChainGetEntry(avccfg->sequenceParameterSets, i);
			BS_WriteU32(bs, 1);
			BS_WriteData(bs, sl->data, sl->size);
		}
		count = ChainGetCount(avccfg->pictureParameterSets);
		for (i=0;i<count;i++) {
			AVCConfigSlot *sl = ChainGetEntry(avccfg->pictureParameterSets, i);
			BS_WriteU32(bs, 1);
			BS_WriteData(bs, sl->data, sl->size);
		}
	}

	qcp_rates = NULL;
	count = M4_GetSampleCount(dumper->file, track);
	rt_cnt = 0;

	if (m_stype==M4_3GP_AMR_SubType) BS_WriteData(bs, "#!AMR\n", 6);
	else if (m_stype==M4_3GP_AMR_WB_SubType) BS_WriteData(bs, "#!AMR-WB\n", 9);
	else if ((qcp_type>1) && !(dumper->flags & M4TD_USE_QCP)) {
		if (qcp_type==2) BS_WriteData(bs, "#!SMV\n", 6);
		else BS_WriteData(bs, "#!EVRC\n", 7);
		qcp_type = 0;
	}

	/*QCP formatting*/
	if (qcp_type) {
		M4Sample *samp;
		Bool needs_rate_octet;
		u32 tot_size, data_size, sample_size, avg_rate, agg_samp;
		u32 block_size = 160;
		u32 sample_rate = 8000;
		GPPMediaConfig *gpc = M4_3GP_GetStreamConfig(dumper->file, track, 1);
		sample_size = M4_GetConstantSampleSize(dumper->file, track);
		agg_samp = 1;
		if (gpc) {
			agg_samp = gpc->frames_per_sample;
			free(gpc);
		}

		if (qcp_type==1) {
			qcp_rates = (u32 *)qcelp_rates_to_sizes;
			rt_cnt = nb_qcelp_rates_to_sizes;
		} else {
			qcp_rates = (u32 *)smv_evrc_rates_to_sizes;
			rt_cnt = nb_smv_evrc_rates_to_sizes;
		}

		/*if cst size and no aggregation, skip table*/
		if (sample_size && !agg_samp) {
			data_size = sample_size*count;
		} else {
			/*dumps full table...*/
			for (i=0; i<rt_cnt; i++) {
				rtable[i].rate_idx = qcp_rates[2*i];
				rtable[i].pck_size = qcp_rates[2*i+1];
			}
			data_size = (u32) M4_GetMediaDataSize(dumper->file, track);
		}

		/*check sample format - packetvideo doesn't include rate octet...*/
		needs_rate_octet = 1;
		samp = M4_GetSampleInfo(dumper->file, track, 1, NULL, NULL);
		for (i=0; i<rt_cnt; i++) {
			if (qcp_rates[2*i+1]==samp->dataLength) {
				needs_rate_octet = 0;
				break;
			}
		}
		M4_DeleteSample(&samp);
		if (needs_rate_octet) data_size += count;
		has_qcp_pad = (data_size % 2) ? 1 : 0;

		avg_rate = 8*data_size*sample_rate/count/block_size;
		/*QLCM + fmt + vrat + data*/
		tot_size = 4+ 8+150 + 8+8 + 8 + data_size;
		/*pad is included in riff size*/
		if (has_qcp_pad) tot_size++;

		BS_WriteData(bs, "RIFF", 4);
		BS_WriteU32LE(bs, tot_size);
		BS_WriteData(bs, "QLCM", 4);
		BS_WriteData(bs, "fmt ", 4);
		BS_WriteU32LE(bs, 150);/*fmt chunk size*/
		BS_WriteU8(bs, 1);
		BS_WriteU8(bs, 0);
		BS_WriteData(bs, GUID, 16);
		BS_WriteU16LE(bs, 1);
		memset(szName, 0, 80);
		strcpy(szName, (qcp_type==1) ? "QCELP-GPACExport" : ((qcp_type==2) ? "SMV-GPACExport" : "EVRC-GPACExport"));
		BS_WriteData(bs, szName, 80);
		BS_WriteU16LE(bs, avg_rate);
		BS_WriteU16LE(bs, sample_size);
		BS_WriteU16LE(bs, block_size);
		BS_WriteU16LE(bs, sample_rate);
		BS_WriteU16LE(bs, 16);
		BS_WriteU32LE(bs, rt_cnt);
		for (i=0; i<8; i++) {
			if (i<rt_cnt) {
				/*frame size MINUS rate octet*/
				BS_WriteU8(bs, rtable[i].pck_size - 1);
				BS_WriteU8(bs, rtable[i].rate_idx);
			} else {
				BS_WriteU16(bs, 0);
			}
		}
		memset(szName, 0, 80);
		BS_WriteData(bs, szName, 20);/*reserved*/
		BS_WriteData(bs, "vrat", 4);
		BS_WriteU32LE(bs, 8);/*vrat chunk size*/
		BS_WriteU32LE(bs, rt_cnt);
		BS_WriteU32LE(bs, count);
		BS_WriteData(bs, "data", 4);
		BS_WriteU32LE(bs, data_size);/*data chunk size*/

		qcp_type = needs_rate_octet ? 1 : 0;
	}


	for (i=0; i<count; i++) {
		M4Sample *samp = M4_GetSample(dumper->file, track, i+1, &di);
		if (!samp) break;
		/*AVC sample to NALU*/
		if (avccfg) {
			u32 j, nal_size, remain;
			unsigned char *ptr = samp->data;
			remain = samp->dataLength;
			while (remain) {
				nal_size = 0;
				for (j=0; j<avccfg->nal_unit_size; j++) {
					nal_size |= *ptr;
					if (j+1<avccfg->nal_unit_size) nal_size<<=8;
					remain--;
					ptr++;
				}
				BS_WriteU32(bs, 1);
				BS_WriteData(bs, ptr, nal_size);
				ptr += nal_size;
				remain -= nal_size;
			}
		}
		/*adts frame header*/
		else if (is_aac) {
			BS_WriteInt(bs, 0xFFF, 12);/*sync*/
			BS_WriteInt(bs, (is_aac==1) ? 1 : 0, 1);/*mpeg2 aac*/
			BS_WriteInt(bs, 0, 2); /*layer*/
			BS_WriteInt(bs, 1, 1); /* protection_absent*/
			BS_WriteInt(bs, aac_type, 2);
			BS_WriteInt(bs, a_cfg.base_sr_index, 4);
			BS_WriteInt(bs, 0, 1);
			BS_WriteInt(bs, a_cfg.nb_chan, 3);
			BS_WriteInt(bs, 0, 4);
			BS_WriteInt(bs, 7+samp->dataLength, 13);
			BS_WriteInt(bs, 0x7FF, 11);
			BS_WriteInt(bs, 0, 2);
		}
		/*fix rate octet for QCP*/
		else if (qcp_type) {
			u32 j;
			for (j=0; j<rt_cnt; j++) {
				if (qcp_rates[2*j+1]==1+samp->dataLength) {
					BS_WriteU8(bs, qcp_rates[2*j]);
					break;
				}
			}
		}
		if (!avccfg) BS_WriteData(bs, samp->data, samp->dataLength);
		M4_DeleteSample(&samp);
		dump_progress(dumper, i+1, count);
		if (dumper->flags & M4TD_DO_ABORT) break;
	}
	if (has_qcp_pad) BS_WriteU8(bs, 0);

	if (avccfg) AVC_DeleteConfig(avccfg);
	DeleteBitStream(bs);
	fclose(out);
	return M4OK;
}

M4Err MP4T_DumpAVITrackNative(M4TrackDumper *dumper)
{
	M4Err e;
	u32 max_size, tot_size, num_samples, i;
	s32 size;
	char *comp, *frame;
	char szOutFile[1024];	
	avi_t *in;
	FILE *fout;

	in = AVI_open_input_file(dumper->in_name, 1);
	if (!in) return dump_message(dumper, M4URLNotFound, "Unsupported avi file");
	fout = NULL;

	e = M4OK;
	if (dumper->trackID==1) {
		Bool key;
		comp = AVI_video_compressor(in);
		if (!stricmp(comp, "DIVX") || !stricmp(comp, "DX50")	/*DivX*/
		|| !stricmp(comp, "XVID") /*XviD*/
		|| !stricmp(comp, "3iv2") /*3ivX*/
		|| !stricmp(comp, "fvfw") /*ffmpeg*/
		|| !stricmp(comp, "NDIG") /*nero*/
		|| !stricmp(comp, "MP4V") /*!! not tested*/
		|| !stricmp(comp, "M4CC") /*Divio - not tested*/
		|| !stricmp(comp, "PVMM") /*PacketVideo - not tested*/
		|| !stricmp(comp, "SEDG") /*Samsung - not tested*/
		|| !stricmp(comp, "RMP4") /*Sigma - not tested*/
		) {
			sprintf(szOutFile, "%s.cmp", dumper->out_name);
		} else if (!stricmp(comp, "VSSH")) {
			sprintf(szOutFile, "%s.h264", dumper->out_name);
		} else {
			sprintf(szOutFile, "%s.%s", dumper->out_name, comp);
		}
		dump_message(dumper, M4OK, "Extracting AVI video (format %s) to %s", comp, szOutFile);

		fout = fopen(szOutFile, "wb");

		max_size = 0;
		frame = NULL;
		num_samples = AVI_video_frames(in);
		for (i=0; i<num_samples; i++) {
			size = AVI_frame_size(in, i);
			if (!size) {
				AVI_read_frame(in, NULL, &key);
				continue;
			}
			if ((u32) size > max_size) {
				frame = realloc(frame, sizeof(char) * size);
				max_size = size;
			}
			AVI_read_frame(in, frame, &key);
			if ((u32) size>4) fwrite(frame, 1, size, fout);
			dump_progress(dumper, i+1, num_samples);
		}
		free(frame);
		fclose(fout);
		fout = NULL;
		goto exit;
	}
	i = 0;
	tot_size = max_size = 0;
	while ((size = AVI_audio_size(in, i) )>0) {
		if (max_size<(u32) size) max_size=size;
		tot_size += size;
		i++;
	}
	frame = malloc(sizeof(char) * max_size);
	AVI_seek_start(in);
	AVI_set_audio_position(in, 0);

	switch (in->track[in->aptr].a_fmt) {
	case WAVE_FORMAT_PCM: comp = "pcm"; break;
	case WAVE_FORMAT_ADPCM: comp = "adpcm"; break;
	case WAVE_FORMAT_IBM_CVSD: comp = "cvsd"; break;
	case WAVE_FORMAT_ALAW: comp = "alaw"; break;
	case WAVE_FORMAT_MULAW: comp = "mulaw"; break;
	case WAVE_FORMAT_OKI_ADPCM: comp = "oki_adpcm"; break;
	case WAVE_FORMAT_DVI_ADPCM: comp = "dvi_adpcm"; break;
	case WAVE_FORMAT_DIGISTD: comp = "digistd"; break;
	case WAVE_FORMAT_YAMAHA_ADPCM: comp = "yam_adpcm"; break;
	case WAVE_FORMAT_DSP_TRUESPEECH: comp = "truespeech"; break;
	case WAVE_FORMAT_GSM610: comp = "gsm610"; break;
	case IBM_FORMAT_MULAW: comp = "ibm_mulaw"; break;
	case IBM_FORMAT_ALAW: comp = "ibm_alaw"; break;
	case IBM_FORMAT_ADPCM: comp = "ibm_adpcm"; break;
	case 0x55: comp = "mp3"; break;
	default: comp = "raw"; break;
	}
	sprintf(szOutFile, "%s.%s", dumper->out_name, comp);
	dump_message(dumper, M4OK, "Extracting AVI %s audio", comp);
	fout = fopen(szOutFile, "wb");
	num_samples = 0;
	while (1) {
		Bool continuous;
		size = AVI_read_audio(in, frame, max_size, &continuous);
		if (!size) break;
		num_samples += size;
		fwrite(frame, 1, size, fout);
		dump_progress(dumper, num_samples, tot_size);

	}


exit:
	if (fout) fclose(fout);
	AVI_close(in);
	return e;
}

M4Err MP4T_DumpTrackNHNT(M4TrackDumper *dumper)
{
	ESDescriptor *esd;
	char szName[1000];
	FILE *out_med, *out_inf, *out_nhnt;
	BitStream *bs;
	Bool has_b_frames;
	u32 track, i, di, count, pos;

	track = M4_GetTrackByID(dumper->file, dumper->trackID);
	esd = M4_GetStreamDescriptor(dumper->file, track, 1);
	if (!esd) return dump_message(dumper, M4NonCompliantBitStream, "Invalid MPEG-4 stream in track ID %d", dumper->trackID);

	if (dumper->flags & M4TD_PROBE_ONLY) {
		OD_DeleteDescriptor((Descriptor **) &esd);
		return M4OK;
	}

	sprintf(szName, "%s.media", dumper->out_name);
	out_med = f64_open(szName, "wb");
	if (!out_med) {
		OD_DeleteDescriptor((Descriptor **) &esd);
		return dump_message(dumper, M4IOErr, "Error opening %s for writing - check disk access & permissions", szName);
	}

	sprintf(szName, "%s.nhnt", dumper->out_name);
	out_nhnt = fopen(szName, "wb");
	if (!out_nhnt) {
		fclose(out_med);
		OD_DeleteDescriptor((Descriptor **) &esd);
		return dump_message(dumper, M4IOErr, "Error opening %s for writing - check disk access & permissions", szName);
	}


	bs = NewBitStreamFromFile(out_nhnt, BS_FILE_WRITE_ONLY);

	if (esd->decoderConfig->decoderSpecificInfo  && esd->decoderConfig->decoderSpecificInfo->data) {
		sprintf(szName, "%s.info", dumper->out_name);
		out_inf = fopen(szName, "wb");
		if (out_inf) fwrite(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, 1, out_inf);
		fclose(out_inf);
	}

	/*write header*/
	/*'NHnt' format*/
	BS_WriteData(bs, "NHnt", 4);
	/*version 1*/
	BS_WriteU8(bs, 0);
	/*streamType*/
	BS_WriteU8(bs, esd->decoderConfig->streamType);
	/*OTI*/
	BS_WriteU8(bs, esd->decoderConfig->objectTypeIndication);
	/*reserved*/
	BS_WriteU16(bs, 0);
	/*bufferDB*/
	BS_WriteU24(bs, esd->decoderConfig->bufferSizeDB);
	/*avg BitRate*/
	BS_WriteU32(bs, esd->decoderConfig->avgBitrate);
	/*max bitrate*/
	BS_WriteU32(bs, esd->decoderConfig->maxBitrate);
	/*timescale*/
	BS_WriteU32(bs, esd->slConfig->timestampResolution);

	OD_DeleteDescriptor((Descriptor **) &esd);

	has_b_frames = M4_HasTimeOffsets(dumper->file, track);

	pos = 0;
	count = M4_GetSampleCount(dumper->file, track);
	for (i=0; i<count; i++) {
		M4Sample *samp = M4_GetSample(dumper->file, track, i+1, &di);
		if (!samp) break;
		fwrite(samp->data, samp->dataLength, 1, out_med);
		
		/*dump nhnt info*/
		BS_WriteU24(bs, samp->dataLength);
		BS_WriteInt(bs, samp->IsRAP, 1);
		/*AU start & end flag always true*/
		BS_WriteInt(bs, 1, 1);
		BS_WriteInt(bs, 1, 1);
		/*reserved*/
		BS_WriteInt(bs, 0, 3);
		/*type - try to guess it*/
		if (has_b_frames) {
			if (samp->IsRAP) BS_WriteInt(bs, 0, 2);
			/*if CTS offset, assime P*/
			else if (samp->CTS_Offset) BS_WriteInt(bs, 1, 2);
			else BS_WriteInt(bs, 2, 2);
		} else {
			BS_WriteInt(bs, samp->IsRAP ? 0 : 1, 2);
		}
		BS_WriteU32(bs, pos);
		BS_WriteU32(bs, samp->DTS + samp->CTS_Offset);
		BS_WriteU32(bs, samp->DTS);

		pos += samp->dataLength;
		M4_DeleteSample(&samp);
		dump_progress(dumper, i+1, count);
		if (dumper->flags & M4TD_DO_ABORT) break;
	}
	fclose(out_med);
	DeleteBitStream(bs);
	fclose(out_nhnt);
	return M4OK;
}

static M4Err MP4T_CopyTrack(M4TrackDumper *dumper, M4File *infile, u32 inTrackNum, M4File *outfile, Bool ResetDependancies, Bool AddToIOD)
{
	ESDescriptor *esd;
	InitialObjectDescriptor *iod;
	u32 TrackID, newTk, descIndex, i, ts, rate, dur, pos, di, count, msubtype;
	M4Sample *samp;

	if (!inTrackNum) {
		if (M4_GetTrackCount(infile) != 1) return dump_message(dumper, M4BadParam, "Please specify trackID to export");
		inTrackNum = 1;
	}
	//check the ID is available
	TrackID = M4_GetTrackID(infile, inTrackNum);
	newTk = M4_GetTrackByID(outfile, TrackID);
	if (newTk) TrackID = 0;

	//get the ESD and remove dependancies
	esd = NULL;
	msubtype = M4_GetMediaSubType(infile, inTrackNum, 1);

	if (msubtype == M4_MPEG4_SubType) {
		esd = M4_GetStreamDescriptor(infile, inTrackNum, 1);
		if (esd && ResetDependancies) {
			esd->dependsOnESID = 0;
			esd->OCRESID = 0;
		}
	}

	newTk = M4_NewTrack(outfile, TrackID, M4_GetMediaType(infile, inTrackNum), M4_GetMediaTimeScale(infile, inTrackNum));
	M4_SetTrackEnabled(outfile, newTk, 1);

	if (esd) {
		M4_NewStreamDescription(outfile, newTk, esd, NULL, NULL, &descIndex);
		if ((esd->decoderConfig->streamType == M4ST_VISUAL) || (esd->decoderConfig->streamType == M4ST_SCENE)) {
			u32 w, h;
			M4_GetVisualEntrySize(infile, inTrackNum, 1, &w, &h);
			/*this is because so many files have reserved values of 320x240 from v1 ... */
			if ((esd->decoderConfig->objectTypeIndication == 0x20) ) {
				M4VDecoderSpecificInfo dsi;
				M4V_GetConfig(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &dsi);
				w = dsi.width;
				h = dsi.height;
			}
			M4_SetVisualEntrySize(outfile, newTk, 1, w, h);
		}
		esd->decoderConfig->avgBitrate = 0;
		esd->decoderConfig->maxBitrate = 0;
	} else {
		M4_CloneStreamDescription(outfile, newTk, infile, inTrackNum, 1, NULL, NULL, &descIndex);
	}

	pos = 0;
	rate = 0;
	ts = M4_GetMediaTimeScale(infile, inTrackNum);
	count = M4_GetSampleCount(infile, inTrackNum); 
	for (i=0; i<count; i++) {
		samp = M4_GetSample(infile, inTrackNum, i+1, &di);
		M4_AddSample(outfile, newTk, descIndex, samp);
		if (esd) {
			rate += samp->dataLength;
			esd->decoderConfig->avgBitrate += samp->dataLength;
			if (esd->decoderConfig->bufferSizeDB<samp->dataLength) esd->decoderConfig->bufferSizeDB = samp->dataLength;
			if (samp->DTS - pos > ts) {
				if (esd->decoderConfig->maxBitrate<rate) esd->decoderConfig->maxBitrate = rate;
				rate = 0;
				pos = 0;
			}
		}
		M4_DeleteSample(&samp);
		dump_progress(dumper, i, count);
	}
	dump_progress(dumper, count, count);

	if (msubtype == M4_ENC_MPEG4_SubType) {
		esd = M4_GetStreamDescriptor(infile, inTrackNum, 1);
	} else if (msubtype == M4_AVC_H264_SubType) {
		return M4_SetMoviePLIndication(outfile, M4_PL_VISUAL, 0x0F);
	} 
	/*likely 3gp or any non-MPEG-4 isomedia file*/
	else if (!esd) return M4_RemoveRootOD(outfile);

	dur = (u32) M4_GetMediaDuration(outfile, newTk);
	if (!dur) dur = ts;
	esd->decoderConfig->maxBitrate *= 8;
	esd->decoderConfig->avgBitrate *= 8 * ts / dur;
	M4_ChangeStreamDescriptor(outfile, newTk, 1, esd);


	iod = (InitialObjectDescriptor *) M4_GetRootOD(infile);
	switch (esd->decoderConfig->streamType) {
	case M4ST_SCENE:
		if (iod && (iod->tag==InitialObjectDescriptor_Tag)) {
			M4_SetMoviePLIndication(outfile, M4_PL_SCENE, iod->scene_profileAndLevel);
			M4_SetMoviePLIndication(outfile, M4_PL_GRAPHICS, iod->graphics_profileAndLevel);
		} else if (esd->decoderConfig->objectTypeIndication==0x20) {
			dump_message(dumper, M4OK, "Warning: Scene PLs not found in original MP4 - defaulting to No Profile Specified");
			M4_SetMoviePLIndication(outfile, M4_PL_SCENE, 0xFE);
			M4_SetMoviePLIndication(outfile, M4_PL_GRAPHICS, 0xFE);
		}
		break;
	case M4ST_VISUAL:
		if (iod && (iod->tag==InitialObjectDescriptor_Tag)) {
			M4_SetMoviePLIndication(outfile, M4_PL_VISUAL, iod->visual_profileAndLevel);
		} else if (esd->decoderConfig->objectTypeIndication==0x20) {
			M4VDecoderSpecificInfo dsi;
			M4V_GetConfig(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &dsi);
			M4_SetMoviePLIndication(outfile, M4_PL_VISUAL, dsi.VideoPL);
		} else {
			dump_message(dumper, M4OK, "Warning: Visual PLs not found in original MP4 - defaulting to No Profile Specified");
			M4_SetMoviePLIndication(outfile, M4_PL_VISUAL, 0xFE);
		}
		break;
	case M4ST_AUDIO:
		if (iod && (iod->tag==InitialObjectDescriptor_Tag)) {
			M4_SetMoviePLIndication(outfile, M4_PL_AUDIO, iod->audio_profileAndLevel);
		} else if (esd->decoderConfig->objectTypeIndication==0x40) {
			M4ADecoderSpecificInfo cfg;
			M4A_GetConfig(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &cfg);
			M4_SetMoviePLIndication(outfile, M4_PL_AUDIO, cfg.audioPL);
		} else {
			dump_message(dumper, M4OK, "Warning: Audio PLs not found in original MP4 - defaulting to No Profile Specified");
			M4_SetMoviePLIndication(outfile, M4_PL_AUDIO, 0xFE);
		}
	default:
		break;
	}
	if (iod) OD_DeleteDescriptor((Descriptor **) &iod);	
	OD_DeleteDescriptor((Descriptor **)&esd);

	if (AddToIOD) M4_AddTrackToRootOD(outfile, newTk);

	return M4OK;
}

M4Err MP4T_DumpTrackMP4(M4TrackDumper *dumper)
{
	M4File *outfile;
	M4Err e;
	Bool add_to_iod;
	char szName[1000], *ext;
	u32 track;
	u8 mode;

	track = M4_GetTrackByID(dumper->file, dumper->trackID);
	if (M4_GetMediaType(dumper->file, dumper->trackID)==M4_ODMediaType) {
		return dump_message(dumper, M4BadParam, "Cannot extract OD track, result is  meaningless");
	}

	if (dumper->flags & M4TD_PROBE_ONLY) {
		dumper->flags |= M4TD_MERGE_TRACKS;
		return M4OK;
	}
	ext = (char *) M4_GetFilename(dumper->file);
	if (ext) ext = strrchr(ext, '.');
	sprintf(szName, "%s%s", dumper->out_name, ext ? ext : ".mp4");

	add_to_iod = 1;
	mode = M4_WRITE_EDIT;
	if (dumper->flags & M4TD_MERGE_TRACKS) {
		FILE *t = fopen(szName, "rb");
		if (t) {
			add_to_iod = 0;
			mode = M4_OPEN_EDIT;
			fclose(t);
		}
	}
	outfile = M4_MovieOpen(szName, mode);

	if (mode == M4_WRITE_EDIT) {
		M4_SetMoviePLIndication(outfile, M4_PL_AUDIO, 0xFF);
		M4_SetMoviePLIndication(outfile, M4_PL_VISUAL, 0xFF);
		M4_SetMoviePLIndication(outfile, M4_PL_GRAPHICS, 0xFF);
		M4_SetMoviePLIndication(outfile, M4_PL_SCENE, 0xFF);
		M4_SetMoviePLIndication(outfile, M4_PL_OD, 0xFF);
		M4_SetMoviePLIndication(outfile, M4_PL_MPEGJ, 0xFF);
	}

	e = MP4T_CopyTrack(dumper, dumper->file, track, outfile, 1, add_to_iod);
	if (!add_to_iod) {
		u32 i;
		for (i=0; i<M4_GetTrackCount(outfile); i++) {
			M4_RemoveTrackFromRootOD(outfile, i+1);
		}
	}
	if (e) M4_MovieDelete(outfile);
	else M4_MovieClose(outfile);

	return e;
}

M4Err MP4T_DumpTrackAVI(M4TrackDumper *dumper)
{
	ESDescriptor *esd;
	M4Sample *samp;
	char szName[1000];
	avi_t *avi_out;
	char dumdata[1];
	u32 track, i, di, count, w, h, frame_d;
	M4VDecoderSpecificInfo dsi;
	Double FPS;

	track = M4_GetTrackByID(dumper->file, dumper->trackID);
	esd = M4_GetStreamDescriptor(dumper->file, track, 1);
	if (!esd) return dump_message(dumper, M4NonCompliantBitStream, "Invalid MPEG-4 stream in track ID %d", dumper->trackID);

	if ((esd->decoderConfig->streamType!=M4ST_VISUAL) || 
	( (esd->decoderConfig->objectTypeIndication!=0x20) && (esd->decoderConfig->objectTypeIndication!=0x21)) ) {
		OD_DeleteDescriptor((Descriptor**)&esd);
		return dump_message(dumper, M4NonCompliantBitStream, "Track ID %d is not MPEG-4 Visual - cannot extract to AVI", dumper->trackID);
	}
	if (!esd->decoderConfig->decoderSpecificInfo) {
		OD_DeleteDescriptor((Descriptor**)&esd);
		return dump_message(dumper, M4NonCompliantBitStream, "Missing decoder config for track ID %d", dumper->trackID);
	}
	if (dumper->flags & M4TD_PROBE_ONLY) return M4OK;

	sprintf(szName, "%s.avi", dumper->out_name);
	avi_out = AVI_open_output_file(szName);
	if (!avi_out) {
		OD_DeleteDescriptor((Descriptor **)&esd);
		return dump_message(dumper, M4IOErr, "Error opening %s for writing - check disk access & permissions", szName);
	}
	/*ignore visual size info, get it from dsi*/
	M4V_GetConfig(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &dsi);
	w = dsi.width;
	h = dsi.height;

	/*compute FPS - note we assume constant frame rate without droped frames...*/
	count = M4_GetSampleCount(dumper->file, track);
	FPS = M4_GetMediaTimeScale(dumper->file, track);
	FPS *= (count-1);
	samp = M4_GetSample(dumper->file, track, count, &di);
	FPS /= samp->DTS;
	M4_DeleteSample(&samp);

	frame_d = 0;
	if (M4_HasTimeOffsets(dumper->file, track)) {
		u32 DTS, max_CTSO;
		DTS = max_CTSO = 0;
		for (i=0; i<count; i++) {
			samp = M4_GetSampleInfo(dumper->file, track, i+1, NULL, NULL);
			if (!samp) break;
			if (samp->CTS_Offset>max_CTSO) max_CTSO = samp->CTS_Offset;
			DTS = samp->DTS;
			M4_DeleteSample(&samp);
		}
		DTS /= (count-1);
		frame_d = max_CTSO / DTS;
		frame_d -= 1;
		/*dummy delay frame for xvid unpacked bitstreams*/
		dumdata[0] = 127;
	}

	/*AVC*/
	if (esd->decoderConfig->objectTypeIndication==0x21) {
		AVI_set_video(avi_out, w, h, FPS, "VSSH");
	} 
	/*MPEG4*/
	else {
		AVI_set_video(avi_out, w, h, FPS, "XVID");
	}
	dump_message(dumper, M4OK, "Creating AVI file %d x %d @ %.2f FPS - 4CC \"XVID\"", w, h, FPS);
	if (frame_d) dump_message(dumper, M4OK, "B-Frames detected - using unpacked bitstream with max B-VOP delta %d", frame_d);

	for (i=0; i<count; i++) {
		samp = M4_GetSample(dumper->file, track, i+1, &di);
		if (!samp) break;

		if (!i) {
			char *data = malloc(sizeof(char) * (samp->dataLength + esd->decoderConfig->decoderSpecificInfo->dataLength));
			memcpy(data, esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength);
			memcpy(data + esd->decoderConfig->decoderSpecificInfo->dataLength, samp->data, samp->dataLength);
			AVI_write_frame(avi_out, data, samp->dataLength + esd->decoderConfig->decoderSpecificInfo->dataLength, 1);
			free(data);
		} else {
			AVI_write_frame(avi_out, samp->data, samp->dataLength, samp->IsRAP);
		}
		M4_DeleteSample(&samp);
		while (frame_d) {
			AVI_write_frame(avi_out, dumdata, 1, 0);
			frame_d--;
		}
		dump_progress(dumper, i+1, count);
		if (dumper->flags & M4TD_DO_ABORT) break;
	}

	OD_DeleteDescriptor((Descriptor **) &esd);
	AVI_close(avi_out);
	return M4OK;
}


M4Err MP4T_DumpTrack(M4TrackDumper *dumper)
{
	if (!dumper) return M4BadParam;
	if (!dumper->out_name && !dumper->flags & M4TD_PROBE_ONLY) return M4BadParam;
	
	if (dumper->flags & M4TD_NATIVE) return MP4T_DumpTrackNative(dumper);
	else if (dumper->flags & M4TD_RAW_SAMPLES) return MP4T_DumpRawSamples(dumper);
	else if (dumper->flags & M4TD_NHNT) return MP4T_DumpTrackNHNT(dumper);
	else if (dumper->flags & M4TD_AVI) return MP4T_DumpTrackAVI(dumper);
	else if (dumper->flags & M4TD_MP4) return MP4T_DumpTrackMP4(dumper);
	else if (dumper->flags & M4TD_AVI_NATIVE) return MP4T_DumpAVITrackNative(dumper);
	else return M4BadParam;
}

#endif

