Skip to content

Commit

Permalink
Fix ADTS canParse (last sample), typos, and logging
Browse files Browse the repository at this point in the history
Fixes PIPELINE_ERROR_DECODE errors in certain streams with acc file segments
  • Loading branch information
Rob Walch committed Mar 31, 2021
1 parent 324cac1 commit b9a1a3f
Show file tree
Hide file tree
Showing 4 changed files with 25 additions and 27 deletions.
40 changes: 20 additions & 20 deletions src/demux/adts.ts
Expand Up @@ -29,7 +29,7 @@ export function getAudioConfig(
audioCodec: string
): AudioConfig | void {
let adtsObjectType: number;
let adtsExtensionSampleingIndex: number;
let adtsExtensionSamplingIndex: number;
let adtsChanelConfig: number;
let config: number[];
const userAgent = navigator.userAgent.toLowerCase();
Expand All @@ -51,41 +51,41 @@ export function getAudioConfig(
];
// byte 2
adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
const adtsSampleingIndex = (data[offset + 2] & 0x3c) >>> 2;
if (adtsSampleingIndex > adtsSampleingRates.length - 1) {
const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
if (adtsSamplingIndex > adtsSampleingRates.length - 1) {
observer.trigger(Events.ERROR, {
type: ErrorTypes.MEDIA_ERROR,
details: ErrorDetails.FRAG_PARSING_ERROR,
fatal: true,
reason: `invalid ADTS sampling index:${adtsSampleingIndex}`,
reason: `invalid ADTS sampling index:${adtsSamplingIndex}`,
});
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}`
`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`
);
// firefox: freq less than 24kHz = AAC SBR (HE-AAC)
if (/firefox/i.test(userAgent)) {
if (adtsSampleingIndex >= 6) {
if (adtsSamplingIndex >= 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;
adtsExtensionSamplingIndex = adtsSamplingIndex - 3;
} else {
adtsObjectType = 2;
config = new Array(2);
adtsExtensionSampleingIndex = adtsSampleingIndex;
adtsExtensionSamplingIndex = adtsSamplingIndex;
}
// Android : always use AAC
} else if (userAgent.indexOf('android') !== -1) {
adtsObjectType = 2;
config = new Array(2);
adtsExtensionSampleingIndex = adtsSampleingIndex;
adtsExtensionSamplingIndex = adtsSamplingIndex;
} 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 ...)
Expand All @@ -97,26 +97,26 @@ export function getAudioConfig(
(audioCodec &&
(audioCodec.indexOf('mp4a.40.29') !== -1 ||
audioCodec.indexOf('mp4a.40.5') !== -1)) ||
(!audioCodec && adtsSampleingIndex >= 6)
(!audioCodec && adtsSamplingIndex >= 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;
adtsExtensionSamplingIndex = adtsSamplingIndex - 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) ||
((adtsSamplingIndex >= 6 && adtsChanelConfig === 1) ||
/vivaldi/i.test(userAgent))) ||
(!audioCodec && adtsChanelConfig === 1)
) {
adtsObjectType = 2;
config = new Array(2);
}
adtsExtensionSampleingIndex = adtsSampleingIndex;
adtsExtensionSamplingIndex = adtsSamplingIndex;
}
}
/* refer to http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio#Audio_Specific_Config
Expand Down Expand Up @@ -155,22 +155,22 @@ export function getAudioConfig(
// 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;
config[0] |= (adtsSamplingIndex & 0x0e) >> 1;
config[1] |= (adtsSamplingIndex & 0x01) << 7;
// channelConfiguration
config[1] |= adtsChanelConfig << 3;
if (adtsObjectType === 5) {
// adtsExtensionSampleingIndex
config[1] |= (adtsExtensionSampleingIndex & 0x0e) >> 1;
config[2] = (adtsExtensionSampleingIndex & 0x01) << 7;
config[1] |= (adtsExtensionSamplingIndex & 0x0e) >> 1;
config[2] = (adtsExtensionSamplingIndex & 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],
samplerate: adtsSampleingRates[adtsSamplingIndex],
channelCount: adtsChanelConfig,
codec: 'mp4a.40.' + adtsObjectType,
manifestCodec,
Expand Down Expand Up @@ -208,7 +208,7 @@ export function canParse(data: Uint8Array, offset: number): boolean {
return (
canGetFrameLength(data, offset) &&
isHeaderPattern(data, offset) &&
getFullFrameLength(data, offset) < data.length - offset
getFullFrameLength(data, offset) <= data.length - offset
);
}

Expand Down Expand Up @@ -251,7 +251,7 @@ export function initTrackConfig(
track.codec = config.codec;
track.manifestCodec = config.manifestCodec;
logger.log(
`parsed codec:${track.codec},rate:${config.samplerate},nb channel:${config.channelCount}`
`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`
);
}
}
Expand Down
7 changes: 4 additions & 3 deletions src/demux/base-audio-demuxer.ts
Expand Up @@ -127,12 +127,13 @@ class BaseAudioDemuxer implements Demuxer {

flush(timeOffset: number): DemuxerResult {
// Parse cache in case of remaining frames.
if (this.cachedData) {
this.demux(this.cachedData, 0);
const cachedData = this.cachedData;
if (cachedData) {
this.cachedData = null;
this.demux(cachedData, 0);
}

this.frameIndex = 0;
this.cachedData = null;

return {
audioTrack: this._audioTrack,
Expand Down
2 changes: 1 addition & 1 deletion src/demux/mpegaudio.ts
Expand Up @@ -227,7 +227,7 @@ export function isHeader(data: Uint8Array, offset: number): boolean {
export function canParse(data: Uint8Array, offset: number): boolean {
const headerSize = 4;

return isHeaderPattern(data, offset) && data.length - offset >= headerSize;
return isHeaderPattern(data, offset) && headerSize <= data.length - offset;
}

export function probe(data: Uint8Array, offset: number): boolean {
Expand Down
3 changes: 0 additions & 3 deletions src/remux/mp4-remuxer.ts
Expand Up @@ -281,9 +281,6 @@ export default class MP4Remuxer implements Remuxer {
// using audio sampling rate here helps having an integer MP4 frame duration
// this avoids potential rounding issue and AV sync issue
audioTrack.timescale = audioTrack.samplerate;
logger.log(
`[mp4-remuxer]: audio sampling rate : ${audioTrack.samplerate}`
);
if (!audioTrack.isAAC) {
if (typeSupported.mpeg) {
// Chrome and Safari
Expand Down

0 comments on commit b9a1a3f

Please sign in to comment.