/*
 *			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>
#include <gpac/m4_isomedia.h>


/*
	offset for group ID for hint tracks in SimpleAV mode when all media data
	is copied to the hint track (no use interleaving hint and original in this case)
	this offset is applied internally by the track hinter. Thus you shouldn't
	specify a GroupID >= OFFSET_HINT_GROUP_ID if you want the lib to perform efficient
	interleaving in any cases (referenced or copied media)
*/
#define OFFSET_HINT_GROUP_ID	0x8000

void MP4T_DumpSDP(M4File *file, const char *name)
{
	const char *sdp;
	u32 size, i;
	FILE *f;

	f = fopen(name, "wt");
	//get the movie SDP
	M4H_SDP_GetSDP(file, &sdp, &size);
	fwrite(sdp, size, 1, f);
	fprintf(f, "\r\n");

	//then tracks
	for (i=0; i<M4_GetTrackCount(file); i++) {
		if (M4_GetMediaType(file, i+1) != M4_HintMediaType) continue;
		M4H_SDP_GetTrackSDP(file, i+1, &sdp, &size);
		fwrite(sdp, size, 1, f);
	}
	fclose(f);
}

#ifndef M4_READ_ONLY



Bool MP4T_CanEmbbedData(char *data, u32 data_size, u32 streamType)
{
	char data64[5000];
	u32 size64;

	size64 = Base64_enc(data, data_size, data64, 5000);
	if (!size64) return 0;
	switch (streamType) {
	case M4ST_OD:
		size64 += strlen("data:application/mpeg4-od-au;base64,");
		break;
	case M4ST_SCENE:
		size64 += strlen("data:application/mpeg4-bifs-au;base64,");
		break;
	default:
		/*NOT NORMATIVE*/
		size64 += strlen("data:application/mpeg4-es-au;base64,");
		break;
	}
	if (size64>=255) return 0;
	return 1;
}


/*out-of-band sample desc (128 and 255 reserved in RFC)*/
#define SIDX_OFFSET_3GPP		129

u32 M4RTP_RSLHSize(M4RTPBuilder *, SLHeader *slh);

void GetAvgSampleInfos(M4File *file, u32 Track, u32 *avgSize, u32 *MaxSize, u32 *TimeDelta, u32 *maxCTSDelta, u32 *const_duration, u32 *bandwidth)
{
	u32 i, prevTS, count, DTS;
	Double bw;
	M4Sample *samp;

	*avgSize = *MaxSize = 0;
	*TimeDelta = 0;
	*maxCTSDelta = 0;
	bw = 0;
	prevTS = 0;
	DTS = 0;

	count = M4_GetSampleCount(file, Track);
	*const_duration = 0;

	for (i=0; i<count; i++) {
		samp = M4_GetSampleInfo(file, Track, i+1, NULL, NULL);
		//get the size
		*avgSize += samp->dataLength;
		if (*MaxSize < samp->dataLength) *MaxSize = samp->dataLength;
		//get the time
		*TimeDelta += samp->DTS+samp->CTS_Offset - prevTS;

		if (i==1) {
			*const_duration = samp->DTS+samp->CTS_Offset - prevTS;
		} else if ( (i<count-1) && (*const_duration != samp->DTS+samp->CTS_Offset - prevTS)) {
			*const_duration = 0;
		}

		prevTS = samp->DTS+samp->CTS_Offset;
		bw += 8*samp->dataLength;
		
		//get the CTS delta
		if (samp->CTS_Offset > *maxCTSDelta) *maxCTSDelta = samp->CTS_Offset;
		M4_DeleteSample(&samp);
	}
	if (count>1) *TimeDelta /= (count-1);
	*avgSize /= count;
	bw *= M4_GetMediaTimeScale(file, Track);
	bw /= (s64) M4_GetMediaDuration(file, Track);
	bw /= 1000;
	(*bandwidth) = (u32) (bw+0.5);

	//delta is NOT an average, we need to know exactly how many bits are
	//needed to encode CTS-DTS for ANY samples
}

void InitSL_RTP(SLConfigDescriptor *slc)
{
	memset(slc, 0, sizeof(SLConfigDescriptor));
	slc->tag = SLConfigDescriptor_Tag;
	slc->useTimestampsFlag = 1;
	slc->timestampLength = 32;
}

void InitSL_NULL(SLConfigDescriptor *slc)
{
	memset(slc, 0, sizeof(SLConfigDescriptor));
	slc->tag = SLConfigDescriptor_Tag;
	slc->predefined = 0x01;
}



void MP4T_OnPacketDone(void *cbk, RTPHeader *header)
{
	u8 disposable;
	M4TrackHinterRTP *tkHint = (M4TrackHinterRTP *)cbk;
	if (!tkHint || !tkHint->HintSample) return;
	assert(header->TimeStamp == tkHint->RTPTime);
	
	disposable = 0;
	if (tkHint->avc_nalu_size) {
		disposable = tkHint->rtp_p->avc_non_idr ? 1 : 0;
	}
	/*for all other, assume that CTS=DTS means B-frame -> disposable*/
	else if (tkHint->has_ctts && (tkHint->rtp_p->sl_header.compositionTimeStamp==tkHint->rtp_p->sl_header.decodingTimeStamp)) {
		disposable = 1;
	}

	M4H_RTP_SetPacketFlags(tkHint->file, tkHint->HintTrack, 0, 0, header->Marker, disposable, 0);
}


void MP4T_OnDataRef(void *cbk, u32 payload_size, u32 offset_from_orig)
{
	M4TrackHinterRTP *tkHint = (M4TrackHinterRTP *)cbk;
	if (!tkHint || !payload_size) return;

	/*add reference*/
	M4H_AddSampleData(tkHint->file, tkHint->HintTrack, tkHint->TrackID,
			tkHint->CurrentSample, (u16) payload_size, offset_from_orig + tkHint->base_offset_in_sample, 
			NULL, 0);
}

