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
text/typescript
/**
* 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;
}