extern "C" bool MP4AV_RfcIsmaHinter( MP4FileHandle mp4File, MP4TrackId mediaTrackId, bool interleave, u_int16_t maxPayloadSize) { // gather information, and check for validity u_int32_t numSamples = MP4GetTrackNumberOfSamples(mp4File, mediaTrackId); if (numSamples == 0) { return false; } u_int32_t timeScale = MP4GetTrackTimeScale(mp4File, mediaTrackId); if (timeScale == 0) { return false; } u_int8_t audioType = MP4GetTrackEsdsObjectTypeId(mp4File, mediaTrackId); if (audioType != MP4_MPEG4_AUDIO_TYPE && !MP4_IS_AAC_AUDIO_TYPE(audioType)) { return false; } u_int8_t mpeg4AudioType = MP4GetTrackAudioMpeg4Type(mp4File, mediaTrackId); if (audioType == MP4_MPEG4_AUDIO_TYPE) { // check that track contains either MPEG-4 AAC or CELP if (!MP4_IS_MPEG4_AAC_AUDIO_TYPE(mpeg4AudioType) && mpeg4AudioType != MP4_MPEG4_CELP_AUDIO_TYPE) { return false; } } MP4Duration sampleDuration = MP4AV_GetAudioSampleDuration(mp4File, mediaTrackId); if (sampleDuration == MP4_INVALID_DURATION) { return false; } /* get the ES configuration */ u_int8_t* pConfig = NULL; u_int32_t configSize; uint8_t channels; if (MP4GetTrackESConfiguration(mp4File, mediaTrackId, &pConfig, &configSize) == false) return false; if (!pConfig) { return false; } channels = MP4AV_AacConfigGetChannels(pConfig); /* convert ES Config into ASCII form */ char* sConfig = MP4BinaryToBase16(pConfig, configSize); free(pConfig); if (!sConfig) { return false; } /* create the appropriate SDP attribute */ uint sdpBufLen = strlen(sConfig) + 256; char* sdpBuf = (char*)malloc(sdpBufLen); if (!sdpBuf) { free(sConfig); return false; } // now add the hint track MP4TrackId hintTrackId = MP4AddHintTrack(mp4File, mediaTrackId); if (hintTrackId == MP4_INVALID_TRACK_ID) { free(sConfig); free(sdpBuf); return false; } u_int8_t payloadNumber = MP4_SET_DYNAMIC_PAYLOAD; char buffer[10]; if (channels != 1) { snprintf(buffer, sizeof(buffer), "%u", channels); } if (MP4SetHintTrackRtpPayload(mp4File, hintTrackId, "mpeg4-generic", &payloadNumber, 0, channels != 1 ? buffer : NULL) == false) { MP4DeleteTrack(mp4File, hintTrackId); free(sConfig); free(sdpBuf); return false; } MP4Duration maxLatency; bool OneByteHeader = false; if (mpeg4AudioType == MP4_MPEG4_CELP_AUDIO_TYPE) { snprintf(sdpBuf, sdpBufLen, "a=fmtp:%u " "streamtype=5; profile-level-id=15; mode=CELP-vbr; config=%s; " "SizeLength=6; IndexLength=2; IndexDeltaLength=2; Profile=0;" "\015\012", payloadNumber, sConfig); // 200 ms max latency for ISMA profile 1 maxLatency = timeScale / 5; OneByteHeader = true; } else { // AAC snprintf(sdpBuf, sdpBufLen, "a=fmtp:%u " "streamtype=5; profile-level-id=15; mode=AAC-hbr; config=%s; " "SizeLength=13; IndexLength=3; IndexDeltaLength=3;" "\015\012", payloadNumber, sConfig); // 500 ms max latency for ISMA profile 1 maxLatency = timeScale / 2; } /* add this to the track's sdp */ bool val = MP4AppendHintTrackSdp(mp4File, hintTrackId, sdpBuf); free(sConfig); free(sdpBuf); if (val == false) { MP4DeleteTrack(mp4File, hintTrackId); return false; } u_int32_t samplesPerPacket = 0; if (interleave) { u_int32_t maxSampleSize = MP4GetTrackMaxSampleSize(mp4File, mediaTrackId); // compute how many maximum size samples would fit in a packet samplesPerPacket = (maxPayloadSize - 2) / (maxSampleSize + 2); // can't interleave if this number is 0 or 1 if (samplesPerPacket < 2) { interleave = false; } } bool rc; if (interleave) { u_int32_t samplesPerGroup = maxLatency / sampleDuration; u_int32_t stride; stride = samplesPerGroup / samplesPerPacket; if (OneByteHeader && stride > 3) stride = 3; if (!OneByteHeader && stride > 7) stride = 7; #if 0 printf("max latency %llu sampleDuration %llu spg %u spp %u strid %u\n", maxLatency, sampleDuration, samplesPerGroup, samplesPerPacket, stride); #endif rc = MP4AV_AudioInterleaveHinter( mp4File, mediaTrackId, hintTrackId, sampleDuration, stride, // stride samplesPerPacket, // bundle maxPayloadSize, MP4AV_RfcIsmaConcatenator); } else { rc = MP4AV_AudioConsecutiveHinter( mp4File, mediaTrackId, hintTrackId, sampleDuration, 2, // perPacketHeaderSize 2, // perSampleHeaderSize maxLatency / sampleDuration, // maxSamplesPerPacket maxPayloadSize, MP4GetSampleSize, MP4AV_RfcIsmaConcatenator, MP4AV_RfcIsmaFragmenter); } if (!rc) { MP4DeleteTrack(mp4File, hintTrackId); return false; } return true; }
bool MP4AV_RfcIsmaConcatenator( 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; } u_int8_t auPayloadHdrSize; // LATER would be more efficient if this were a parameter u_int8_t mpeg4AudioType = MP4GetTrackAudioMpeg4Type(mp4File, mediaTrackId); if (mpeg4AudioType == MP4_MPEG4_CELP_AUDIO_TYPE) { auPayloadHdrSize = 1; } else { auPayloadHdrSize = 2; } // construct the new hint if (MP4AddRtpHint(mp4File, hintTrackId) == false || MP4AddRtpPacket(mp4File, hintTrackId, true) == false) return false; u_int8_t payloadHeader[2]; u_int16_t numHdrBits = samplesThisHint * auPayloadHdrSize * 8; payloadHeader[0] = numHdrBits >> 8; payloadHeader[1] = numHdrBits & 0xFF; if (MP4AddRtpImmediateData(mp4File, hintTrackId, (u_int8_t*)&payloadHeader, sizeof(payloadHeader)) == false) return false; u_int8_t i; // first the headers for (i = 0; i < samplesThisHint; i++) { MP4SampleId sampleId = pSampleIds[i]; u_int32_t sampleSize = MP4GetSampleSize(mp4File, mediaTrackId, sampleId); if (auPayloadHdrSize == 1) { // AU payload header is 6 bits of size // follow by 2 bits of the difference between sampleId's - 1 payloadHeader[0] = sampleSize << 2; } else { // auPayloadHdrSize == 2 // AU payload header is 13 bits of size // follow by 3 bits of the difference between sampleId's - 1 payloadHeader[0] = sampleSize >> 5; payloadHeader[1] = (sampleSize & 0x1F) << 3; } if (i > 0) { payloadHeader[auPayloadHdrSize - 1] |= ((sampleId - pSampleIds[i-1]) - 1); } #if 0 printf("sample %u size %u %02x %02x prev sample %d\n", sampleId, sampleSize, payloadHeader[0], payloadHeader[1], pSampleIds[i-1]); #endif if (MP4AddRtpImmediateData(mp4File, hintTrackId, (u_int8_t*)&payloadHeader, auPayloadHdrSize) == false) return false; } // then the samples for (i = 0; i < samplesThisHint; i++) { MP4SampleId sampleId = pSampleIds[i]; u_int32_t sampleSize = MP4GetSampleSize(mp4File, mediaTrackId, sampleId); if (MP4AddRtpSampleData(mp4File, hintTrackId, sampleId, 0, sampleSize) == false) return false; } // write the hint return MP4WriteRtpHint(mp4File, hintTrackId, hintDuration); }
static char* PrintAudioInfo( MP4FileHandle mp4File, MP4TrackId trackId) { static const char* mpeg4AudioNames[] = { "MPEG-4 AAC main", "MPEG-4 AAC LC", "MPEG-4 AAC SSR", "MPEG-4 AAC LTP", "MPEG-4 AAC HE", "MPEG-4 AAC Scalable", "MPEG-4 TwinVQ", "MPEG-4 CELP", "MPEG-4 HVXC", NULL, NULL, "MPEG-4 TTSI", "MPEG-4 Main Synthetic", "MPEG-4 Wavetable Syn", "MPEG-4 General MIDI", "MPEG-4 Algo Syn and Audio FX", "MPEG-4 ER AAC LC", NULL, "MPEG-4 ER AAC LTP", "MPEG-4 ER AAC Scalable", "MPEG-4 ER TwinVQ", "MPEG-4 ER BSAC", "MPEG-4 ER ACC LD", "MPEG-4 ER CELP", "MPEG-4 ER HVXC", "MPEG-4 ER HILN", "MPEG-4 ER Parametric", "MPEG-4 SSC", "MPEG-4 PS", "MPEG-4 MPEG Surround", NULL, "MPEG-4 Layer-1", "MPEG-4 Layer-2", "MPEG-4 Layer-3", "MPEG-4 DST", "MPEG-4 Audio Lossless", "MPEG-4 SLS", "MPEG-4 SLS non-core", }; static const u_int8_t mpegAudioTypes[] = { MP4_MPEG2_AAC_MAIN_AUDIO_TYPE, // 0x66 MP4_MPEG2_AAC_LC_AUDIO_TYPE, // 0x67 MP4_MPEG2_AAC_SSR_AUDIO_TYPE, // 0x68 MP4_MPEG2_AUDIO_TYPE, // 0x69 MP4_MPEG1_AUDIO_TYPE, // 0x6B // private types MP4_PCM16_LITTLE_ENDIAN_AUDIO_TYPE, MP4_VORBIS_AUDIO_TYPE, MP4_ALAW_AUDIO_TYPE, MP4_ULAW_AUDIO_TYPE, MP4_G723_AUDIO_TYPE, MP4_PCM16_BIG_ENDIAN_AUDIO_TYPE, }; static const char* mpegAudioNames[] = { "MPEG-2 AAC Main", "MPEG-2 AAC LC", "MPEG-2 AAC SSR", "MPEG-2 Audio (13818-3)", "MPEG-1 Audio (11172-3)", // private types "PCM16 (little endian)", "Vorbis", "G.711 aLaw", "G.711 uLaw", "G.723.1", "PCM16 (big endian)", }; u_int8_t numMpegAudioTypes = sizeof(mpegAudioTypes) / sizeof(u_int8_t); const char* typeName = "Unknown"; bool foundType = false; u_int8_t type = 0; const char *media_data_name; media_data_name = MP4GetTrackMediaDataName(mp4File, trackId); if (media_data_name == NULL) { typeName = "Unknown - no media data name"; } else if (strcasecmp(media_data_name, "samr") == 0) { typeName = "AMR"; foundType = true; } else if (strcasecmp(media_data_name, "sawb") == 0) { typeName = "AMR-WB"; foundType = true; } else if (strcasecmp(media_data_name, "mp4a") == 0) { type = MP4GetTrackEsdsObjectTypeId(mp4File, trackId); switch (type) { case MP4_INVALID_AUDIO_TYPE: typeName = "AAC from .mov"; foundType = true; break; case MP4_MPEG4_AUDIO_TYPE: { type = MP4GetTrackAudioMpeg4Type(mp4File, trackId); if (type == MP4_MPEG4_INVALID_AUDIO_TYPE || type > NUM_ELEMENTS_IN_ARRAY(mpeg4AudioNames) || mpeg4AudioNames[type - 1] == NULL) { typeName = "MPEG-4 Unknown Profile"; } else { typeName = mpeg4AudioNames[type - 1]; foundType = true; } break; } // fall through default: for (u_int8_t i = 0; i < numMpegAudioTypes; i++) { if (type == mpegAudioTypes[i]) { typeName = mpegAudioNames[i]; foundType = true; break; } } } } else { typeName = media_data_name; foundType = true; } u_int32_t timeScale = MP4GetTrackTimeScale(mp4File, trackId); MP4Duration trackDuration = MP4GetTrackDuration(mp4File, trackId); double msDuration = UINT64_TO_DOUBLE(MP4ConvertFromTrackDuration(mp4File, trackId, trackDuration, MP4_MSECS_TIME_SCALE)); u_int32_t avgBitRate = MP4GetTrackBitRate(mp4File, trackId); char *sInfo = (char*)MP4Malloc(256); // type duration avgBitrate samplingFrequency if (foundType) snprintf(sInfo, 256, "%u\taudio\t%s%s, %.3f secs, %u kbps, %u Hz\n", trackId, MP4IsIsmaCrypMediaTrack(mp4File, trackId) ? "enca - " : "", typeName, msDuration / 1000.0, (avgBitRate + 500) / 1000, timeScale); else snprintf(sInfo, 256, "%u\taudio\t%s%s(%u), %.3f secs, %u kbps, %u Hz\n", trackId, MP4IsIsmaCrypMediaTrack(mp4File, trackId) ? "enca - " : "", typeName, type, msDuration / 1000.0, (avgBitRate + 500) / 1000, timeScale); return sInfo; }