mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-28 03:13:51 +02:00
Add FLAC support to ISOBMFF demuxer
This commit is contained in:
+6
-2
@@ -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}'.`);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user