Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix PIPELINE_ERROR_DECODE error streams with AAC segments #3703

Merged
merged 1 commit into from Mar 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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