/** * Opus in Ogg as a bake is kept (#340): the pages around the packets, their checksums, where each * page says it has got to, and the pre-skip and length a decoder trims by. */ import { describe, expect, it } from 'vitest'; import { OggOpusReader, oggCrc, oggOpus, opusHead, opusPacketSamples, preSkipOf, readOggOpus, } from '../../src/core/music/ogg'; const packet = (size: number, fill: number) => ({ data: new Uint8Array(size).fill(fill), samples: 960 }); /** The flags and the granule position of every page, in order. */ function pagesOf(file: Uint8Array): { flags: number; granule: number }[] { const view = new DataView(file.buffer); const pages: { flags: number; granule: number }[] = []; for (let at = 0; at < file.length;) { pages.push({ flags: file[at + 5]!, granule: view.getUint32(at + 6, true) }); const count = file[at + 26]!; at += 27 + count + file.subarray(at + 27, at + 27 + count).reduce((s, v) => s + v, 0); } return pages; } describe('Opus in Ogg', () => { it('checks a page with the CRC Ogg uses', () => { expect(oggCrc(Uint8Array.from('123456789', (c) => c.charCodeAt(0)))).toBe(0x89a1897f); }); it('reads back the head, the packets and the length it was given', () => { const packets = [packet(3, 1), packet(255, 2), packet(600, 3), packet(0, 4)]; const file = oggOpus({ channels: 2, preSkip: 312, frames: 3000, packets }); const back = readOggOpus(file); expect(back.channels).toBe(2); expect(back.preSkip).toBe(312); expect(back.granule).toBe(312 + 3000); expect(back.packets.map((p) => [p.length, p[0] ?? -1])).toEqual([ [3, 1], [255, 2], [600, 3], [0, -1], ]); }); it('opens with the head alone, flags the first and the last page', () => { const file = oggOpus({ channels: 1, preSkip: 312, frames: 960, packets: [packet(10, 1)] }); expect(String.fromCharCode(...file.subarray(28, 36))).toBe('OpusHead'); expect(pagesOf(file).map((page) => page.flags)).toEqual([0x02, 0x00, 0x04]); }); it('starts a new page before 255 lacing values, each counting the samples finished on it', () => { const packets = Array.from({ length: 300 }, (_, i) => packet(100, i % 256)); const file = oggOpus({ channels: 2, preSkip: 312, frames: 300 * 960 - 500, packets }); const back = readOggOpus(file); expect(back.packets).toHaveLength(300); expect(back.pages).toBe(4); expect(pagesOf(file).map((page) => page.granule)).toEqual([0, 0, 255 * 960, 312 + 300 * 960 - 500]); }); it('refuses a page whose bytes were changed', () => { const file = oggOpus({ channels: 2, preSkip: 312, frames: 960, packets: [packet(20, 7)] }); file[file.length - 3] = file[file.length - 3]! ^ 0xff; expect(() => readOggOpus(file)).toThrow(/checksum/); }); it('reads the pre-skip of a head an encoder hands out', () => { expect(preSkipOf(opusHead(2, 3840))).toBe(3840); expect(preSkipOf(new Uint8Array(19))).toBeNull(); }); }); describe('Opus in Ogg as it comes in (#349)', () => { /** A CELT packet of 20 ms at full band: config 31, one frame. */ const celt = (size: number, fill: number) => { const data = new Uint8Array(size).fill(fill); data[0] = 31 << 3; return { data, samples: 960 }; }; const file = oggOpus({ channels: 2, preSkip: 312, frames: 300 * 960 - 312, packets: Array.from({ length: 300 }, (_, i) => celt(100 + (i % 300), i % 256)), }); it('reads a file pushed in pieces as it reads it whole, each packet placed where it starts', () => { const reader = new OggOpusReader(); for (let at = 0, size = 1; at < file.length; at += size, size = (size * 7 + 3) % 4000) { reader.push(file.subarray(at, at + size)); } const whole = readOggOpus(file); expect(reader.packets.map((p) => p.data)).toEqual(whole.packets); expect(reader.packets.slice(0, 3).map((p) => p.at)).toEqual([0, 960, 1920]); expect(reader.packets.at(-1)!.at).toBe(299 * 960); expect([reader.channels, reader.preSkip, reader.ended]).toEqual([2, 312, true]); }); it('holds a page back until all of it is there', () => { const reader = new OggOpusReader(); reader.push(file.subarray(0, 4000)); const seen = reader.packets.length; expect(reader.ended).toBe(false); reader.push(file.subarray(4000)); expect(reader.packets.length).toBeGreaterThan(seen); expect(reader.packets).toHaveLength(300); }); it('counts the samples of a packet from its TOC byte', () => { expect(opusPacketSamples(Uint8Array.of(31 << 3))).toBe(960); expect(opusPacketSamples(Uint8Array.of((28 << 3) | 1))).toBe(240); expect(opusPacketSamples(Uint8Array.of((3 << 3) | 2))).toBe(2 * 2880); expect(opusPacketSamples(Uint8Array.of((13 << 3) | 3, 3))).toBe(3 * 960); expect(opusPacketSamples(Uint8Array.of(12 << 3))).toBe(480); expect(opusPacketSamples(new Uint8Array(0))).toBe(0); }); });