From 8a8fbc264e8beecf99fdcc31919e824320c7b0a2 Mon Sep 17 00:00:00 2001 From: Vanilagy <1696106+Vanilagy@users.noreply.github.com> Date: Thu, 2 Jan 2025 00:03:32 +0100 Subject: [PATCH] Add FLAC support to ISOBMFF demuxer --- src/codec.ts | 8 ++- src/isobmff/isobmff-demuxer.ts | 89 ++++++++++++++++++++++++++-------- src/isobmff/isobmff-reader.ts | 19 +++----- 3 files changed, 81 insertions(+), 35 deletions(-) diff --git a/src/codec.ts b/src/codec.ts index 05db943..4678ff1 100644 --- a/src/codec.ts +++ b/src/codec.ts @@ -35,6 +35,8 @@ export const AUDIO_CODECS = [ 'aac', 'mp3', 'opus', + 'vorbis', + 'flac', 'ulaw', 'alaw', ...PCM_CODECS, @@ -398,14 +400,16 @@ export const extractAudioCodecString = (codec: AudioCodec, description: Uint8Arr return 'mp3'; } else if (codec === 'opus') { return 'opus'; + } else if (codec === 'vorbis') { + return 'vorbis'; + } else if (codec === 'flac') { + return 'flac'; } else if (codec.startsWith('pcm-')) { return codec; } else if (codec === 'ulaw') { return 'ulaw'; } else if (codec === 'alaw') { return 'alaw'; - } else if (codec === 'vorbis') { - return 'vorbis'; } throw new TypeError(`Unhandled codec '${codec}'.`); diff --git a/src/isobmff/isobmff-demuxer.ts b/src/isobmff/isobmff-demuxer.ts index 222ddfb..e96ba09 100644 --- a/src/isobmff/isobmff-demuxer.ts +++ b/src/isobmff/isobmff-demuxer.ts @@ -707,6 +707,8 @@ export class IsobmffDemuxer extends Demuxer { // We don't know the codec yet (might be AAC, might be MP3), need to read the esds box } else if (lowercaseBoxName === 'opus') { track.info.codec = 'opus'; + } else if (lowercaseBoxName === 'flac') { + track.info.codec = 'flac'; } else if ( lowercaseBoxName === 'twos' || lowercaseBoxName === 'sowt' @@ -833,20 +835,14 @@ export class IsobmffDemuxer extends Demuxer { const track = this.currentTrack; assert(track && track.info); - track.info.codecDescription = this.isobmffReader.readRange( - this.isobmffReader.pos, - this.isobmffReader.pos + boxInfo.contentSize, - ); + track.info.codecDescription = this.isobmffReader.readBytes(boxInfo.contentSize); }; break; case 'hvcC': { const track = this.currentTrack; assert(track && track.info); - track.info.codecDescription = this.isobmffReader.readRange( - this.isobmffReader.pos, - this.isobmffReader.pos + boxInfo.contentSize, - ); + track.info.codecDescription = this.isobmffReader.readBytes(boxInfo.contentSize); }; break; case 'vpcC': { @@ -980,6 +976,9 @@ export class IsobmffDemuxer extends Demuxer { track.info.codec = 'aac'; } else if (objectTypeIndication === 0x69 || objectTypeIndication === 0x6b) { track.info.codec = 'mp3'; + } else if (objectTypeIndication === 0xdd) { + // TODO + track.info.codec = 'vorbis'; // "nonstandard, gpac uses it" - FFmpeg } else { console.warn( `Unsupported audio codec (objectTypeIndication ${objectTypeIndication}) - discarding track.`, @@ -995,11 +994,7 @@ export class IsobmffDemuxer extends Demuxer { assert(decoderSpecificInfoTag === 0x05); // DecoderSpecificInfo const decoderSpecificInfoLength = this.isobmffReader.readIsomVariableInteger(); - - track.info.codecDescription = this.isobmffReader.readRange( - this.isobmffReader.pos, - this.isobmffReader.pos + decoderSpecificInfoLength, - ); + track.info.codecDescription = this.isobmffReader.readBytes(decoderSpecificInfoLength); } }; break; @@ -1022,6 +1017,62 @@ export class IsobmffDemuxer extends Demuxer { } }; break; + case 'dfLa': { // Used for FLAC audio + const track = this.currentTrack; + assert(track && track.info?.type === 'audio'); + + this.isobmffReader.pos += 4; // Version + flags + + // https://datatracker.ietf.org/doc/rfc9639/ + + const BLOCK_TYPE_MASK = 0x7f; + const LAST_METADATA_BLOCK_FLAG_MASK = 0x80; + + const startPos = this.isobmffReader.pos; + + while (true) { + const flagAndType = this.isobmffReader.readU8(); + const metadataBlockLength = this.isobmffReader.readU24(); + const type = flagAndType & BLOCK_TYPE_MASK; + + // It's a STREAMINFO block; let's extract the actual sample rate and channel count + if (type === 0) { + this.isobmffReader.pos += 10; + + // Extract sample rate + const word = this.isobmffReader.readU32(); + const sampleRate = word >>> 12; + const numberOfChannels = ((word >> 9) & 0b111) + 1; + + track.info.sampleRate = sampleRate; + track.info.numberOfChannels = numberOfChannels; + + this.isobmffReader.pos += 20; + } else { + // Simply skip ahead to the next block + this.isobmffReader.pos += metadataBlockLength; + } + + if (flagAndType & LAST_METADATA_BLOCK_FLAG_MASK) { + break; + } + } + + const endPos = this.isobmffReader.pos; + this.isobmffReader.pos = startPos; + const bytes = this.isobmffReader.readBytes(endPos - startPos); + + const description = new Uint8Array(4 + bytes.byteLength); + description[0] = 0x66; // f + description[1] = 0x4C; // L + description[2] = 0x61; // a + description[3] = 0x43; // C + description.set(bytes, 4); + + // Set the codec description to be 'fLaC' + all metadata blocks + track.info.codecDescription = description; + }; break; + case 'stts': { const track = this.currentTrack; assert(track); @@ -1807,10 +1858,8 @@ abstract class IsobmffTrackBacking