mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-30 20:33:55 +02:00
123 lines
3.0 KiB
TypeScript
123 lines
3.0 KiB
TypeScript
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 };
|
||
};
|