UNPKG

hjplayer

Version:

hjplayer, a HTML5 Player, can play flv and hls by Media Source Extension, based on typescript;

333 lines (313 loc) 11.2 kB
/** * ADTS(Audio Data Transport Stream) parser helper * @link https://wiki.multimedia.cx/index.php?title=ADTS */ import EventEmitter from 'eventemitter3'; import logger from '../../../Utils/Logger'; import { ErrorTypes, ErrorDetails } from '../errors'; import Event from '../../../Events/index'; import { track, TSAudioTrack, TSAACTrack } from '../TSCodecInterface'; export function getAudioConfig( observer: EventEmitter, data: Uint8Array, offset: number, audioCodec: string | undefined ) { let adtsObjectType: number; // :int let adtsExtensionSampleingIndex: number; // :int let adtsChanelConfig: number; // :int let config: Array<number>; const userAgent: string = navigator.userAgent.toLowerCase(); const manifestCodec: string | undefined = audioCodec; const adtsSampleingRates: Array<number> = [ 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350 ]; // byte 2 adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1; const adtsSampleingIndex = (data[offset + 2] & 0x3c) >>> 2; if(adtsSampleingIndex > adtsSampleingRates.length - 1) { observer.emit(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( 'adts', `manifest codec:${audioCodec},ADTS data:type:${adtsObjectType},sampleingIndex:${adtsSampleingIndex}[${adtsSampleingRates[adtsSampleingIndex]}Hz],channelConfig:${adtsChanelConfig}` ); // firefox: freq less than 24kHz = AAC SBR (HE-AAC) if(/firefox/i.test(userAgent)) { 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/Vivaldi/Opera ...) 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 HE-AACv2) OR (manifest codec not specified AND frequency less than 24kHz) if( (audioCodec && (audioCodec.indexOf('mp4a.40.29') !== -1 || 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 AND nb channel is 1) OR (manifest codec not specified and mono audio) // Chrome fails to play back with low frequency AAC LC mono when initialized with HE-AAC. This is not a problem with stereo. if( (audioCodec && audioCodec.indexOf('mp4a.40.2') !== -1 && ((adtsSampleingIndex >= 6 && adtsChanelConfig === 1) || /vivaldi/i.test(userAgent))) || (!audioCodec && 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, samplerate: adtsSampleingRates[adtsSampleingIndex], channelCount: adtsChanelConfig, codec: `mp4a.40.${adtsObjectType}`, manifestCodec }; } /** * 是否符合Header的格式 */ export function isHeaderPattern(data: Uint8Array, offset: number) { return data[offset] === 0xff && (data[offset + 1] & 0xf6) === 0xf0; } /** * 获取Header的长度 */ export function getHeaderLength(data: Uint8Array, offset: number) { return data[offset + 1] & 0x01 ? 7 : 9; } /** * 获取整个帧的数据长度 */ export function getFullFrameLength(data: Uint8Array, offset: number) { return ( ((data[offset + 3] & 0x03) << 11) | (data[offset + 4] << 3) | ((data[offset + 5] & 0xe0) >>> 5) ); } /** * 是不是Header */ export function isHeader(data: Uint8Array, offset: number) { // Look for ADTS header | 1111 1111 | 1111 X00X | where X can be either 0 or 1 // Layer bits (position 14 and 15) in header should be always 0 for ADTS // More info https://wiki.multimedia.cx/index.php?title=ADTS if(offset + 1 < data.length && isHeaderPattern(data, offset)) { return true; } return false; } /** * 探测是不是音频数据 * @param data 音频数据块 * @param offset 偏移量 */ export function probe(data: Uint8Array, offset: number): boolean { // same as isHeader but we also check that ADTS frame follows last ADTS frame // or end of data is reached if(isHeader(data, offset)) { // ADTS header Length const headerLength = getHeaderLength(data, offset); // ADTS frame Length let frameLength = headerLength; if(offset + 5 < data.length) { frameLength = getFullFrameLength(data, offset); } const newOffset = offset + frameLength; if( newOffset === data.length || (newOffset + 1 < data.length && isHeaderPattern(data, newOffset)) ) { return true; } } return false; } /** * 初始化Track设置, 给Audiotrack添加属性 * @param track track 信息 * @param observer 事件中心 * @param data 音频数据块 * @param offset 偏移量 * @param audioCodec 音频编码格式 */ export function initTrackConfig( track: TSAudioTrack, observer: EventEmitter, data: Uint8Array, offset: number, audioCodec: string | undefined ) { if(!track.samplerate) { const config = getAudioConfig(observer, data, offset, audioCodec); if(!config) return; track.config = config.config; track.samplerate = config.samplerate; track.channelCount = config.channelCount; track.codec = config.codec; track.manifestCodec = config.manifestCodec; logger.log( 'initTrackConfig', `parsed codec:${track.codec},rate:${config.samplerate},nb channel:${config.channelCount}` ); } } /** * 获取帧播放时长 * @param samplerate sample码率 */ export function getFrameDuration(samplerate: number) { return (1024 * 90000) / samplerate; } /** * 解析帧头部信息 * @param data 音频数据块 * @param offset 偏移 * @param pts 展示时间 * @param frameIndex 帧索引值 * @param frameDuration 帧持续时间 */ export function parseFrameHeader( data: Uint8Array, offset: number, pts: number, frameIndex: number, frameDuration: number ) { let frameLength; let stamp; const { length } = data; // The protection skip bit tells us if we have 2 bytes of CRC data at the end of the ADTS header const headerLength = getHeaderLength(data, offset); // retrieve frame size frameLength = getFullFrameLength(data, offset); frameLength -= headerLength; if(frameLength > 0 && offset + headerLength + frameLength <= length) { stamp = pts + frameIndex * frameDuration; // logger.log(`AAC frame, offset/length/total/pts:${offset+headerLength}/${frameLength}/${data.byteLength}/${(stamp/90).toFixed(0)}`); return { headerLength, frameLength, stamp }; } return undefined; } export function appendFrame( track: track, data: Uint8Array, offset: number, pts: number, frameIndex: number ) { const frameDuration = getFrameDuration(track.samplerate); const header = parseFrameHeader(data, offset, pts, frameIndex, frameDuration); if(header) { const { stamp } = header; const { headerLength } = header; const { frameLength } = header; // logger.log(`AAC frame, offset/length/total/pts:${offset+headerLength}/${frameLength}/${data.byteLength}/${(stamp/90).toFixed(0)}`); const aacSample = { unit: data.subarray(offset + headerLength, offset + headerLength + frameLength), pts: stamp, dts: stamp }; track.samples.push(aacSample); return { sample: aacSample, length: frameLength + headerLength }; } return undefined; }