Skip to content

Commit

Permalink
Remove UA specific workarounds from ADTS to mp4 esds remuxing (#6627)
Browse files Browse the repository at this point in the history
  • Loading branch information
robwalch committed Aug 27, 2024
1 parent 4ac240c commit 08e8f3b
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 257 deletions.
3 changes: 2 additions & 1 deletion src/demux/audio/aacdemuxer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as MpegAudio from './mpegaudio';
import { getId3Data } from '@svta/common-media-library/id3/getId3Data';
import type { HlsEventEmitter } from '../../events';
import type { HlsConfig } from '../../config';
import type { DemuxedAudioTrack } from '../../types/demuxer';
import type { ILogger } from '../../utils/logger';

class AACDemuxer extends BaseAudioDemuxer {
Expand Down Expand Up @@ -71,7 +72,7 @@ class AACDemuxer extends BaseAudioDemuxer {
return ADTS.canParse(data, offset);
}

appendFrame(track, data, offset) {
appendFrame(track: DemuxedAudioTrack, data: Uint8Array, offset: number) {
ADTS.initTrackConfig(
track,
this.observer,
Expand Down
133 changes: 31 additions & 102 deletions src/demux/audio/adts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import type {
} from '../../types/demuxer';

type AudioConfig = {
config: number[];
config: [number, number];
samplerate: number;
channelCount: number;
codec: string;
parsedCodec: string;
manifestCodec: string;
manifestCodec: string | undefined;
};

type FrameHeader = {
Expand All @@ -30,24 +30,15 @@ export function getAudioConfig(
observer: HlsEventEmitter,
data: Uint8Array,
offset: number,
audioCodec: string,
manifestCodec: string | undefined,
): AudioConfig | void {
let adtsObjectType: number;
let originalAdtsObjectType: number;
let adtsExtensionSamplingIndex: number;
let adtsChannelConfig: number;
let config: number[];
const userAgent = navigator.userAgent.toLowerCase();
const manifestCodec = audioCodec;
const adtsSamplingRates = [
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025,
8000, 7350,
];
// byte 2
adtsObjectType = originalAdtsObjectType =
((data[offset + 2] & 0xc0) >>> 6) + 1;
const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
const byte2 = data[offset + 2];
const adtsSamplingIndex = (byte2 >> 2) & 0xf;
if (adtsSamplingIndex > 12) {
const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
observer.emit(Events.ERROR, Events.ERROR, {
type: ErrorTypes.MEDIA_ERROR,
Expand All @@ -58,66 +49,12 @@ export function getAudioConfig(
});
return;
}
adtsChannelConfig = (data[offset + 2] & 0x01) << 2;
// byte 3
adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
logger.log(
`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`,
);
// Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
if (/firefox|palemoon/i.test(userAgent)) {
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)
adtsExtensionSamplingIndex = adtsSamplingIndex - 3;
} else {
adtsObjectType = 2;
config = new Array(2);
adtsExtensionSamplingIndex = adtsSamplingIndex;
}
// Android : always use AAC
} else if (userAgent.indexOf('android') !== -1) {
adtsObjectType = 2;
config = new Array(2);
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 ...)
*/
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 && 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)
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 &&
((adtsSamplingIndex >= 6 && adtsChannelConfig === 1) ||
/vivaldi/i.test(userAgent))) ||
(!audioCodec && adtsChannelConfig === 1)
) {
adtsObjectType = 2;
config = new Array(2);
}
adtsExtensionSamplingIndex = adtsSamplingIndex;
}
}
// MPEG-4 Audio Object Type (profile_ObjectType+1)
const adtsObjectType = ((byte2 >> 6) & 0x3) + 1;
const channelCount = ((data[offset + 3] >> 6) & 0x3) | ((byte2 & 1) << 2);
const codec = 'mp4a.40.' + adtsObjectType;
/* 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()
ISO/IEC 14496-3 - Table 1.13 — Syntax of AudioSpecificConfig()
Audio Profile / Audio Object Type
0: Null
1: AAC Main
Expand Down Expand Up @@ -150,27 +87,27 @@ export function getAudioConfig(
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] |= (adtsSamplingIndex & 0x0e) >> 1;
config[1] |= (adtsSamplingIndex & 0x01) << 7;
// channelConfiguration
config[1] |= adtsChannelConfig << 3;
if (adtsObjectType === 5) {
// adtsExtensionSamplingIndex
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;
const samplerate = adtsSamplingRates[adtsSamplingIndex];
let aacSampleIndex = adtsSamplingIndex;
if (adtsObjectType === 5 || adtsObjectType === 29) {
// 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 above, equivalent to substract 3)
aacSampleIndex -= 3;
}
const config: [number, number] = [
(adtsObjectType << 3) | ((aacSampleIndex & 0x0e) >> 1),
((aacSampleIndex & 0x01) << 7) | (channelCount << 3),
];
logger.log(
`manifest codec:${manifestCodec}, parsed codec:${codec}, channels:${channelCount}, rate:${samplerate} (ADTS object type:${adtsObjectType} sampling index:${adtsSamplingIndex})`,
);
return {
config,
samplerate: adtsSamplingRates[adtsSamplingIndex],
channelCount: adtsChannelConfig,
codec: 'mp4a.40.' + adtsObjectType,
parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
samplerate,
channelCount,
codec,
parsedCodec: codec,
manifestCodec,
};
}
Expand Down Expand Up @@ -236,22 +173,14 @@ export function initTrackConfig(
observer: HlsEventEmitter,
data: Uint8Array,
offset: number,
audioCodec: string,
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;
track.parsedCodec = config.parsedCodec;
logger.log(
`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`,
);
Object.assign(track, config);
}
}

Expand Down
8 changes: 1 addition & 7 deletions src/demux/tsdemuxer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -648,13 +648,7 @@ class TSDemuxer implements Demuxer {
}
}

ADTS.initTrackConfig(
track,
this.observer,
data,
offset,
this.audioCodec as string,
);
ADTS.initTrackConfig(track, this.observer, data, offset, this.audioCodec);

let pts: number;
if (pes.pts !== undefined) {
Expand Down
71 changes: 37 additions & 34 deletions src/remux/mp4-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Generate MP4 Box
*/

import type { DemuxedAudioTrack } from '../types/demuxer';
import { appendUint8Array } from '../utils/mp4-tools';

type HdlrTypes = {
Expand Down Expand Up @@ -739,43 +740,45 @@ class MP4 {
);
}

static esds(track) {
const configlen = track.config.length;
return new Uint8Array(
[
0x00, // version 0
0x00,
0x00,
0x00, // flags
static esds(track: DemuxedAudioTrack) {
const config = track.config as [number, number];
return new Uint8Array([
0x00, // version 0
0x00,
0x00,
0x00, // flags

0x03, // descriptor_type
0x17 + configlen, // length
0x00,
0x01, // es_id
0x00, // stream_priority
0x03, // descriptor_type
0x19, // length

0x04, // descriptor_type
0x0f + configlen, // length
0x40, // codec : mpeg4_audio
0x15, // stream_type
0x00,
0x00,
0x00, // buffer_size
0x00,
0x00,
0x00,
0x00, // maxBitrate
0x00,
0x00,
0x00,
0x00, // avgBitrate
0x00,
0x01, // es_id

0x00, // stream_priority

0x04, // descriptor_type
0x11, // length
0x40, // codec : mpeg4_audio
0x15, // stream_type
0x00,
0x00,
0x00, // buffer_size
0x00,
0x00,
0x00,
0x00, // maxBitrate
0x00,
0x00,
0x00,
0x00, // avgBitrate

0x05, // descriptor_type
]
.concat([configlen])
.concat(track.config)
.concat([0x06, 0x01, 0x02]),
); // GASpecificConfig)); // length + audio config descriptor
0x05, // descriptor_type
0x02, // length
...config,
0x06,
0x01,
0x02, // GASpecificConfig)); // length + audio config descriptor
]);
}

static audioStsd(track) {
Expand Down
Loading

0 comments on commit 08e8f3b

Please sign in to comment.