void MP4T_OnData(void *cbk, char *data, u32 data_size, Bool is_header)
{
	u8 at_begin;
	M4TrackHinterRTP *tkHint = (M4TrackHinterRTP *)cbk;
	if (!data_size) return;

	at_begin = is_header ? 1 : 0;
	if (data_size <= 14) {
		M4H_AddDirectData(tkHint->file, tkHint->HintTrack, data, data_size, at_begin);
	} else {
		M4H_AddSampleData(tkHint->file, tkHint->HintTrack, tkHint->HintID, 0, (u16) data_size, 0, data, at_begin);
	}
}


void MP4T_OnNewPacket(void *cbk, RTPHeader *header)
{
	s32 res;
	M4TrackHinterRTP *tkHint = (M4TrackHinterRTP *)cbk;
	if (!tkHint) return;

	res = (s32) (tkHint->rtp_p->sl_header.compositionTimeStamp - tkHint->rtp_p->sl_header.decodingTimeStamp);
	assert( !res || tkHint->has_ctts);
	/*do we need a new sample*/
	if (!tkHint->HintSample || (tkHint->RTPTime != header->TimeStamp)) {
		/*close current sample*/
		if (tkHint->HintSample) M4H_EndHintSample(tkHint->file, tkHint->HintTrack, tkHint->SampleIsRAP);

		/*start new sample: We use DTS as the sampling instant (RTP TS) to make sure
		all packets are sent in order*/
		M4H_BeginHintSample(tkHint->file, tkHint->HintTrack, 1, header->TimeStamp-res);
		tkHint->HintSample ++;
		tkHint->RTPTime = header->TimeStamp;
		tkHint->SampleIsRAP = tkHint->rtp_p->sl_config.hasRandomAccessUnitsOnlyFlag ? 1 : tkHint->rtp_p->sl_header.randomAccessPointFlag;
	}
	/*create an RTP Packet with the appropriated marker flag - note: the flags are temp ones, 
	they are set when the full packet is signaled (to handle multi AUs per RTP)*/
	M4H_RTP_NewPacket(tkHint->file, tkHint->HintTrack, 0, 0, 0, header->Marker, header->PayloadType, 0, 0, header->SequenceNumber);
	/*Add the delta TS to make sure RTP TS is indeed the CTS (sampling time)*/
	if (res) M4H_RTP_SetPacketTimeOffset(tkHint->file, tkHint->HintTrack, res);
}

