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.value = value;
this.pending = false; this.pending = false;
} }
reset() {
this.pending = true;
}
} }
export class AsyncMutex2 { export class AsyncMutex2 {
+50 -40
View File
@@ -12,7 +12,7 @@ import { Input } from '../input';
import { InputAudioTrack, InputAudioTrackBacking } from '../input-track'; import { InputAudioTrack, InputAudioTrackBacking } from '../input-track';
import { PacketRetrievalOptions } from '../media-sink'; import { PacketRetrievalOptions } from '../media-sink';
import { DEFAULT_TRACK_DISPOSITION, MetadataTags } from '../metadata'; 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 { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
import { readAscii, readBytes, Reader, readU16, readU32, readU64 } from '../reader'; import { readAscii, readBytes, Reader, readU16, readU32, readU64 } from '../reader';
import { parseId3V2Tag, readId3V2Header } from '../id3'; import { parseId3V2Tag, readId3V2Header } from '../id3';
@@ -327,15 +327,6 @@ export class WaveDemuxer extends Demuxer {
return 'audio/wav'; return 'audio/wav';
} }
async computeDuration() {
await this.readMetadata();
const track = this.tracks[0];
assert(track);
return track.computeDuration();
}
async getTracks() { async getTracks() {
await this.readMetadata(); await this.readMetadata();
return this.tracks; 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() { getNumberOfChannels() {
assert(this.demuxer.audioInfo); assert(this.demuxer.audioInfo);
return this.demuxer.audioInfo.numberOfChannels; return this.demuxer.audioInfo.numberOfChannels;
@@ -413,18 +399,15 @@ class WaveAudioTrackBacking implements InputAudioTrackBacking {
}; };
} }
async getFirstTimestamp() {
return 0;
}
private async getPacketAtIndex( private async getPacketAtIndex(
res: ResultValue<EncodedPacket | null>,
packetIndex: number, packetIndex: number,
options: PacketRetrievalOptions, options: PacketRetrievalOptions,
): Promise<EncodedPacket | null> { ): Promise<Yo> {
assert(this.demuxer.audioInfo); assert(this.demuxer.audioInfo);
const startOffset = packetIndex * PACKET_SIZE_IN_FRAMES * this.demuxer.audioInfo.blockSizeInBytes; const startOffset = packetIndex * PACKET_SIZE_IN_FRAMES * this.demuxer.audioInfo.blockSizeInBytes;
if (startOffset >= this.demuxer.dataSize) { if (startOffset >= this.demuxer.dataSize) {
return null; return res.set(null);
} }
const sizeInBytes = Math.min( const sizeInBytes = Math.min(
@@ -441,7 +424,7 @@ class WaveAudioTrackBacking implements InputAudioTrackBacking {
if (slice instanceof Promise) slice = await slice; if (slice instanceof Promise) slice = await slice;
if (!slice) { if (!slice) {
return null; return res.set(null);
} }
} }
@@ -464,21 +447,25 @@ class WaveAudioTrackBacking implements InputAudioTrackBacking {
timestamp, timestamp,
); );
return new EncodedPacket( return res.set(new EncodedPacket(
data, data,
'key', 'key',
timestamp, timestamp,
duration, duration,
packetIndex, packetIndex,
sizeInBytes, sizeInBytes,
); ));
} }
getFirstPacket(options: PacketRetrievalOptions) { getFirstPacket(res: ResultValue<EncodedPacket | null>, options: PacketRetrievalOptions): Promise<Yo> {
return this.getPacketAtIndex(0, options); 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); assert(this.demuxer.audioInfo);
const packetIndex = Math.floor(Math.min( 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), (this.demuxer.dataSize - 1) / (PACKET_SIZE_IN_FRAMES * this.demuxer.audioInfo.blockSizeInBytes),
)); ));
const packet = await this.getPacketAtIndex(packetIndex, options); const result = new ResultValue<EncodedPacket | null>();
if (packet) { let promise = this.getPacketAtIndex(result, packetIndex, options);
return packet; if (result.pending) await promise;
if (result.value) {
return res.set(result.value);
} }
if (packetIndex === 0) { if (packetIndex === 0) {
return null; // Empty data chunk return res.set(null); // Empty data chunk
} }
assert(this.demuxer.reader.fileSize === null); 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 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. // 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) { 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) { if (!nextPacket) {
break; break;
} }
@@ -509,21 +507,33 @@ class WaveAudioTrackBacking implements InputAudioTrackBacking {
currentPacket = nextPacket; 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); assert(this.demuxer.audioInfo);
const packetIndex = Math.round(packet.timestamp * this.demuxer.audioInfo.sampleRate / PACKET_SIZE_IN_FRAMES); 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) { getKeyPacket(
return this.getPacket(timestamp, options); res: ResultValue<EncodedPacket | null>,
timestamp: number,
options: PacketRetrievalOptions,
): Promise<Yo> {
return this.getPacket(res, timestamp, options);
} }
getNextKeyPacket(packet: EncodedPacket, options: PacketRetrievalOptions) { getNextKeyPacket(
return this.getNextPacket(packet, options); 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 { BufferSource, FilePathSource } from '../../src/source.js';
import path from 'node:path'; import path from 'node:path';
import fs from 'node:fs'; 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 { PacketReader } from '../../src/cursors.js';
import { InputAudioTrack, InputTrack } from '../../src/input-track.js'; import { InputAudioTrack, InputTrack } from '../../src/input-track.js';
import { assert } from '../../src/misc.js'; import { assert } from '../../src/misc.js';
@@ -47,6 +47,24 @@ const testBasicPacketReading = async (track: InputTrack) => {
expect(afterLast).toBe(null); 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 () => { test('Regular ISOBMFF demuxing', async () => {
using input = new Input({ using input = new Input({
source: new FilePathSource(path.join(__dirname, '../public/trim-buck-bunny.mov')), source: new FilePathSource(path.join(__dirname, '../public/trim-buck-bunny.mov')),
@@ -177,24 +195,6 @@ test('MP3 demuxing', async () => {
expect(duration).toBeGreaterThan(0); 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 () => { test('MP3 sync reading', async () => {
using input = new Input({ using input = new Input({
source: new BufferSource(fs.readFileSync(path.join(__dirname, '../public/AudacityTest1.mp3'))), source: new BufferSource(fs.readFileSync(path.join(__dirname, '../public/AudacityTest1.mp3'))),
@@ -207,3 +207,35 @@ test('MP3 sync reading', async () => {
const count = testSyncPacketReading(audioTrack); const count = testSyncPacketReading(audioTrack);
expect(count).toBe(475); 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);
});