ref: 3b2e3d159a714243528bc259f4e20e4858bd4db6
dir: /common/mp4av/rfc3119.cpp/
/*
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 * 
 * The Original Code is MPEG4IP.
 * 
 * The Initial Developer of the Original Code is Cisco Systems Inc.
 * Portions created by Cisco Systems Inc. are
 * Copyright (C) Cisco Systems Inc. 2000-2002.  All Rights Reserved.
 * 
 * Contributor(s): 
 *		Dave Mackie		dmackie@cisco.com
 */
/* 
 * Notes:
 *  - file formatted with tabstops == 4 spaces 
 */
#include <mp4av_common.h>
// LATER get these on the stack so library is thread-safe!
// file globals
static bool doInterleave;
static u_int32_t samplesPerPacket;
static u_int32_t samplesPerGroup;
static MP4AV_Mp3Header* pFrameHeaders = NULL;
static u_int16_t* pAduOffsets = NULL;
static bool GetFrameInfo(
	MP4FileHandle mp4File, 
	MP4TrackId mediaTrackId,
	MP4AV_Mp3Header** ppFrameHeaders,
	u_int16_t** ppAduOffsets)
{
	// allocate memory to hold the frame info that we need
	u_int32_t numSamples =
		MP4GetTrackNumberOfSamples(mp4File, mediaTrackId);
	*ppFrameHeaders = 
		(MP4AV_Mp3Header*)calloc((numSamples + 2), sizeof(u_int32_t));
	if (*ppFrameHeaders == NULL) {
		return false;
	}
	*ppAduOffsets = 
		(u_int16_t*)calloc((numSamples + 2), sizeof(u_int16_t));
	if (*ppAduOffsets == NULL) {
		free(*ppFrameHeaders);
		return false;
	}
	// for each sample
	for (MP4SampleId sampleId = 1; sampleId <= numSamples; sampleId++) { 
		u_int8_t* pSample = NULL;
		u_int32_t sampleSize = 0;
		// read it
		MP4ReadSample(
			mp4File,
			mediaTrackId,
			sampleId,
			&pSample,
			&sampleSize);
		// extract the MP3 frame header
		MP4AV_Mp3Header mp3hdr = 
			MP4AV_Mp3HeaderFromBytes(pSample);
		// store what we need
		(*ppFrameHeaders)[sampleId] = mp3hdr;
		(*ppAduOffsets)[sampleId] = 
			MP4AV_Mp3GetAduOffset(pSample, sampleSize);
		free(pSample);
	}
	return true;
}
static u_int16_t GetFrameHeaderSize(MP4SampleId sampleId)
{
	return 4 + MP4AV_Mp3GetCrcSize(pFrameHeaders[sampleId]) 
		+ MP4AV_Mp3GetSideInfoSize(pFrameHeaders[sampleId]);
}
static u_int16_t GetFrameDataSize(
	MP4FileHandle mp4File, 
	MP4TrackId mediaTrackId, 
	MP4SampleId sampleId)
{
	return MP4GetSampleSize(mp4File, mediaTrackId, sampleId)
		- GetFrameHeaderSize(sampleId);
}
u_int32_t MP4AV_Rfc3119GetAduSize(
	MP4FileHandle mp4File, 
	MP4TrackId mediaTrackId, 
	MP4SampleId sampleId)
{
	u_int32_t sampleSize = MP4GetSampleSize(mp4File, mediaTrackId, sampleId);
	return pAduOffsets[sampleId] + sampleSize - pAduOffsets[sampleId + 1];
}
static u_int16_t GetMaxAduSize(
	MP4FileHandle mp4File, 
	MP4TrackId mediaTrackId)
{
	u_int32_t numSamples =
		MP4GetTrackNumberOfSamples(mp4File, mediaTrackId);
	u_int16_t maxAduSize = 0;
	for (MP4SampleId sampleId = 1; sampleId <= numSamples; sampleId++) { 
		u_int16_t aduSize =
			MP4AV_Rfc3119GetAduSize(mp4File, mediaTrackId, sampleId);
		if (aduSize > maxAduSize) {
			maxAduSize = aduSize;
		}
	}
	return maxAduSize;
}
static u_int16_t GetAduDataSize(
	MP4FileHandle mp4File, 
	MP4TrackId mediaTrackId, 
	MP4SampleId sampleId)
{
	return MP4AV_Rfc3119GetAduSize(mp4File, mediaTrackId, sampleId) 
		- GetFrameHeaderSize(sampleId);
}
static void AddFrameHeader(
	MP4FileHandle mp4File, 
	MP4TrackId mediaTrackId, 
	MP4TrackId hintTrackId,
	MP4SampleId sampleId)
{
	// when interleaving we replace the 11 bit mp3 frame sync
	if (doInterleave) {
		// compute interleave index and interleave cycle from sampleId
		u_int8_t interleaveIndex =
			(sampleId - 1) % samplesPerGroup;
		u_int8_t interleaveCycle =
			((sampleId - 1) / samplesPerGroup) & 0x7;
		u_int8_t interleaveHeader[4];
		interleaveHeader[0] = 
			interleaveIndex;
		interleaveHeader[1] = 
			(interleaveCycle << 5) | ((pFrameHeaders[sampleId] >> 16) & 0x1F);
		interleaveHeader[2] = 
			(pFrameHeaders[sampleId] >> 8) & 0xFF;
		interleaveHeader[3] = 
			pFrameHeaders[sampleId] & 0xFF;
		MP4AddRtpImmediateData(mp4File, hintTrackId,
			interleaveHeader, 4);
		// add crc and side info from current mp3 frame
		MP4AddRtpSampleData(mp4File, hintTrackId,
			sampleId, 4, GetFrameHeaderSize(sampleId) - 4);
	} else {
		// add mp3 header, crc, and side info from current mp3 frame
		MP4AddRtpSampleData(mp4File, hintTrackId,
			sampleId, 0, GetFrameHeaderSize(sampleId));
	}
}
static void CollectAduDataBlocks(
	MP4FileHandle mp4File, 
	MP4TrackId mediaTrackId, 
	MP4TrackId hintTrackId,
	MP4SampleId sampleId,
	u_int8_t* pNumBlocks,
	u_int32_t** ppOffsets,
	u_int32_t** ppSizes)
{
	// go back from sampleId until 
	// accumulated data bytes can fill sample's ADU
	MP4SampleId sid = sampleId;
	u_int8_t numBlocks = 1;
	u_int32_t prevDataBytes = 0;
	const u_int8_t maxBlocks = 8;
	*ppOffsets = new u_int32_t[maxBlocks];
	*ppSizes = new u_int32_t[maxBlocks];
	(*ppOffsets)[0] = GetFrameHeaderSize(sampleId);
	(*ppSizes)[0] = GetFrameDataSize(mp4File, mediaTrackId, sid);
	while (true) {
		if (prevDataBytes >= pAduOffsets[sampleId]) {
			u_int32_t adjust =
				prevDataBytes - pAduOffsets[sampleId];
			(*ppOffsets)[numBlocks-1] += adjust; 
			(*ppSizes)[numBlocks-1] -= adjust;
			break;
		}
		
		sid--;
		numBlocks++;
		if (sid == 0 || numBlocks > maxBlocks) {
			throw EIO;	// media bitstream error
		}
		(*ppOffsets)[numBlocks-1] = GetFrameHeaderSize(sid);
		(*ppSizes)[numBlocks-1] = GetFrameDataSize(mp4File, mediaTrackId, sid);
		prevDataBytes += (*ppSizes)[numBlocks-1]; 
	}
	*pNumBlocks = numBlocks;
}
bool MP4AV_Rfc3119Concatenator(
	MP4FileHandle mp4File, 
	MP4TrackId mediaTrackId, 
	MP4TrackId hintTrackId,
	u_int8_t samplesThisHint, 
	MP4SampleId* pSampleIds, 
	MP4Duration hintDuration,
	u_int16_t maxPayloadSize)
{
	// handle degenerate case
	if (samplesThisHint == 0) {
		return true;
	}
	// construct the new hint
	MP4AddRtpHint(mp4File, hintTrackId);
	MP4AddRtpPacket(mp4File, hintTrackId, false);
	// rfc 3119 per adu payload header
	u_int8_t payloadHeader[2];
	// for each given sample
	for (u_int8_t i = 0; i < samplesThisHint; i++) {
		MP4SampleId sampleId = pSampleIds[i];
		u_int16_t aduSize = 
			MP4AV_Rfc3119GetAduSize(mp4File, mediaTrackId, sampleId);
		// add the per ADU payload header
		payloadHeader[0] = 0x40 | ((aduSize >> 8) & 0x3F);
		payloadHeader[1] = aduSize & 0xFF;
		MP4AddRtpImmediateData(mp4File, hintTrackId,
			(u_int8_t*)&payloadHeader, sizeof(payloadHeader));
		// add the mp3 frame header and side info
		AddFrameHeader(mp4File, mediaTrackId, hintTrackId, sampleId);
		// collect the info on the adu main data fragments
		u_int8_t numDataBlocks;
		u_int32_t* pDataOffsets;
		u_int32_t* pDataSizes;
		CollectAduDataBlocks(mp4File, mediaTrackId, hintTrackId, sampleId,
			&numDataBlocks, &pDataOffsets, &pDataSizes);
		// collect the needed blocks of data
		u_int16_t dataSize = 0;
		u_int16_t aduDataSize = 
			GetAduDataSize(mp4File, mediaTrackId, sampleId);
		for (int8_t i = numDataBlocks - 1;
		  i >= 0 && dataSize < aduDataSize; i--) {
			u_int32_t blockSize = pDataSizes[i];
			if ((u_int32_t)(aduDataSize - dataSize) < blockSize) {
				blockSize = (u_int32_t)(aduDataSize - dataSize);
			}
			MP4AddRtpSampleData(mp4File, hintTrackId,
				sampleId - i, pDataOffsets[i], blockSize);
			dataSize += blockSize;
		}
		delete [] pDataOffsets;
		delete [] pDataSizes;
	}
	// write the hint
	MP4WriteRtpHint(mp4File, hintTrackId, hintDuration);
	return true;
}
bool MP4AV_Rfc3119Fragmenter(
	MP4FileHandle mp4File, 
	MP4TrackId mediaTrackId, 
	MP4TrackId hintTrackId,
	MP4SampleId sampleId, 
	u_int32_t aduSize, 
	MP4Duration sampleDuration,
	u_int16_t maxPayloadSize)
{
	MP4AddRtpHint(mp4File, hintTrackId);
	MP4AddRtpPacket(mp4File, hintTrackId, false);
	// rfc 3119 payload header
	u_int8_t payloadHeader[2];
	u_int16_t payloadSize = 
		sizeof(payloadHeader) + GetFrameHeaderSize(sampleId);
	// guard against ridiculous payload sizes
	if (payloadSize > maxPayloadSize) {
		return false;
	}
	// add the per ADU fragment payload header
	payloadHeader[0] = 0x40 | ((aduSize >> 8) & 0x3F);
	payloadHeader[1] = aduSize & 0xFF;
	MP4AddRtpImmediateData(mp4File, hintTrackId,
		(u_int8_t*)&payloadHeader, sizeof(payloadHeader));
	payloadHeader[0] |= 0x80;	// mark future packets as continuations
	// add the mp3 frame header and side info
	AddFrameHeader(mp4File, mediaTrackId, hintTrackId, sampleId);
	// collect the info on the adu main data fragments
	u_int8_t numDataBlocks;
	u_int32_t* pDataOffsets;
	u_int32_t* pDataSizes;
	CollectAduDataBlocks(mp4File, mediaTrackId, hintTrackId, sampleId,
		&numDataBlocks, &pDataOffsets, &pDataSizes);
	u_int16_t dataSize = 0;
	u_int16_t aduDataSize = 
		GetAduDataSize(mp4File, mediaTrackId, sampleId);
	// for each data block
	for (int8_t i = numDataBlocks - 1; i >= 0 && dataSize < aduDataSize; i--) {
		u_int32_t blockSize = pDataSizes[i];
		u_int32_t blockOffset = pDataOffsets[i];
		// we may not need all of a block
		if ((u_int32_t)(aduDataSize - dataSize) < blockSize) {
			blockSize = (u_int32_t)(aduDataSize - dataSize);
		}
		dataSize += blockSize;
		// while data left in this block
		while (blockSize > 0) {
			u_int16_t payloadRemaining = maxPayloadSize - payloadSize;
			if (blockSize < payloadRemaining) {
				// the entire block fits in this packet
				MP4AddRtpSampleData(mp4File, hintTrackId,
					sampleId - i, blockOffset, blockSize);
				payloadSize += blockSize;
				blockSize = 0;
			} else {
				// the block fills this packet
				MP4AddRtpSampleData(mp4File, hintTrackId,
					sampleId - i, blockOffset, payloadRemaining);
				blockOffset += payloadRemaining;
				blockSize -= payloadRemaining;
				// start a new RTP packet
				MP4AddRtpPacket(mp4File, hintTrackId, false);
				// add the fragment payload header
				MP4AddRtpImmediateData(mp4File, hintTrackId,
					(u_int8_t*)&payloadHeader, sizeof(payloadHeader));
				payloadSize = sizeof(payloadHeader);
			}
		}
	}
	// write the hint
	MP4WriteRtpHint(mp4File, hintTrackId, sampleDuration);
	// cleanup
	delete [] pDataOffsets;
	delete [] pDataSizes;
	return true;
}
extern "C" bool MP4AV_Rfc3119Hinter(
	MP4FileHandle mp4File, 
	MP4TrackId mediaTrackId, 
	bool interleave,
	u_int16_t maxPayloadSize)
{
	int rc;
	u_int32_t numSamples =
		MP4GetTrackNumberOfSamples(mp4File, mediaTrackId);
	if (numSamples == 0) {
		return false;
	}
	u_int8_t audioType =
		MP4GetTrackEsdsObjectTypeId(mp4File, mediaTrackId);
	if (!MP4_IS_MP3_AUDIO_TYPE(audioType)) {
		return false;
	}
	MP4Duration sampleDuration = 
		MP4AV_GetAudioSampleDuration(mp4File, mediaTrackId);
	if (sampleDuration == MP4_INVALID_DURATION) {
		return false;
	}
	// choose 500 ms max latency
	MP4Duration maxLatency = 
		MP4GetTrackTimeScale(mp4File, mediaTrackId) / 2;
	if (maxLatency == 0) {
		return false;
	}
	MP4TrackId hintTrackId =
		MP4AddHintTrack(mp4File, mediaTrackId);
	if (hintTrackId == MP4_INVALID_TRACK_ID) {
		return false;
	}
	doInterleave = interleave;
	u_int8_t payloadNumber = MP4_SET_DYNAMIC_PAYLOAD;
	MP4SetHintTrackRtpPayload(mp4File, hintTrackId, 
		"mpa-robust", &payloadNumber, 0);
	// load mp3 frame information into memory
	rc = GetFrameInfo(
		mp4File, 
		mediaTrackId, 
		&pFrameHeaders, 
		&pAduOffsets);
	if (!rc) {
		MP4DeleteTrack(mp4File, hintTrackId);
		return false;
	}
 
	if (doInterleave) {
		u_int32_t maxAduSize =
			GetMaxAduSize(mp4File, mediaTrackId);
		// compute how many maximum size samples would fit in a packet
		samplesPerPacket = 
			maxPayloadSize / (maxAduSize + 2);
		// can't interleave if this number is 0 or 1
		if (samplesPerPacket < 2) {
			doInterleave = false;
		}
	}
	if (doInterleave) {
		// initial estimate of samplesPerGroup
		samplesPerGroup = maxLatency / sampleDuration;
		// use that to compute an integral stride
		u_int8_t stride = samplesPerGroup / samplesPerPacket;
		// and then recompute samples per group to deal with rounding
		samplesPerGroup = stride * samplesPerPacket;
		rc = MP4AV_AudioInterleaveHinter(
			mp4File, 
			mediaTrackId, 
			hintTrackId,
			sampleDuration, 
			samplesPerGroup / samplesPerPacket,		// stride
			samplesPerPacket,						// bundle
			maxPayloadSize,
			MP4AV_Rfc3119Concatenator);
	} else {
		rc = MP4AV_AudioConsecutiveHinter(
			mp4File, 
			mediaTrackId, 
			hintTrackId,
			sampleDuration, 
			0,										// perPacketHeaderSize
			2,										// perSampleHeaderSize
			maxLatency / sampleDuration,			// maxSamplesPerPacket
			maxPayloadSize,
			MP4AV_Rfc3119GetAduSize,
			MP4AV_Rfc3119Concatenator,
			MP4AV_Rfc3119Fragmenter);
	}
	// cleanup
	free(pFrameHeaders);
	pFrameHeaders = NULL;
	free(pAduOffsets);
	pAduOffsets = NULL;
	if (!rc) {
		MP4DeleteTrack(mp4File, hintTrackId);
		return false;
	}
	return true;
}