mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-30 04:13:51 +02:00
Migrate FLAC demuxer to new packet system, move computeDuration impl to Input
This commit is contained in:
@@ -20,15 +20,4 @@ export abstract class Demuxer {
|
||||
abstract getTracks(): Promise<InputTrack[]>;
|
||||
abstract getMimeType(): Promise<string>;
|
||||
abstract getMetadataTags(): Promise<MetadataTags>;
|
||||
|
||||
async computeDuration(): Promise<number> {
|
||||
const tracks = await this.getTracks();
|
||||
if (tracks.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
||||
const trackDurations = await Promise.all(tracks.map(x => x.computeDuration()));
|
||||
return Math.max(...trackDurations);
|
||||
}
|
||||
}
|
||||
|
||||
+114
-99
@@ -13,11 +13,13 @@ import { InputAudioTrack, InputAudioTrackBacking } from '../input-track';
|
||||
import { PacketRetrievalOptions } from '../media-sink';
|
||||
import {
|
||||
assert,
|
||||
AsyncMutex,
|
||||
AsyncMutex4,
|
||||
binarySearchLessOrEqual,
|
||||
Bitstream,
|
||||
ResultValue,
|
||||
textDecoder,
|
||||
UNDETERMINED_LANGUAGE,
|
||||
Yo,
|
||||
} from '../misc';
|
||||
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
|
||||
import {
|
||||
@@ -77,7 +79,7 @@ export class FlacDemuxer extends Demuxer {
|
||||
lastLoadedPos: number | null = null;
|
||||
blockingBit: number | null = null;
|
||||
|
||||
readingMutex = new AsyncMutex();
|
||||
readingMutex = new AsyncMutex4();
|
||||
lastSampleLoaded = false;
|
||||
|
||||
constructor(input: Input) {
|
||||
@@ -86,12 +88,6 @@ export class FlacDemuxer extends Demuxer {
|
||||
this.reader = input._reader;
|
||||
}
|
||||
|
||||
override async computeDuration(): Promise<number> {
|
||||
await this.readMetadata();
|
||||
assert(this.track);
|
||||
return this.track.computeDuration();
|
||||
}
|
||||
|
||||
override async getMetadataTags(): Promise<MetadataTags> {
|
||||
await this.readMetadata();
|
||||
return this.metadataTags;
|
||||
@@ -108,9 +104,9 @@ export class FlacDemuxer extends Demuxer {
|
||||
}
|
||||
|
||||
async readMetadata() {
|
||||
let currentPos = 4; // Skip 'fLaC'
|
||||
|
||||
return (this.metadataPromise ??= (async () => {
|
||||
let currentPos = 4; // Skip 'fLaC'
|
||||
|
||||
while (
|
||||
this.reader.fileSize === null
|
||||
|| currentPos < this.reader.fileSize
|
||||
@@ -261,13 +257,16 @@ export class FlacDemuxer extends Demuxer {
|
||||
})());
|
||||
}
|
||||
|
||||
async readNextFlacFrame({
|
||||
startPos,
|
||||
isFirstPacket,
|
||||
}: {
|
||||
startPos: number;
|
||||
isFirstPacket: boolean;
|
||||
}): Promise<NextFlacFrameResult | null> {
|
||||
async readNextFlacFrame(
|
||||
res: ResultValue<NextFlacFrameResult | null>,
|
||||
{
|
||||
startPos,
|
||||
isFirstPacket,
|
||||
}: {
|
||||
startPos: number;
|
||||
isFirstPacket: boolean;
|
||||
},
|
||||
): Promise<Yo> {
|
||||
assert(this.audioInfo);
|
||||
// we expect that there are at least `minimumFrameSize` bytes left in the file
|
||||
|
||||
@@ -287,14 +286,15 @@ export class FlacDemuxer extends Demuxer {
|
||||
const maximumSliceLength
|
||||
= this.audioInfo.maximumFrameSize + maximumHeaderSize;
|
||||
|
||||
const slice = await this.reader.requestSliceRange(
|
||||
let slice = this.reader.requestSliceRange(
|
||||
startPos,
|
||||
this.audioInfo.minimumFrameSize,
|
||||
maximumSliceLength,
|
||||
);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
|
||||
if (!slice) {
|
||||
return null;
|
||||
return res.set(null);
|
||||
}
|
||||
|
||||
const frameHeader = this.readFlacFrameHeader({
|
||||
@@ -303,7 +303,7 @@ export class FlacDemuxer extends Demuxer {
|
||||
});
|
||||
|
||||
if (!frameHeader) {
|
||||
return null;
|
||||
return res.set(null);
|
||||
}
|
||||
|
||||
// We don't know exactly how long the packet is, we only know the `minimumFrameSize` and `maximumFrameSize`
|
||||
@@ -317,13 +317,13 @@ export class FlacDemuxer extends Demuxer {
|
||||
while (true) {
|
||||
// Reached end of the file, packet is over
|
||||
if (slice.filePos > slice.end - minimumHeaderLength) {
|
||||
return {
|
||||
return res.set({
|
||||
num: frameHeader.num,
|
||||
blockSize: frameHeader.blockSize,
|
||||
sampleRate: frameHeader.sampleRate,
|
||||
size: slice.end - startPos,
|
||||
isLastFrame: true,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const nextByte = readU8(slice);
|
||||
@@ -369,13 +369,13 @@ export class FlacDemuxer extends Demuxer {
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
return res.set({
|
||||
num: frameHeader.num,
|
||||
blockSize: frameHeader.blockSize,
|
||||
sampleRate: frameHeader.sampleRate,
|
||||
size: lengthIfNextFlacFrameHeaderIsLegit,
|
||||
isLastFrame: false,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -483,21 +483,24 @@ export class FlacDemuxer extends Demuxer {
|
||||
return { num, blockSize, sampleRate };
|
||||
}
|
||||
|
||||
async advanceReader() {
|
||||
await this.readMetadata();
|
||||
async advanceReader(res: ResultValue<void>): Promise<Yo> {
|
||||
assert(this.lastLoadedPos !== null);
|
||||
assert(this.audioInfo);
|
||||
const startPos = this.lastLoadedPos;
|
||||
const frame = await this.readNextFlacFrame({
|
||||
|
||||
const frameResult = new ResultValue<NextFlacFrameResult | null>();
|
||||
const promise = this.readNextFlacFrame(frameResult, {
|
||||
startPos,
|
||||
isFirstPacket: this.loadedSamples.length === 0,
|
||||
});
|
||||
if (frameResult.pending) await promise;
|
||||
|
||||
const frame = frameResult.value;
|
||||
if (!frame) {
|
||||
// Unexpected case, failed to read next FLAC frame
|
||||
// handling gracefully
|
||||
this.lastSampleLoaded = true;
|
||||
return;
|
||||
return res.set();
|
||||
}
|
||||
|
||||
const lastSample = this.loadedSamples[this.loadedSamples.length - 1];
|
||||
@@ -517,8 +520,9 @@ export class FlacDemuxer extends Demuxer {
|
||||
|
||||
if (frame.isLastFrame) {
|
||||
this.lastSampleLoaded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
return res.set();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -542,11 +546,6 @@ class FlacAudioTrackBacking implements InputAudioTrackBacking {
|
||||
return this.demuxer.audioInfo.numberOfChannels;
|
||||
}
|
||||
|
||||
async computeDuration() {
|
||||
const lastPacket = await this.getPacket(Infinity, { metadataOnly: true });
|
||||
return (lastPacket?.timestamp ?? 0) + (lastPacket?.duration ?? 0);
|
||||
}
|
||||
|
||||
getSampleRate() {
|
||||
assert(this.demuxer.audioInfo);
|
||||
return this.demuxer.audioInfo.sampleRate;
|
||||
@@ -571,10 +570,6 @@ class FlacAudioTrackBacking implements InputAudioTrackBacking {
|
||||
};
|
||||
}
|
||||
|
||||
async getFirstTimestamp() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
async getDecoderConfig(): Promise<AudioDecoderConfig | null> {
|
||||
assert(this.demuxer.audioInfo);
|
||||
|
||||
@@ -587,101 +582,115 @@ class FlacAudioTrackBacking implements InputAudioTrackBacking {
|
||||
}
|
||||
|
||||
async getPacket(
|
||||
res: ResultValue<EncodedPacket | null>,
|
||||
timestamp: number,
|
||||
options: PacketRetrievalOptions,
|
||||
): Promise<EncodedPacket | null> {
|
||||
): Promise<Yo> {
|
||||
assert(this.demuxer.audioInfo);
|
||||
if (timestamp < 0) {
|
||||
throw new Error('Timestamp cannot be negative');
|
||||
}
|
||||
|
||||
const release = await this.demuxer.readingMutex.acquire();
|
||||
using lock = this.demuxer.readingMutex.lock();
|
||||
if (lock.pending) await lock.ready;
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const packetIndex = binarySearchLessOrEqual(
|
||||
this.demuxer.loadedSamples,
|
||||
timestamp,
|
||||
x => x.blockOffset / this.demuxer.audioInfo!.sampleRate,
|
||||
);
|
||||
if (packetIndex === -1) {
|
||||
await this.demuxer.advanceReader();
|
||||
continue;
|
||||
}
|
||||
const advanceResult = new ResultValue<void>();
|
||||
|
||||
const packet = this.demuxer.loadedSamples[packetIndex]!;
|
||||
const sampleTimestamp
|
||||
while (true) {
|
||||
const packetIndex = binarySearchLessOrEqual(
|
||||
this.demuxer.loadedSamples,
|
||||
timestamp,
|
||||
x => x.blockOffset / this.demuxer.audioInfo!.sampleRate,
|
||||
);
|
||||
if (packetIndex === -1) {
|
||||
advanceResult.reset();
|
||||
const promise = this.demuxer.advanceReader(advanceResult);
|
||||
if (advanceResult.pending) await promise;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const packet = this.demuxer.loadedSamples[packetIndex]!;
|
||||
const sampleTimestamp
|
||||
= packet.blockOffset / this.demuxer.audioInfo.sampleRate;
|
||||
const sampleDuration
|
||||
const sampleDuration
|
||||
= packet.blockSize / this.demuxer.audioInfo.sampleRate;
|
||||
|
||||
if (sampleTimestamp + sampleDuration <= timestamp) {
|
||||
if (this.demuxer.lastSampleLoaded) {
|
||||
return this.getPacketAtIndex(
|
||||
this.demuxer.loadedSamples.length - 1,
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
await this.demuxer.advanceReader();
|
||||
continue;
|
||||
if (sampleTimestamp + sampleDuration <= timestamp) {
|
||||
if (this.demuxer.lastSampleLoaded) {
|
||||
return this.getPacketAtIndex(
|
||||
res,
|
||||
this.demuxer.loadedSamples.length - 1,
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
return this.getPacketAtIndex(packetIndex, options);
|
||||
advanceResult.reset();
|
||||
const promise = this.demuxer.advanceReader(advanceResult);
|
||||
if (advanceResult.pending) await promise;
|
||||
|
||||
continue;
|
||||
}
|
||||
} finally {
|
||||
release();
|
||||
|
||||
return this.getPacketAtIndex(res, packetIndex, options);
|
||||
}
|
||||
}
|
||||
|
||||
async getNextPacket(
|
||||
res: ResultValue<EncodedPacket | null>,
|
||||
packet: EncodedPacket,
|
||||
options: PacketRetrievalOptions,
|
||||
): Promise<EncodedPacket | null> {
|
||||
const release = await this.demuxer.readingMutex.acquire();
|
||||
try {
|
||||
const nextIndex = packet.sequenceNumber + 1;
|
||||
if (
|
||||
this.demuxer.lastSampleLoaded
|
||||
&& nextIndex >= this.demuxer.loadedSamples.length
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
): Promise<Yo> {
|
||||
using lock = this.demuxer.readingMutex.lock();
|
||||
if (lock.pending) await lock.ready;
|
||||
|
||||
// Ensure the next sample exists
|
||||
while (
|
||||
nextIndex >= this.demuxer.loadedSamples.length
|
||||
&& !this.demuxer.lastSampleLoaded
|
||||
) {
|
||||
await this.demuxer.advanceReader();
|
||||
}
|
||||
return this.getPacketAtIndex(nextIndex, options);
|
||||
} finally {
|
||||
release();
|
||||
const nextIndex = packet.sequenceNumber + 1;
|
||||
if (
|
||||
this.demuxer.lastSampleLoaded
|
||||
&& nextIndex >= this.demuxer.loadedSamples.length
|
||||
) {
|
||||
return res.set(null);
|
||||
}
|
||||
|
||||
const advanceResult = new ResultValue<void>();
|
||||
|
||||
// Ensure the next sample exists
|
||||
while (
|
||||
nextIndex >= this.demuxer.loadedSamples.length
|
||||
&& !this.demuxer.lastSampleLoaded
|
||||
) {
|
||||
advanceResult.reset();
|
||||
const promise = this.demuxer.advanceReader(advanceResult);
|
||||
if (advanceResult.pending) await promise;
|
||||
}
|
||||
|
||||
return this.getPacketAtIndex(res, nextIndex, options);
|
||||
}
|
||||
|
||||
getKeyPacket(
|
||||
res: ResultValue<EncodedPacket | null>,
|
||||
timestamp: number,
|
||||
options: PacketRetrievalOptions,
|
||||
): Promise<EncodedPacket | null> {
|
||||
return this.getPacket(timestamp, options);
|
||||
): Promise<Yo> {
|
||||
return this.getPacket(res, timestamp, options);
|
||||
}
|
||||
|
||||
getNextKeyPacket(
|
||||
res: ResultValue<EncodedPacket | null>,
|
||||
packet: EncodedPacket,
|
||||
options: PacketRetrievalOptions,
|
||||
): Promise<EncodedPacket | null> {
|
||||
return this.getNextPacket(packet, options);
|
||||
): Promise<Yo> {
|
||||
return this.getNextPacket(res, packet, options);
|
||||
}
|
||||
|
||||
async getPacketAtIndex(
|
||||
res: ResultValue<EncodedPacket | null>,
|
||||
sampleIndex: number,
|
||||
options: PacketRetrievalOptions,
|
||||
): Promise<EncodedPacket | null> {
|
||||
): Promise<Yo> {
|
||||
const rawSample = this.demuxer.loadedSamples[sampleIndex];
|
||||
if (!rawSample) {
|
||||
return null;
|
||||
return res.set(null);
|
||||
}
|
||||
|
||||
let data: Uint8Array;
|
||||
@@ -695,7 +704,7 @@ class FlacAudioTrackBacking implements InputAudioTrackBacking {
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
|
||||
if (!slice) {
|
||||
return null; // Data didn't fit into the rest of the file
|
||||
return res.set(null); // Data didn't fit into the rest of the file
|
||||
}
|
||||
|
||||
data = readBytes(slice, rawSample.byteSize);
|
||||
@@ -704,27 +713,33 @@ class FlacAudioTrackBacking implements InputAudioTrackBacking {
|
||||
assert(this.demuxer.audioInfo);
|
||||
const timestamp = rawSample.blockOffset / this.demuxer.audioInfo.sampleRate;
|
||||
const duration = rawSample.blockSize / this.demuxer.audioInfo.sampleRate;
|
||||
return new EncodedPacket(
|
||||
|
||||
return res.set(new EncodedPacket(
|
||||
data,
|
||||
'key',
|
||||
timestamp,
|
||||
duration,
|
||||
sampleIndex,
|
||||
rawSample.byteSize,
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
async getFirstPacket(
|
||||
res: ResultValue<EncodedPacket | null>,
|
||||
options: PacketRetrievalOptions,
|
||||
): Promise<EncodedPacket | null> {
|
||||
): Promise<Yo> {
|
||||
const advanceResult = new ResultValue<void>();
|
||||
|
||||
// Ensure the next sample exists
|
||||
while (
|
||||
this.demuxer.loadedSamples.length === 0
|
||||
&& !this.demuxer.lastSampleLoaded
|
||||
) {
|
||||
await this.demuxer.advanceReader();
|
||||
advanceResult.reset();
|
||||
const promise = this.demuxer.advanceReader(advanceResult);
|
||||
if (advanceResult.pending) await promise;
|
||||
}
|
||||
|
||||
return this.getPacketAtIndex(0, options);
|
||||
return this.getPacketAtIndex(res, 0, options);
|
||||
}
|
||||
}
|
||||
|
||||
+7
-2
@@ -118,8 +118,13 @@ export class Input<S extends Source = Source> implements Disposable {
|
||||
* all tracks.
|
||||
*/
|
||||
async computeDuration() {
|
||||
const demuxer = await this._getDemuxer();
|
||||
return demuxer.computeDuration();
|
||||
const tracks = await this.getTracks();
|
||||
if (tracks.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const trackDurations = await Promise.all(tracks.map(x => x.computeDuration()));
|
||||
return Math.max(...trackDurations);
|
||||
}
|
||||
|
||||
/** Returns the list of all tracks of this input file. */
|
||||
|
||||
Reference in New Issue
Block a user