LPMP4RTPHINTER NewTrackHinter(M4File *file, u32 TrackNum, 
							u32 Path_MTU, u32 max_ptime, u32 default_rtp_rate, u32 flags, u8 PayloadID, 
							Bool copy_media, u32 InterleaveGroupID, u8 InterleaveGroupPriority,
							void (*OnProgress)(void *cbk_obj, u32 done, u32 total), void *cbk_obj, M4Err *e)
{

	SLConfigDescriptor my_sl;
	u32 descIndex, MinSize, MaxSize, avgTS, maxDTS, streamType, oti, const_dur, nb_ch;
	u8 IV_length, KI_length, OfficialPayloadID;
	u32 TrackMediaSubType, TrackMediaType, hintType, nbEdts, required_rate, force_dts_delta, avc_nalu_size, PL_ID, bandwidth;
	const char *url, *urn;
	char *mpeg4mode;
	Bool is_crypted, has_mpeg4_mapping;
	M4TrackHinterRTP *tmp;
	ESDescriptor *esd;
	u32 GetNumBitsForMaxValue(u32 MaxValue);

	*e = M4BadParam;
	if (!file || !TrackNum || !M4_GetTrackID(file, TrackNum)) return NULL;

	if (!M4_GetSampleCount(file, TrackNum)) {
		*e = M4OK;
		return NULL;
	}
	*e = M4NotSupported;
	nbEdts = M4_GetEditSegmentCount(file, TrackNum);
	if (nbEdts>1) {
		u64 et, sd, mt;
		u8 em;
		M4_GetEditSegment(file, TrackNum, 1, &et, &sd, &mt, &em);
		if ((nbEdts>2) || (em!=M4_EDIT_EMPTY)) {
			fprintf(stdout, "Cannot hint track whith EditList\n");
			return NULL;
		}
	}
	if (nbEdts) M4_RemoveEditSegments(file, TrackNum);

	if (!M4_IsTrackEnabled(file, TrackNum)) return NULL;

	/*by default NO PL signaled*/
	PL_ID = 0;
	OfficialPayloadID = 0;
	force_dts_delta = 0;
	streamType = oti = 0;
	mpeg4mode = NULL;
	required_rate = 0;
	is_crypted = 0;
	IV_length = KI_length = 0;
	oti = 0;
	nb_ch = 0;
	avc_nalu_size = 0;
	has_mpeg4_mapping = 1;
	TrackMediaType = M4_GetMediaType(file, TrackNum);
	TrackMediaSubType = M4_GetMediaSubType(file, TrackNum, 1);
	
	/*for max compatibility with QT*/
	if (!default_rtp_rate) default_rtp_rate = 90000;

	/*timed-text is a bit special, we support multiple stream descriptions & co*/
	if (TrackMediaType==M4_TimedTextMediaType) {
		hintType = RTP_PAYT_3GPP_TEXT;
		oti = 0x08;
		streamType = M4ST_TEXT;
		/*fixme - this works cos there's only one PL for text in mpeg4 at the current time*/
		PL_ID = 0x10;
	} else {
		if (M4_GetStreamDescriptionCount(file, TrackNum) > 1) return NULL;

		TrackMediaSubType = M4_GetMediaSubType(file, TrackNum, 1);
		switch (TrackMediaSubType) {
		case M4_ENC_MPEG4_SubType: 
			is_crypted = 1;
		case M4_MPEG4_SubType:
			esd = M4_GetStreamDescriptor(file, TrackNum, 1);
			hintType = RTP_PAYT_MPEG4;
			if (esd) {
				streamType = esd->decoderConfig->streamType;
				oti = esd->decoderConfig->objectTypeIndication;
				if (esd->URLString) hintType = 0;
				/*AAC*/
				if ((streamType==M4ST_AUDIO) && esd->decoderConfig->decoderSpecificInfo
				/*(nb: we use mpeg4 for MPEG-2 AAC)*/
				&& ((oti==0x40) || (oti==0x40) || (oti==0x66) || (oti==0x67) || (oti==0x68)) ) {

					u32 sample_rate;
					M4ADecoderSpecificInfo a_cfg;
					M4A_GetConfig(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &a_cfg);
					nb_ch = a_cfg.nb_chan;
					sample_rate = a_cfg.base_sr;
					PL_ID = a_cfg.audioPL;
					switch (a_cfg.base_object_type) {
					case M4A_AAC_Main:
					case M4A_AAC_LC:
						if (flags & M4HF_UseLATM) {
							hintType = RTP_PAYT_LATM;
							break;
						}
					case M4A_AAC_SBR:
					case M4A_AAC_LTP:
					case M4A_AAC_Scalable:
					case M4A_ER_AAC_LC:
					case M4A_ER_AAC_LTP:
					case M4A_ER_AAC_scalable:
						mpeg4mode = "AAC";
						break;
					case M4A_CELP:
					case M4A_ER_CELP:
						mpeg4mode = "CELP";
						break;
					}
					required_rate = sample_rate;
				}
				/*MPEG1/2 audio*/
				else if ((streamType==M4ST_AUDIO) && ((oti==0x69) || (oti==0x6B))) {
					u32 sample_rate;
					if (!is_crypted) {
						M4Sample *samp = M4_GetSample(file, TrackNum, 1, NULL);
						u32 hdr = FOUR_CHAR_INT((u8)samp->data[0], (u8)samp->data[1], (u8)samp->data[2], (u8)samp->data[3]);
						nb_ch = MP3_GetNumChannels(hdr);
						sample_rate = MP3_GetSamplingRate(hdr);
						M4_DeleteSample(&samp);
						hintType = RTP_PAYT_MPEG12;
						/*use official RTP/AVP payload type*/
						OfficialPayloadID = 14;
						required_rate = 90000;
					}
					/*encrypted MP3 must be sent through MPEG-4 generic to signal all ISMACryp stuff*/
					else {
						u8 bps;
						M4_GetAudioEntryInfo(file, TrackNum, 1, &sample_rate, &nb_ch, &bps);
						required_rate = sample_rate;
					}
				}
				/*QCELP audio*/
				else if ((streamType==M4ST_AUDIO) && (oti==0xE1)) {
					hintType = RTP_PAYT_QCELP;
					OfficialPayloadID = 12;
					required_rate = 8000;
					streamType = M4ST_AUDIO;
					nb_ch = 1;
				}
				/*EVRC/SVM audio*/
				else if ((streamType==M4ST_AUDIO) && ((oti==0xA0) || (oti==0xA1)) ) {
					hintType = RTP_PAYT_EVRC_SMV;
					required_rate = 8000;
					streamType = M4ST_AUDIO;
					nb_ch = 1;
				}
				else if (streamType==M4ST_VISUAL) {
					if (oti==0x20) {
						M4VDecoderSpecificInfo dsi;
						M4V_GetConfig(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &dsi);
						PL_ID = dsi.VideoPL;
					}
					/*MPEG1/2 video*/
					if ( ((oti>=0x60) && (oti<=0x65)) || (oti==0x6A)) {
						if (!is_crypted) {
							hintType = RTP_PAYT_MPEG12;
							OfficialPayloadID = 32;
						}
					}
					/*for ISMA*/
					if (is_crypted) {
						/*that's another pain with ISMACryp, even if no B-frames the DTS is signaled...*/
						if (oti==0x20) force_dts_delta = 22;
						flags |= M4HF_SignalRAP | M4HF_SignalTS;
					}

					required_rate = default_rtp_rate;
				}
				OD_DeleteDescriptor((Descriptor**)&esd);
			}
			break;
		case M4_3GP_H263_SubType:
			hintType = RTP_PAYT_H263;
			required_rate = 90000;
			streamType = M4ST_VISUAL;
			OfficialPayloadID = 34;
			/*not 100% compliant (short header is missing) but should still work*/
			oti = 0x20;
			PL_ID = 0x01;
			break;
		case M4_3GP_AMR_SubType:
			required_rate = 8000;
			hintType = RTP_PAYT_AMR;
			streamType = M4ST_AUDIO;
			has_mpeg4_mapping = 0;
			nb_ch = 1;
			break;
		case M4_3GP_AMR_WB_SubType:
			required_rate = 16000;
			hintType = RTP_PAYT_AMR_WB;
			streamType = M4ST_AUDIO;
			has_mpeg4_mapping = 0;
			nb_ch = 1;
			break;
		case M4_AVC_H264_SubType:
		{
			AVCConfig *avcc = M4_AVC_GetStreamConfig(file, TrackNum, 1);
			required_rate = 90000;	/* "90 kHz clock rate MUST be used"*/
			hintType = RTP_PAYT_H264_AVC;
			streamType = M4ST_VISUAL;
			avc_nalu_size = avcc->nal_unit_size;
			oti = 0x21;
			PL_ID = 0x0F;
			AVC_DeleteConfig(avcc);
		}
			break;
		case M4_3GP_QCELP_SubType:
			required_rate = 8000;
			hintType = RTP_PAYT_QCELP;
			streamType = M4ST_AUDIO;
			oti = 0xE1;
			OfficialPayloadID = 12;
			nb_ch = 1;
			break;
		case M4_3GP_EVRC_SubType:
		case M4_3GP_SMV_SubType:
			required_rate = 8000;
			hintType = RTP_PAYT_EVRC_SMV;
			streamType = M4ST_AUDIO;
			oti = (TrackMediaSubType==M4_3GP_EVRC_SubType) ? 0xA0 : 0xA1;
			nb_ch = 1;
			break;
		default:
			/*ERROR*/
			hintType = 0;
			break;
		}
	}

	/*not hintable*/
	if (!hintType) return NULL;
	/*we only support self-contained files for hinting*/
	M4_GetStreamDataReference(file, TrackNum, 1, &url, &urn);
	if (url || urn) return NULL;
	
	*e = M4OutOfMem;
	tmp = malloc(sizeof(M4TrackHinterRTP ));
	if (!tmp) return NULL;
	memset(tmp, 0, sizeof(M4TrackHinterRTP ));

	/*override hinter type if requested and possible*/
	if (has_mpeg4_mapping && (flags & M4HF_ForceMPEG4)) {
		hintType = RTP_PAYT_MPEG4;
		avc_nalu_size = 0;
	}
	/*use static payload ID if enabled*/
	else if (OfficialPayloadID && (flags & M4HF_UseStatic) ) {
		PayloadID = OfficialPayloadID;
	}

	tmp->file = file;
	tmp->TrackNum = TrackNum;
	tmp->OnProgress = OnProgress;
	tmp->cbk_obj = cbk_obj;
	tmp->avc_nalu_size = avc_nalu_size;
	tmp->nb_chan = nb_ch;

	/*spatial scalability check*/
	tmp->has_ctts = M4_HasTimeOffsets(file, TrackNum);

	/*get sample info*/
	GetAvgSampleInfos(file, TrackNum, &MinSize, &MaxSize, &avgTS, &maxDTS, &const_dur, &bandwidth);

	/*update flags in MultiSL*/
	if (flags & M4HF_UseMulti) {
		if (MinSize != MaxSize) flags |= M4HF_SignalSize;
		if (!const_dur) flags |= M4HF_SignalTS;
	}
	if (tmp->has_ctts) flags |= M4HF_SignalTS;

	/*default SL for RTP */
	InitSL_RTP(&my_sl);

	my_sl.timestampResolution = M4_GetMediaTimeScale(file, TrackNum);
	/*override clockrate if set*/
	if (required_rate) {
		Double sc = required_rate;
		sc /= my_sl.timestampResolution;
		maxDTS = (u32) (maxDTS*sc);
		my_sl.timestampResolution = required_rate;
	}
	/*switch to RTP TS*/
	max_ptime = (u32) (max_ptime * my_sl.timestampResolution / 1000);

	my_sl.AUSeqNumLength = GetNumBitsForMaxValue(M4_GetSampleCount(file, TrackNum));
	my_sl.CUDuration = const_dur;

	if (M4_HasSyncPoints(file, TrackNum)) {
		my_sl.useRandomAccessPointFlag = 1;
	} else {
		my_sl.useRandomAccessPointFlag = 0;
		my_sl.hasRandomAccessUnitsOnlyFlag = 1;
	}

	if (is_crypted) {
		Bool use_sel_enc;
		M4_GetISMACrypInfo(file, TrackNum, 1, NULL, NULL, NULL, NULL, NULL, &use_sel_enc, &IV_length, &KI_length);
		if (use_sel_enc) flags |= M4HF_SelectiveEncryption;
	}

	// in case a different timescale was provided
	tmp->OrigTimeScale = M4_GetMediaTimeScale(file, TrackNum);
	tmp->rtp_p = NewRTPBuilder(hintType, &my_sl, flags, tmp, 
								MP4T_OnNewPacket, MP4T_OnPacketDone, 
								/*if copy, no data ref*/
								copy_media ? NULL : MP4T_OnDataRef, 
								MP4T_OnData);

	//init the builder
	M4RTP_InitBuilder(tmp->rtp_p, PayloadID, Path_MTU, max_ptime,
					   streamType, oti, PL_ID, MinSize, MaxSize, avgTS, maxDTS, IV_length, KI_length, mpeg4mode);

	/*ISMA compliance is a pain...*/
	if (force_dts_delta) tmp->rtp_p->slMap.DTSDeltaLength = force_dts_delta;


	/*		Hint Track Setup	*/
	tmp->TrackID = M4_GetTrackID(file, TrackNum);
	tmp->HintID = tmp->TrackID + 65535;
	while (M4_GetTrackByID(file, tmp->HintID)) tmp->HintID++;

	tmp->HintTrack = M4_NewTrack(file, tmp->HintID, M4_HintMediaType, my_sl.timestampResolution);
	M4H_SetupHintTrack(file, tmp->HintTrack, M4_Hint_RTP);
	/*create a hint description*/
	M4H_NewHintDescription(file, tmp->HintTrack, -1, -1, 0, &descIndex);
	M4H_RTP_SetTimeScale(file, tmp->HintTrack, descIndex, my_sl.timestampResolution);

	if (hintType==RTP_PAYT_MPEG4) {
		tmp->rtp_p->slMap.ObjectTypeIndication = oti;
		/*set this SL for extraction.*/
		M4_SetExtractionSLConfig(file, TrackNum, 1, &my_sl);
	}
	tmp->bandwidth = bandwidth;

	/*set interleaving*/
	M4_SetTrackGroup(file, TrackNum, InterleaveGroupID);
	if (!copy_media) {
		/*if we don't copy data set hint track and media track in the same group*/
		M4_SetTrackGroup(file, tmp->HintTrack, InterleaveGroupID);
	} else {
		M4_SetTrackGroup(file, tmp->HintTrack, InterleaveGroupID + OFFSET_HINT_GROUP_ID);
	}
	/*use user-secified priority*/
	InterleaveGroupPriority*=2;
	M4_SetTrackPriorityInGroup(file, TrackNum, InterleaveGroupPriority+1);
	M4_SetTrackPriorityInGroup(file, tmp->HintTrack, InterleaveGroupPriority);

#if 0
	/*QT FF: not setting these flags = server uses a random offset*/
	M4H_RTP_SetTimeOffset(file, tmp->HintTrack, 1, 0);
	/*we don't use seq offset for maintainance pruposes*/
	M4H_RTP_SetSequenceNumberOffset(file, tmp->HintTrack, 1, 0);
#endif
	*e = M4OK;
	return tmp;
}

