import { expect, test } from 'vitest'; import path from 'node:path'; import { Input } from '../../src/input.js'; import { BufferSource, FilePathSource } from '../../src/source.js'; import { ADTS, ALL_FORMATS } from '../../src/input-format.js'; import { EncodedPacketSink } from '../../src/media-sink.js'; import { Output } from '../../src/output.js'; import { BufferTarget } from '../../src/target.js'; import { MkvOutputFormat } from '../../src/output-format.js'; import { Conversion } from '../../src/conversion.js'; import { assert } from '../../src/misc.js'; import { EncodedVideoPacketSource } from '../../src/media-source.js'; import { EncodedPacket } from '../../src/packet.js'; const __dirname = new URL('.', import.meta.url).pathname; test('Matroska muxer internally converts ADTS to AAC', async () => { using input = new Input({ source: new FilePathSource(path.join(__dirname, '../public/sample3.aac')), formats: ALL_FORMATS, }); expect(await input.getFormat()).toBe(ADTS); const inputTrack = await input.getPrimaryAudioTrack(); assert(inputTrack); const inputDecoderConfig = await inputTrack.getDecoderConfig(); expect(inputDecoderConfig!.description).toBeUndefined(); // ADTS input has no description const output = new Output({ format: new MkvOutputFormat(), target: new BufferTarget(), }); const conversion = await Conversion.init({ input, output, showWarnings: false }); await conversion.execute(); using outputAsInput = new Input({ source: new BufferSource(output.target.buffer!), formats: ALL_FORMATS, }); const outputTrack = await outputAsInput.getPrimaryAudioTrack(); assert(outputTrack); expect(await outputTrack.getCodec()).toBe('aac'); expect(await outputTrack.getSampleRate()).toBe(await inputTrack.getSampleRate()); expect(await outputTrack.getNumberOfChannels()).toBe(await inputTrack.getNumberOfChannels()); const outputDecoderConfig = await outputTrack.getDecoderConfig(); expect(outputDecoderConfig!.description).toBeDefined(); const outputSink = new EncodedPacketSink(outputTrack); let count = 0; for await (const packet of outputSink.packets()) { // Packets should NOT be ADTS frames (should not start with 0xFFF sync word) const isAdts = packet.data[0] === 0xff && (packet.data[1]! & 0xf0) === 0xf0; expect(isAdts).toBe(false); count++; } expect(count).toBe(4557); }); test('Negative start timestamps', async () => { await testNegativeTimestampRoundTrip( Array.from({ length: 50 }, (_, index) => (index - 10) / 10), 0.1, 10, ); }); test('Wholly negative timestamps', async () => { await testNegativeTimestampRoundTrip([-1, -0.9, -0.8, -0.7, -0.6], 0.1, 10); }); const testNegativeTimestampRoundTrip = async (timestamps: number[], duration: number, frameRate: number) => { const output = new Output({ format: new MkvOutputFormat(), target: new BufferTarget(), }); const source = new EncodedVideoPacketSource('vp8'); output.addVideoTrack(source, { frameRate }); await output.start(); const meta = { decoderConfig: { codec: 'vp8', codedWidth: 1280, codedHeight: 720 } }; const inputPackets = timestamps.map((timestamp, index) => new EncodedPacket( new Uint8Array(1024).fill(index), 'key', timestamp, duration, )); for (let i = 0; i < inputPackets.length; i++) { await source.add(inputPackets[i]!, i === 0 ? meta : undefined); } await output.finalize(); using input = new Input({ source: new BufferSource(output.target.buffer!), formats: ALL_FORMATS, }); const track = await input.getPrimaryVideoTrack(); assert(track); const sink = new EncodedPacketSink(track); const outputPackets: EncodedPacket[] = []; for await (const packet of sink.packets()) { outputPackets.push(packet); } expect(outputPackets.map(packet => ({ timestamp: packet.timestamp, duration: packet.duration, }))).toEqual(inputPackets.map(packet => ({ timestamp: packet.timestamp, duration: packet.duration, }))); for (const inputPacket of inputPackets) { const outputPacket = await sink.getPacket(inputPacket.timestamp); assert(outputPacket); expect({ timestamp: outputPacket.timestamp, duration: outputPacket.duration, }).toEqual({ timestamp: inputPacket.timestamp, duration: inputPacket.duration, }); } };