From d203d75bdd8165112d4f9793cd556256f14983d6 Mon Sep 17 00:00:00 2001 From: Vanilagy <1696106+Vanilagy@users.noreply.github.com> Date: Mon, 30 Dec 2024 23:47:42 +0100 Subject: [PATCH] Add MP3 support to ISOBMFF demuxer --- src/codec.ts | 6 +++- src/isobmff/isobmff-demuxer.ts | 51 +++++++++++++++++++++------------- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/src/codec.ts b/src/codec.ts index 5117df7..a2e4e85 100644 --- a/src/codec.ts +++ b/src/codec.ts @@ -13,7 +13,7 @@ import { SubtitleMetadata } from './subtitles'; /** @public */ export const VIDEO_CODECS = ['avc', 'hevc', 'vp8', 'vp9', 'av1'] as const; /** @public */ -export const AUDIO_CODECS = ['aac', 'opus'] as const; // TODO add the rest +export const AUDIO_CODECS = ['aac', 'mp3', 'opus'] as const; // TODO add the rest /** @public */ export const SUBTITLE_CODECS = ['webvtt'] as const; // TODO add the rest @@ -353,6 +353,8 @@ export const buildAudioCodecString = (codec: AudioCodec, numberOfChannels: numbe // Default to standard AAC-LC for higher sample rates return 'mp4a.40.2'; // AAC-LC + } else if (codec === 'mp3') { + return 'mp3'; // Easy, this one } else if (codec === 'opus') { return 'opus'; // Easy, this one } else if (codec === 'vorbis') { @@ -367,6 +369,8 @@ export const extractAudioCodecString = (codec: AudioCodec, description: Uint8Arr if (codec === 'aac') { const audioSpecificConfig = parseAacAudioSpecificConfig(description); return `mp4a.40.${audioSpecificConfig.objectType}`; + } else if (codec === 'mp3') { + return 'mp3'; } else if (codec === 'opus') { return 'opus'; } else if (codec === 'vorbis') { diff --git a/src/isobmff/isobmff-demuxer.ts b/src/isobmff/isobmff-demuxer.ts index 4d775b1..0708ef9 100644 --- a/src/isobmff/isobmff-demuxer.ts +++ b/src/isobmff/isobmff-demuxer.ts @@ -613,7 +613,7 @@ export class IsobmffDemuxer extends Demuxer { this.readContiguousBoxes(startPos + sampleBoxInfo.totalSize - this.isobmffReader.pos); } else { if (sampleBoxInfo.name === 'mp4a') { - track.info.codec = 'aac'; + // We don't know the codec yet, need to read the esds box } else if (sampleBoxInfo.name.toLowerCase() === 'opus') { track.info.codec = 'opus'; } else { @@ -811,12 +811,12 @@ export class IsobmffDemuxer extends Demuxer { case 'esds': { const track = this.currentTrack; - assert(track && track.info); + assert(track && track.info?.type === 'audio'); this.isobmffReader.pos += 4; // Version + flags const tag = this.isobmffReader.readU8(); - assert(tag === 0x03); + assert(tag === 0x03); // ES Descriptor this.isobmffReader.readIsomVariableInteger(); // Length @@ -839,24 +839,38 @@ export class IsobmffDemuxer extends Demuxer { } const decoderConfigTag = this.isobmffReader.readU8(); - assert(decoderConfigTag === 0x04); + assert(decoderConfigTag === 0x04); // DecoderConfigDescriptor - this.isobmffReader.readIsomVariableInteger(); // Length + const decoderConfigDescriptorLength = this.isobmffReader.readIsomVariableInteger(); // Length + + const payloadStart = this.isobmffReader.pos; const objectTypeIndication = this.isobmffReader.readU8(); - assert(objectTypeIndication === 0x40); // Assert it's MPEG-4 audio + if (objectTypeIndication === 0x40) { + track.info.codec = 'aac'; + } else if (objectTypeIndication === 0x69 || objectTypeIndication === 0x6b) { + track.info.codec = 'mp3'; + } else { + console.warn( + `Unsupported audio codec (objectTypeIndication ${objectTypeIndication}) - discarding track.`, + ); + } this.isobmffReader.pos += 1 + 3 + 4 + 4; - const decoderSpecificInfoTag = this.isobmffReader.readU8(); - assert(decoderSpecificInfoTag === 0x05); + if (decoderConfigDescriptorLength > this.isobmffReader.pos - payloadStart) { + // There's a DecoderSpecificInfo at the end, let's read it - const decoderSpecificInfoLength = this.isobmffReader.readIsomVariableInteger(); + const decoderSpecificInfoTag = this.isobmffReader.readU8(); + assert(decoderSpecificInfoTag === 0x05); // DecoderSpecificInfo - track.info.codecDescription = this.isobmffReader.readRange( - this.isobmffReader.pos, - this.isobmffReader.pos + decoderSpecificInfoLength, - ); + const decoderSpecificInfoLength = this.isobmffReader.readIsomVariableInteger(); + + track.info.codecDescription = this.isobmffReader.readRange( + this.isobmffReader.pos, + this.isobmffReader.pos + decoderSpecificInfoLength, + ); + } }; break; case 'stts': { @@ -2003,17 +2017,16 @@ const getSampleInfo = (sampleTable: SampleTable, sampleIndex: number): SampleInf + Math.floor((sampleIndex - chunkEntry.startSampleIndex) / chunkEntry.samplesPerChunk); const chunkOffset = sampleTable.chunkOffsets[chunkIndex]!; + const startSampleIndexOfChunk = chunkEntry.startSampleIndex + + (chunkIndex - chunkEntry.startChunkIndex) * chunkEntry.samplesPerChunk; let chunkSize = 0; - let sampleOffset = chunkOffset; + if (sampleTable.sampleSizes.length === 1) { - sampleOffset += sampleSize * (sampleIndex - chunkEntry.startSampleIndex); + sampleOffset += sampleSize * (sampleIndex - startSampleIndexOfChunk); chunkSize += sampleSize * chunkEntry.samplesPerChunk; } else { - const startSampleIndex = chunkEntry.startSampleIndex - + (chunkIndex - chunkEntry.startChunkIndex) * chunkEntry.samplesPerChunk; - - for (let i = startSampleIndex; i < startSampleIndex + chunkEntry.samplesPerChunk; i++) { + for (let i = startSampleIndexOfChunk; i < startSampleIndexOfChunk + chunkEntry.samplesPerChunk; i++) { const sampleSize = sampleTable.sampleSizes[i]!; if (i < sampleIndex) {