u32 MP4T_GetHinterBandwith(M4TrackHinterRTP *tkHinter)
{
	return tkHinter->bandwidth;
}

u32 MP4T_GetHinterFlags(M4TrackHinterRTP *tkHinter)
{
	return tkHinter->rtp_p->flags;
}
void MP4T_GetHinterPayloadName(M4TrackHinterRTP *tkHinter, char *payloadName)
{
	char mediaName[30];
	M4RTP_GetPayloadName(tkHinter->rtp_p, payloadName, mediaName);
}

void MP4T_DeleteHinter(M4TrackHinterRTP *tkHinter)
{
	if (!tkHinter) return;

	if (tkHinter->rtp_p) M4RTP_DeleteBuilder(tkHinter->rtp_p);
	free(tkHinter);
}

M4Err MP4T_ProcessTrack(M4TrackHinterRTP *tkHint)
{
	u32 i, descIndex, ts, duration;
	u8 PadBits;
	Double ft;
	M4Sample *samp;

	tkHint->HintSample = tkHint->RTPTime = 0;

	tkHint->TotalSample = M4_GetSampleCount(tkHint->file, tkHint->TrackNum);
	ft = tkHint->rtp_p->sl_config.timestampResolution;
	ft /= tkHint->OrigTimeScale;

	for (i=0; i<tkHint->TotalSample; i++) {
		samp = M4_GetSample(tkHint->file, tkHint->TrackNum, i+1, &descIndex);
		if (!samp) return M4IOErr;

		//setup SL
		tkHint->CurrentSample = i + 1;

		ts = (u32) ((samp->DTS+samp->CTS_Offset)*ft);
		tkHint->rtp_p->sl_header.compositionTimeStamp = ts;

		ts = (u32) (samp->DTS*ft);
		tkHint->rtp_p->sl_header.decodingTimeStamp = ts;
		tkHint->rtp_p->sl_header.randomAccessPointFlag = samp->IsRAP;

		tkHint->base_offset_in_sample = 0;
		/*crypted*/
		if (tkHint->rtp_p->slMap.IV_length) {
			ISMASample *s = M4_GetISMACrypSample(tkHint->file, tkHint->TrackNum, samp, descIndex);
			/*one byte take for selective_enc flag*/
			if (s->flags & ISMA_UseSelEnc) tkHint->base_offset_in_sample += 1;
			if (s->flags & ISMA_IsEncrypted) tkHint->base_offset_in_sample += s->IV_length + s->KI_length;
			free(samp->data);
			samp->data = s->data;
			samp->dataLength = s->dataLength;
			M4RTP_SetCrypInfo(tkHint->rtp_p, s->IV, s->key_indicator, (s->flags & ISMA_IsEncrypted) ? 1 : 0);
			s->data = NULL;
			s->dataLength = 0;
			ISMA_DeleteSample(s);
		}

		if (tkHint->rtp_p->sl_config.usePaddingFlag) {
			M4_GetSamplePaddingBits(tkHint->file, tkHint->TrackNum, i+1, &PadBits);
			tkHint->rtp_p->sl_header.paddingBits = PadBits;
		} else {
			tkHint->rtp_p->sl_header.paddingBits = 0;
		}
		
		duration = M4_GetSampleDuration(tkHint->file, tkHint->TrackNum, i+1);

		/*unpack nal units*/
		if (tkHint->avc_nalu_size) {
			u32 v, size;
			u32 remain = samp->dataLength;
			char *ptr = samp->data;

			tkHint->rtp_p->sl_header.accessUnitStartFlag = 1;
			tkHint->rtp_p->sl_header.accessUnitEndFlag = 0;
			while (remain) {
				size = 0;
				v = tkHint->avc_nalu_size;
				while (v) {
					size |= (u8) *ptr;
					ptr++;
					remain--;
					v-=1;
					if (v) size<<=8;
				}
				tkHint->base_offset_in_sample = samp->dataLength-remain;
				remain -= size;
				tkHint->rtp_p->sl_header.accessUnitEndFlag = remain ? 0 : 1;
				M4RTP_ProcessData(tkHint->rtp_p, ptr, size, (u8) !remain, samp->dataLength, duration, (u8) (descIndex + SIDX_OFFSET_3GPP) );
				ptr += size;
				tkHint->rtp_p->sl_header.accessUnitStartFlag = 0;
			}
		} else {
			M4RTP_ProcessData(tkHint->rtp_p, samp->data, samp->dataLength, 1, samp->dataLength, duration, (u8) (descIndex + SIDX_OFFSET_3GPP) );
		}
		tkHint->rtp_p->sl_header.packetSequenceNumber += 1;

		M4_DeleteSample(&samp);

		//signal some progress
		if(tkHint->OnProgress != NULL){
			tkHint->OnProgress(tkHint->cbk_obj, tkHint->CurrentSample, tkHint->TotalSample);
		}

		tkHint->rtp_p->sl_header.AU_sequenceNumber += 1;
	}

	//flush
	M4RTP_ProcessData(tkHint->rtp_p, NULL, 0, 1, 0, 0, 0);

	M4H_EndHintSample(tkHint->file, tkHint->HintTrack, (u8) tkHint->SampleIsRAP);
	return M4OK;
}

