Migrate WAVE demuxer to new packet system

This commit is contained in:
Vanilagy
2025-12-21 19:59:42 +01:00
parent 3285d5ea7d
commit b2c86ec4ba
3 changed files with 105 additions and 59 deletions
+4
View File
@@ -885,6 +885,10 @@ export class ResultValue<T> {
this.value = value;
this.pending = false;
}
reset() {
this.pending = true;
}
}
export class AsyncMutex2 {
+50 -40
View File
@@ -12,7 +12,7 @@ import { Input } from '../input';
import { InputAudioTrack, InputAudioTrackBacking } from '../input-track';
import { PacketRetrievalOptions } from '../media-sink';
import { DEFAULT_TRACK_DISPOSITION, MetadataTags } from '../metadata';
import { assert, UNDETERMINED_LANGUAGE } from '../misc';
import { assert, ResultValue, UNDETERMINED_LANGUAGE, Yo } from '../misc';
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
import { readAscii, readBytes, Reader, readU16, readU32, readU64 } from '../reader';
import { parseId3V2Tag, readId3V2Header } from '../id3';
@@ -327,15 +327,6 @@ export class WaveDemuxer extends Demuxer {
return 'audio/wav';
}
async computeDuration() {
await this.readMetadata();
const track = this.tracks[0];
assert(track);
return track.computeDuration();
}
async getTracks() {
await this.readMetadata();
return this.tracks;
@@ -379,11 +370,6 @@ class WaveAudioTrackBacking implements InputAudioTrackBacking {
};
}
async computeDuration() {
const lastPacket = await this.getPacket(Infinity, { metadataOnly: true });
return (lastPacket?.timestamp ?? 0) + (lastPacket?.duration ?? 0);
}
getNumberOfChannels() {
assert(this.demuxer.audioInfo);
return this.demuxer.audioInfo.numberOfChannels;
@@ -413,18 +399,15 @@ class WaveAudioTrackBacking implements InputAudioTrackBacking {
};
}
async getFirstTimestamp() {
return 0;
}
private async getPacketAtIndex(
res: ResultValue<EncodedPacket | null>,
packetIndex: number,
options: PacketRetrievalOptions,
): Promise<EncodedPacket | null> {
): Promise<Yo> {
assert(this.demuxer.audioInfo);
const startOffset = packetIndex * PACKET_SIZE_IN_FRAMES * this.demuxer.audioInfo.blockSizeInBytes;
if (startOffset >= this.demuxer.dataSize) {
return null;
return res.set(null);
}
const sizeInBytes = Math.min(
@@ -441,7 +424,7 @@ class WaveAudioTrackBacking implements InputAudioTrackBacking {
if (slice instanceof Promise) slice = await slice;
if (!slice) {
return null;
return res.set(null);
}
}
@@ -464,21 +447,25 @@ class WaveAudioTrackBacking implements InputAudioTrackBacking {
timestamp,
);
return new EncodedPacket(
return res.set(new EncodedPacket(
data,
'key',
timestamp,
duration,
packetIndex,
sizeInBytes,
);
));
}
getFirstPacket(options: PacketRetrievalOptions) {
return this.getPacketAtIndex(0, options);
getFirstPacket(res: ResultValue<EncodedPacket | null>, options: PacketRetrievalOptions): Promise<Yo> {
return this.getPacketAtIndex(res, 0, options);
}
async getPacket(timestamp: number, options: PacketRetrievalOptions) {
async getPacket(
res: ResultValue<EncodedPacket | null>,
timestamp: number,
options: PacketRetrievalOptions,
): Promise<Yo> {
assert(this.demuxer.audioInfo);
const packetIndex = Math.floor(Math.min(
@@ -486,22 +473,33 @@ class WaveAudioTrackBacking implements InputAudioTrackBacking {
(this.demuxer.dataSize - 1) / (PACKET_SIZE_IN_FRAMES * this.demuxer.audioInfo.blockSizeInBytes),
));
const packet = await this.getPacketAtIndex(packetIndex, options);
if (packet) {
return packet;
const result = new ResultValue<EncodedPacket | null>();
let promise = this.getPacketAtIndex(result, packetIndex, options);
if (result.pending) await promise;
if (result.value) {
return res.set(result.value);
}
if (packetIndex === 0) {
return null; // Empty data chunk
return res.set(null); // Empty data chunk
}
assert(this.demuxer.reader.fileSize === null);
// The file is shorter than we thought, meaning the packet we were looking for doesn't exist. So, let's find
// the last packet by doing a sequential scan, instead.
let currentPacket = await this.getPacketAtIndex(this.demuxer.lastKnownPacketIndex, options);
result.reset();
promise = this.getPacketAtIndex(result, this.demuxer.lastKnownPacketIndex, options);
if (result.pending) await promise;
let currentPacket = result.value;
while (currentPacket) {
const nextPacket = await this.getNextPacket(currentPacket, options);
result.reset();
promise = this.getNextPacket(result, currentPacket, options);
if (result.pending) await promise;
const nextPacket = result.value;
if (!nextPacket) {
break;
}
@@ -509,21 +507,33 @@ class WaveAudioTrackBacking implements InputAudioTrackBacking {
currentPacket = nextPacket;
}
return currentPacket;
return res.set(currentPacket);
}
getNextPacket(packet: EncodedPacket, options: PacketRetrievalOptions) {
getNextPacket(
res: ResultValue<EncodedPacket | null>,
packet: EncodedPacket,
options: PacketRetrievalOptions,
): Promise<Yo> {
assert(this.demuxer.audioInfo);
const packetIndex = Math.round(packet.timestamp * this.demuxer.audioInfo.sampleRate / PACKET_SIZE_IN_FRAMES);
return this.getPacketAtIndex(packetIndex + 1, options);
return this.getPacketAtIndex(res, packetIndex + 1, options);
}
getKeyPacket(timestamp: number, options: PacketRetrievalOptions) {
return this.getPacket(timestamp, options);
getKeyPacket(
res: ResultValue<EncodedPacket | null>,
timestamp: number,
options: PacketRetrievalOptions,
): Promise<Yo> {
return this.getPacket(res, timestamp, options);
}
getNextKeyPacket(packet: EncodedPacket, options: PacketRetrievalOptions) {
return this.getNextPacket(packet, options);
getNextKeyPacket(
res: ResultValue<EncodedPacket | null>,
packet: EncodedPacket,
options: PacketRetrievalOptions,
): Promise<Yo> {
return this.getNextPacket(res, packet, options);
}
}
+51 -19
View File
@@ -3,7 +3,7 @@ import { Input } from '../../src/input.js';
import { BufferSource, FilePathSource } from '../../src/source.js';
import path from 'node:path';
import fs from 'node:fs';
import { ALL_FORMATS, MP3, MP4, QTFF, WEBM } from '../../src/input-format.js';
import { ALL_FORMATS, MP3, MP4, QTFF, WAVE, WEBM } from '../../src/input-format.js';
import { PacketReader } from '../../src/cursors.js';
import { InputAudioTrack, InputTrack } from '../../src/input-track.js';
import { assert } from '../../src/misc.js';
@@ -47,6 +47,24 @@ const testBasicPacketReading = async (track: InputTrack) => {
expect(afterLast).toBe(null);
};
const testSyncPacketReading = (track: InputTrack) => {
const reader = new PacketReader(track);
let current = reader.readFirst() as EncodedPacket | null;
expect(current).toBeInstanceOf(EncodedPacket);
let count = 0;
while (current) {
current = reader.readNext(current) as EncodedPacket | null;
expect(current instanceof EncodedPacket || current === null).toBe(true);
count++;
}
expect(count).toBeGreaterThan(0);
return count;
};
test('Regular ISOBMFF demuxing', async () => {
using input = new Input({
source: new FilePathSource(path.join(__dirname, '../public/trim-buck-bunny.mov')),
@@ -177,24 +195,6 @@ test('MP3 demuxing', async () => {
expect(duration).toBeGreaterThan(0);
});
const testSyncPacketReading = (track: InputTrack) => {
const reader = new PacketReader(track);
let current = reader.readFirst() as EncodedPacket | null;
expect(current).toBeInstanceOf(EncodedPacket);
let count = 0;
while (current) {
current = reader.readNext(current) as EncodedPacket | null;
expect(current instanceof EncodedPacket || current === null).toBe(true);
count++;
}
expect(count).toBeGreaterThan(0);
return count;
};
test('MP3 sync reading', async () => {
using input = new Input({
source: new BufferSource(fs.readFileSync(path.join(__dirname, '../public/AudacityTest1.mp3'))),
@@ -207,3 +207,35 @@ test('MP3 sync reading', async () => {
const count = testSyncPacketReading(audioTrack);
expect(count).toBe(475);
});
test('WAVE demuxing', async () => {
using input = new Input({
source: new FilePathSource(path.join(__dirname, '../public/glitch-hop-is-dead.wav')),
formats: ALL_FORMATS,
});
expect(await input.getFormat()).toBe(WAVE);
const tracks = await input.getTracks();
expect(tracks).toHaveLength(1);
const audioTrack = await input.getPrimaryAudioTrack();
assert(audioTrack);
await testBasicPacketReading(audioTrack);
expect(await input.computeDuration()).toBeCloseTo(9.637188208616779);
});
test('WAVE sync reading', async () => {
using input = new Input({
source: new BufferSource(fs.readFileSync(path.join(__dirname, '../public/glitch-hop-is-dead.wav'))),
formats: ALL_FORMATS,
});
const audioTrack = await input.getPrimaryAudioTrack();
assert(audioTrack);
const count = testSyncPacketReading(audioTrack);
expect(count).toBe(208);
});