mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-09 00:33:46 +02:00
Add FLAC support to ISOBMFF demuxer
This commit is contained in:
+6
-2
@@ -35,6 +35,8 @@ export const AUDIO_CODECS = [
|
|||||||
'aac',
|
'aac',
|
||||||
'mp3',
|
'mp3',
|
||||||
'opus',
|
'opus',
|
||||||
|
'vorbis',
|
||||||
|
'flac',
|
||||||
'ulaw',
|
'ulaw',
|
||||||
'alaw',
|
'alaw',
|
||||||
...PCM_CODECS,
|
...PCM_CODECS,
|
||||||
@@ -398,14 +400,16 @@ export const extractAudioCodecString = (codec: AudioCodec, description: Uint8Arr
|
|||||||
return 'mp3';
|
return 'mp3';
|
||||||
} else if (codec === 'opus') {
|
} else if (codec === 'opus') {
|
||||||
return 'opus';
|
return 'opus';
|
||||||
|
} else if (codec === 'vorbis') {
|
||||||
|
return 'vorbis';
|
||||||
|
} else if (codec === 'flac') {
|
||||||
|
return 'flac';
|
||||||
} else if (codec.startsWith('pcm-')) {
|
} else if (codec.startsWith('pcm-')) {
|
||||||
return codec;
|
return codec;
|
||||||
} else if (codec === 'ulaw') {
|
} else if (codec === 'ulaw') {
|
||||||
return 'ulaw';
|
return 'ulaw';
|
||||||
} else if (codec === 'alaw') {
|
} else if (codec === 'alaw') {
|
||||||
return 'alaw';
|
return 'alaw';
|
||||||
} else if (codec === 'vorbis') {
|
|
||||||
return 'vorbis';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new TypeError(`Unhandled codec '${codec}'.`);
|
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
|
// We don't know the codec yet (might be AAC, might be MP3), need to read the esds box
|
||||||
} else if (lowercaseBoxName === 'opus') {
|
} else if (lowercaseBoxName === 'opus') {
|
||||||
track.info.codec = 'opus';
|
track.info.codec = 'opus';
|
||||||
|
} else if (lowercaseBoxName === 'flac') {
|
||||||
|
track.info.codec = 'flac';
|
||||||
} else if (
|
} else if (
|
||||||
lowercaseBoxName === 'twos'
|
lowercaseBoxName === 'twos'
|
||||||
|| lowercaseBoxName === 'sowt'
|
|| lowercaseBoxName === 'sowt'
|
||||||
@@ -833,20 +835,14 @@ export class IsobmffDemuxer extends Demuxer {
|
|||||||
const track = this.currentTrack;
|
const track = this.currentTrack;
|
||||||
assert(track && track.info);
|
assert(track && track.info);
|
||||||
|
|
||||||
track.info.codecDescription = this.isobmffReader.readRange(
|
track.info.codecDescription = this.isobmffReader.readBytes(boxInfo.contentSize);
|
||||||
this.isobmffReader.pos,
|
|
||||||
this.isobmffReader.pos + boxInfo.contentSize,
|
|
||||||
);
|
|
||||||
}; break;
|
}; break;
|
||||||
|
|
||||||
case 'hvcC': {
|
case 'hvcC': {
|
||||||
const track = this.currentTrack;
|
const track = this.currentTrack;
|
||||||
assert(track && track.info);
|
assert(track && track.info);
|
||||||
|
|
||||||
track.info.codecDescription = this.isobmffReader.readRange(
|
track.info.codecDescription = this.isobmffReader.readBytes(boxInfo.contentSize);
|
||||||
this.isobmffReader.pos,
|
|
||||||
this.isobmffReader.pos + boxInfo.contentSize,
|
|
||||||
);
|
|
||||||
}; break;
|
}; break;
|
||||||
|
|
||||||
case 'vpcC': {
|
case 'vpcC': {
|
||||||
@@ -980,6 +976,9 @@ export class IsobmffDemuxer extends Demuxer {
|
|||||||
track.info.codec = 'aac';
|
track.info.codec = 'aac';
|
||||||
} else if (objectTypeIndication === 0x69 || objectTypeIndication === 0x6b) {
|
} else if (objectTypeIndication === 0x69 || objectTypeIndication === 0x6b) {
|
||||||
track.info.codec = 'mp3';
|
track.info.codec = 'mp3';
|
||||||
|
} else if (objectTypeIndication === 0xdd) {
|
||||||
|
// TODO
|
||||||
|
track.info.codec = 'vorbis'; // "nonstandard, gpac uses it" - FFmpeg
|
||||||
} else {
|
} else {
|
||||||
console.warn(
|
console.warn(
|
||||||
`Unsupported audio codec (objectTypeIndication ${objectTypeIndication}) - discarding track.`,
|
`Unsupported audio codec (objectTypeIndication ${objectTypeIndication}) - discarding track.`,
|
||||||
@@ -995,11 +994,7 @@ export class IsobmffDemuxer extends Demuxer {
|
|||||||
assert(decoderSpecificInfoTag === 0x05); // DecoderSpecificInfo
|
assert(decoderSpecificInfoTag === 0x05); // DecoderSpecificInfo
|
||||||
|
|
||||||
const decoderSpecificInfoLength = this.isobmffReader.readIsomVariableInteger();
|
const decoderSpecificInfoLength = this.isobmffReader.readIsomVariableInteger();
|
||||||
|
track.info.codecDescription = this.isobmffReader.readBytes(decoderSpecificInfoLength);
|
||||||
track.info.codecDescription = this.isobmffReader.readRange(
|
|
||||||
this.isobmffReader.pos,
|
|
||||||
this.isobmffReader.pos + decoderSpecificInfoLength,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}; break;
|
}; break;
|
||||||
|
|
||||||
@@ -1022,6 +1017,62 @@ export class IsobmffDemuxer extends Demuxer {
|
|||||||
}
|
}
|
||||||
}; break;
|
}; 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': {
|
case 'stts': {
|
||||||
const track = this.currentTrack;
|
const track = this.currentTrack;
|
||||||
assert(track);
|
assert(track);
|
||||||
@@ -1807,10 +1858,8 @@ abstract class IsobmffTrackBacking<Chunk extends EncodedVideoChunk | EncodedAudi
|
|||||||
sampleInfo.chunkOffset + sampleInfo.chunkSize,
|
sampleInfo.chunkOffset + sampleInfo.chunkSize,
|
||||||
);
|
);
|
||||||
|
|
||||||
data = this.internalTrack.demuxer.chunkReader.readRange(
|
this.internalTrack.demuxer.chunkReader.pos = sampleInfo.sampleOffset;
|
||||||
sampleInfo.sampleOffset,
|
data = this.internalTrack.demuxer.chunkReader.readBytes(sampleInfo.sampleSize);
|
||||||
sampleInfo.sampleOffset + sampleInfo.sampleSize,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const timestamp = 1e6 * sampleInfo.presentationTimestamp / this.internalTrack.timescale;
|
const timestamp = 1e6 * sampleInfo.presentationTimestamp / this.internalTrack.timescale;
|
||||||
@@ -1838,10 +1887,8 @@ abstract class IsobmffTrackBacking<Chunk extends EncodedVideoChunk | EncodedAudi
|
|||||||
// Load the entire fragment
|
// Load the entire fragment
|
||||||
await this.internalTrack.demuxer.chunkReader.reader.loadRange(fragment.dataStart, fragment.dataEnd);
|
await this.internalTrack.demuxer.chunkReader.reader.loadRange(fragment.dataStart, fragment.dataEnd);
|
||||||
|
|
||||||
data = this.internalTrack.demuxer.chunkReader.readRange(
|
this.internalTrack.demuxer.chunkReader.pos = sample.byteOffset;
|
||||||
sample.byteOffset,
|
data = this.internalTrack.demuxer.chunkReader.readBytes(sample.byteSize);
|
||||||
sample.byteOffset + sample.byteSize,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const timestamp = 1e6 * sample.presentationTimestamp / this.internalTrack.timescale;
|
const timestamp = 1e6 * sample.presentationTimestamp / this.internalTrack.timescale;
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ export class IsobmffReader {
|
|||||||
|
|
||||||
constructor(public reader: Reader) {}
|
constructor(public reader: Reader) {}
|
||||||
|
|
||||||
readRange(start: number, end: number) {
|
readBytes(length: number) {
|
||||||
const { view, offset } = this.reader.getViewAndOffset(start, end);
|
const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + length);
|
||||||
return new Uint8Array(view.buffer, offset, end - start);
|
this.pos += length;
|
||||||
|
|
||||||
|
return new Uint8Array(view.buffer, offset, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
readU8() {
|
readU8() {
|
||||||
@@ -33,13 +35,6 @@ export class IsobmffReader {
|
|||||||
return high * 0x100 + low;
|
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() {
|
readU32() {
|
||||||
const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 4);
|
const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 4);
|
||||||
this.pos += 4;
|
this.pos += 4;
|
||||||
@@ -68,11 +63,11 @@ export class IsobmffReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
readFixed_16_16() {
|
readFixed_16_16() {
|
||||||
return this.readS32() / 0x10000;
|
return this.readI32() / 0x10000;
|
||||||
}
|
}
|
||||||
|
|
||||||
readFixed_2_30() {
|
readFixed_2_30() {
|
||||||
return this.readS32() / 0x40000000;
|
return this.readI32() / 0x40000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
readAscii(length: number) {
|
readAscii(length: number) {
|
||||||
|
|||||||
Reference in New Issue
Block a user