static void M4T_FormatTextSDP(M4RTPBuilder *builder, char *payload_name, char *sdpLine, M4File *file, u32 track)
{
	char buffer[2000];
	u32 w, h, i, m_w, m_h;
	s32 tx, ty;
	s16 l;
	sprintf(sdpLine, "a=fmtp:%d sver=60; ", builder->PayloadType);
	M4_GetTrackVideoInfo(file, track, &w, &h, &tx, &ty, &l);
	sprintf(buffer, "width=%d; height=%d; tx=%d; ty=%d; layer=%d; ", w>>16, h>>16, tx>>16, ty>>16, l);
	strcat(sdpLine, buffer);
	m_w = w;
	m_h = h;
	for (i=0; i<M4_GetTrackCount(file); i++) {
		switch (M4_GetMediaType(file, i+1)) {
		case M4_BIFSMediaType:
		case M4_VisualMediaType:
			M4_GetTrackVideoInfo(file, i+1, &w, &h, &tx, &ty, &l);
			if (w>m_w) m_w = w;
			if (h>m_h) m_h = h;
			break;
		default:
			break;
		}
	}
	sprintf(buffer, "max-w=%d; max-h=%d", m_w>>16, m_h>>16);
	strcat(sdpLine, buffer);

	strcat(sdpLine, "; tx3g=");
	for (i=0; i<M4_GetStreamDescriptionCount(file, track); i++) {
		char *tx3g;
		u32 tx3g_len, len;
		M4_GetEncodedTX3G(file, track, i+1, SIDX_OFFSET_3GPP, &tx3g, &tx3g_len);
		len = Base64_enc(tx3g, tx3g_len, buffer, 2000);
		free(tx3g);
		buffer[len] = 0;
		if (i) strcat(sdpLine, ", ");
		strcat(sdpLine, buffer);
	}
}

