Merge branch 'main' into hls

This commit is contained in:
Vanilagy
2026-03-19 16:52:00 +01:00
15 changed files with 240 additions and 69 deletions
+29 -4
View File
@@ -292,13 +292,38 @@ export class FlacDemuxer extends Demuxer {
// --> 6 bytes
const minimumHeaderLength = 6;
// If we read everything in readFlacFrameHeader, we read 16 bytes
const maximumHeaderSize = 16;
const maximumHeaderLength = 16;
// The shortest valid FLAC frame per RFC 9639:
// 6 bytes header (see minimumHeaderLength above)
// 2 bytes subframe (constant subframe with minimum bit depth,
// padded to byte boundary)
// 2 bytes footer (CRC-16)
// --> 10 bytes
const minimumFrameLength = 10;
// The longest valid FLAC frame per RFC 9639:
// https://www.rfc-editor.org/rfc/rfc9639.html#name-prediction
// https://www.rfc-editor.org/rfc/rfc9639.html#name-frame-structure
// maximumBlockSize * numberOfChannels * 4 bytes (max 32 bps verbatim)
// + 16 bytes header (see maximumHeaderSize above)
// + 2 bytes footer (CRC-16)
const maximumFrameLength = this.audioInfo.maximumBlockSize
* this.audioInfo.numberOfChannels
* 4
+ maximumHeaderLength
+ 2;
// Per RFC 9639, a value of 0 means "unknown" for frame sizes.
const effectiveMinFrameSize = this.audioInfo.minimumFrameSize || minimumFrameLength;
const effectiveMaxFrameSize = this.audioInfo.maximumFrameSize || maximumFrameLength;
const maximumSliceLength
= this.audioInfo.maximumFrameSize + maximumHeaderSize;
= effectiveMaxFrameSize + maximumHeaderLength;
const slice = await this.reader.requestSliceRange(
startPos,
this.audioInfo.minimumFrameSize,
maximumHeaderLength,
maximumSliceLength,
);
@@ -321,7 +346,7 @@ export class FlacDemuxer extends Demuxer {
// The next sync word is expected at earliest when `minimumFrameSize` is reached,
// we can skip over anything before that
slice.filePos = startPos + this.audioInfo.minimumFrameSize;
slice.filePos = startPos + effectiveMinFrameSize;
while (true) {
// Reached end of the file, packet is over
+70 -36
View File
@@ -49,6 +49,10 @@ export class FlacMuxer extends Muxer {
super(output);
this.format = format;
if (this.format._options.appendOnly) {
this.writer.ensureMonotonicity = true;
}
}
async start() {
@@ -169,7 +173,9 @@ export class FlacMuxer extends Muxer {
}
writeVorbisCommentAndPictureBlock() {
this.writer.seek(STREAMINFO_SIZE + FLAC_HEADER.byteLength);
if (!this.format._options.appendOnly) {
this.writer.seek(STREAMINFO_SIZE + FLAC_HEADER.byteLength);
}
if (metadataTagsAreEmpty(this.output._metadataTags)) {
this.metadataWritten = true;
return;
@@ -237,6 +243,30 @@ export class FlacMuxer extends Muxer {
descriptionBitstream.skipBits(103 + 64);
const bitsPerSample = descriptionBitstream.readBits(5) + 1;
this.bitsPerSample = bitsPerSample;
if (this.format._options.appendOnly) {
// Write STREAMINFO immediately since we can't seek back later.
this.writeHeader({
// https://www.rfc-editor.org/rfc/rfc9639.html#name-streaminfo
// Per RFC 9639, min/max block sizes can be looser than
// actual values, so we use the full valid range (16–65535).
// "The actual max block size MAY be smaller than what's
// listed, and the actual min (excluding last block) MAY be
// larger. This is because the encoder has to write these
// fields before receiving any input audio data and cannot
// know beforehand what block sizes it will use."
minimumBlockSize: 16,
maximumBlockSize: 65535,
// https://www.rfc-editor.org/rfc/rfc9639.html#name-streaminfo
// "A value of 0 signifies that the value is not known."
minimumFrameSize: 0,
maximumFrameSize: 0,
sampleRate: this.sampleRate,
channels: this.channels,
bitsPerSample: this.bitsPerSample,
totalSamples: 0,
});
}
}
if (!this.metadataWritten) {
@@ -255,8 +285,10 @@ export class FlacMuxer extends Muxer {
readCodedNumber(slice); // num
const blockSize = readBlockSize(slice, blockSizeOrUncommon);
this.blockSizes.push(blockSize);
this.frameSizes.push(packet.data.length);
if (!this.format._options.appendOnly) {
this.blockSizes.push(blockSize);
this.frameSizes.push(packet.data.length);
}
const startPos = this.writer.getPos();
this.writer.write(packet.data);
@@ -278,43 +310,45 @@ export class FlacMuxer extends Muxer {
async finalize(): Promise<void> {
const release = await this.mutex.acquire();
let minimumBlockSize = Infinity;
let maximumBlockSize = 0;
let minimumFrameSize = Infinity;
let maximumFrameSize = 0;
let totalSamples = 0;
for (let i = 0; i < this.blockSizes.length; i++) {
minimumFrameSize = Math.min(minimumFrameSize, this.frameSizes[i]!);
maximumFrameSize = Math.max(maximumFrameSize, this.frameSizes[i]!);
maximumBlockSize = Math.max(maximumBlockSize, this.blockSizes[i]!);
totalSamples += this.blockSizes[i]!;
if (!this.format._options.appendOnly) {
let minimumBlockSize = Infinity;
let maximumBlockSize = 0;
let minimumFrameSize = Infinity;
let maximumFrameSize = 0;
let totalSamples = 0;
for (let i = 0; i < this.blockSizes.length; i++) {
minimumFrameSize = Math.min(minimumFrameSize, this.frameSizes[i]!);
maximumFrameSize = Math.max(maximumFrameSize, this.frameSizes[i]!);
maximumBlockSize = Math.max(maximumBlockSize, this.blockSizes[i]!);
totalSamples += this.blockSizes[i]!;
// Excluding the last frame from block size calculation
// https://www.rfc-editor.org/rfc/rfc9639.html#name-streaminfo
// "The minimum block size (in samples) used in the stream, excluding the last block."
const isLastFrame = i === this.blockSizes.length - 1;
if (isLastFrame) {
continue;
// Excluding the last frame from block size calculation
// https://www.rfc-editor.org/rfc/rfc9639.html#name-streaminfo
// "The minimum block size (in samples) used in the stream, excluding the last block."
const isLastFrame = i === this.blockSizes.length - 1;
if (isLastFrame) {
continue;
}
minimumBlockSize = Math.min(minimumBlockSize, this.blockSizes[i]!);
}
minimumBlockSize = Math.min(minimumBlockSize, this.blockSizes[i]!);
assert(this.sampleRate !== null);
assert(this.channels !== null);
assert(this.bitsPerSample !== null);
this.writer.seek(4);
this.writeHeader({
minimumBlockSize,
maximumBlockSize,
minimumFrameSize,
maximumFrameSize,
sampleRate: this.sampleRate,
channels: this.channels,
bitsPerSample: this.bitsPerSample,
totalSamples,
});
}
assert(this.sampleRate !== null);
assert(this.channels !== null);
assert(this.bitsPerSample !== null);
this.writer.seek(4);
this.writeHeader({
minimumBlockSize,
maximumBlockSize,
minimumFrameSize,
maximumFrameSize,
sampleRate: this.sampleRate,
channels: this.channels,
bitsPerSample: this.bitsPerSample,
totalSamples,
});
release();
}
}