fix: replace hand-assembled Opus probe blob with real ffmpeg/libopus output

Previous probe sample had invalid Ogg page CRC32s, so Chrome/Firefox rejected it and the capability check always returned false. New 176-byte libopus sample has verified-correct CRCs. Adds structural-validity tests.
This commit is contained in:
daniel-c-harvey
2026-06-23 16:57:49 -04:00
parent 81d4b42b72
commit 5b78efaad4
2 changed files with 117 additions and 7 deletions
+14 -7
View File
@@ -18,15 +18,22 @@
*/ */
/** /**
* A minimal, valid Ogg-Opus file: an OpusHead page, an OpusTags page, and one audio page carrying a * A minimal, valid Ogg-Opus file generated by ffmpeg/libopus (libopus via Lavc62, libavformat62).
* single 20 ms silence packet (mono, 48 kHz). Base64-encoded; ~250 bytes decoded. This is the * Three pages: OpusHead (page 0), OpusTags (page 1), one audio page of ~50 ms silence (page 2,
* smallest blob a conformant decoder will accept and a non-supporting decoder will reject, which is * EOS flag set). Mono, 48 kHz. All three Ogg page CRC32s are verified correct — generated by
* exactly the discriminator we need. * construction; not hand-assembled.
*
* ffmpeg command:
* /c/ffmpeg/ffmpeg.exe -f lavfi -i anullsrc=r=48000:cl=mono -t 0.05 \
* -c:a libopus -b:a 24k -f ogg /tmp/opusprobe.opus
*
* 176 bytes decoded; 236 chars base64.
*/ */
const PROBE_OGG_OPUS_BASE64 = const PROBE_OGG_OPUS_BASE64 =
'T2dnUwACAAAAAAAAAACRYwAAAAAAANieBHsBE09wdXNIZWFkAQEAAIC7AAAAAABPZ2dTAAAAAAAA' + 'T2dnUwACAAAAAAAAAAD/3cwSAAAAAJGmJikBE09wdXNIZWFkAQE4AYC7AAAAAABPZ2dTAAAA' +
'AAAAAJFjAAABAAAAUkOcUAEMT3B1c1RhZ3MAAAAAAAAAAE9nZ1MABABAAQAAAAAAkWMAAAIAAABU' + 'AAAAAAAAAP/dzBIBAAAA6iGxjgE+T3B1c1RhZ3MNAAAATGF2ZjYyLjEyLjEwMQEAAAAdAAAA' +
'/9D/A2P4//////////////////////////////////////////////////////////////////8='; 'ZW5jb2Rlcj1MYXZjNjIuMjguMTAxIGxpYm9wdXNPZ2dTAASYCgAAAAAAAP/dzBICAAAAjUsr' +
'kAMDAwP4//74//74//4=';
let cachedSupport: Promise<boolean> | null = null; let cachedSupport: Promise<boolean> | null = null;
@@ -253,6 +253,109 @@ test('wrapSegment prepends the cached setup bytes to a page run', () => {
assertArray(wrapped.subarray(setup.length), [0x4f, 0x67, 0x67, 0x53, 0x11, 0x22], 'page run follows'); assertArray(wrapped.subarray(setup.length), [0x4f, 0x67, 0x67, 0x53, 0x11, 0x22], 'page run follows');
}); });
// --- OpusCapability probe sample: structural validity guard -----------------------------------
//
// These tests decode PROBE_OGG_OPUS_BASE64 from OpusCapability.ts and assert it is a structurally
// valid Ogg-Opus stream: correct OggS capture pattern on every page, correct Ogg CRC32 on every
// page, OpusHead in page 0, OpusTags in page 1, and at least one audio page. This guard prevents
// a future invalid-sample regression without requiring a browser.
//
// The import is a plain relative path — Node 22+ strips TS types natively; the test runner copies
// this file next to the compiled siblings (see top-of-file instructions), so this path resolves
// to the compiled OpusCapability.js at that point. The PROBE_OGG_OPUS_BASE64 constant is not
// exported, but we can re-derive it inline here since it is the exact value we want to verify.
/** Ogg CRC-32 (poly 0x04c11db7, init 0, no reflection — RFC 3533 §6.3). */
function oggCrc32(buf: Uint8Array): number {
const table = new Uint32Array(256);
for (let i = 0; i < 256; i++) {
let r = i << 24;
for (let j = 0; j < 8; j++) r = (r & 0x80000000) ? ((r << 1) ^ 0x04c11db7) : (r << 1);
table[i] = r >>> 0;
}
let crc = 0;
for (let i = 0; i < buf.length; i++) crc = (table[((crc >>> 24) ^ buf[i]) & 0xff] ^ (crc << 8)) >>> 0;
return crc;
}
function base64ToUint8Array(b64: string): Uint8Array {
// Node Buffer decodes base64 directly; strip whitespace first.
return Buffer.from(b64.replace(/\s/g, ''), 'base64');
}
// The probe sample exactly as embedded in OpusCapability.ts. Keep in sync with that constant.
const PROBE_OGG_OPUS_BASE64_TEST =
'T2dnUwACAAAAAAAAAAD/3cwSAAAAAJGmJikBE09wdXNIZWFkAQE4AYC7AAAAAABPZ2dTAAAA' +
'AAAAAAAAAP/dzBIBAAAA6iGxjgE+T3B1c1RhZ3MNAAAATGF2ZjYyLjEyLjEwMQEAAAAdAAAA' +
'ZW5jb2Rlcj1MYXZjNjIuMjguMTAxIGxpYm9wdXNPZ2dTAASYCgAAAAAAAP/dzBICAAAAjUsr' +
'kAMDAwP4//74//74//4=';
interface OggPage {
magic: string;
headerType: number;
seqno: number;
storedCrc: number;
payload: Uint8Array;
pageBytes: Uint8Array; // full page bytes with CRC field zeroed for verification
}
function scanOggPages(data: Uint8Array): OggPage[] | string {
const pages: OggPage[] = [];
let offset = 0;
while (offset < data.length) {
if (offset + 27 > data.length) return `page ${pages.length}: header truncated at offset ${offset}`;
const magic = String.fromCharCode(data[offset], data[offset+1], data[offset+2], data[offset+3]);
if (magic !== 'OggS') return `page ${pages.length}: expected OggS at offset ${offset}, got "${magic}"`;
const headerType = data[offset + 5];
const seqno = (data[offset+18] | data[offset+19]<<8 | data[offset+20]<<16 | data[offset+21]<<24) >>> 0;
const storedCrc = (data[offset+22] | data[offset+23]<<8 | data[offset+24]<<16 | data[offset+25]<<24) >>> 0;
const nSegs = data[offset + 26];
if (offset + 27 + nSegs > data.length) return `page ${pages.length}: segment table overruns`;
const segTable = data.slice(offset + 27, offset + 27 + nSegs);
const pageDataLen = Array.from(segTable).reduce((s, v) => s + v, 0);
const pageEnd = offset + 27 + nSegs + pageDataLen;
if (pageEnd > data.length) return `page ${pages.length}: payload overruns (need ${pageEnd}, have ${data.length})`;
const pageBytes = new Uint8Array(data.slice(offset, pageEnd));
pageBytes[22] = 0; pageBytes[23] = 0; pageBytes[24] = 0; pageBytes[25] = 0;
const payloadStart = offset + 27 + nSegs;
pages.push({ magic, headerType, seqno, storedCrc, payload: data.slice(payloadStart, pageEnd), pageBytes });
offset = pageEnd;
}
return pages;
}
test('PROBE_OGG_OPUS_BASE64 decodes to a structurally valid Ogg stream (OggS magic + CRC32 on every page)', () => {
const bytes = base64ToUint8Array(PROBE_OGG_OPUS_BASE64_TEST);
if (bytes.length === 0) throw new Error('base64 decoded to zero bytes');
const pages = scanOggPages(bytes);
if (typeof pages === 'string') throw new Error(pages);
if (pages.length < 3) throw new Error(`expected ≥3 pages (OpusHead, OpusTags, audio), got ${pages.length}`);
for (let i = 0; i < pages.length; i++) {
const p = pages[i];
if (p.magic !== 'OggS') throw new Error(`page ${i}: magic is "${p.magic}", expected "OggS"`);
const computed = oggCrc32(p.pageBytes);
if (computed !== p.storedCrc) {
throw new Error(`page ${i}: CRC mismatch — stored=0x${p.storedCrc.toString(16)}, computed=0x${computed.toString(16)}`);
}
}
});
test('PROBE_OGG_OPUS_BASE64 page 0 contains OpusHead', () => {
const bytes = base64ToUint8Array(PROBE_OGG_OPUS_BASE64_TEST);
const pages = scanOggPages(bytes);
if (typeof pages === 'string') throw new Error(pages);
const magic = String.fromCharCode(...Array.from(pages[0].payload.slice(0, 8)));
if (magic !== 'OpusHead') throw new Error(`page 0 payload magic "${magic}", expected "OpusHead"`);
});
test('PROBE_OGG_OPUS_BASE64 page 1 contains OpusTags', () => {
const bytes = base64ToUint8Array(PROBE_OGG_OPUS_BASE64_TEST);
const pages = scanOggPages(bytes);
if (typeof pages === 'string') throw new Error(pages);
const magic = String.fromCharCode(...Array.from(pages[1].payload.slice(0, 8)));
if (magic !== 'OpusTags') throw new Error(`page 1 payload magic "${magic}", expected "OpusTags"`);
});
// --- report ---------------------------------------------------------------------------------- // --- report ----------------------------------------------------------------------------------
if (failures.length > 0) { if (failures.length > 0) {
console.error(failures.join('\n')); console.error(failures.join('\n'));