M4Err MP4T_FinalizeHintTrack(M4TrackHinterRTP *tkHint, Bool AddSystemInfo)
{
	u32 Width, Height;
	DecoderConfigDescriptor *dcd;
	char sdpLine[20000];
	char mediaName[30], payloadName[30];

	Width = Height = 0;
	M4H_SDP_CleanTrack(tkHint->file, tkHint->TrackNum);
	if (M4_GetMediaType(tkHint->file, tkHint->TrackNum) == M4_VisualMediaType)
		M4_GetVisualEntrySize(tkHint->file, tkHint->TrackNum, 1, &Width, &Height);

	M4RTP_GetPayloadName(tkHint->rtp_p, payloadName, mediaName);

	/*TODO- extract out of rtp_p for future live tools*/
	sprintf(sdpLine, "m=%s 0 RTP/%s %d", mediaName, tkHint->rtp_p->slMap.IV_length ? "SAVP" : "AVP", tkHint->rtp_p->PayloadType);
	M4H_SDP_TrackAddLine(tkHint->file, tkHint->HintTrack, sdpLine);
	if (tkHint->bandwidth) {
		sprintf(sdpLine, "b=AS:%d", tkHint->bandwidth);
		M4H_SDP_TrackAddLine(tkHint->file, tkHint->HintTrack, sdpLine);
	}
	if (tkHint->nb_chan) {
		sprintf(sdpLine, "a=rtpmap:%d %s/%d/%d", tkHint->rtp_p->PayloadType, payloadName, tkHint->rtp_p->sl_config.timestampResolution, tkHint->nb_chan);
	} else {
		sprintf(sdpLine, "a=rtpmap:%d %s/%d", tkHint->rtp_p->PayloadType, payloadName, tkHint->rtp_p->sl_config.timestampResolution);
	}
	M4H_SDP_TrackAddLine(tkHint->file, tkHint->HintTrack, sdpLine);
	/*control for MPEG-4*/
	if (AddSystemInfo) {
		sprintf(sdpLine, "a=mpeg4-esid:%d", M4_GetTrackID(tkHint->file, tkHint->TrackNum));
		M4H_SDP_TrackAddLine(tkHint->file, tkHint->HintTrack, sdpLine);
	}
	/*control for QTSS/DSS*/
	sprintf(sdpLine, "a=control:trackID=%d", M4_GetTrackID(tkHint->file, tkHint->HintTrack));
	M4H_SDP_TrackAddLine(tkHint->file, tkHint->HintTrack, sdpLine);

	/*H263 extensions*/
	if (tkHint->rtp_p->rtp_payt == RTP_PAYT_H263) {
		sprintf(sdpLine, "a=cliprect:0,0,%d,%d", Height, Width);
		M4H_SDP_TrackAddLine(tkHint->file, tkHint->HintTrack, sdpLine);
	}
	/*AMR*/
	else if ((tkHint->rtp_p->rtp_payt == RTP_PAYT_AMR) || (tkHint->rtp_p->rtp_payt == RTP_PAYT_AMR_WB)) {
		sprintf(sdpLine, "a=fmtp:%d octet-align", tkHint->rtp_p->PayloadType);
		M4H_SDP_TrackAddLine(tkHint->file, tkHint->HintTrack, sdpLine);
	}
	/*Text*/
	else if (tkHint->rtp_p->rtp_payt == RTP_PAYT_3GPP_TEXT) {
		M4T_FormatTextSDP(tkHint->rtp_p, payloadName, sdpLine, tkHint->file, tkHint->TrackNum);
		M4H_SDP_TrackAddLine(tkHint->file, tkHint->HintTrack, sdpLine);
	}
	/*EVRC/SMV in non header-free mode*/
	else if ((tkHint->rtp_p->rtp_payt == RTP_PAYT_EVRC_SMV) && (tkHint->rtp_p->auh_size>1)) {
		sprintf(sdpLine, "a=fmtp:%d maxptime=%d", tkHint->rtp_p->PayloadType, tkHint->rtp_p->auh_size*20);
		M4H_SDP_TrackAddLine(tkHint->file, tkHint->HintTrack, sdpLine);
	}
	/*H264/AVC*/
	else if (tkHint->rtp_p->rtp_payt == RTP_PAYT_H264_AVC) {
		AVCConfig *avcc = M4_AVC_GetStreamConfig(tkHint->file, tkHint->TrackNum, 1);
		sprintf(sdpLine, "a=fmtp:%d profile-level-id=%02X%02X%02X; packetization-mode=1", tkHint->rtp_p->PayloadType, avcc->AVCProfileIndication, avcc->profile_compatibility, avcc->AVCLevelIndication);
		if (ChainGetCount(avcc->pictureParameterSets) || ChainGetCount(avcc->sequenceParameterSets)) {
			u32 i, count, b64s;
			char b64[200];
			strcat(sdpLine, "; sprop-parameter-sets=");
			count = ChainGetCount(avcc->sequenceParameterSets);
			for (i=0; i<count; i++) {
				AVCConfigSlot *sl = ChainGetEntry(avcc->sequenceParameterSets, i);
				b64s = Base64_enc(sl->data, sl->size, b64, 200);
				b64[b64s]=0;
				strcat(sdpLine, b64);
				if (i+1<count) strcat(sdpLine, ",");
			}
			if (i) strcat(sdpLine, ",");
			count = ChainGetCount(avcc->pictureParameterSets);
			for (i=0; i<count; i++) {
				AVCConfigSlot *sl = ChainGetEntry(avcc->pictureParameterSets, i);
				b64s = Base64_enc(sl->data, sl->size, b64, 200);
				b64[b64s]=0;
				strcat(sdpLine, b64);
				if (i+1<count) strcat(sdpLine, ",");
			}
		}
		M4H_SDP_TrackAddLine(tkHint->file, tkHint->HintTrack, sdpLine);
		AVC_DeleteConfig(avcc);
	}
	/*MPEG-4 decoder config*/
	else if (tkHint->rtp_p->rtp_payt==RTP_PAYT_MPEG4) {
		dcd = M4_GetDecoderInformation(tkHint->file, tkHint->TrackNum, 1);

		if (dcd && dcd->decoderSpecificInfo && dcd->decoderSpecificInfo->data) {
			M4RTP_FormatMPEG4_SDP(tkHint->rtp_p, payloadName, sdpLine, dcd->decoderSpecificInfo->data, dcd->decoderSpecificInfo->dataLength);
		} else {
			M4RTP_FormatMPEG4_SDP(tkHint->rtp_p, payloadName, sdpLine, NULL, 0);
		}
		if (dcd) OD_DeleteDescriptor((Descriptor **)&dcd);

		if (tkHint->rtp_p->slMap.IV_length) {
			const char *kms;
			M4_GetISMACrypInfo(tkHint->file, tkHint->TrackNum, 1, NULL, NULL, NULL, NULL, &kms, NULL, NULL, NULL);
			if (!strnicmp(kms, "(key)", 5) || !strnicmp(kms, "(ipmp)", 6) || !strnicmp(kms, "(uri)", 5)) {
				strcat(sdpLine, "; ISMACrypKey=");
			} else {
				strcat(sdpLine, "; ISMACrypKey=(uri)");
			}
			strcat(sdpLine, kms);
		}

		M4H_SDP_TrackAddLine(tkHint->file, tkHint->HintTrack, sdpLine);
	}
	/*MPEG-4 Audio LATM*/
	else if (tkHint->rtp_p->rtp_payt==RTP_PAYT_LATM) { 
		BitStream *bs; 
		unsigned char *config_bytes; 
		u32 config_size; 
 
		/* form config string */ 
		bs = NewBitStream(NULL, 32, BS_WRITE); 
		BS_WriteInt(bs, 0, 1); /* AudioMuxVersion */ 
		BS_WriteInt(bs, 1, 1); /* all streams same time */ 
		BS_WriteInt(bs, 0, 6); /* numSubFrames */ 
		BS_WriteInt(bs, 0, 4); /* numPrograms */ 
		BS_WriteInt(bs, 0, 3); /* numLayer */ 
 
		/* audio-specific config */ 
		dcd = M4_GetDecoderInformation(tkHint->file, tkHint->TrackNum, 1); 
		if (dcd) { 
			BS_WriteData(bs, dcd->decoderSpecificInfo->data, dcd->decoderSpecificInfo->dataLength); 
			OD_DeleteDescriptor((Descriptor **)&dcd); 
		} 
 
		/* other data */ 
		BS_WriteInt(bs, 0, 3); /* frameLengthType */ 
		BS_WriteInt(bs, 0xff, 8); /* latmBufferFullness */ 
		BS_WriteInt(bs, 0, 1); /* otherDataPresent */ 
		BS_WriteInt(bs, 0, 1); /* crcCheckPresent */ 
		BS_GetContent(bs, &config_bytes, &config_size); 
		DeleteBitStream(bs); 
 
		M4RTP_FormatMPEG4_SDP(tkHint->rtp_p, payloadName, sdpLine, config_bytes, config_size); 
		M4H_SDP_TrackAddLine(tkHint->file, tkHint->HintTrack, sdpLine); 
		free(config_bytes); 
	} 
	M4_SetTrackEnabled(tkHint->file, tkHint->HintTrack, 1);
	return M4OK;
}

