Add FLAC support to ISOBMFF demuxer

This commit is contained in:
Vanilagy
2025-01-02 00:03:32 +01:00
parent 4e0b3a7b3a
commit 8a8fbc264e
3 changed files with 81 additions and 35 deletions
+6 -2
View File
@@ -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}'.`);
+68 -21
View File
@@ -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<Chunk extends EncodedVideoChunk | EncodedAudi
sampleInfo.chunkOffset + sampleInfo.chunkSize,
);
data = this.internalTrack.demuxer.chunkReader.readRange(
sampleInfo.sampleOffset,
sampleInfo.sampleOffset + sampleInfo.sampleSize,
);
this.internalTrack.demuxer.chunkReader.pos = sampleInfo.sampleOffset;
data = this.internalTrack.demuxer.chunkReader.readBytes(sampleInfo.sampleSize);
}
const timestamp = 1e6 * sampleInfo.presentationTimestamp / this.internalTrack.timescale;
@@ -1838,10 +1887,8 @@ abstract class IsobmffTrackBacking<Chunk extends EncodedVideoChunk | EncodedAudi
// Load the entire fragment
await this.internalTrack.demuxer.chunkReader.reader.loadRange(fragment.dataStart, fragment.dataEnd);
data = this.internalTrack.demuxer.chunkReader.readRange(
sample.byteOffset,
sample.byteOffset + sample.byteSize,
);
this.internalTrack.demuxer.chunkReader.pos = sample.byteOffset;
data = this.internalTrack.demuxer.chunkReader.readBytes(sample.byteSize);
}
const timestamp = 1e6 * sample.presentationTimestamp / this.internalTrack.timescale;
+7 -12
View File
@@ -5,9 +5,11 @@ export class IsobmffReader {
constructor(public reader: Reader) {}
readRange(start: number, end: number) {
const { view, offset } = this.reader.getViewAndOffset(start, end);
return new Uint8Array(view.buffer, offset, end - start);
readBytes(length: number) {
const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + length);
this.pos += length;
return new Uint8Array(view.buffer, offset, length);
}
readU8() {
@@ -33,13 +35,6 @@ export class IsobmffReader {
return high * 0x100 + low;
}
readS32() {
const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 4);
this.pos += 4;
return view.getInt32(offset, false);
}
readU32() {
const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 4);
this.pos += 4;
@@ -68,11 +63,11 @@ export class IsobmffReader {
}
readFixed_16_16() {
return this.readS32() / 0x10000;
return this.readI32() / 0x10000;
}
readFixed_2_30() {
return this.readS32() / 0x40000000;
return this.readI32() / 0x40000000;
}
readAscii(length: number) {