Files
mediabunny/src/ogg/vorbis.ts
T
2025-02-04 16:59:25 +01:00

123 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { readBits } from '../misc';
class Bitstream {
bytes: Uint8Array;
pos: number;
constructor(bytes: Uint8Array) {
this.bytes = bytes;
this.pos = 0;
}
read(n: number): number {
const result = readBits(this.bytes, this.pos, this.pos + n);
this.pos += n;
return result;
}
skip(n: number): void {
this.pos += n;
}
getBitsLeft(): number {
return this.bytes.length * 8 - this.pos;
}
getBitCount(): number {
return this.pos;
}
clone(): Bitstream {
const clone = new Bitstream(this.bytes);
clone.pos = this.pos;
return clone;
}
}
// Based on vorbis_parser.c from FFmpeg.
export const parseModesFromSetupPacket = (setupHeader: Uint8Array) => {
// Verify that this is a Setup header.
if (setupHeader.length < 7) {
throw new Error('Setup header is too short.');
}
if (setupHeader[0] !== 5) {
throw new Error('Wrong packet type in Setup header.');
}
const signature = String.fromCharCode(...setupHeader.slice(1, 7));
if (signature !== 'vorbis') {
throw new Error('Invalid packet signature in Setup header.');
}
// Reverse the entire buffer.
const bufSize = setupHeader.length;
const revBuffer = new Uint8Array(bufSize);
for (let i = 0; i < bufSize; i++) {
revBuffer[i] = setupHeader[bufSize - 1 - i]!;
}
// Initialize a Bitstream on the reversed buffer.
const bs = new Bitstream(revBuffer);
// --- Find the framing bit.
// In FFmpeg code, we scan until get_bits1() returns 1.
let gotFramingBit = 0;
while (bs.getBitsLeft() > 97) {
if (bs.read(1) === 1) {
gotFramingBit = bs.getBitCount();
break;
}
}
if (gotFramingBit === 0) {
throw new Error('Invalid Setup header: framing bit not found.');
}
// --- Search backwards for a valid mode header.
// We try to “guess” the number of modes by reading a fixed pattern.
let modeCount = 0;
let gotModeHeader = false;
let lastModeCount = 0;
while (bs.getBitsLeft() >= 97) {
const tempPos = bs.pos;
const a = bs.read(8);
const b = bs.read(16);
const c = bs.read(16);
// If a > 63 or b or c nonzero, assume we’ve gone too far.
if (a > 63 || b !== 0 || c !== 0) {
bs.pos = tempPos;
break;
}
bs.skip(1);
modeCount++;
if (modeCount > 64)
break;
const bsClone = bs.clone();
const candidate = bsClone.read(6) + 1;
if (candidate === modeCount) {
gotModeHeader = true;
lastModeCount = modeCount;
}
}
if (!gotModeHeader) {
throw new Error('Invalid Setup header: mode header not found.');
}
if (lastModeCount > 63) {
throw new Error(`Unsupported mode count: ${lastModeCount}.`);
}
const finalModeCount = lastModeCount;
// --- Reinitialize the bitstream.
bs.pos = 0;
// Skip the bits up to the found framing bit.
bs.skip(gotFramingBit);
// --- Now read, for each mode (in reverse order), 40 bits then one bit.
// That one bit is the mode blockflag.
const modeBlockflags = Array(finalModeCount).fill(0) as number[];
for (let i = finalModeCount - 1; i >= 0; i--) {
bs.skip(40);
modeBlockflags[i] = bs.read(1);
}
return { modeBlockflags };
};