M4Err MP4T_FinalizeHintMovie(M4File *file, u32 IOD_Profile, u32 bandwidth)
{
	u32 i, bifsT, odT, descIndex, size, size64;
	InitialObjectDescriptor *iod;
	SLConfigDescriptor slc;
	ESDescriptor *esd;
	M4Sample *samp;
	Bool remove_ocr;
	char *buffer;
	char buf64[5000], sdpLine[2300];


	M4H_SDP_CleanMovie(file);

	if (bandwidth) {
		sprintf(buf64, "b=AS:%d", bandwidth);
		M4H_SDP_MovieAddLine(file, buf64);
	}
	//xtended attribute for copyright
	sprintf(buf64, "a=x-copyright: %s", "MP4/3GP File hinted with GPAC " M4_VERSION " (C)2000-2005 - http://gpac.sourceforge.net");
	M4H_SDP_MovieAddLine(file, buf64);

	if (IOD_Profile == SDP_IOD_NONE) return M4OK;

	odT = bifsT = 0;
	for (i=0; i<M4_GetTrackCount(file); i++) {
		if (!M4_IsTrackInRootOD(file, i+1)) continue;
		switch (M4_GetMediaType(file,i+1)) {
		case M4_ODMediaType:
			odT = i+1;
			break;
		case M4_BIFSMediaType:
			bifsT = i+1;
			break;
		}
	}
	remove_ocr = 0;
	if (IOD_Profile == SDP_IOD_ISMA_STRICT) {
		IOD_Profile = SDP_IOD_ISMA;
		remove_ocr = 1;
	}

	/*if we want ISMA like iods, we need at least BIFS */
	if ( (IOD_Profile == SDP_IOD_ISMA) && !bifsT ) return M4BadParam;

	/*do NOT change PLs, we assume they are correct*/
	iod = (InitialObjectDescriptor *) M4_GetRootOD(file);
	if (!iod) return M4NotSupported;

	/*rewrite an IOD with good SL config - embbed data if possible*/
	if (IOD_Profile == SDP_IOD_ISMA) {
		Bool is_ok = 1;
		while (ChainGetCount(iod->ESDescriptors)) {
			esd = ChainGetEntry(iod->ESDescriptors, 0);
			OD_DeleteDescriptor((Descriptor **) &esd);
			ChainDeleteEntry(iod->ESDescriptors, 0);
		}


		/*get OD esd, and embbed stream data if possible*/
		if (odT) {
			esd = M4_GetStreamDescriptor(file, odT, 1);
			if (M4_GetSampleCount(file, odT)==1) {
				samp = M4_GetSample(file, odT, 1, &descIndex);
				if (MP4T_CanEmbbedData(samp->data, samp->dataLength, M4ST_OD)) {
					InitSL_NULL(&slc);
					slc.predefined = 0;
					slc.hasRandomAccessUnitsOnlyFlag = 1;
					slc.timeScale = slc.timestampResolution = M4_GetMediaTimeScale(file, odT);	
					slc.OCRResolution = 1000;
					slc.startCTS = samp->DTS+samp->CTS_Offset;
					slc.startDTS = samp->DTS;
					//set the SL for future extraction
					M4_SetExtractionSLConfig(file, odT, 1, &slc);

					size64 = Base64_enc(samp->data, samp->dataLength, buf64, 2000);
					buf64[size64] = 0;
					sprintf(sdpLine, "data:application/mpeg4-od-au;base64,%s", buf64);

					esd->decoderConfig->avgBitrate = 0;
					esd->decoderConfig->bufferSizeDB = samp->dataLength;
					esd->decoderConfig->maxBitrate = 0;
					size64 = strlen(sdpLine)+1;
					esd->URLString = malloc(sizeof(char) * size64);
					strcpy(esd->URLString, sdpLine);
				} else {
					fprintf(stdout, "Warning: OD sample too large to be embedded in IOD - ISAM disabled\n");
					is_ok = 0;
				}
				M4_DeleteSample(&samp);
			}
			if (remove_ocr) esd->OCRESID = 0;
			else if (esd->OCRESID == esd->ESID) esd->OCRESID = 0;
			
			//OK, add this to our IOD
			ChainAddEntry(iod->ESDescriptors, esd);
		}

		esd = M4_GetStreamDescriptor(file, bifsT, 1);
		if (M4_GetSampleCount(file, bifsT)==1) {
			samp = M4_GetSample(file, bifsT, 1, &descIndex);
			if (MP4T_CanEmbbedData(samp->data, samp->dataLength, M4ST_SCENE)) {

				slc.timeScale = slc.timestampResolution = M4_GetMediaTimeScale(file, bifsT);	
				slc.OCRResolution = 1000;
				slc.startCTS = samp->DTS+samp->CTS_Offset;
				slc.startDTS = samp->DTS;
				//set the SL for future extraction
				M4_SetExtractionSLConfig(file, bifsT, 1, &slc);
				//encode in Base64 the sample
				size64 = Base64_enc(samp->data, samp->dataLength, buf64, 2000);
				buf64[size64] = 0;
				sprintf(sdpLine, "data:application/mpeg4-bifs-au;base64,%s", buf64);

				esd->decoderConfig->avgBitrate = 0;
				esd->decoderConfig->bufferSizeDB = samp->dataLength;
				esd->decoderConfig->maxBitrate = 0;
				esd->URLString = malloc(sizeof(char) * (strlen(sdpLine)+1));
				strcpy(esd->URLString, sdpLine);
			} else {
				fprintf(stdout, "Warning: BIFS sample too large to be embedded in IOD - ISMA disabled\n");
				is_ok = 0;
			}
			M4_DeleteSample(&samp);
		}
		if (remove_ocr) esd->OCRESID = 0;
		else if (esd->OCRESID == esd->ESID) esd->OCRESID = 0;

		ChainAddEntry(iod->ESDescriptors, esd);

		if (is_ok) {
			u32 has_a, has_v, has_i_a, has_i_v;
			has_a = has_v = has_i_a = has_i_v = 0;
			for (i=0; i<M4_GetTrackCount(file); i++) {
				esd = M4_GetStreamDescriptor(file, i+1, 1);
				if (!esd) continue;
				if (esd->decoderConfig->streamType==M4ST_VISUAL) {
					if (esd->decoderConfig->objectTypeIndication==0x20) has_i_v ++;
					else has_v++;
				} else if (esd->decoderConfig->streamType==M4ST_AUDIO) {
					if (esd->decoderConfig->objectTypeIndication==0x40) has_i_a ++;
					else has_a++;
				}
				OD_DeleteDescriptor((Descriptor **)&esd);
			}
			/*only 1 MPEG-4 visual max and 1 MPEG-4 audio max for ISMA compliancy*/
			if (!has_v && !has_a && (has_i_v<=1) && (has_i_a<=1)) {
				sprintf(sdpLine, "a=isma-compliance=1,1.0,1");
				M4H_SDP_MovieAddLine(file, sdpLine);
			}
		}
	}

	//encode the IOD
	buffer = NULL;
	size = 0;
	OD_EncDesc((Descriptor *) iod, &buffer, &size);
	OD_DeleteDescriptor((Descriptor **)&iod);

	//encode in Base64 the iod
	size64 = Base64_enc(buffer, size, buf64, 2000);
	buf64[size64] = 0;
	free(buffer);

	sprintf(sdpLine, "a=mpeg4-iod:\"data:application/mpeg4-iod;base64,%s\"", buf64);
	M4H_SDP_MovieAddLine(file, sdpLine);

	return M4OK;
}


#endif //M4_READ_ONLY

