/** * AAC demuxer */ import {logger} from '../utils/logger'; import ID3 from '../demux/id3'; import {ErrorTypes, ErrorDetails} from '../errors'; class AACDemuxer { constructor(observer,remuxerClass) { this.observer = observer; this.remuxerClass = remuxerClass; this.remuxer = new this.remuxerClass(observer); this._aacTrack = {type: 'audio', id :-1, sequenceNumber: 0, samples : [], len : 0}; } static probe(data) { // check if data contains ID3 timestamp and ADTS sync worc var id3 = new ID3(data), adtsStartOffset,len; if(id3.hasTimeStamp) { // look for ADTS header (0xFFFx) for (adtsStartOffset = id3.length, len = data.length; adtsStartOffset < len - 1; adtsStartOffset++) { if ((data[adtsStartOffset] === 0xff) && (data[adtsStartOffset+1] & 0xf0) === 0xf0) { //logger.log('ADTS sync word found !'); return true; } } } return false; } // feed incoming data to the front of the parsing pipeline push(data, audioCodec, videoCodec, timeOffset, cc, level, sn, duration) { var id3 = new ID3(data), adtsStartOffset,len, track = this._aacTrack, pts = id3.timeStamp, config, nbSamples,adtsFrameSize,adtsHeaderLen,stamp,aacSample; // look for ADTS header (0xFFFx) for (adtsStartOffset = id3.length, len = data.length; adtsStartOffset < len - 1; adtsStartOffset++) { if ((data[adtsStartOffset] === 0xff) && (data[adtsStartOffset+1] & 0xf0) === 0xf0) { break; } } if (!track.audiosamplerate) { config = this._ADTStoAudioConfig(data, adtsStartOffset, audioCodec); track.config = config.config; track.audiosamplerate = config.samplerate; track.channelCount = config.channelCount; track.codec = config.codec; track.timescale = this.remuxer.timescale; track.duration = this.remuxer.timescale * duration; logger.log(`parsed codec:${track.codec},rate:${config.samplerate},nb channel:${config.channelCount}`); } nbSamples = 0; while ((adtsStartOffset + 5) < len) { // retrieve frame size adtsFrameSize = ((data[adtsStartOffset + 3] & 0x03) << 11); // byte 4 adtsFrameSize |= (data[adtsStartOffset + 4] << 3); // byte 5 adtsFrameSize |= ((data[adtsStartOffset + 5] & 0xE0) >>> 5); adtsHeaderLen = (!!(data[adtsStartOffset + 1] & 0x01) ? 7 : 9); adtsFrameSize -= adtsHeaderLen; stamp = Math.round(90*pts + nbSamples * 1024 * 90000 / track.audiosamplerate); //stamp = pes.pts; //console.log('AAC frame, offset/length/pts:' + (adtsStartOffset+7) + '/' + adtsFrameSize + '/' + stamp.toFixed(0)); if ((adtsFrameSize > 0) && ((adtsStartOffset + adtsHeaderLen + adtsFrameSize) <= len)) { aacSample = {unit: data.subarray(adtsStartOffset + adtsHeaderLen, adtsStartOffset + adtsHeaderLen + adtsFrameSize), pts: stamp, dts: stamp}; track.samples.push(aacSample); track.len += adtsFrameSize; adtsStartOffset += adtsFrameSize + adtsHeaderLen; nbSamples++; // look for ADTS header (0xFFFx) for ( ; adtsStartOffset < (len - 1); adtsStartOffset++) { if ((data[adtsStartOffset] === 0xff) && ((data[adtsStartOffset + 1] & 0xf0) === 0xf0)) { break; } } } else { break; } } this.remuxer.remux(this._aacTrack,{samples : []}, {samples : []}, timeOffset); } _ADTStoAudioConfig(data, offset, audioCodec) { var adtsObjectType, // :int adtsSampleingIndex, // :int adtsExtensionSampleingIndex, // :int adtsChanelConfig, // :int config, userAgent = navigator.userAgent.toLowerCase(), adtsSampleingRates = [ 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350]; // byte 2 adtsObjectType = ((data[offset + 2] & 0xC0) >>> 6) + 1; adtsSampleingIndex = ((data[offset + 2] & 0x3C) >>> 2); if(adtsSampleingIndex > adtsSampleingRates.length-1) { this.observer.trigger(Event.ERROR, {type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_PARSING_ERROR, fatal: true, reason: `invalid ADTS sampling index:${adtsSampleingIndex}`}); return; } adtsChanelConfig = ((data[offset + 2] & 0x01) << 2); // byte 3 adtsChanelConfig |= ((data[offset + 3] & 0xC0) >>> 6); logger.log(`manifest codec:${audioCodec},ADTS data:type:${adtsObjectType},sampleingIndex:${adtsSampleingIndex}[${adtsSampleingRates[adtsSampleingIndex]}Hz],channelConfig:${adtsChanelConfig}`); // firefox: freq less than 24kHz = AAC SBR (HE-AAC) if (userAgent.indexOf('firefox') !== -1) { if (adtsSampleingIndex >= 6) { adtsObjectType = 5; config = new Array(4); // HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies // there is a factor 2 between frame sample rate and output sample rate // multiply frequency by 2 (see table below, equivalent to substract 3) adtsExtensionSampleingIndex = adtsSampleingIndex - 3; } else { adtsObjectType = 2; config = new Array(2); adtsExtensionSampleingIndex = adtsSampleingIndex; } // Android : always use AAC } else if (userAgent.indexOf('android') !== -1) { adtsObjectType = 2; config = new Array(2); adtsExtensionSampleingIndex = adtsSampleingIndex; } else { /* for other browsers (chrome ...) always force audio type to be HE-AAC SBR, as some browsers do not support audio codec switch properly (like Chrome ...) */ adtsObjectType = 5; config = new Array(4); // if (manifest codec is HE-AAC) OR (manifest codec not specified AND frequency less than 24kHz) if ((audioCodec && audioCodec.indexOf('mp4a.40.5') !== -1) || (!audioCodec && adtsSampleingIndex >= 6)) { // HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies // there is a factor 2 between frame sample rate and output sample rate // multiply frequency by 2 (see table below, equivalent to substract 3) adtsExtensionSampleingIndex = adtsSampleingIndex - 3; } else { // if (manifest codec is AAC) AND (frequency less than 24kHz OR nb channel is 1) if (audioCodec && audioCodec.indexOf('mp4a.40.2') !== -1 && (adtsSampleingIndex >= 6 || adtsChanelConfig === 1)) { adtsObjectType = 2; config = new Array(2); } adtsExtensionSampleingIndex = adtsSampleingIndex; } } /* refer to http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio#Audio_Specific_Config ISO 14496-3 (AAC).pdf - Table 1.13 — Syntax of AudioSpecificConfig() Audio Profile / Audio Object Type 0: Null 1: AAC Main 2: AAC LC (Low Complexity) 3: AAC SSR (Scalable Sample Rate) 4: AAC LTP (Long Term Prediction) 5: SBR (Spectral Band Replication) 6: AAC Scalable sampling freq 0: 96000 Hz 1: 88200 Hz 2: 64000 Hz 3: 48000 Hz 4: 44100 Hz 5: 32000 Hz 6: 24000 Hz 7: 22050 Hz 8: 16000 Hz 9: 12000 Hz 10: 11025 Hz 11: 8000 Hz 12: 7350 Hz 13: Reserved 14: Reserved 15: frequency is written explictly Channel Configurations These are the channel configurations: 0: Defined in AOT Specifc Config 1: 1 channel: front-center 2: 2 channels: front-left, front-right */ // audioObjectType = profile => profile, the MPEG-4 Audio Object Type minus 1 config[0] = adtsObjectType << 3; // samplingFrequencyIndex config[0] |= (adtsSampleingIndex & 0x0E) >> 1; config[1] |= (adtsSampleingIndex & 0x01) << 7; // channelConfiguration config[1] |= adtsChanelConfig << 3; if (adtsObjectType === 5) { // adtsExtensionSampleingIndex config[1] |= (adtsExtensionSampleingIndex & 0x0E) >> 1; config[2] = (adtsExtensionSampleingIndex & 0x01) << 7; // adtsObjectType (force to 2, chrome is checking that object type is less than 5 ??? // https://chromium.googlesource.com/chromium/src.git/+/master/media/formats/mp4/aac.cc config[2] |= 2 << 2; config[3] = 0; } return {config: config, samplerate: adtsSampleingRates[adtsSampleingIndex], channelCount: adtsChanelConfig, codec: ('mp4a.40.' + adtsObjectType)}; } destroy() { } } export default AACDemuxer;