/** * The Xing/Info header, and why an MP3 needs one. * * A Layer III stream has no container and no header describing the whole file. * Duration has to be guessed from the bitrate, seeking has to be guessed from * byte position, and — the part that is actually audible — there is no way to * say where the audio really starts and stops. * * That last one matters because MP3 cannot represent an arbitrary number of * samples. Frames hold exactly 1152 each, so any real recording gets padded to * the next frame boundary, and every encoder's filterbank prepends silence * before the first sample emerges. Decoded naively, a file gains silence at both * ends. Play two such files back to back and the join is audible: this is the * gap in "gapless". * * The fix is a convention rather than a standard: a first frame that codes no * audio and instead carries a tag with the frame count, the byte count, and the * exact number of samples to drop from each end. Decoders that recognise it skip * the frame entirely; decoders that do not simply decode 1152 samples of silence * and are none the worse. * * ## Naming the encoder honestly * * The delay and padding fields live in the "LAME tag" extension, and decoders * gate on the 9-byte encoder string that precedes them — many accept only * `LAME`, `Lavc` or `Lavf`. Writing one of those would buy wider gapless support * by claiming to be software this is not, so this writes `audiobox`. The cost is * real and worth stating plainly: decoders that check the name against a * whitelist will ignore our delay and padding and decode a fraction of a second * of extra silence at each end. The audio itself is unaffected, and audiobox * reads its own tag, so `decode(encode(x))` is sample-exact. */ import type { ChannelMode } from './frame.js'; /** The 9-byte encoder string. Exactly 9 bytes, space-padded. */ export declare const ENCODER_STRING = "audiobox "; export interface InfoTagParameters { bitrateKbps: number; sampleRate: number; channels: number; mode: ChannelMode; modeExtension: number; /** Audio frames that follow, not counting this one. */ frameCount: number; /** Total stream length in bytes, including this frame. */ streamBytes: number; /** Samples of priming silence before the first real sample. */ encoderDelay: number; /** Samples of silence after the last real sample. */ encoderPadding: number; /** Variable bitrate: tag as `Xing` and carry a seek table. */ vbr?: boolean; /** The assembled audio frames, needed to build the seek table. */ frames?: Uint8Array; /** Size of this tag frame, since it precedes `frames` in the stream. */ tagBytes?: number; } /** * Builds the complete leading frame, header and tag together. * * `Info` rather than `Xing` because the stream is constant-bitrate; the two are * the same structure, and the name is how a decoder tells CBR from VBR without * scanning. No seek table: it only helps VBR, where byte position and time are * not proportional. */ export declare function writeInfoTag(params: InfoTagParameters): Uint8Array; /** Bytes the tag frame occupies, so the caller can size the stream first. */ export declare function infoTagFrameLength(bitrateKbps: number, sampleRate: number, channels: number, vbr?: boolean): number;