From b2c86ec4bafcef49d9ac32e6b36a6134951569dd Mon Sep 17 00:00:00 2001 From: Vanilagy <1696106+Vanilagy@users.noreply.github.com> Date: Sun, 21 Dec 2025 19:59:42 +0100 Subject: [PATCH] Migrate WAVE demuxer to new packet system --- src/misc.ts | 4 ++ src/wave/wave-demuxer.ts | 90 ++++++++++++++++++++++------------------ test/node/demux.test.ts | 70 ++++++++++++++++++++++--------- 3 files changed, 105 insertions(+), 59 deletions(-) diff --git a/src/misc.ts b/src/misc.ts index cfdc23a..0a6377a 100644 --- a/src/misc.ts +++ b/src/misc.ts @@ -885,6 +885,10 @@ export class ResultValue { this.value = value; this.pending = false; } + + reset() { + this.pending = true; + } } export class AsyncMutex2 { diff --git a/src/wave/wave-demuxer.ts b/src/wave/wave-demuxer.ts index 56b5db5..d1aab5e 100644 --- a/src/wave/wave-demuxer.ts +++ b/src/wave/wave-demuxer.ts @@ -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, packetIndex: number, options: PacketRetrievalOptions, - ): Promise { + ): Promise { 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, options: PacketRetrievalOptions): Promise { + return this.getPacketAtIndex(res, 0, options); } - async getPacket(timestamp: number, options: PacketRetrievalOptions) { + async getPacket( + res: ResultValue, + timestamp: number, + options: PacketRetrievalOptions, + ): Promise { 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(); + 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, + packet: EncodedPacket, + options: PacketRetrievalOptions, + ): Promise { 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, + timestamp: number, + options: PacketRetrievalOptions, + ): Promise { + return this.getPacket(res, timestamp, options); } - getNextKeyPacket(packet: EncodedPacket, options: PacketRetrievalOptions) { - return this.getNextPacket(packet, options); + getNextKeyPacket( + res: ResultValue, + packet: EncodedPacket, + options: PacketRetrievalOptions, + ): Promise { + return this.getNextPacket(res, packet, options); } } diff --git a/test/node/demux.test.ts b/test/node/demux.test.ts index 89efc4f..ab135d2 100644 --- a/test/node/demux.test.ts +++ b/test/node/demux.test.ts @@ -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); +});