From f68e4369eb77475b6542a4814489ed11f7312edc Mon Sep 17 00:00:00 2001 From: alehel Date: Thu, 12 Nov 2026 18:41:44 +0100 Subject: [PATCH 1/5] craft-audio: decode the HE-AAC SBR high band MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit aac::Decoder::set_sbr(true) decodes HE-AAC at twice the core rate with the SBR tool of ISO/IEC 14496-3 §4.6.18: QMF analysis and synthesis, frequency band tables, patching with LPC inverse filtering, envelope adjustment with limiter, boost, noise and sinusoids. TrackDecoder uses it in place of the 2x upsampler. Tables are transcribed from ETSI ES 201 980 Annex I. Matches ffmpeg's decoder on AudioToolbox HE-AAC streams of 1-6 channels: the same 962-sample delay, ~54 dB SNR, high band level within 0.01 dB. Parametric stereo is still not decoded. Refs #1121 Co-Authored-By: Claude Opus 5.5 --- crates/audio/README.md | 13 +- crates/audio/src/aac/decoder.rs | 131 ++++- crates/audio/src/aac/huffman.rs | 6 +- crates/audio/src/aac/mod.rs | 22 +- crates/audio/src/aac/sbr/bitstream.rs | 489 +++++++++++++++++ crates/audio/src/aac/sbr/freq.rs | 353 ++++++++++++ crates/audio/src/aac/sbr/hf.rs | 332 ++++++++++++ crates/audio/src/aac/sbr/mod.rs | 513 ++++++++++++++++++ crates/audio/src/aac/sbr/qmf.rs | 155 ++++++ crates/audio/src/aac/sbr/tables.rs | 357 ++++++++++++ crates/audio/src/track.rs | 48 +- .../audio/tests/codecs/aac/he_aac_oracle.rs | 165 ++++++ crates/audio/tests/codecs/aac/mod.rs | 1 + 13 files changed, 2526 insertions(+), 59 deletions(-) create mode 100644 crates/audio/src/aac/sbr/bitstream.rs create mode 100644 crates/audio/src/aac/sbr/freq.rs create mode 100644 crates/audio/src/aac/sbr/hf.rs create mode 100644 crates/audio/src/aac/sbr/mod.rs create mode 100644 crates/audio/src/aac/sbr/qmf.rs create mode 100644 crates/audio/src/aac/sbr/tables.rs create mode 100644 crates/audio/tests/codecs/aac/he_aac_oracle.rs diff --git a/crates/audio/README.md b/crates/audio/README.md index 30f09ea57..abd3228f4 100644 --- a/crates/audio/README.md +++ b/crates/audio/README.md @@ -309,7 +309,8 @@ match craft_audio::decode(&bytes, None) { | AIFF, AIFF-C | native | native | | FLAC | symphonia | native | | AAC-LC | native | native | -| HE-AAC v1/v2 | native, core only (see [Limits](#limits)) | — | +| HE-AAC v1 | native (SBR) | — | +| HE-AAC v2 | native, without parametric stereo (see [Limits](#limits)) | — | | ALAC | native | native, packets (`alac::Encoder`; no file writer) | | MP1, MP2 (MPEG audio Layer I/II) | native | — | | MP3 (Layer III) | native | native, frames (`mp3::Encoder`; the frames in a row are an `.mp3` file) | @@ -329,10 +330,10 @@ match craft_audio::decode(&bytes, None) { ## Limits -- HE-AAC: the AAC-LC core is decoded. `decode`, `Decoder` and `TrackDecoder` upsample it 2×; - `aac::Decoder` alone outputs the core rate. SBR is not reconstructed, so there is no content - above the core's Nyquist frequency. -- HE-AAC v2: parametric stereo is not reconstructed either, so the mono core decodes as mono. +- HE-AAC: `decode`, `Decoder` and `TrackDecoder` decode SBR at twice the core rate. + `aac::Decoder` outputs the core rate unless `set_sbr(true)` is called. Downsampled SBR (an + output rate equal to the core rate) plays the core alone. +- HE-AAC v2: parametric stereo is not reconstructed, so the mono core decodes as mono, with SBR. - AAC: Main, SSR and LTP profiles, coupling channels and 960-sample frames are unsupported. - Raw ADTS: frames with several raw data blocks are unsupported: a stream of them is refused, and one among other frames decodes as silence of its length and counts as damaged, as does a frame @@ -413,7 +414,7 @@ Each codec module's rustdoc lists the coding tools it implements. |---|---|---|---| | WAV, AIFF | Microsoft RIFF, Apple AIFF/AIFF-C | read: integer 8–32-bit, float 32/64-bit, `WAVE_FORMAT_EXTENSIBLE` masks, RF64/BW64, BWF `bext`, truncated files; write: 16/24/32-bit integer and 32-bit float, RF64 above 4 GiB, any layout, TPDF dither | round trips; hostile-input tests | | FLAC encoder | RFC 9639 | 16, 24, 32 bits; 1–8 channels, layouts decoders would not assume for the count as a `WAVEFORMATEXTENSIBLE_CHANNEL_MASK` comment; up to 655,350 Hz; LPC, stereo decorrelation, wasted bits, STREAMINFO MD5 | bit-exact through ffmpeg, STREAMINFO MD5 checked against ffmpeg | -| AAC (`aac`) | ISO/IEC 14496-3 | AAC-LC decode and encode (CBR, VBR, ADTS), 1–8 channels; HE-AAC: see [Limits](#limits) | ffmpeg: ~135 dB SNR; PNS noise differs per sample at equal band energy | +| AAC (`aac`) | ISO/IEC 14496-3; SBR tables from ETSI ES 201 980 Annex I | AAC-LC decode and encode (CBR, VBR, ADTS), 1–8 channels; HE-AAC (SBR) decode, see [Limits](#limits) | ffmpeg: ~135 dB SNR; PNS noise differs per sample at equal band energy. HE-AAC from AudioToolbox, 1–6 channels: same 962-sample delay as ffmpeg, ~54 dB SNR, high band level within 0.01 dB | | Opus (`opus`) | RFC 6716, 8251, 7845 | decoder: SILK, hybrid, CELT, all frame sizes, PLC and FEC, multistream (`Decoder`) and single stream (`StreamDecoder`), 8–48 kHz output | all 36 RFC 8251 conformance cases | | AC-3/E-AC-3 (`ac3`) | decoder: ATSC A/52:2012; encoder/programme API: ETSI TS 102 366 V1.4.1 | single-syncframe and complete-programme decoding; packet encoding through discrete 7.1 (see below) | decoder comparisons and encoder output through ffmpeg, CRC/channel/timing/bit-budget regressions; synthetic quality limitations below | | MP1/MP2 (`mpa`) | ISO/IEC 11172-3, 13818-3 | Layer I and II, MPEG-1/2/2.5, 8–48 kHz; also the Layer II rate/mode pairs the spec disallows (ffmpeg's default mono 384 kbit/s); no free format | ffmpeg-encoded streams, 73–84 dB SNR | diff --git a/crates/audio/src/aac/decoder.rs b/crates/audio/src/aac/decoder.rs index 8c4d41300..1d8713f54 100644 --- a/crates/audio/src/aac/decoder.rs +++ b/crates/audio/src/aac/decoder.rs @@ -6,6 +6,7 @@ use crate::aac::config::{AudioSpecificConfig, ElementType, ProgramConfig}; use crate::aac::huffman::{self, INTENSITY_HCB, INTENSITY_HCB2, NOISE_HCB, ZERO_HCB}; use crate::aac::ics::{IcsInfo, WindowSequence}; use crate::aac::mdct::{MdctScratch, WindowShape, mdct_long, mdct_short, window}; +use crate::aac::sbr; use crate::aac::tables::{pow43, pow43_table, sf_gain}; use crate::aac::tns::{self, TnsData}; use crate::aac::{Error, Result}; @@ -63,7 +64,14 @@ impl Scratch { } } -/// AAC-LC decoder. +/// Where the SBR data of a channel element sits in its access unit: the bit range of the fill +/// element's payload after its extension type, and whether a CRC leads it. +type SbrRef = (usize, usize, bool); + +/// An access unit's core output, and the SBR data of each layout entry. +type Decoded = (Vec>, Vec>); + +/// AAC-LC decoder, with the SBR tool of HE-AAC when [`Decoder::set_sbr`] turns it on. #[derive(Clone)] pub struct Decoder { asc: AudioSpecificConfig, @@ -75,6 +83,8 @@ pub struct Decoder { scratch: Scratch, /// A fill element carried SBR data (implicitly signalled HE-AAC). sbr_seen: bool, + /// The SBR decoder of each layout entry, when output is at twice the core rate. + sbr_out: Option>, } impl Decoder { @@ -110,6 +120,7 @@ impl Decoder { noise_state: NOISE_SEED, scratch: Scratch::new(), sbr_seen: false, + sbr_out: None, }; d.set_layout(layout); Ok(d) @@ -131,16 +142,27 @@ impl Decoder { pub fn config(&self) -> &AudioSpecificConfig { &self.asc } - /// Output sampling rate in Hz: the AAC-LC core rate. + /// Output sampling rate in Hz: the AAC-LC core rate, or twice it with [`Self::set_sbr`] on. pub fn sample_rate(&self) -> u32 { - self.asc.sample_rate + if self.sbr_out.is_some() { self.asc.sample_rate.saturating_mul(2) } else { self.asc.sample_rate } } /// Whether the stream is HE-AAC: SBR signalled in the `AudioSpecificConfig` (explicit) or SBR - /// data seen in a fill element of a decoded access unit (implicit). Only the AAC-LC core is - /// decoded, at [`Self::sample_rate`]; the SBR high band is not reconstructed. + /// data seen in a fill element of a decoded access unit (implicit). Without [`Self::set_sbr`], + /// only the AAC-LC core is decoded. pub fn sbr(&self) -> bool { self.asc.sbr || self.sbr_seen } + /// Decode HE-AAC at twice the core rate (`true`) or the AAC-LC core alone (`false`, the + /// default). With SBR on, [`Self::decode`] returns 2048 samples per channel: the core through + /// the SBR filter banks, delayed by [`SBR_DELAY`](crate::aac::SBR_DELAY) samples, with the high + /// band rebuilt from each frame's SBR data. Until the stream's first SBR header, and for a + /// frame whose SBR data is missing or damaged, the high band is empty. Parametric stereo + /// (HE-AAC v2) is not decoded: its mono core stays mono. + pub fn set_sbr(&mut self, on: bool) { + if on != self.sbr_out.is_some() { + self.sbr_out = on.then(Vec::new); + } + } /// Output channels of the current element layout. pub fn channels(&self) -> usize { self.channels.len() @@ -158,30 +180,60 @@ impl Decoder { c.overlap.fill(0.0); c.prev_shape = WindowShape::Sine; } + for e in self.sbr_out.iter_mut().flatten() { + e.reset(); + } } fn sf_index(&self) -> u8 { self.asc.sf_index } - /// Decode one access unit (one `raw_data_block`) into 1024 samples per channel. An access unit - /// that fails leaves the decoder as it was, but for [`Self::sbr`]: its damage, including a damaged - /// program config element, does not reach the access units after it. + /// Decode one access unit (one `raw_data_block`) into 1024 samples per channel (2048 with + /// [`Self::set_sbr`] on). An access unit that fails leaves the decoder as it was, but for + /// [`Self::sbr`]: its damage, including a damaged program config element, does not reach the + /// access units after it. Damaged SBR data alone does not fail it. pub fn decode(&mut self, au: &[u8]) -> Result>> { let before = (self.asc.pce.clone(), self.layout.clone(), self.offsets.clone(), self.channels.clone(), self.noise_state); - let decoded = self.decode_block(au, true); - if decoded.is_err() { - (self.asc.pce, self.layout, self.offsets, self.channels, self.noise_state) = before; + match self.decode_block(au, true) { + Ok((pcm, sbr_refs)) => Ok(self.apply_sbr(au, pcm, &sbr_refs)), + Err(e) => { + (self.asc.pce, self.layout, self.offsets, self.channels, self.noise_state) = before; + Err(e) + } } - decoded } - fn decode_block(&mut self, au: &[u8], allow_reconfig: bool) -> Result>> { + /// Run each layout entry's channels through its SBR decoder, when SBR is on. + fn apply_sbr(&mut self, au: &[u8], pcm: Vec>, refs: &[Option]) -> Vec> { + let rate = self.sample_rate(); + let Some(elements) = self.sbr_out.as_mut() else { + return pcm; + }; + // a new layout (a program config element) starts the SBR decoders afresh + let fits = elements.len() == self.layout.len() && elements.iter().zip(&self.layout).all(|(e, l)| e.channels() == l.0.channels()); + if !fits { + *elements = self.layout.iter().map(|l| sbr::Element::new(l.0.channels(), rate)).collect(); + } + let mut out = Vec::with_capacity(pcm.len()); + for (i, e) in elements.iter_mut().enumerate() { + let first = self.offsets.get(i).copied().unwrap_or(0); + let core = pcm.get(first..first + e.channels()).unwrap_or_default(); + let payload = refs.get(i).copied().flatten().map(|(start, end, crc)| sbr::Payload { data: au, start, end, crc }); + out.extend(e.process(core, payload)); + } + out + } + + fn decode_block(&mut self, au: &[u8], allow_reconfig: bool) -> Result { let mut br = BitReader::new(au); let nch = self.channels.len(); let mut saw_element = false; let mut out: Vec>> = vec![None; nch]; let mut used = vec![false; self.layout.len()]; + // SBR data belongs to the single channel or pair element before its fill element + let mut sbr_refs: Vec> = vec![None; self.layout.len()]; + let mut last_slot = None; loop { let id = br.read_bits(3)?; saw_element |= matches!(id, ID_SCE | ID_CPE | ID_LFE); @@ -191,6 +243,7 @@ impl Decoder { let ty = if id == ID_SCE { ElementType::Sce } else { ElementType::Lfe }; let slot = self.find_slot(ty, tag, &used)?; used[slot] = true; + last_slot = (id == ID_SCE).then_some(slot); let mut ics = self.decode_ics(&mut br, None)?; let ch = self.offsets[slot]; self.fill_noise(&mut ics, None); @@ -200,6 +253,7 @@ impl Decoder { let tag = br.read_bits(4)? as u8; let slot = self.find_slot(ElementType::Cpe, tag, &used)?; used[slot] = true; + last_slot = Some(slot); let ch = self.offsets[slot]; let [a, b] = self.decode_cpe(&mut br, ch)?; out[ch] = Some(a); @@ -237,8 +291,13 @@ impl Decoder { } if count > 0 { let ext = br.read_bits(4)?; - self.sbr_seen |= matches!(ext, EXT_SBR_DATA | EXT_SBR_DATA_CRC); + let is_sbr = matches!(ext, EXT_SBR_DATA | EXT_SBR_DATA_CRC); + self.sbr_seen |= is_sbr; + let start = br.position(); br.skip(count * 8 - 4)?; + if let (true, Some(slot)) = (is_sbr, last_slot.take()) { + sbr_refs[slot] = Some((start, br.position(), ext == EXT_SBR_DATA_CRC)); + } } } _ => break, // ID_END @@ -251,7 +310,7 @@ impl Decoder { for o in out { res.push(o.ok_or(Error::Bitstream("access unit is missing a channel element"))?); } - Ok(res) + Ok((res, sbr_refs)) } fn find_slot(&self, ty: ElementType, tag: u8, used: &[bool]) -> Result { @@ -637,6 +696,48 @@ mod tests { assert_eq!(dec.sample_rate(), 22_050); } + /// With SBR on, an access unit decodes to 2048 samples, and over a silent core the high band + /// its SBR fill element describes is there; with SBR off, the core alone (silence). + #[test] + fn sbr_fill_elements_rebuild_the_high_band() { + let payload = sbr::test_payload(44_100, |_| 40, 0).unwrap(); + let mut bw = BitWriter::new(); + bw.write_bits(ID_SCE, 3); + bw.write_bits(0, 4); + bw.write_bits(0, 8 + 11 + 3); // global_gain, ics_info (max_sfb 0), no pulse/TNS/gain control + bw.write_bits(ID_FIL, 3); + let count = (4 + 8 * payload.len()).div_ceil(8); + if count < 15 { + bw.write_bits(count as u32, 4); + } else { + bw.write_bits(15, 4); + bw.write_bits((count - 14) as u32, 8); + } + bw.write_bits(EXT_SBR_DATA, 4); + for &b in &payload { + bw.write_bits(u32::from(b), 8); + } + bw.write_bits(0, 4); + bw.write_bits(ID_END, 3); + let au = bw.finish(); + // AAC-LC, 22.05 kHz, mono + let asc = AudioSpecificConfig::lc(7, 1); + let mut dec = Decoder::from_config(asc.clone()).unwrap(); + let core = dec.decode(&au).unwrap(); + assert_eq!((core[0].len(), dec.sample_rate()), (1024, 22_050)); + assert!(dec.sbr() && core[0].iter().all(|&s| s == 0.0)); + let mut dec = Decoder::from_config(asc).unwrap(); + dec.set_sbr(true); + assert_eq!(dec.sample_rate(), 44_100); + let mut out = Vec::new(); + for _ in 0..4 { + out.extend(dec.decode(&au).unwrap().remove(0)); + } + assert_eq!(out.len(), 4 * 2048); + let rms = (out[2048..].iter().map(|s| s * s).sum::() / (out.len() - 2048) as f32).sqrt(); + assert!(rms > 1e-4, "{rms}"); + } + /// An access unit whose elements are all empty: `elements` are (id, tag), then ID_END. fn silent_au(pce: Option<&ProgramConfig>, elements: &[(u32, u32)]) -> Vec { let mut bw = BitWriter::new(); diff --git a/crates/audio/src/aac/huffman.rs b/crates/audio/src/aac/huffman.rs index adac5c9c9..0c29673e7 100644 --- a/crates/audio/src/aac/huffman.rs +++ b/crates/audio/src/aac/huffman.rs @@ -62,12 +62,12 @@ const SUB: u32 = 1 << 31; /// entry with `SUB` set is `SUB | offset << 5 | width` and selects a sub-table of `1 << width` /// leaves indexed by the next `width` bits. An entry of 0 is a bit pattern that no codeword starts /// with (the codebooks are complete, so this does not occur). -struct Lut { +pub(crate) struct Lut { t: Vec, } impl Lut { - fn build(codes: &[(u32, u8)]) -> Lut { + pub(crate) fn build(codes: &[(u32, u8)]) -> Lut { let mut t = vec![0u32; 1 << L1]; // sub-table width per first-level prefix let mut width = vec![0u32; 1 << L1]; @@ -103,7 +103,7 @@ impl Lut { /// The symbol of the codeword at the reader position; `Eof` if the codeword is cut off. #[inline] - fn decode(&self, br: &mut BitReader) -> Result { + pub(crate) fn decode(&self, br: &mut BitReader) -> Result { let bits = br.peek(32); let mut e = self.t[(bits >> (32 - L1)) as usize]; if e & SUB != 0 { diff --git a/crates/audio/src/aac/mod.rs b/crates/audio/src/aac/mod.rs index 0a5f639a4..297aa7972 100644 --- a/crates/audio/src/aac/mod.rs +++ b/crates/audio/src/aac/mod.rs @@ -5,13 +5,13 @@ //! model, window switching, M/S, TNS, bit reservoir, CBR/VBR, 1–8 channels. //! - [`Decoder`] (feature `aac-decode`): `AudioSpecificConfig` + raw access units → planar f32. //! Supports M/S, intensity stereo, PNS, TNS, pulse data, both window shapes and every window -//! sequence. +//! sequence, and the SBR tool of HE-AAC ([`Decoder::set_sbr`]). //! - [`latm_config`] / [`latm_split`] (feature `aac-decode`): the configuration and payload of //! LOAS / LATM frames (AAC in MPEG transport streams), for the decoder. //! -//! HE-AAC (SBR/PS), Main/SSR/LTP profiles, coupling channels and 960-sample frames are not -//! supported; for HE-AAC only the AAC-LC core is decoded (at the core rate; [`Decoder::sbr`] says -//! when). +//! Parametric stereo (HE-AAC v2), Main/SSR/LTP profiles, coupling channels and 960-sample frames +//! are not supported. HE-AAC decodes at twice the core rate with [`Decoder::set_sbr`] on +//! ([`Decoder::sbr`] says when a stream uses SBR), and as its AAC-LC core otherwise. //! //! From FilmCraft's `filmcraft-aac` (storytold/filmcraft@8fda2f2). Public items keep their names, //! so FilmCraft can re-export this module. @@ -53,6 +53,8 @@ mod ics; #[cfg(feature = "aac-decode")] mod latm; mod mdct; +#[cfg(feature = "aac-decode")] +mod sbr; mod tables; mod tns; @@ -68,6 +70,12 @@ pub use latm::{LatmConfig, latm_config, latm_split}; pub use mdct::WindowShape; pub use tables::{SAMPLE_RATES, sample_rate_index}; +/// Output samples by which [`Decoder`] with [`Decoder::set_sbr`] on delays the core: the SBR filter +/// banks and the SBR frame's lag behind the core frame. HE-AAC encoders count it in the priming the +/// container signals. +#[cfg(feature = "aac-decode")] +pub const SBR_DELAY: usize = sbr::DELAY; + /// Samples per AAC-LC frame (per channel). pub const FRAME_LEN: usize = 1024; @@ -197,9 +205,9 @@ const UPSAMPLE_DELAY: usize = 481; #[cfg(feature = "aac-decode")] const UPSAMPLE_HISTORY: usize = UPSAMPLE_DELAY + UPSAMPLE_HALF; -/// 2× upsampler for HE-AAC. The decoder reconstructs the AAC-LC core at half the output rate and -/// not yet the SBR high band, so the core is interpolated to the output rate: timing and pitch are -/// right and the band above the core's Nyquist stays empty. Even outputs are the input samples, +/// 2× upsampler: interpolates an AAC-LC core to twice its rate, leaving the band above the core's +/// Nyquist empty. [`Decoder::set_sbr`] decodes HE-AAC with its high band instead; this stays for +/// callers that want the core alone at the output rate. Even outputs are the input samples, /// odd ones a Blackman-windowed sinc half-band interpolation over 2 × `UPSAMPLE_HALF` inputs. /// The output is delayed by `UPSAMPLE_DELAY` input samples, as an SBR decoder's is; the state /// carries across packets, so a seek primed with the preceding packet gives the same samples as diff --git a/crates/audio/src/aac/sbr/bitstream.rs b/crates/audio/src/aac/sbr/bitstream.rs new file mode 100644 index 000000000..6233504fa --- /dev/null +++ b/crates/audio/src/aac/sbr/bitstream.rs @@ -0,0 +1,489 @@ +//! The SBR bitstream (ISO/IEC 14496-3 §4.4.2.8 syntax, §4.5.2.8 semantics): the header and the +//! single and channel pair elements of an `EXT_SBR_DATA` fill element. + +use std::sync::OnceLock; + +use craft_media::bitstream::BitReader; + +use super::freq::FreqTables; +use super::tables::{ + F_ENV_1_5DB, F_ENV_3_0DB, F_ENV_BAL_1_5DB, F_ENV_BAL_3_0DB, T_ENV_1_5DB, T_ENV_3_0DB, T_ENV_BAL_1_5DB, T_ENV_BAL_3_0DB, T_NOISE_3_0DB, + T_NOISE_BAL_3_0DB, +}; +use super::{Header, MAX_ENV}; +use crate::aac::huffman::Lut; +use crate::aac::{Error, Result}; + +/// `bs_frame_class`: fixed leading and trailing frame borders. +pub(crate) const FIXFIX: u8 = 0; +/// Fixed leading border, variable trailing border. +pub(crate) const FIXVAR: u8 = 1; +/// Variable leading border, fixed trailing border. +pub(crate) const VARFIX: u8 = 2; +/// Variable leading and trailing borders. +pub(crate) const VARVAR: u8 = 3; + +/// SBR time slots per frame (`numTimeSlots`; two QMF slots each). +const TIME_SLOTS: usize = 16; + +/// Decoding tables for the SBR Huffman codes, in [`Book`] order. +fn luts() -> &'static [Lut; 10] { + static LUTS: OnceLock<[Lut; 10]> = OnceLock::new(); + LUTS.get_or_init(|| { + [ + Lut::build(&T_ENV_1_5DB), + Lut::build(&F_ENV_1_5DB), + Lut::build(&T_ENV_BAL_1_5DB), + Lut::build(&F_ENV_BAL_1_5DB), + Lut::build(&T_ENV_3_0DB), + Lut::build(&F_ENV_3_0DB), + Lut::build(&T_ENV_BAL_3_0DB), + Lut::build(&F_ENV_BAL_3_0DB), + Lut::build(&T_NOISE_3_0DB), + Lut::build(&T_NOISE_BAL_3_0DB), + ] + }) +} + +/// One of the SBR Huffman codes, and its largest absolute value. +#[derive(Clone, Copy)] +struct Book(usize, i32); + +const T_ENV_15: Book = Book(0, 60); +const F_ENV_15: Book = Book(1, 60); +const T_BAL_15: Book = Book(2, 24); +const F_BAL_15: Book = Book(3, 24); +const T_ENV_30: Book = Book(4, 31); +const F_ENV_30: Book = Book(5, 31); +const T_BAL_30: Book = Book(6, 12); +const F_BAL_30: Book = Book(7, 12); +const T_NOISE: Book = Book(8, 31); +const T_NOISE_BAL: Book = Book(9, 12); + +fn huff(br: &mut BitReader, book: Book) -> Result { + Ok(luts()[book.0].decode(br)? as i32 - book.1) +} + +fn bits(br: &mut BitReader, n: u32) -> Result { + Ok(br.read_bits(n)? as usize) +} + +impl Header { + /// `sbr_header()`, with the defaults of the fields its extra parts leave out. + pub(crate) fn read(br: &mut BitReader) -> Result
{ + let amp_res = br.read_flag()?; + let start_freq = br.read_bits(4)? as u8; + let stop_freq = br.read_bits(4)? as u8; + let xover_band = br.read_bits(3)? as u8; + br.skip(2)?; // bs_reserved + let (extra_1, extra_2) = (br.read_flag()?, br.read_flag()?); + let mut h = Header { + amp_res, + start_freq, + stop_freq, + xover_band, + freq_scale: 2, + alter_scale: true, + noise_bands: 2, + limiter_bands: 2, + limiter_gains: 2, + interpol_freq: true, + smoothing_mode: true, + }; + if extra_1 { + h.freq_scale = br.read_bits(2)? as u8; + h.alter_scale = br.read_flag()?; + h.noise_bands = br.read_bits(2)? as u8; + } + if extra_2 { + h.limiter_bands = br.read_bits(2)? as u8; + h.limiter_gains = br.read_bits(2)? as u8; + h.interpol_freq = br.read_flag()?; + h.smoothing_mode = br.read_flag()?; + } + Ok(h) + } +} + +/// The time/frequency grid of one channel in one frame (`sbr_grid()`, §4.6.18.3.3). +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct Grid { + pub(crate) class: u8, + /// Envelopes (1 to 5). + pub(crate) num_env: usize, + /// Envelope borders in time slots; `t_env[num_env]` may reach past the frame (up to 19). + pub(crate) t_env: [usize; MAX_ENV + 1], + /// Noise floors (1 or 2). + pub(crate) num_noise: usize, + /// Noise floor borders. + pub(crate) t_noise: [usize; 3], + /// High frequency resolution per envelope. + pub(crate) freq_res: [bool; MAX_ENV], + /// The transient envelope `l_A`, if any (it may be `num_env`: the next frame's first). + pub(crate) la: Option, + /// 3 dB envelope steps (the header's choice, but 1.5 dB for a single FIXFIX envelope). + pub(crate) amp_res: bool, +} + +/// `ceil(log2(num_env + 1))`: the width of `bs_pointer`. +fn pointer_bits(num_env: usize) -> u32 { + usize::BITS - num_env.leading_zeros() +} + +fn read_rel(br: &mut BitReader, n: usize) -> Result<[usize; 3]> { + let mut rel = [0; 3]; + for r in rel.iter_mut().take(n) { + *r = 2 * bits(br, 2)? + 2; + } + Ok(rel) +} + +impl Grid { + fn read(br: &mut BitReader, amp_res: bool) -> Result { + let bad = || Error::Bitstream("invalid SBR time grid"); + let class = br.read_bits(2)? as u8; + let mut t = [0i64; MAX_ENV + 1]; + let mut freq_res = [false; MAX_ENV]; + let (n, pointer) = match class { + FIXFIX => { + let n = 1usize << br.read_bits(2)?; + if n > 4 { + return Err(bad()); + } + freq_res = [br.read_flag()?; MAX_ENV]; + for (l, t) in t.iter_mut().enumerate().take(n + 1) { + *t = (l * TIME_SLOTS / n) as i64; + } + (n, 0) + } + FIXVAR | VARFIX => { + let abs = bits(br, 2)?; + let rels = bits(br, 2)?; + let n = rels + 1; + let rel = read_rel(br, rels)?; + let pointer = bits(br, pointer_bits(n))?; + for l in 0..n { + freq_res[if class == FIXVAR { n - 1 - l } else { l }] = br.read_flag()?; + } + if class == FIXVAR { + t[n] = (abs + TIME_SLOTS) as i64; + for l in (1..n).rev() { + t[l] = t[l + 1] - rel[n - 1 - l] as i64; + } + } else { + (t[0], t[n]) = (abs as i64, TIME_SLOTS as i64); + for l in 1..n { + t[l] = t[l - 1] + rel[l - 1] as i64; + } + } + (n, pointer) + } + _ => { + let abs0 = bits(br, 2)?; + let abs1 = bits(br, 2)? + TIME_SLOTS; + let (rels0, rels1) = (bits(br, 2)?, bits(br, 2)?); + let n = rels0 + rels1 + 1; + if n > MAX_ENV { + return Err(bad()); + } + let (rel0, rel1) = (read_rel(br, rels0)?, read_rel(br, rels1)?); + let pointer = bits(br, pointer_bits(n))?; + for r in freq_res.iter_mut().take(n) { + *r = br.read_flag()?; + } + (t[0], t[n]) = (abs0 as i64, abs1 as i64); + for l in 1..=rels0 { + t[l] = t[l - 1] + rel0[l - 1] as i64; + } + for l in (rels0 + 1..n).rev() { + t[l] = t[l + 1] - rel1[n - 1 - l] as i64; + } + (n, pointer) + } + }; + if t[0] < 0 || t[..=n].windows(2).any(|w| w[0] >= w[1]) { + return Err(bad()); + } + let la = match (pointer, class) { + (0, _) => None, + (p, FIXVAR | VARVAR) => Some((n + 1).checked_sub(p).ok_or_else(bad)?), + (p, _) => Some(p - 1), + }; + if la.is_some_and(|la| la > n) { + return Err(bad()); + } + let t_env: [usize; MAX_ENV + 1] = std::array::from_fn(|i| t[i] as usize); + let (num_noise, t_noise) = if n == 1 { + (1, [t_env[0], t_env[1], 0]) + } else { + let middle = match (class, pointer) { + (FIXFIX, _) => n / 2, + (VARFIX, 0) => 1, + (VARFIX, 1) => n - 1, + (VARFIX, p) => p - 1, + (_, 0 | 1) => n - 1, + (_, p) => (n + 1).checked_sub(p).ok_or_else(bad)?, + }; + if middle == 0 || middle >= n { + return Err(bad()); + } + (2, [t_env[0], t_env[middle], t_env[n]]) + }; + Ok(Grid { class, num_env: n, t_env, num_noise, t_noise, freq_res, la, amp_res: amp_res && !(class == FIXFIX && n == 1) }) + } +} + +/// The data of one channel as transmitted: delta-coded envelope and noise floor values (the +/// first frequency-coded value of each is absolute), not yet scaled for a balance channel. +#[derive(Clone, Debug)] +pub(crate) struct ChannelData { + pub(crate) grid: Grid, + pub(crate) df_env: [bool; MAX_ENV], + pub(crate) df_noise: [bool; 2], + pub(crate) invf: [u8; 5], + pub(crate) env: [[i32; 64]; MAX_ENV], + pub(crate) noise: [[i32; 5]; 2], + /// `bs_add_harmonic` per high-resolution band, when `bs_add_harmonic_flag` is set. + pub(crate) harmonic: Option<[bool; 64]>, +} + +impl ChannelData { + fn new(grid: Grid) -> ChannelData { + ChannelData { + grid, + df_env: [false; MAX_ENV], + df_noise: [false; 2], + invf: [0; 5], + env: [[0; 64]; MAX_ENV], + noise: [[0; 5]; 2], + harmonic: None, + } + } + + fn read_dtdf(&mut self, br: &mut BitReader) -> Result<()> { + for d in self.df_env.iter_mut().take(self.grid.num_env) { + *d = br.read_flag()?; + } + for d in self.df_noise.iter_mut().take(self.grid.num_noise) { + *d = br.read_flag()?; + } + Ok(()) + } + + fn read_invf(&mut self, br: &mut BitReader, t: &FreqTables) -> Result<()> { + for m in self.invf.iter_mut().take(t.n_q()) { + *m = br.read_bits(2)? as u8; + } + Ok(()) + } + + /// `sbr_envelope()`; `balance` for the second channel of a coupled pair. + fn read_envelope(&mut self, br: &mut BitReader, t: &FreqTables, balance: bool) -> Result<()> { + let (t_book, f_book, start_bits) = match (self.grid.amp_res, balance) { + (false, false) => (T_ENV_15, F_ENV_15, 7), + (false, true) => (T_BAL_15, F_BAL_15, 6), + (true, false) => (T_ENV_30, F_ENV_30, 6), + (true, true) => (T_BAL_30, F_BAL_30, 5), + }; + for l in 0..self.grid.num_env { + let n = t.bands(self.grid.freq_res[l]).len() - 1; + let row = &mut self.env[l]; + if self.df_env[l] { + for v in row.iter_mut().take(n) { + *v = huff(br, t_book)?; + } + } else { + row[0] = br.read_bits(start_bits)? as i32; + for v in row.iter_mut().take(n).skip(1) { + *v = huff(br, f_book)?; + } + } + } + Ok(()) + } + + /// `sbr_noise()`. + fn read_noise(&mut self, br: &mut BitReader, t: &FreqTables, balance: bool) -> Result<()> { + let (t_book, f_book) = if balance { (T_NOISE_BAL, F_BAL_30) } else { (T_NOISE, F_ENV_30) }; + for l in 0..self.grid.num_noise { + let row = &mut self.noise[l]; + if self.df_noise[l] { + for v in row.iter_mut().take(t.n_q()) { + *v = huff(br, t_book)?; + } + } else { + row[0] = br.read_bits(5)? as i32; + for v in row.iter_mut().take(t.n_q()).skip(1) { + *v = huff(br, f_book)?; + } + } + } + Ok(()) + } + + /// `bs_add_harmonic_flag` and `sbr_sinusoidal_coding()`. + fn read_harmonics(&mut self, br: &mut BitReader, t: &FreqTables) -> Result<()> { + if br.read_flag()? { + let mut h = [false; 64]; + for v in h.iter_mut().take(t.n_high()) { + *v = br.read_flag()?; + } + self.harmonic = Some(h); + } + Ok(()) + } +} + +/// Skip `bs_extended_data` (parametric stereo, for one, is not decoded). +fn skip_extended(br: &mut BitReader) -> Result<()> { + if br.read_flag()? { + let mut count = bits(br, 4)?; + if count == 15 { + count += bits(br, 8)?; + } + br.skip(8 * count)?; + } + Ok(()) +} + +/// The SBR data of one channel element. +#[derive(Clone, Debug)] +pub(crate) struct Frame { + /// The pair codes its second channel as a balance against the first. + pub(crate) coupling: bool, + pub(crate) channels: Vec, +} + +/// `sbr_single_channel_element()` (`channels` 1) or `sbr_channel_pair_element()` (2). +pub(crate) fn read_data(br: &mut BitReader, t: &FreqTables, amp_res: bool, channels: usize) -> Result { + if br.read_flag()? { + br.skip(4 * channels)?; // bs_reserved + } + if channels == 1 { + let mut c = ChannelData::new(Grid::read(br, amp_res)?); + c.read_dtdf(br)?; + c.read_invf(br, t)?; + c.read_envelope(br, t, false)?; + c.read_noise(br, t, false)?; + c.read_harmonics(br, t)?; + skip_extended(br)?; + return Ok(Frame { coupling: false, channels: vec![c] }); + } + let coupling = br.read_flag()?; + let (mut a, mut b); + if coupling { + a = ChannelData::new(Grid::read(br, amp_res)?); + b = ChannelData::new(a.grid.clone()); + a.read_dtdf(br)?; + b.read_dtdf(br)?; + a.read_invf(br, t)?; + b.invf = a.invf; + a.read_envelope(br, t, false)?; + a.read_noise(br, t, false)?; + b.read_envelope(br, t, true)?; + b.read_noise(br, t, true)?; + } else { + a = ChannelData::new(Grid::read(br, amp_res)?); + b = ChannelData::new(Grid::read(br, amp_res)?); + a.read_dtdf(br)?; + b.read_dtdf(br)?; + a.read_invf(br, t)?; + b.read_invf(br, t)?; + a.read_envelope(br, t, false)?; + b.read_envelope(br, t, false)?; + a.read_noise(br, t, false)?; + b.read_noise(br, t, false)?; + } + a.read_harmonics(br, t)?; + b.read_harmonics(br, t)?; + skip_extended(br)?; + Ok(Frame { coupling, channels: vec![a, b] }) +} + +#[cfg(test)] +mod tests { + use craft_media::bitstream::BitWriter; + + use super::*; + + /// Every SBR Huffman table is a complete prefix code, with the shortest codeword for a zero + /// delta, and its decoder reads back every codeword. + #[test] + fn huffman_tables_are_complete_prefix_codes() { + let tables: [&[(u32, u8)]; 10] = [ + &T_ENV_1_5DB, + &F_ENV_1_5DB, + &T_ENV_BAL_1_5DB, + &F_ENV_BAL_1_5DB, + &T_ENV_3_0DB, + &F_ENV_3_0DB, + &T_ENV_BAL_3_0DB, + &F_ENV_BAL_3_0DB, + &T_NOISE_3_0DB, + &T_NOISE_BAL_3_0DB, + ]; + let lavs = [60, 60, 24, 24, 31, 31, 12, 12, 31, 12]; + for (i, (codes, lav)) in tables.iter().zip(lavs).enumerate() { + assert_eq!(codes.len(), 2 * lav + 1, "table {i}"); + let max = u32::from(codes.iter().map(|c| c.1).max().unwrap()); + assert_eq!(codes.iter().map(|&(_, l)| 1u64 << (max - u32::from(l))).sum::(), 1 << max, "table {i}: Kraft sum"); + let shortest = codes.iter().map(|c| c.1).min().unwrap(); + assert_eq!(codes[lav].1, shortest, "table {i}"); + for (sym, &(code, len)) in codes.iter().enumerate() { + let mut bw = BitWriter::new(); + bw.write_bits(code, u32::from(len)); + bw.write_bits(0x5A5A5A, 24); + let data = bw.finish(); + let mut br = BitReader::new(&data); + assert_eq!(huff(&mut br, Book(i, lav as i32)).unwrap(), sym as i32 - lav as i32, "table {i}"); + assert_eq!(br.position(), usize::from(len)); + } + } + } + + fn grid(bits: &[(u32, u32)]) -> Result { + let mut bw = BitWriter::new(); + for &(v, n) in bits { + bw.write_bits(v, n); + } + bw.write_bits(0, 16); + let data = bw.finish(); + Grid::read(&mut BitReader::new(&data), true) + } + + /// The four frame classes give the borders, noise floors and transient envelope of + /// §4.6.18.3.3; a FIXFIX frame of one envelope uses 1.5 dB steps. + #[test] + fn grids_of_each_frame_class() { + // FIXFIX, 4 envelopes, high resolution + let g = grid(&[(FIXFIX as u32, 2), (2, 2), (1, 1)]).unwrap(); + assert_eq!((g.num_env, &g.t_env[..5], g.num_noise, g.t_noise, g.la, g.amp_res), (4, &[0, 4, 8, 12, 16][..], 2, [0, 8, 16], None, true)); + let g = grid(&[(FIXFIX as u32, 2), (0, 2), (0, 1)]).unwrap(); + assert_eq!((g.num_env, g.t_noise, g.amp_res), (1, [0, 16, 0], false)); + // FIXVAR: trailing border 16 + 2, two relative borders (4, 2) back from it, pointer 1, + // resolutions sent last envelope first + let g = grid(&[(FIXVAR as u32, 2), (2, 2), (2, 2), (1, 2), (0, 2), (1, 2), (0, 1), (1, 1), (1, 1)]).unwrap(); + assert_eq!((g.num_env, &g.t_env[..4]), (3, &[0, 12, 14, 18][..])); + assert_eq!((g.la, g.t_noise, g.freq_res[..3].to_vec()), (Some(3), [0, 14, 18], vec![true, true, false])); + // VARFIX: leading border 1, relative 2, pointer 2: l_A = 1 + let g = grid(&[(VARFIX as u32, 2), (1, 2), (1, 2), (0, 2), (2, 2), (1, 1), (0, 1)]).unwrap(); + assert_eq!((g.num_env, &g.t_env[..3], g.la, g.t_noise), (2, &[1, 3, 16][..], Some(1), [1, 3, 16])); + // VARVAR: 1 .. 17 with one border from each end + let g = grid(&[(VARVAR as u32, 2), (1, 2), (1, 2), (1, 2), (1, 2), (0, 2), (0, 2), (0, 2), (0, 1), (0, 1), (0, 1)]).unwrap(); + assert_eq!((g.num_env, &g.t_env[..4], g.la), (3, &[1, 3, 15, 17][..], None)); + } + + /// Grids whose borders do not increase, too many envelopes and a pointer past the envelopes + /// are refused. + #[test] + fn invalid_grids_are_refused() { + // FIXFIX with 8 envelopes + assert!(grid(&[(FIXFIX as u32, 2), (3, 2), (0, 1)]).is_err()); + // VARVAR with 7 envelopes + assert!(grid(&[(VARVAR as u32, 2), (0, 2), (0, 2), (3, 2), (3, 2)]).is_err()); + // FIXVAR whose relative borders run back past the start: 16 - (8 + 8 + 8) + assert!(grid(&[(FIXVAR as u32, 2), (0, 2), (3, 2), (3, 2), (3, 2), (3, 2), (0, 3), (0, 4)]).is_err()); + // VARFIX, 2 envelopes, pointer 3: the middle border would be the trailing one + assert!(grid(&[(VARFIX as u32, 2), (0, 2), (1, 2), (0, 2), (3, 2), (0, 1), (0, 1)]).is_err()); + } +} diff --git a/crates/audio/src/aac/sbr/freq.rs b/crates/audio/src/aac/sbr/freq.rs new file mode 100644 index 000000000..dc2809c10 --- /dev/null +++ b/crates/audio/src/aac/sbr/freq.rs @@ -0,0 +1,353 @@ +//! Frequency band tables (ISO/IEC 14496-3 §4.6.18.3.2) and the patches and limiter bands built on +//! them (§4.6.18.6.3, §4.6.18.7.3). Every table holds QMF channel borders. + +use super::Header; + +/// The most patches the HF generator may build. +const MAX_PATCHES: usize = 5; + +/// The tables of one SBR header at one output rate. +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct FreqTables { + /// First QMF channel of the master table (the start of the SBR range before the crossover). + pub(crate) k0: usize, + /// First QMF channel of the SBR range. + pub(crate) kx: usize, + /// Channels in the SBR range (`kx + m` is its end). + pub(crate) m: usize, + /// High-resolution envelope bands (`n_high + 1` borders). + pub(crate) high: Vec, + /// Low-resolution envelope bands. + pub(crate) low: Vec, + /// Noise floor bands (1 to 5). + pub(crate) noise: Vec, + /// Limiter bands. + pub(crate) lim: Vec, + /// `(first source channel, channels)` of each patch, in order from `kx` up. + pub(crate) patches: Vec<(usize, usize)>, +} + +/// Round half up, as the specification's `NINT`. +fn nint(x: f64) -> i64 { + (x + 0.5).floor() as i64 +} + +/// The start channel offsets of `bs_start_freq`, by output rate (§4.6.18.3.2.1). +fn start_offsets(fs: u32) -> &'static [i8; 16] { + match fs { + 0..=16_000 => &[-8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7], + 16_001..=22_050 => &[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 9, 11, 13], + 22_051..=24_000 => &[-5, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 9, 11, 13, 16], + 24_001..=32_000 => &[-6, -4, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 9, 11, 13, 16], + 32_001..=64_000 => &[-4, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 9, 11, 13, 16, 20], + _ => &[-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 9, 11, 13, 16, 20, 24], + } +} + +/// `NINT(f · 128 / fs)` for the 3/4/5 kHz (start) or 6/8/10 kHz (stop) minimum at `fs`. +fn min_channel(fs: u32, low: f64, mid: f64, high: f64) -> i64 { + let f = if fs < 32_000 { + low + } else if fs < 64_000 { + mid + } else { + high + }; + nint(f * 128.0 / f64::from(fs)) +} + +/// The start and stop channels `k0`, `k2` of the master table. +fn start_stop(h: &Header, fs: u32) -> Option<(usize, usize)> { + let k0 = min_channel(fs, 3000.0, 4000.0, 5000.0) + i64::from(start_offsets(fs)[usize::from(h.start_freq & 15)]); + let k2 = match h.stop_freq { + 14 => 2 * k0, + 15 => 3 * k0, + s => { + let stop_min = min_channel(fs, 6000.0, 8000.0, 10_000.0); + if stop_min <= 0 { + return None; + } + let ratio = 64.0 / stop_min as f64; + let at = |p: i64| nint(stop_min as f64 * ratio.powf(p as f64 / 13.0)); + let mut dk: Vec = (0..13).map(|p| at(p + 1) - at(p)).collect(); + dk.sort_unstable(); + stop_min + dk.iter().take(usize::from(s)).sum::() + } + } + .min(64); + // the low band reaches only the analysis bank's 32 channels + (1..32).contains(&k0).then_some(())?; + (k2 > k0).then_some((k0 as usize, k2 as usize)) +} + +/// The master table for `bs_freq_scale` 0: bands of one or two channels. +fn master_linear(k0: usize, k2: usize, alter_scale: bool) -> Option> { + let (dk, bands) = if alter_scale { (2, 2 * nint((k2 - k0) as f64 / 4.0) as usize) } else { (1, 2 * ((k2 - k0) / 2)) }; + if bands == 0 { + return None; + } + let mut v = vec![dk as i64; bands]; + let mut diff = k2 as i64 - (k0 + bands * dk) as i64; + // take the difference from the lowest bands (too wide) or give it to the highest (too narrow) + let mut k = if diff < 0 { 0 } else { bands - 1 }; + while diff != 0 { + let step = if diff < 0 { 1 } else { -1 }; + *v.get_mut(k)? -= step; + diff += step; + k = if step == 1 { k + 1 } else { k.checked_sub(1)? }; + } + cumulative(k0, &v) +} + +/// The borders `start, start + d[0], …`; `None` unless every band is at least a channel wide. +fn cumulative(start: usize, d: &[i64]) -> Option> { + let mut out = Vec::with_capacity(d.len() + 1); + out.push(start); + let mut at = start as i64; + for &d in d { + if d <= 0 { + return None; + } + at += d; + out.push(at as usize); + } + Some(out) +} + +/// The band widths of `2 · NINT(bands · log2(hi / lo) / (2 · warp))` logarithmic bands. +fn log_widths(lo: usize, hi: usize, bands: f64, warp: f64) -> Vec { + let ratio = hi as f64 / lo as f64; + let n = 2 * nint(bands * ratio.log2() / (2.0 * warp)); + let at = |k: i64| nint(lo as f64 * ratio.powf(k as f64 / n as f64)); + let mut d: Vec = (0..n.max(0)).map(|k| at(k + 1) - at(k)).collect(); + d.sort_unstable(); + d +} + +/// The master table for `bs_freq_scale` 1–3: 12, 10 or 8 bands per octave, warped above +/// `2 · k0` by `bs_alter_scale`. +fn master_log(k0: usize, k2: usize, freq_scale: u8, alter_scale: bool) -> Option> { + let bands = [12.0, 10.0, 8.0][usize::from(freq_scale.clamp(1, 3) - 1)]; + let two_regions = k2 as f64 / k0 as f64 > 2.2449; + let k1 = if two_regions { 2 * k0 } else { k2 }; + let d0 = log_widths(k0, k1, bands, 1.0); + let mut table = cumulative(k0, &d0)?; + if table.len() < 2 { + return None; + } + if two_regions { + let mut d1 = log_widths(k1, k2, bands, if alter_scale { 1.3 } else { 1.0 }); + let max0 = d0.iter().copied().max()?; + if d1.first().is_some_and(|&d| d < max0) { + // widen the first upper band to the widest lower one, at the cost of the last + let change = max0 - d1[0]; + d1[0] = max0; + *d1.last_mut()? -= change; + d1.sort_unstable(); + } + table.extend(cumulative(k1, &d1)?.into_iter().skip(1)); + } + Some(table) +} + +impl FreqTables { + /// The tables of `h` at output rate `fs`, or `None` if the header asks for an invalid layout. + pub(crate) fn new(h: &Header, fs: u32) -> Option { + let (k0, k2) = start_stop(h, fs)?; + let master = if h.freq_scale == 0 { master_linear(k0, k2, h.alter_scale)? } else { master_log(k0, k2, h.freq_scale, h.alter_scale)? }; + let n_master = master.len().checked_sub(1)?; + let xover = usize::from(h.xover_band); + if xover >= n_master { + return None; + } + let high: Vec = master.get(xover..)?.to_vec(); + let n_high = high.len() - 1; + let (kx, end) = (high[0], *high.last()?); + if kx > 32 || end > 64 || end <= kx { + return None; + } + let m = end - kx; + // every other border, keeping the first (and the second when the count is odd) + let n_low = n_high - n_high / 2; + let low: Vec = (0..=n_low).map(|k| high[if k == 0 { 0 } else { 2 * k - (n_high & 1) }]).collect(); + let n_q = if h.noise_bands == 0 { 1 } else { (nint(f64::from(h.noise_bands) * (end as f64 / kx as f64).log2()) as usize).max(1) }; + if n_q > 5 || n_q > n_low { + return None; + } + let mut noise = vec![low[0]]; + let mut i = 0; + for k in 1..=n_q { + i += (n_low - i) / (n_q + 1 - k); + noise.push(*low.get(i)?); + } + let patches = patches(&master, k0, kx, m, fs)?; + let lim = limiter(&low, &patches, kx, h.limiter_bands); + Some(FreqTables { k0, kx, m, high, low, noise, lim, patches }) + } + + /// High-resolution envelope bands. + pub(crate) fn n_high(&self) -> usize { + self.high.len() - 1 + } + + /// Noise floor bands. + pub(crate) fn n_q(&self) -> usize { + self.noise.len() - 1 + } + + /// The envelope band table of a resolution (`true`: high). + pub(crate) fn bands(&self, high_res: bool) -> &[usize] { + if high_res { &self.high } else { &self.low } + } +} + +/// Map the low band onto the SBR range (§4.6.18.6.3): runs of source channels, each ending below +/// `k0`, that together cover `kx .. kx + m`. +fn patches(master: &[usize], k0: usize, kx: usize, m: usize, fs: u32) -> Option> { + let n_master = master.len() - 1; + let goal = nint(2.048e6 / f64::from(fs)).max(0) as usize; + let mut k = if goal < kx + m { master.iter().position(|&f| f >= goal).unwrap_or(n_master) } else { n_master }; + let (mut msb, mut usb) = (k0, kx); + let mut out: Vec<(usize, usize)> = Vec::new(); + // each pass either adds a patch or lowers `msb` to `kx`; a stream that does neither twice + // running cannot finish + for _ in 0..2 * (MAX_PATCHES + 1) { + // the highest master border the patch can reach from below k0 + let mut j = k + 1; + let (sb, odd) = loop { + j = j.checked_sub(1)?; + let sb = *master.get(j)?; + let odd = (sb + k0) % 2; + if sb + odd < k0 + msb { + break (sb, odd); + } + }; + let count = sb.saturating_sub(usb); + if count > 0 { + let start = (k0 - odd).checked_sub(count)?; + out.push((start, count)); + if out.len() > MAX_PATCHES { + return None; + } + (usb, msb) = (sb, sb); + } else { + msb = kx; + } + if master.get(k)?.saturating_sub(sb) < 3 { + k = n_master; + } + if sb == kx + m { + if out.len() > 1 && out.last().is_some_and(|p| p.1 < 3) { + out.pop(); + } + return (!out.is_empty()).then_some(out); + } + } + None +} + +/// The limiter band table (§4.6.18.7.3): one band over the range, or about 1.2, 2 or 3 bands per +/// octave on the low-resolution and patch borders. +fn limiter(low: &[usize], patches: &[(usize, usize)], kx: usize, limiter_bands: u8) -> Vec { + let (Some(&first), Some(&last)) = (low.first(), low.last()) else { + return Vec::new(); + }; + if limiter_bands == 0 { + return vec![first, last]; + } + let per_octave = [1.2, 2.0, 3.0][usize::from(limiter_bands.clamp(1, 3) - 1)]; + let mut borders = vec![kx]; + for p in patches { + borders.push(borders.last().copied().unwrap_or(kx) + p.1); + } + let mut t: Vec = low.to_vec(); + t.extend(borders.iter().skip(1).take(patches.len().saturating_sub(1))); + t.sort_unstable(); + let is_border = |v: usize| borders.contains(&v); + let mut k = 1; + while k < t.len() { + let octaves = if t[k - 1] == 0 { f64::INFINITY } else { (t[k] as f64 / t[k - 1] as f64).log2() }; + if octaves * per_octave >= 0.49 { + k += 1; + } else if t[k] == t[k - 1] || !is_border(t[k]) { + t.remove(k); + } else if !is_border(t[k - 1]) { + t.remove(k - 1); + } else { + k += 1; + } + } + if t.len() < 2 { + return vec![first, last]; + } + t +} + +#[cfg(test)] +mod tests { + use super::*; + + fn header(start: u8, stop: u8, xover: u8, freq_scale: u8, alter: bool, noise: u8) -> Header { + Header { + amp_res: true, + start_freq: start, + stop_freq: stop, + xover_band: xover, + freq_scale, + alter_scale: alter, + noise_bands: noise, + limiter_bands: 2, + limiter_gains: 2, + interpol_freq: true, + smoothing_mode: true, + } + } + + /// Every header field combination at every output rate gives tables that are strictly + /// increasing, start at `kx`, end at `kx + m`, keep the low band within the analysis bank and + /// patch from below `k0` (or is refused). + #[test] + fn every_header_gives_consistent_tables_or_none() { + let mut valid = 0; + for fs in [16_000, 22_050, 24_000, 32_000, 44_100, 48_000, 64_000, 88_200, 96_000] { + for start in 0..16 { + for stop in 0..16 { + for xover in 0..8 { + for fsc in 0..4 { + for alter in [false, true] { + for noise in 0..4 { + let Some(t) = FreqTables::new(&header(start, stop, xover, fsc, alter, noise), fs) else { continue }; + valid += 1; + for v in [&t.high, &t.low, &t.noise, &t.lim] { + assert!(v.windows(2).all(|w| w[0] < w[1]), "{v:?}"); + assert_eq!(v[0], t.kx); + } + for v in [&t.high, &t.low, &t.noise] { + assert_eq!(*v.last().unwrap(), t.kx + t.m); + } + assert!(t.kx <= 32 && t.kx + t.m <= 64 && t.n_q() <= 5); + // a last patch of under 3 channels is dropped, and the limiter + // bands may then stop short of the range's end + assert!(t.patches.iter().map(|p| p.1).sum::() <= t.m); + assert!(t.lim.len() >= 2 && *t.lim.last().unwrap() <= t.kx + t.m); + assert!(t.patches.iter().all(|&(s, n)| s + n <= t.k0)); + } + } + } + } + } + } + } + assert!(valid > 10_000, "{valid}"); + } + + /// A typical 44.1 kHz header: start channel 12 + offset, stop near 64, three noise bands. + #[test] + fn a_44k_header_has_the_expected_layout() { + let t = FreqTables::new(&header(5, 9, 0, 2, true, 2), 44_100).unwrap(); + // startMin 12 + offset 2 = 14 + assert_eq!((t.k0, t.kx), (14, 14)); + assert_eq!(t.low.len() - 1, t.n_high() - t.n_high() / 2); + assert!(t.n_q() >= 1); + } +} diff --git a/crates/audio/src/aac/sbr/hf.rs b/crates/audio/src/aac/sbr/hf.rs new file mode 100644 index 000000000..43ae572cf --- /dev/null +++ b/crates/audio/src/aac/sbr/hf.rs @@ -0,0 +1,332 @@ +//! HF generation (ISO/IEC 14496-3 §4.6.18.6) and HF adjustment (§4.6.18.7) of one channel. + +use super::bitstream::Grid; +use super::freq::FreqTables; +use super::tables::NOISE; +use super::{Cx, Header, MAX_ENV, T_HF_ADJ, X_SLOTS}; + +/// Low band subband samples of a frame: slots -8 .. 32 relative to the frame (`X_Low`). +pub(crate) type LowBand = [[Cx; 32]; X_SLOTS]; +/// High band subband samples, at slot `l + T_HF_ADJ` for QMF slot `l` of the frame's envelopes. +pub(crate) type HighBand = [[Cx; 64]; X_SLOTS]; + +/// The per-channel state the HF tools carry from frame to frame. +#[derive(Clone, Debug)] +pub(crate) struct History { + /// `bs_invf_mode` and chirp factor per noise band. + invf: [u8; 5], + bw: [f32; 5], + /// The last frame's transient envelope was its last (`l_A = L_E`): no noise in this frame's + /// first. + la_at_end: bool, + /// Sinusoids present in the last envelope, by channel above `kx`. + sine: [bool; 64], + /// Gains and noise levels of the last four slots, newest first (smoothing). + gains: [[f32; 64]; 4], + noise_levels: [[f32; 64]; 4], + /// The smoothing history holds data of the current tables. + primed: bool, + noise_index: usize, + sine_index: usize, +} + +impl History { + pub(crate) fn new() -> History { + History { + invf: [0; 5], + bw: [0.0; 5], + la_at_end: false, + sine: [false; 64], + gains: [[0.0; 64]; 4], + noise_levels: [[0.0; 64]; 4], + primed: false, + noise_index: 0, + sine_index: 0, + } + } + + /// Forget what depends on the frequency tables (after a header that changes them). + pub(crate) fn reset_tables(&mut self) { + self.sine = [false; 64]; + self.primed = false; + self.noise_index = 0; + } +} + +/// The chirp factor of a noise band from its inverse filtering mode and the last frame's. +fn chirp(mode: u8, prev_mode: u8, prev_bw: f32) -> f32 { + let new_bw = match (mode, prev_mode) { + (0, 1) => 0.6, + (0, _) => 0.0, + (1, 0) => 0.6, + (1, _) => 0.75, + (2, _) => 0.9, + _ => 0.98, + }; + let bw = if new_bw < prev_bw { 0.75 * new_bw + 0.25 * prev_bw } else { 0.90625 * new_bw + 0.09375 * prev_bw }; + if bw < 0.015625 { 0.0 } else { bw } +} + +/// Second order linear prediction of one low band channel over the frame and the 6 slots +/// before it (the covariance method, §4.6.18.6.2): `(α0, α1)`. +fn predictor(x: &LowBand, k: usize) -> (Cx, Cx) { + // φ(i, j) = Σ x(n − i + 2) · conj(x(n − j + 2)), n = 0 .. 37 + let phi = |i: usize, j: usize| { + let (mut re, mut im) = (0f64, 0f64); + for n in 0..SLOTS_PLUS_6 { + let (a, b) = (x[n + 2 - i][k], x[n + 2 - j][k]); + re += f64::from(a.re) * f64::from(b.re) + f64::from(a.im) * f64::from(b.im); + im += f64::from(a.im) * f64::from(b.re) - f64::from(a.re) * f64::from(b.im); + } + (re, im) + }; + let (p01, p02, p11, p12, p22) = (phi(0, 1), phi(0, 2), phi(1, 1).0, phi(1, 2), phi(2, 2).0); + let d = p22 * p11 - (p12.0 * p12.0 + p12.1 * p12.1) / (1.0 + 1e-6); + let a1 = if d != 0.0 { + // (φ01 · φ12 − φ02 · φ11) / d + ((p01.0 * p12.0 - p01.1 * p12.1 - p02.0 * p11) / d, (p01.0 * p12.1 + p01.1 * p12.0 - p02.1 * p11) / d) + } else { + (0.0, 0.0) + }; + let a0 = if p11 != 0.0 { + // −(φ01 + α1 · conj(φ12)) / φ11 + (-(p01.0 + a1.0 * p12.0 + a1.1 * p12.1) / p11, -(p01.1 + a1.1 * p12.0 - a1.0 * p12.1) / p11) + } else { + (0.0, 0.0) + }; + let mag = |a: (f64, f64)| (a.0 * a.0 + a.1 * a.1).sqrt(); + if mag(a0) >= 4.0 || mag(a1) >= 4.0 { + return (Cx::default(), Cx::default()); + } + (Cx { re: a0.0 as f32, im: a0.1 as f32 }, Cx { re: a1.0 as f32, im: a1.1 as f32 }) +} + +/// Slots of the covariance sums: the frame's 32 and 6 before it. +const SLOTS_PLUS_6: usize = super::SLOTS + 6; + +/// The index of the band of `bands` holding channel `k` (bands are increasing borders). +fn band_of(bands: &[usize], k: usize) -> usize { + bands.partition_point(|&b| b <= k).saturating_sub(1).min(bands.len().saturating_sub(2)) +} + +/// Patch the low band into the SBR range, inverse filtered (§4.6.18.6.3), for QMF slots +/// `from .. to` of the frame. +pub(crate) fn generate(x: &LowBand, t: &FreqTables, invf: &[u8; 5], h: &mut History, (from, to): (usize, usize), out: &mut HighBand) { + let mut bw = [0f32; 5]; + for i in 0..t.n_q() { + bw[i] = chirp(invf[i], h.invf[i], h.bw[i]); + } + (h.invf, h.bw) = (*invf, bw); + let mut pred: [Option<(Cx, Cx)>; 32] = [None; 32]; + let mut k = t.kx; + for &(start, count) in &t.patches { + for p in start..start + count { + let (a0, a1) = *pred[p].get_or_insert_with(|| predictor(x, p)); + let g = bw[band_of(&t.noise, k)]; + let (c0, c1) = (a0.scale(g), a1.scale(g * g)); + for l in from..to { + let i = l + T_HF_ADJ; + out[i][k] = x[i][p] + c0 * x[i - 1][p] + c1 * x[i - 2][p]; + } + k += 1; + } + } +} + +/// Dequantised envelope and noise floor values of one channel in one frame. +#[derive(Clone, Debug)] +pub(crate) struct Levels { + /// Envelope energy per envelope and band (of that envelope's resolution). + pub(crate) energy: [[f32; 64]; MAX_ENV], + /// Noise to signal ratio per noise floor and noise band. + pub(crate) noise: [[f32; 5]; 2], +} + +/// Gains for one envelope, per channel of the SBR range. +struct EnvelopeGains { + gain: [f32; 64], + noise: [f32; 64], + sine: [f32; 64], +} + +const LIMITER_GAINS: [f32; 4] = [0.70795, 1.0, 1.41254, 1e10]; +/// `h_smooth`, as the specification gives it. +#[allow(clippy::excessive_precision)] +const SMOOTHING: [f32; 5] = [0.33333333333333, 0.30150283239582, 0.21816949906249, 0.11516383427084, 0.03183050093751]; +const EPS: f32 = 1.0; +const EPS0: f32 = 1e-12; + +/// What the adjustment of one channel's frame works from. +pub(crate) struct Frame<'a> { + /// The generated high band. + pub(crate) x: &'a HighBand, + pub(crate) t: &'a FreqTables, + pub(crate) hdr: &'a Header, + pub(crate) g: &'a Grid, + pub(crate) lv: &'a Levels, + /// `bs_add_harmonic`, when the frame codes sinusoids. + pub(crate) harmonic: Option<&'a [bool; 64]>, +} + +/// The gains of envelope `l` (§4.6.18.7.3–4): `delta` is 0 for a transient envelope, which gets +/// no noise. +fn envelope_gains(f: &Frame, l: usize, sine_index: &[bool; 64], delta: bool) -> EnvelopeGains { + let Frame { x, t, hdr, g, lv, .. } = *f; + let (kx, m) = (t.kx, t.m); + let bands = t.bands(g.freq_res[l]); + let (from, to) = (2 * g.t_env[l] + T_HF_ADJ, 2 * g.t_env[l + 1] + T_HF_ADJ); + let slots = (to - from) as f32; + let floor = usize::from(g.num_noise == 2 && g.t_env[l] >= g.t_noise[1]); + // per channel: original energy, current energy, noise ratio, sinusoid in its band + let mut e_orig = [0f32; 64]; + let mut e_curr = [0f32; 64]; + let mut q = [0f32; 64]; + let mut s_band = [false; 64]; + for j in 0..bands.len() - 1 { + let (lo, hi) = (bands[j] - kx, bands[j + 1] - kx); + let any_sine = sine_index[lo..hi].iter().any(|&s| s); + let mut sum = 0f32; + for c in lo..hi { + let ch = c + kx; + let e: f32 = (from..to).map(|i| x[i][ch].norm_sqr()).sum::() / slots; + e_curr[c] = e; + sum += e; + e_orig[c] = lv.energy[l][j]; + q[c] = lv.noise[floor][band_of(&t.noise, ch)]; + s_band[c] = any_sine; + } + if !hdr.interpol_freq { + e_curr[lo..hi].fill(sum / (hi - lo) as f32); + } + } + let mut out = EnvelopeGains { gain: [0.0; 64], noise: [0.0; 64], sine: [0.0; 64] }; + for c in 0..m { + let (eo, ec, q) = (e_orig[c], e_curr[c], q[c]); + out.noise[c] = (eo * q / (1.0 + q)).sqrt(); + if sine_index[c] { + out.sine[c] = (eo / (1.0 + q)).sqrt(); + } + out.gain[c] = + if s_band[c] { (eo / (EPS + ec) * q / (1.0 + q)).sqrt() } else { (eo / ((EPS + ec) * (1.0 + if delta { q } else { 0.0 }))).sqrt() }; + } + // limit per limiter band, then boost back the energy the limiter took + let lim_gain = LIMITER_GAINS[usize::from(hdr.limiter_gains & 3)]; + for w in t.lim.windows(2) { + let (lo, hi) = (w[0] - kx, w[1] - kx); + let (so, sc) = (e_orig[lo..hi].iter().sum::(), e_curr[lo..hi].iter().sum::()); + let max = (lim_gain * ((EPS0 + so) / (EPS0 + sc)).sqrt()).min(1e5); + let mut den = 0f32; + for c in lo..hi { + if out.gain[c] > max { + out.noise[c] *= max / out.gain[c]; + out.gain[c] = max; + } + den += e_curr[c] * out.gain[c] * out.gain[c]; + if out.sine[c] != 0.0 { + den += out.sine[c] * out.sine[c]; + } else if delta { + den += out.noise[c] * out.noise[c]; + } + } + let boost = ((EPS0 + so) / (EPS0 + den)).sqrt().min(1.584_893_2); + for c in lo..hi { + out.gain[c] *= boost; + out.noise[c] *= boost; + out.sine[c] *= boost; + } + } + out +} + +/// Adjust the generated high band to the transmitted envelopes and add noise and sinusoids +/// (§4.6.18.7), for the frame's envelopes, into `y`. +pub(crate) fn adjust(f: &Frame, h: &mut History, y: &mut HighBand) { + let Frame { x, t, hdr, g, harmonic, .. } = *f; + let (kx, m) = (t.kx, t.m); + let smooth = !hdr.smoothing_mode; + let prev_la_at_end = h.la_at_end; + for l in 0..g.num_env { + // sinusoids start at the transient envelope, unless one was already playing in the band + let mut sine_index = [false; 64]; + if let Some(harm) = harmonic { + for i in 0..t.n_high() { + let c = (t.high[i] + t.high[i + 1]) / 2 - kx; + sine_index[c] = harm[i] && (g.la.is_none_or(|la| l >= la) || h.sine[c]); + } + } + let transient = g.la == Some(l) || (l == 0 && prev_la_at_end); + let e = envelope_gains(f, l, &sine_index, !transient); + if !h.primed { + h.gains = [e.gain; 4]; + h.noise_levels = [e.noise; 4]; + h.primed = true; + } + for i in 2 * g.t_env[l]..2 * g.t_env[l + 1] { + let (mut gain, mut noise) = (e.gain, e.noise); + if smooth && !transient { + for c in 0..m { + gain[c] = SMOOTHING[0] * e.gain[c] + (0..4).map(|j| SMOOTHING[j + 1] * h.gains[j][c]).sum::(); + noise[c] = SMOOTHING[0] * e.noise[c] + (0..4).map(|j| SMOOTHING[j + 1] * h.noise_levels[j][c]).sum::(); + } + } + h.gains.rotate_right(1); + h.noise_levels.rotate_right(1); + (h.gains[0], h.noise_levels[0]) = (e.gain, e.noise); + let (sin_re, sin_im) = [(1.0, 0.0), (0.0, 1.0), (-1.0, 0.0), (0.0, -1.0)][h.sine_index]; + let row = &mut y[i + T_HF_ADJ]; + for c in 0..m { + let ch = c + kx; + let mut v = x[i + T_HF_ADJ][ch].scale(gain[c]); + h.noise_index = (h.noise_index + 1) & 511; + if e.sine[c] != 0.0 { + let sign = if ch % 2 == 0 { 1.0 } else { -1.0 }; + v.re += e.sine[c] * sin_re; + v.im += e.sine[c] * sin_im * sign; + } else if !transient { + let [nr, ni] = NOISE[h.noise_index]; + v.re += noise[c] * nr; + v.im += noise[c] * ni; + } + row[ch] = v; + } + h.sine_index = (h.sine_index + 1) & 3; + } + if l + 1 == g.num_env { + h.sine = sine_index; + } + } + h.la_at_end = g.la == Some(g.num_env); +} + +#[cfg(test)] +mod tests { + use super::*; + + /// The chirp factor follows §4.6.18.6.2: rising quickly, falling slowly, and zero below 1/64. + #[test] + fn chirp_factors() { + assert!((chirp(3, 3, 0.0) - 0.90625 * 0.98).abs() < 1e-6); + assert!((chirp(0, 3, 0.98) - 0.25 * 0.98).abs() < 1e-6); + assert_eq!(chirp(0, 0, 0.05), 0.0); + assert!((chirp(1, 0, 0.0) - 0.90625 * 0.6).abs() < 1e-6); + } + + /// A channel that is a decaying complex exponential is predicted exactly by its first order + /// term. + #[test] + fn prediction_of_a_resonance() { + let mut x: LowBand = [[Cx::default(); 32]; X_SLOTS]; + let r = Cx { re: 0.9 * 0.3f32.cos(), im: 0.9 * 0.3f32.sin() }; + let mut v = Cx { re: 1.0, im: 0.0 }; + for slot in x.iter_mut() { + slot[3] = v; + v = v * r; + } + let (a0, a1) = predictor(&x, 3); + // x(n) + α0·x(n−1) + α1·x(n−2) = 0 with α0 = −r, α1 = 0 + assert!((a0.re + r.re).abs() < 1e-3 && (a0.im + r.im).abs() < 1e-3, "{a0:?}"); + assert!(a1.re.abs() < 1e-3 && a1.im.abs() < 1e-3, "{a1:?}"); + // a silent channel has no predictor + assert_eq!(predictor(&x, 4), (Cx::default(), Cx::default())); + } +} diff --git a/crates/audio/src/aac/sbr/mod.rs b/crates/audio/src/aac/sbr/mod.rs new file mode 100644 index 000000000..32a6c55b0 --- /dev/null +++ b/crates/audio/src/aac/sbr/mod.rs @@ -0,0 +1,513 @@ +//! Spectral band replication (HE-AAC, ISO/IEC 14496-3 §4.6.18): rebuilds the band above the +//! AAC-LC core from the core's own low band and the envelope data of the stream's SBR fill +//! elements, at twice the core rate. +//! +//! Each channel's core output goes through a 32-band QMF analysis bank. The HF generator copies +//! (patches) low band channels into the SBR range, inverse filtered; the HF adjuster scales them +//! to the transmitted envelopes and adds noise and sinusoids; a 64-band QMF synthesis bank merges +//! the delayed low band and the new high band. A frame without usable SBR data (none sent yet, or +//! damaged) still passes through the banks, so the output keeps its rate and delay. +//! +//! Written from the SBR description and tables of ETSI ES 201 980 (DRM, Annex I has the tables it +//! shares with ISO/IEC 14496-3) and the decoder process of ISO/IEC 14496-3. Parametric stereo +//! (HE-AAC v2) is not decoded: its extension is skipped and the mono core is extended alone. + +mod bitstream; +mod freq; +mod hf; +mod qmf; +mod tables; + +use std::ops::{Add, Mul}; + +use craft_media::bitstream::BitReader; + +use self::bitstream::{ChannelData, Frame}; +use self::freq::FreqTables; +use self::hf::{HighBand, History, Levels, LowBand}; +use self::qmf::{Analysis, Synthesis}; + +/// QMF slots per frame: 1024 core samples in slots of 32. +pub(crate) const SLOTS: usize = 32; +/// The most envelopes in a frame. +pub(crate) const MAX_ENV: usize = 5; +/// Slots of the last frame kept ahead of this one's low band (`t_HFGen`). +const T_HF_GEN: usize = 8; +/// The SBR frame lags the core frame by `T_HF_GEN − T_HF_ADJ` slots. +pub(crate) const T_HF_ADJ: usize = 2; +/// Slots in the low and high band buffers: the frame and `T_HF_GEN` before it. +pub(crate) const X_SLOTS: usize = SLOTS + T_HF_GEN; +/// The 16-bit full scale the specification's energies are in. +const PCM_SCALE: f32 = 32768.0; +/// `NOISE_FLOOR_OFFSET`. +const NOISE_OFFSET: i32 = 6; + +/// Output samples by which the SBR decoder delays the core: the filter banks, and the SBR frame +/// lagging the core frame by `T_HF_GEN − T_HF_ADJ` slots of 64. +pub const DELAY: usize = qmf::DELAY + 64 * (T_HF_GEN - T_HF_ADJ); + +/// A complex subband sample. +#[derive(Clone, Copy, Default, Debug, PartialEq)] +pub(crate) struct Cx { + pub(crate) re: f32, + pub(crate) im: f32, +} + +impl Cx { + fn scale(self, g: f32) -> Cx { + Cx { re: self.re * g, im: self.im * g } + } + fn norm_sqr(self) -> f32 { + self.re * self.re + self.im * self.im + } +} + +impl Add for Cx { + type Output = Cx; + fn add(self, o: Cx) -> Cx { + Cx { re: self.re + o.re, im: self.im + o.im } + } +} + +impl Mul for Cx { + type Output = Cx; + fn mul(self, o: Cx) -> Cx { + Cx { re: self.re * o.re - self.im * o.im, im: self.re * o.im + self.im * o.re } + } +} + +/// `sbr_header()` (§4.5.2.8.1). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) struct Header { + pub(crate) amp_res: bool, + pub(crate) start_freq: u8, + pub(crate) stop_freq: u8, + pub(crate) xover_band: u8, + pub(crate) freq_scale: u8, + pub(crate) alter_scale: bool, + pub(crate) noise_bands: u8, + pub(crate) limiter_bands: u8, + pub(crate) limiter_gains: u8, + pub(crate) interpol_freq: bool, + pub(crate) smoothing_mode: bool, +} + +impl Header { + /// Whether `o` keeps the frequency band tables (a change of any of these fields is an SBR + /// reset). + fn same_tables(&self, o: &Header) -> bool { + (self.start_freq, self.stop_freq, self.freq_scale, self.alter_scale, self.xover_band, self.noise_bands) + == (o.start_freq, o.stop_freq, o.freq_scale, o.alter_scale, o.xover_band, o.noise_bands) + } +} + +/// Where an element's SBR data is: the access unit, and the bit range of the fill element's +/// payload after its extension type. +#[derive(Clone, Copy, Debug)] +pub(crate) struct Payload<'a> { + pub(crate) data: &'a [u8], + pub(crate) start: usize, + pub(crate) end: usize, + /// `EXT_SBR_DATA_CRC`: a 10-bit CRC leads the data. + pub(crate) crc: bool, +} + +/// Quantised envelope values per envelope and band, and noise floor values per floor and band. +type Quantised = ([[i32; 64]; MAX_ENV], [[i32; 5]; 2]); + +/// One channel's state across frames. +#[derive(Clone)] +struct Channel { + analysis: Analysis, + synthesis: Synthesis, + /// Low band of the frame, with the last `T_HF_GEN` slots of the frame before ahead of it. + x_low: Box, + /// Adjusted high band of the last frame (its envelopes may reach into this one). + y_prev: Box, + /// Where `y_prev` holds data: QMF channels, and the end of its slots. + y_prev_range: (usize, usize), + y_prev_end: usize, + /// Quantised envelope values of the last envelope, and its resolution; noise floor values of + /// the last noise floor (time-direction delta coding). + env: [i32; 64], + env_high_res: bool, + noise: [i32; 5], + history: History, +} + +impl Channel { + fn new() -> Channel { + Channel { + analysis: Analysis::new(), + synthesis: Synthesis::new(), + x_low: Box::new([[Cx::default(); 32]; X_SLOTS]), + y_prev: Box::new([[Cx::default(); 64]; X_SLOTS]), + y_prev_range: (0, 0), + y_prev_end: 0, + env: [0; 64], + env_high_res: false, + noise: [0; 5], + history: History::new(), + } + } + + /// Decode the delta-coded envelope and noise floor values (§4.6.18.3.4); `scale` is 2 for a + /// balance channel. + fn absolute(&mut self, d: &ChannelData, t: &FreqTables, scale: i32) -> Quantised { + let mut env = [[0i32; 64]; MAX_ENV]; + for l in 0..d.grid.num_env { + let res = d.grid.freq_res[l]; + let n = t.bands(res).len() - 1; + if d.df_env[l] { + for k in 0..n { + // the value this band refers to in the last envelope, mapped across resolutions + let reference = if res == self.env_high_res { + k + } else if res { + hf_band_of(t.bands(false), t.high[k]) + } else { + t.high.iter().position(|&b| b == t.low[k]).unwrap_or(0) + }; + env[l][k] = self.env.get(reference).copied().unwrap_or(0) + scale * d.env[l][k]; + } + } else { + env[l][0] = scale * d.env[l][0]; + for k in 1..n { + env[l][k] = env[l][k - 1] + scale * d.env[l][k]; + } + } + (self.env, self.env_high_res) = (env[l], res); + } + let mut noise = [[0i32; 5]; 2]; + for l in 0..d.grid.num_noise { + for k in 0..t.n_q() { + let delta = scale * d.noise[l][k]; + noise[l][k] = if d.df_noise[l] { + self.noise[k] + delta + } else if k == 0 { + delta + } else { + noise[l][k - 1] + delta + }; + } + self.noise = noise[l]; + } + (env, noise) + } +} + +/// The index of the band of `bands` holding channel `k`. +fn hf_band_of(bands: &[usize], k: usize) -> usize { + bands.partition_point(|&b| b <= k).saturating_sub(1) +} + +/// `2^x`, kept to a range whose energies stay finite. +fn exp2(x: f32) -> f32 { + x.clamp(-64.0, 64.0).exp2() +} + +/// Dequantise one channel (§4.6.18.3.5), or a coupled pair from its level and balance channels. +fn dequantise(frame: &Frame, values: &[Quantised], t: &FreqTables) -> Vec { + let g = &frame.channels[0].grid; + let amp = if g.amp_res { 1.0 } else { 0.5 }; + let mut out = vec![Levels { energy: [[0.0; 64]; MAX_ENV], noise: [[0.0; 5]; 2] }; values.len()]; + if frame.coupling && values.len() == 2 { + let pan = if g.amp_res { 12.0 } else { 24.0 }; + let ((e0, q0), (e1, q1)) = (&values[0], &values[1]); + for l in 0..g.num_env { + for k in 0..t.bands(g.freq_res[l]).len() - 1 { + let level = 64.0 * exp2(amp * e0[l][k] as f32 + 1.0); + let balance = amp * (pan - e1[l][k] as f32); + out[0].energy[l][k] = level / (1.0 + exp2(balance)); + out[1].energy[l][k] = level / (1.0 + exp2(-balance)); + } + } + for l in 0..g.num_noise { + for k in 0..t.n_q() { + let level = exp2((NOISE_OFFSET - q0[l][k] + 1) as f32); + let balance = (12 - q1[l][k]) as f32; + out[0].noise[l][k] = level / (1.0 + exp2(balance)); + out[1].noise[l][k] = level / (1.0 + exp2(-balance)); + } + } + return out; + } + for ((lv, (e, q)), d) in out.iter_mut().zip(values).zip(&frame.channels) { + let g = &d.grid; + let amp = if g.amp_res { 1.0 } else { 0.5 }; + for l in 0..g.num_env { + for k in 0..t.bands(g.freq_res[l]).len() - 1 { + lv.energy[l][k] = 64.0 * exp2(amp * e[l][k] as f32); + } + } + for l in 0..g.num_noise { + for k in 0..t.n_q() { + lv.noise[l][k] = exp2((NOISE_OFFSET - q[l][k]) as f32); + } + } + } + out +} + +/// The SBR decoder of one channel element: a single channel (SCE, or an LFE, which has no SBR +/// data) or a channel pair (CPE). +#[derive(Clone)] +pub(crate) struct Element { + /// Output sampling rate. + rate: u32, + header: Option
, + tables: Option, + channels: Vec, +} + +impl Element { + pub(crate) fn new(channels: usize, rate: u32) -> Element { + Element { rate, header: None, tables: None, channels: (0..channels).map(|_| Channel::new()).collect() } + } + + pub(crate) fn channels(&self) -> usize { + self.channels.len() + } + + /// Forget the signal (a seek). The header is kept: it holds until the stream sends another. + pub(crate) fn reset(&mut self) { + for c in &mut self.channels { + *c = Channel::new(); + } + } + + /// Read an element's SBR data: `None` without a header to read it by. + fn read(&mut self, p: Payload) -> Option { + let mut br = BitReader::new(p.data); + br.seek_bits(p.start); + if p.crc { + br.skip(10).ok()?; + } + if br.read_flag().ok()? { + let h = Header::read(&mut br).ok()?; + if self.header != Some(h) { + if self.header.is_none_or(|o| !o.same_tables(&h)) { + for c in &mut self.channels { + c.history.reset_tables(); + } + } + self.tables = FreqTables::new(&h, self.rate); + self.header = Some(h); + } + } + let (h, t) = (self.header?, self.tables.as_ref()?); + let frame = bitstream::read_data(&mut br, t, h.amp_res, self.channels.len()).ok()?; + (br.position() <= p.end).then_some(frame) + } + + /// Decode one frame: 1024 core samples per channel in, 2048 out. + pub(crate) fn process(&mut self, core: &[Vec], payload: Option) -> Vec> { + for (c, pcm) in self.channels.iter_mut().zip(core) { + c.x_low.copy_within(SLOTS..X_SLOTS, 0); + let scaled: Vec = pcm.iter().map(|s| s * PCM_SCALE).collect(); + c.analysis.process(&scaled, &mut c.x_low[T_HF_GEN..]); + } + let frame = payload.and_then(|p| self.read(p)); + let mut y: Vec> = self.channels.iter().map(|_| Box::new([[Cx::default(); 64]; X_SLOTS])).collect(); + // per channel: kx, m, and the first and end slot of `y` this frame fills + let mut current: Vec> = vec![None; self.channels.len()]; + if let (Some(frame), Some(h), Some(t)) = (&frame, self.header, self.tables.as_ref()) { + let values: Vec<_> = self + .channels + .iter_mut() + .zip(&frame.channels) + .enumerate() + .map(|(i, (c, d))| c.absolute(d, t, if frame.coupling && i == 1 { 2 } else { 1 })) + .collect(); + let levels = dequantise(frame, &values, t); + for (((c, d), (lv, y)), cur) in self.channels.iter_mut().zip(&frame.channels).zip(levels.iter().zip(&mut y)).zip(&mut current) { + let g = &d.grid; + let slots = (2 * g.t_env[0], 2 * g.t_env[g.num_env]); + let mut x_high: Box = Box::new([[Cx::default(); 64]; X_SLOTS]); + hf::generate(&c.x_low, t, &d.invf, &mut c.history, slots, &mut x_high); + let f = hf::Frame { x: &x_high, t, hdr: &h, g, lv, harmonic: d.harmonic.as_ref() }; + hf::adjust(&f, &mut c.history, y); + *cur = Some((t.kx, t.m, slots.0 + T_HF_ADJ, slots.1 + T_HF_ADJ)); + } + } + let mut out = vec![vec![0f32; 2 * 1024]; self.channels.len()]; + for (((c, y), out), current) in self.channels.iter_mut().zip(y).zip(&mut out).zip(current) { + for (l, block) in out.as_chunks_mut::<64>().0.iter_mut().enumerate() { + let i = l + T_HF_ADJ; + let mut x = [Cx::default(); 64]; + // the high band of this slot: this frame's envelopes, the last frame's reaching + // into this one, or none + let high = match current { + Some((kx, m, from, to)) if (from..to).contains(&i) => Some((&y[i], kx, m)), + _ if i + SLOTS < c.y_prev_end => Some((&c.y_prev[i + SLOTS], c.y_prev_range.0, c.y_prev_range.1)), + _ => None, + }; + let low = high.map_or(current.map_or(32, |cur| cur.0), |h| h.1); + x[..low].copy_from_slice(&c.x_low[i][..low]); + if let Some((row, kx, m)) = high { + x[kx..kx + m].copy_from_slice(&row[kx..kx + m]); + } + c.synthesis.process(&x, 64, block); + } + for s in out.iter_mut() { + *s /= PCM_SCALE; + } + c.y_prev = y; + (c.y_prev_range, c.y_prev_end) = current.map_or(((0, 0), 0), |cur| ((cur.0, cur.1), cur.3)); + } + out + } +} + +/// The header of [`test_payload`]. +#[cfg(test)] +pub(crate) const TEST_HEADER: Header = Header { + amp_res: true, + start_freq: 5, + stop_freq: 9, + xover_band: 0, + freq_scale: 2, + alter_scale: true, + noise_bands: 2, + limiter_bands: 2, + limiter_gains: 2, + interpol_freq: true, + smoothing_mode: true, +}; + +/// An SBR single channel element for tests at output rate `rate`: [`TEST_HEADER`] (3 dB steps, +/// start 5, stop 9, the default extra fields), then one FIXFIX envelope at high resolution with +/// band `k` at `env(k)` (1.5 dB steps, as a single FIXFIX envelope has) and every noise floor +/// value `noise`. +#[cfg(test)] +pub(crate) fn test_payload(rate: u32, env: impl Fn(usize) -> i32, noise: u32) -> Option> { + use craft_media::bitstream::BitWriter; + let t = FreqTables::new(&TEST_HEADER, rate)?; + let (env, n_q): (Vec, usize) = ((0..t.n_high()).map(env).collect(), t.n_q()); + let mut bw = BitWriter::new(); + bw.write_bits(1, 1); // bs_header_flag + for (v, n) in [(1, 1), (5, 4), (9, 4), (0, 3), (0, 2), (0, 1), (0, 1)] { + bw.write_bits(v, n); // amp_res, start, stop, xover, reserved, no extra fields + } + bw.write_bits(0, 1); // bs_data_extra + bw.write_bits(u32::from(bitstream::FIXFIX), 2); + bw.write_bits(0, 2); // one envelope + bw.write_bits(1, 1); // high resolution + bw.write_bits(0, 2); // frequency-direction envelope and noise floor + for _ in 0..n_q { + bw.write_bits(0, 2); // no inverse filtering + } + bw.write_bits(env[0] as u32, 7); + for w in env.windows(2) { + let (code, len) = tables::F_ENV_1_5DB[(w[1] - w[0] + 60) as usize]; + bw.write_bits(code, u32::from(len)); + } + bw.write_bits(noise, 5); + for _ in 1..n_q { + let (code, len) = tables::F_ENV_3_0DB[31]; + bw.write_bits(code, u32::from(len)); + } + bw.write_bits(0, 2); // no sinusoids, no extended data + Some(bw.finish()) +} + +#[cfg(test)] +mod tests { + use std::f64::consts::PI; + + use super::*; + + const RATE: u32 = 44_100; + + fn tables() -> FreqTables { + FreqTables::new(&TEST_HEADER, RATE).unwrap() + } + + /// Mean energy per DFT bin of `x` between `lo` and `hi` QMF channels (64 per half rate). + fn energy(x: &[f32], lo: usize, hi: usize) -> f64 { + let n = x.len(); + let (b0, b1) = (lo * n / 128, hi * n / 128); + let mut e = 0f64; + for b in b0 + 2..b1 - 2 { + let (mut re, mut im) = (0f64, 0f64); + for (i, &s) in x.iter().enumerate() { + let w = 0.5 - 0.5 * (2.0 * PI * i as f64 / n as f64).cos(); + let ph = 2.0 * PI * (b * i) as f64 / n as f64; + re += f64::from(s) * w * ph.cos(); + im += f64::from(s) * w * ph.sin(); + } + e += re * re + im * im; + } + e / (b1 - b0 - 4) as f64 + } + + fn run(e: &mut Element, data: &[u8], frames: usize) -> Vec { + let core = vec![vec![0f32; 1024]]; + let mut out = Vec::new(); + for _ in 0..frames { + let p = Payload { data, start: 0, end: 8 * data.len(), crc: false }; + let block = e.process(&core, Some(p)); + assert_eq!((block.len(), block[0].len()), (1, 2048)); + assert!(block[0].iter().all(|s| s.is_finite())); + out.extend_from_slice(&block[0]); + } + out + } + + /// Over a silent core the high band is the noise the envelope asks for: the upper bands, + /// 12 steps of 1.5 dB above the lower ones, come out 18 dB louder, and nothing below `kx`. + #[test] + fn the_high_band_follows_the_envelope() { + let t = tables(); + let mid = t.n_high() / 2; + let data = test_payload(RATE, |k| if k < mid { 30 } else { 42 }, 0).unwrap(); + let mut e = Element::new(1, RATE); + let out = run(&mut e, &data, 6); + let tail = &out[out.len() - 4096..]; + let (lower, upper) = (energy(tail, t.high[0], t.high[mid]), energy(tail, t.high[mid], t.high[t.n_high()])); + let step = 10.0 * (upper / lower).log10(); + assert!((step - 18.06).abs() < 1.5, "{step:.2} dB"); + let below = energy(tail, 1, t.kx - 1); + assert!(10.0 * (lower / below.max(1e-30)).log10() > 60.0); + // the same envelope without its header is not read: an element that has seen none + // passes the core through alone + let mut fresh = Element::new(1, RATE); + let mut no_header = data.clone(); + no_header[0] &= 0x7F; + assert!(run(&mut fresh, &no_header, 3).iter().all(|&s| s == 0.0)); + } + + /// Damaged SBR data (flipped bits, cut short, or noise) never panics and keeps the output + /// finite and of full length. + #[test] + fn damaged_sbr_data_is_survived() { + let good = test_payload(RATE, |k| 20 + k as i32, 3).unwrap(); + let mut seed = 0x1234_5678u32; + let mut next = || { + seed ^= seed << 13; + seed ^= seed >> 17; + seed ^= seed << 5; + seed + }; + let mut e = Element::new(1, RATE); + let mut pair = Element::new(2, RATE); + for i in 0..600 { + let mut data = good.clone(); + for _ in 0..1 + next() % 6 { + let bit = next() as usize % (8 * data.len()); + data[bit / 8] ^= 0x80 >> (bit % 8); + } + if i % 3 == 0 { + data.truncate(1 + next() as usize % data.len()); + } + if i % 5 == 0 { + data = (0..1 + next() % 40).map(|_| next() as u8).collect(); + data[0] |= 0x80; + } + run(&mut e, &data, 1); + let p = Payload { data: &data, start: 0, end: 8 * data.len(), crc: i % 2 == 0 }; + let out = pair.process(&[vec![0.1; 1024], vec![-0.1; 1024]], Some(p)); + assert!(out.iter().all(|c| c.len() == 2048 && c.iter().all(|s| s.is_finite()))); + } + } +} diff --git a/crates/audio/src/aac/sbr/qmf.rs b/crates/audio/src/aac/sbr/qmf.rs new file mode 100644 index 000000000..de947f303 --- /dev/null +++ b/crates/audio/src/aac/sbr/qmf.rs @@ -0,0 +1,155 @@ +//! The SBR filter banks (ISO/IEC 14496-3 §4.6.18.4): a 32-band complex QMF analysis bank at the +//! core rate and a 64-band complex QMF synthesis bank at the output rate, sharing the window `c`. +//! +//! Each bank is a polyphase window followed by a complex modulation: analysis +//! `2·exp(iπ(k + ½)(2n − ½)/64)`, synthesis `exp(iπ(k + ½)(2n − 251)/128)/64`. With the window's +//! indexing, the two phase offsets put the banks' combined response on its centre tap, so the low +//! band comes back unchanged (to about -60 dB, the window's own limit), 576 output samples late. +//! The synthesis bank holds back its last two output samples, which makes it 578: with the SBR +//! frame's lag, the 962 samples by which HE-AAC decoders (ffmpeg's among them) delay the core, and +//! which the priming encoders signal counts on. + +use std::f64::consts::PI; +use std::sync::OnceLock; + +use super::Cx; +use super::tables::QMF_WINDOW; + +/// Output samples by which analysis followed by synthesis delays the low band. +pub(crate) const DELAY: usize = 578; + +/// Modulation matrices, computed once. +struct Kernels { + /// Analysis, at `[k][n]`: 32 × 64. + analysis: Vec<[Cx; 64]>, + /// Synthesis, at `[n][k]`: 128 × 64. + synthesis: Vec<[Cx; 64]>, +} + +fn kernels() -> &'static Kernels { + static K: OnceLock = OnceLock::new(); + K.get_or_init(|| { + let cx = |phase: f64, scale: f64| Cx { re: (phase.cos() * scale) as f32, im: (phase.sin() * scale) as f32 }; + let analysis = (0..32).map(|k| std::array::from_fn(|n| cx(PI * (k as f64 + 0.5) * (2.0 * n as f64 - 0.5) / 64.0, 2.0))).collect(); + let synthesis = (0..128).map(|n| std::array::from_fn(|k| cx(PI * (k as f64 + 0.5) * (2.0 * n as f64 - 251.0) / 128.0, 1.0 / 64.0))).collect(); + Kernels { analysis, synthesis } + }) +} + +/// The 32-band analysis bank of one channel. +#[derive(Clone)] +pub(crate) struct Analysis { + /// The last 320 input samples, newest first. + x: Box<[f32; 320]>, +} + +impl Analysis { + pub(crate) fn new() -> Analysis { + Analysis { x: Box::new([0.0; 320]) } + } + + /// Split `32 · out.len()` input samples into `out.len()` slots of 32 subbands. + pub(crate) fn process(&mut self, input: &[f32], out: &mut [[Cx; 32]]) { + let k = kernels(); + for (block, slot) in input.as_chunks::<32>().0.iter().zip(out.iter_mut()) { + self.x.copy_within(0..288, 32); + for (n, &s) in block.iter().enumerate() { + self.x[31 - n] = s; + } + let mut u = [0f32; 64]; + for (n, u) in u.iter_mut().enumerate() { + *u = (0..5).map(|j| self.x[n + 64 * j] * QMF_WINDOW[2 * (n + 64 * j)]).sum(); + } + for (sb, row) in slot.iter_mut().zip(&k.analysis) { + let (mut re, mut im) = (0f32, 0f32); + for (u, m) in u.iter().zip(row) { + re += u * m.re; + im += u * m.im; + } + *sb = Cx { re, im }; + } + } + } +} + +/// The 64-band synthesis bank of one channel. +#[derive(Clone)] +pub(crate) struct Synthesis { + v: Box<[f32; 1280]>, + /// The last two output samples, which the next slot puts out first. + held: [f32; 2], +} + +impl Synthesis { + pub(crate) fn new() -> Synthesis { + Synthesis { v: Box::new([0.0; 1280]), held: [0.0; 2] } + } + + /// Merge one slot of 64 subbands into 64 output samples (`out[..64]`, of which it needs 64). + /// Bands from `bands` up are taken as zero. + pub(crate) fn process(&mut self, x: &[Cx; 64], bands: usize, out: &mut [f32]) { + let k = kernels(); + let bands = bands.min(64); + self.v.copy_within(0..1152, 128); + for (n, row) in k.synthesis.iter().enumerate() { + self.v[n] = x[..bands].iter().zip(row).map(|(x, m)| x.re * m.re - x.im * m.im).sum(); + } + let mut slot = [0f32; 66]; + slot[..2].copy_from_slice(&self.held); + for (n, o) in slot[2..].iter_mut().enumerate() { + let mut s = 0f32; + for i in 0..5 { + s += self.v[256 * i + n] * QMF_WINDOW[128 * i + n]; + s += self.v[256 * i + 192 + n] * QMF_WINDOW[128 * i + 64 + n]; + } + *o = s; + } + self.held = [slot[64], slot[65]]; + for (o, s) in out.iter_mut().zip(&slot[..64]) { + *o = *s; + } + } +} + +#[cfg(test)] +mod tests { + use super::super::SLOTS; + use super::*; + + fn tones(t: f64) -> f64 { + [0.0371, 0.0913, 0.1527, 0.2211, 0.2903, 0.3519, 0.4127].iter().enumerate().map(|(j, f)| (2.0 * PI * f * t + j as f64).sin() * 0.1).sum() + } + + /// Tones across the core band through analysis and synthesis (low 32 bands only) come out at + /// twice the rate, [`DELAY`] samples late, with the error 60 dB down. + #[test] + fn analysis_then_synthesis_reconstructs_the_input() { + let input: Vec = (0..8 * 1024).map(|i| tones(i as f64) as f32).collect(); + let (mut a, mut s) = (Analysis::new(), Synthesis::new()); + let mut out = Vec::new(); + let mut slots = [[Cx::default(); 32]; SLOTS]; + for frame in input.as_chunks::<1024>().0 { + a.process(frame, &mut slots); + for slot in &slots { + let mut x = [Cx::default(); 64]; + x[..32].copy_from_slice(slot); + let mut o = [0f32; 64]; + s.process(&x, 32, &mut o); + out.extend_from_slice(&o); + } + } + assert_eq!(out.len(), 2 * input.len()); + let snr_at = |delay: usize| { + let (mut err, mut sig) = (0f64, 0f64); + for (i, &o) in out.iter().enumerate().skip(3000) { + let want = tones((i as f64 - delay as f64) / 2.0); + err += (o as f64 - want).powi(2); + sig += want * want; + } + 10.0 * (sig / err).log10() + }; + let snr = snr_at(DELAY); + assert!(snr > 60.0, "reconstruction SNR {snr:.1} dB"); + assert!(snr_at(DELAY - 1) < 40.0 && snr_at(DELAY + 1) < 40.0); + } +} diff --git a/crates/audio/src/aac/sbr/tables.rs b/crates/audio/src/aac/sbr/tables.rs new file mode 100644 index 000000000..7a648120e --- /dev/null +++ b/crates/audio/src/aac/sbr/tables.rs @@ -0,0 +1,357 @@ +//! SBR tables (ETSI ES 201 980 V1.2.2 Annex I, the SBR tables it shares with ISO/IEC 14496-3 §4.A.6). +//! +//! Transcribed from the specification's tables, which give the window to 14 digits (more than an +//! `f32` holds). Annex I misprints the labels of `c[297]` and `c[305]`; the values are in order. +//! Each Huffman table is a complete prefix code (see the tests in `sbr::bitstream`). + +#![allow(clippy::excessive_precision)] + +/// The QMF bank window `c` (640 coefficients). +#[rustfmt::skip] +pub(crate) static QMF_WINDOW: [f32; 640] = [ + -0.00055252865047, -0.00056176925738, -0.00049475180896, -0.00048752279712, -0.00048937912498, -0.00050407143497, -0.00052265642972, -0.00054665656337, + -0.00056778025613, -0.00058709304852, -0.00061327473938, -0.00063124935319, -0.00065403333621, -0.00067776907764, -0.00069416146273, -0.00071577364744, + -0.00072550431222, -0.00074409418541, -0.00074905980532, -0.00076813719270, -0.00077248485949, -0.00078343322877, -0.00077798694927, -0.00078036647100, + -0.00078014496257, -0.00077579773310, -0.00076307935757, -0.00075300014201, -0.00073193571525, -0.00072153919876, -0.00069179375372, -0.00066504150893, + -0.00063415949025, -0.00059461189330, -0.00055645763906, -0.00051455722108, -0.00046063254803, -0.00040951214522, -0.00035011758756, -0.00028969811748, + -0.00020983373440, -0.00014463809349, -0.00006173344072, 0.00001349497418, 0.00010943831274, 0.00020430170688, 0.00029495311041, 0.00040265402160, + 0.00051073884952, 0.00062393761391, 0.00074580258865, 0.00086084433262, 0.00098859883015, 0.00112501551307, 0.00125778846475, 0.00139024948272, + 0.00154432198471, 0.00168680832531, 0.00183482654224, 0.00198411407369, 0.00214615835557, 0.00230172547746, 0.00246256169126, 0.00262017586902, + 0.00278704643465, 0.00294694477165, 0.00311254206525, 0.00327396134847, 0.00344188741828, 0.00360082681231, 0.00376039229104, 0.00392074323703, + 0.00408197531935, 0.00422642692270, 0.00437307196781, 0.00452098527825, 0.00466064606118, 0.00479325608498, 0.00491376035745, 0.00503930226013, + 0.00514073539032, 0.00524611661324, 0.00534716811982, 0.00541967759307, 0.00548760401507, 0.00554757145088, 0.00559380230045, 0.00562206432097, + 0.00564551969164, 0.00563891995151, 0.00562661141932, 0.00559171286630, 0.00554043639400, 0.00547537830770, 0.00538389758970, 0.00527157587272, + 0.00513822754514, 0.00498396877629, 0.00481094690600, 0.00460395301471, 0.00438018617447, 0.00412516423270, 0.00384564081246, 0.00354012465507, + 0.00320918858098, 0.00284467578623, 0.00245085400321, 0.00202741761850, 0.00157846825768, 0.00109023290512, 0.00058322642480, 0.00002760451905, + -0.00054642808664, -0.00115681355227, -0.00180394725893, -0.00248267236449, -0.00319337783900, -0.00394011240522, -0.00472225962400, -0.00553372111088, + -0.00637922932685, -0.00726158168517, -0.00817982333726, -0.00913253296085, -0.01011502154986, -0.01113155480321, -0.01218499959508, -0.01327182200351, + 0.01439046660792, 0.01554055533423, 0.01673247129989, 0.01794333813443, 0.01918724313698, 0.02045317933555, 0.02174675502535, 0.02306801692862, + 0.02441609920285, 0.02578758475467, 0.02718594296329, 0.02860721736385, 0.03005026574279, 0.03150176087389, 0.03297540810337, 0.03446209487686, + 0.03596975605542, 0.03748128504252, 0.03900536794745, 0.04053491705584, 0.04206490946367, 0.04360975421304, 0.04514884056413, 0.04668430272642, + 0.04821657200672, 0.04973857556014, 0.05125561555216, 0.05276307465207, 0.05424527683589, 0.05571736482138, 0.05716164501299, 0.05859156836260, + 0.05998374801761, 0.06134551717207, 0.06268578081172, 0.06397158980681, 0.06522471064380, 0.06643675122104, 0.06760759851228, 0.06870438283512, + 0.06976302447127, 0.07076287107266, 0.07170026731102, 0.07256825833083, 0.07336202550803, 0.07410036424342, 0.07474525581194, 0.07531373362019, + 0.07580083586584, 0.07619924793396, 0.07649921704119, 0.07670934904245, 0.07681739756964, 0.07682300113923, 0.07672049241746, 0.07650507183194, + 0.07617483218536, 0.07573057565061, 0.07515762552870, 0.07446643947564, 0.07364060057620, 0.07267746427299, 0.07158263647903, 0.07035330735093, + 0.06896640131951, 0.06745250215166, 0.06576906686508, 0.06394448059633, 0.06196027790387, 0.05981665708090, 0.05751526919867, 0.05504600343009, + 0.05240938217366, 0.04959786763445, 0.04663033051701, 0.04347687821958, 0.04014582784127, 0.03664181168133, 0.03295839306691, 0.02908240060125, + 0.02503075618909, 0.02079970728622, 0.01637012582228, 0.01176238327857, 0.00696368621617, 0.00197656014503, -0.00320868968304, -0.00857117491366, + -0.01412888273558, -0.01988341292573, -0.02582272888064, -0.03195312745332, -0.03827765720822, -0.04478068215856, -0.05148041767934, -0.05837053268336, + -0.06544098531359, -0.07269433008129, -0.08013729344279, -0.08775475365593, -0.09555333528914, -0.10353295311463, -0.11168269317730, -0.12000779846800, + -0.12850028503878, -0.13715517611934, -0.14597664911870, -0.15496070710605, -0.16409588556669, -0.17338081721706, -0.18281725485142, -0.19239667457267, + -0.20212501768103, -0.21197358538056, -0.22196526964149, -0.23206908706791, -0.24230168845974, -0.25264803095722, -0.26310532994603, -0.27366340405625, + -0.28432141891085, -0.29507167170646, -0.30590985751916, -0.31682789136456, -0.32781137272105, -0.33887226938665, -0.34999141229310, -0.36115899031355, + 0.37237955463061, 0.38363500139043, 0.39492117615675, 0.40623176767625, 0.41756968968409, 0.42891199207373, 0.44025537543665, 0.45159965356824, + 0.46293080852757, 0.47424532146115, 0.48552530911099, 0.49677082545707, 0.50798175000434, 0.51912349702391, 0.53022408956855, 0.54125534487322, + 0.55220512585061, 0.56307891401370, 0.57385241316923, 0.58454032354679, 0.59511230862496, 0.60557835389180, 0.61591099320291, 0.62612426956055, + 0.63619801077286, 0.64612696959461, 0.65590163024671, 0.66551398801627, 0.67496631901712, 0.68423532934598, 0.69332823767032, 0.70223887193539, + 0.71094104263095, 0.71944626349561, 0.72774489002994, 0.73582117582769, 0.74368278636488, 0.75131374561237, 0.75870807608242, 0.76586748650939, + 0.77277808813327, 0.77942875190216, 0.78583531203920, 0.79197358416424, 0.79784664137700, 0.80344857518505, 0.80876950044491, 0.81381912706217, + 0.81857760046468, 0.82304198905409, 0.82722753473360, 0.83110384571520, 0.83469373618402, 0.83797173378865, 0.84095413924722, 0.84362382812005, + 0.84598184698206, 0.84803157770763, 0.84978051984268, 0.85119715249343, 0.85230470352147, 0.85310209497017, 0.85357205739107, 0.85373856005937, + 0.85357205739107, 0.85310209497017, 0.85230470352147, 0.85119715249343, 0.84978051984268, 0.84803157770763, 0.84598184698206, 0.84362382812005, + 0.84095413924722, 0.83797173378865, 0.83469373618402, 0.83110384571520, 0.82722753473360, 0.82304198905409, 0.81857760046468, 0.81381912706217, + 0.80876950044491, 0.80344857518505, 0.79784664137700, 0.79197358416424, 0.78583531203920, 0.77942875190216, 0.77277808813327, 0.76586748650939, + 0.75870807608242, 0.75131374561237, 0.74368278636488, 0.73582117582769, 0.72774489002994, 0.71944626349561, 0.71094104263095, 0.70223887193539, + 0.69332823767032, 0.68423532934598, 0.67496631901712, 0.66551398801627, 0.65590163024671, 0.64612696959461, 0.63619801077286, 0.62612426956055, + 0.61591099320291, 0.60557835389180, 0.59511230862496, 0.58454032354679, 0.57385241316923, 0.56307891401370, 0.55220512585061, 0.54125534487322, + 0.53022408956855, 0.51912349702391, 0.50798175000434, 0.49677082545707, 0.48552530911099, 0.47424532146115, 0.46293080852757, 0.45159965356824, + 0.44025537543665, 0.42891199207373, 0.41756968968409, 0.40623176767625, 0.39492117615675, 0.38363500139043, 0.37237955463061, 0.36115899031355, + -0.34999141229310, -0.33887226938665, -0.32781137272105, -0.31682789136456, -0.30590985751916, -0.29507167170646, -0.28432141891085, -0.27366340405625, + -0.26310532994603, -0.25264803095722, -0.24230168845974, -0.23206908706791, -0.22196526964149, -0.21197358538056, -0.20212501768103, -0.19239667457267, + -0.18281725485142, -0.17338081721706, -0.16409588556669, -0.15496070710605, -0.14597664911870, -0.13715517611934, -0.12850028503878, -0.12000779846800, + -0.11168269317730, -0.10353295311463, -0.09555333528914, -0.08775475365593, -0.08013729344279, -0.07269433008129, -0.06544098531359, -0.05837053268336, + -0.05148041767934, -0.04478068215856, -0.03827765720822, -0.03195312745332, -0.02582272888064, -0.01988341292573, -0.01412888273558, -0.00857117491366, + -0.00320868968304, 0.00197656014503, 0.00696368621617, 0.01176238327857, 0.01637012582228, 0.02079970728622, 0.02503075618909, 0.02908240060125, + 0.03295839306691, 0.03664181168133, 0.04014582784127, 0.04347687821958, 0.04663033051701, 0.04959786763445, 0.05240938217366, 0.05504600343009, + 0.05751526919867, 0.05981665708090, 0.06196027790387, 0.06394448059633, 0.06576906686508, 0.06745250215166, 0.06896640131951, 0.07035330735093, + 0.07158263647903, 0.07267746427299, 0.07364060057620, 0.07446643947564, 0.07515762552870, 0.07573057565061, 0.07617483218536, 0.07650507183194, + 0.07672049241746, 0.07682300113923, 0.07681739756964, 0.07670934904245, 0.07649921704119, 0.07619924793396, 0.07580083586584, 0.07531373362019, + 0.07474525581194, 0.07410036424342, 0.07336202550803, 0.07256825833083, 0.07170026731102, 0.07076287107266, 0.06976302447127, 0.06870438283512, + 0.06760759851228, 0.06643675122104, 0.06522471064380, 0.06397158980681, 0.06268578081172, 0.06134551717207, 0.05998374801761, 0.05859156836260, + 0.05716164501299, 0.05571736482138, 0.05424527683589, 0.05276307465207, 0.05125561555216, 0.04973857556014, 0.04821657200672, 0.04668430272642, + 0.04514884056413, 0.04360975421304, 0.04206490946367, 0.04053491705584, 0.03900536794745, 0.03748128504252, 0.03596975605542, 0.03446209487686, + 0.03297540810337, 0.03150176087389, 0.03005026574279, 0.02860721736385, 0.02718594296329, 0.02578758475467, 0.02441609920285, 0.02306801692862, + 0.02174675502535, 0.02045317933555, 0.01918724313698, 0.01794333813443, 0.01673247129989, 0.01554055533423, 0.01439046660792, 0.01327182200351, + -0.01218499959508, -0.01113155480321, -0.01011502154986, -0.00913253296085, -0.00817982333726, -0.00726158168517, -0.00637922932685, -0.00553372111088, + -0.00472225962400, -0.00394011240522, -0.00319337783900, -0.00248267236449, -0.00180394725893, -0.00115681355227, -0.00054642808664, 0.00002760451905, + 0.00058322642480, 0.00109023290512, 0.00157846825768, 0.00202741761850, 0.00245085400321, 0.00284467578623, 0.00320918858098, 0.00354012465507, + 0.00384564081246, 0.00412516423270, 0.00438018617447, 0.00460395301471, 0.00481094690600, 0.00498396877629, 0.00513822754514, 0.00527157587272, + 0.00538389758970, 0.00547537830770, 0.00554043639400, 0.00559171286630, 0.00562661141932, 0.00563891995151, 0.00564551969164, 0.00562206432097, + 0.00559380230045, 0.00554757145088, 0.00548760401507, 0.00541967759307, 0.00534716811982, 0.00524611661324, 0.00514073539032, 0.00503930226013, + 0.00491376035745, 0.00479325608498, 0.00466064606118, 0.00452098527825, 0.00437307196781, 0.00422642692270, 0.00408197531935, 0.00392074323703, + 0.00376039229104, 0.00360082681231, 0.00344188741828, 0.00327396134847, 0.00311254206525, 0.00294694477165, 0.00278704643465, 0.00262017586902, + 0.00246256169126, 0.00230172547746, 0.00214615835557, 0.00198411407369, 0.00183482654224, 0.00168680832531, 0.00154432198471, 0.00139024948272, + 0.00125778846475, 0.00112501551307, 0.00098859883015, 0.00086084433262, 0.00074580258865, 0.00062393761391, 0.00051073884952, 0.00040265402160, + 0.00029495311041, 0.00020430170688, 0.00010943831274, 0.00001349497418, -0.00006173344072, -0.00014463809349, -0.00020983373440, -0.00028969811748, + -0.00035011758756, -0.00040951214522, -0.00046063254803, -0.00051455722108, -0.00055645763906, -0.00059461189330, -0.00063415949025, -0.00066504150893, + -0.00069179375372, -0.00072153919876, -0.00073193571525, -0.00075300014201, -0.00076307935757, -0.00077579773310, -0.00078014496257, -0.00078036647100, + -0.00077798694927, -0.00078343322877, -0.00077248485949, -0.00076813719270, -0.00074905980532, -0.00074409418541, -0.00072550431222, -0.00071577364744, + -0.00069416146273, -0.00067776907764, -0.00065403333621, -0.00063124935319, -0.00061327473938, -0.00058709304852, -0.00056778025613, -0.00054665656337, + -0.00052265642972, -0.00050407143497, -0.00048937912498, -0.00048752279712, -0.00049475180896, -0.00056176925738, -0.00055252865047, 0.00000000000000, +]; + +/// The SBR noise table `V` (Table I.13): 512 unit-variance complex values (re, im). +#[rustfmt::skip] +pub(crate) static NOISE: [[f32; 2]; 512] = [ + [-0.99948153278296, -0.59483417516607], [0.97113454393991, -0.67528515225647], [0.14130051758487, -0.95090983575689], [-0.47005496701697, -0.37340549728647], + [0.80705063769351, 0.29653668284408], [-0.38981478896926, 0.89572605717087], [-0.01053049862020, -0.66959058036166], [-0.91266367957293, -0.11522938140034], + [0.54840422910309, 0.75221367176302], [0.40009252867955, -0.98929400334421], [-0.99867974711855, -0.88147068645358], [-0.95531076805040, 0.90908757154593], + [-0.45725933317144, -0.56716323646760], [-0.72929675029275, -0.98008272727324], [0.75622801399036, 0.20950329995549], [0.07069442601050, -0.78247898470706], + [0.74496252926055, -0.91169004445807], [-0.96440182703856, -0.94739918296622], [0.30424629369539, -0.49438267012479], [0.66565033746925, 0.64652935542491], + [0.91697008020594, 0.17514097332009], [-0.70774918760427, 0.52548653416543], [-0.70051415345560, -0.45340028808763], [-0.99496513054797, -0.90071908066973], + [0.98164490790123, -0.77463155528697], [-0.54671580548181, -0.02570928536004], [-0.01689629065389, 0.00287506445732], [-0.86110349531986, 0.42548583726477], + [-0.98892980586032, -0.87881132267556], [0.51756627678691, 0.66926784710139], [-0.99635026409640, -0.58107730574765], [-0.99969370862163, 0.98369989360250], + [0.55266258627194, 0.59449057465591], [0.34581177741673, 0.94879421061866], [0.62664209577999, -0.74402970906471], [-0.77149701404973, -0.33883658042801], + [-0.91592244254432, 0.03687901376713], [-0.76285492357887, -0.91371867919124], [0.79788337195331, -0.93180971199849], [0.54473080610200, -0.11919206037186], + [-0.85639281671058, 0.42429854760451], [-0.92882402971423, 0.27871809078609], [-0.11708371046774, -0.99800843444966], [0.21356749817493, -0.90716295627033], + [-0.76191692573909, 0.99768118356265], [0.98111043100884, -0.95854459734407], [-0.85913269895572, 0.95766566168880], [-0.93307242253692, 0.49431757696466], + [0.30485754879632, -0.70540034357529], [0.85289650925190, 0.46766131791044], [0.91328082618125, -0.99839597361769], [-0.05890199924154, 0.70741827819497], + [0.28398686150148, 0.34633555702188], [0.95258164539612, -0.54893416026939], [-0.78566324168507, -0.75568541079691], [-0.95789495447877, -0.20423194696966], + [0.82411158711197, 0.96654618432562], [-0.65185446735885, -0.88734990773289], [-0.93643603134666, 0.99870790442385], [0.91427159529618, -0.98290505544444], + [-0.70395684036886, 0.58796798221039], [0.00563771969365, 0.61768196727244], [0.89065051931895, 0.52783352697585], [-0.68683707712762, 0.80806944710339], + [0.72165342518718, -0.69259857349564], [-0.62928247730667, 0.13627037407335], [0.29938434065514, -0.46051329682246], [-0.91781958879280, -0.74012716684186], + [0.99298717043688, 0.40816610075661], [0.82368298622748, -0.74036047190173], [-0.98512833386833, -0.99972330709594], [-0.95915368242257, -0.99237800466040], + [-0.21411126572790, -0.93424819052545], [-0.68821476106884, -0.26892306315457], [0.91851997982317, 0.09358228901785], [-0.96062769559127, 0.36099095133739], + [0.51646184922287, -0.71373332873917], [0.61130721139669, 0.46950141175917], [0.47336129371299, -0.27333178296162], [0.90998308703519, 0.96715662938132], + [0.44844799194357, 0.99211574628306], [0.66614891079092, 0.96590176169121], [0.74922239129237, -0.89879858826087], [-0.99571588506485, 0.52785521494349], + [0.97401082477563, -0.16855870075190], [0.72683747733879, -0.48060774432251], [0.95432193457128, 0.68849603408441], [-0.72962208425191, -0.76608443420917], + [-0.85359479233537, 0.88738125901579], [-0.81412430338535, -0.97480768049637], [-0.87930772356786, 0.74748307690436], [-0.71573331064977, -0.98570608178923], + [0.83524300028228, 0.83702537075163], [-0.48086065601423, -0.98848504923531], [0.97139128574778, 0.80093621198236], [0.51992825347895, 0.80247631400510], + [-0.00848591195325, -0.76670128000486], [-0.70294374303036, 0.55359910445577], [-0.95894428168140, -0.43265504344783], [0.97079252950321, 0.09325857238682], + [-0.92404293670797, 0.85507704027855], [-0.69506469500450, 0.98633412625459], [0.26559203620024, 0.73314307966524], [0.28038443336943, 0.14537913654427], + [-0.74138124825523, 0.99310339807762], [-0.01752795995444, -0.82616635284178], [-0.55126773094930, -0.98898543862153], [0.97960898850996, -0.94021446752851], + [-0.99196309146936, 0.67019017358456], [-0.67684928085260, 0.12631491649378], [0.09140039465500, -0.20537731453108], [-0.71658965751996, -0.97788200391224], + [0.81014640078925, 0.53722648362443], [0.40616991671205, -0.26469008598449], [-0.67680188682972, 0.94502052337695], [0.86849774348749, -0.18333598647899], + [-0.99500381284851, -0.02634122068550], [0.84329189340667, 0.10406957462213], [-0.09215968531446, 0.69540012101253], [0.99956173327206, -0.12358542001404], + [-0.79732779473535, -0.91582524736159], [0.96349973642406, 0.96640458041000], [-0.79942778496547, 0.64323902822857], [-0.11566039853896, 0.28587846253726], + [-0.39922954514662, 0.94129601616966], [0.99089197565987, -0.92062625581587], [0.28631285179909, -0.91035047143603], [-0.83302725605608, -0.67330410892084], + [0.95404443402072, 0.49162765398743], [-0.06449863579434, 0.03250560813135], [-0.99575054486311, 0.42389784469507], [-0.65501142790847, 0.82546114655624], + [-0.81254441908887, -0.51627234660629], [-0.99646369485481, 0.84490533520752], [0.00287840603348, 0.64768261158166], [0.70176989408455, -0.20453028573322], + [0.96361882270190, 0.40706967140989], [-0.68883758192426, 0.91338958840772], [-0.34875585502238, 0.71472290693300], [0.91980081243087, 0.66507455644919], + [-0.99009048343881, 0.85868021604848], [0.68865791458395, 0.55660316809678], [-0.99484402129368, -0.20052559254934], [0.94214511408023, -0.99696425367461], + [-0.67414626793544, 0.49548221180078], [-0.47339353684664, -0.85904328834047], [0.14323651387360, -0.94145598222488], [-0.29268293575672, 0.05759224927952], + [0.43793861458754, -0.78904969892724], [-0.36345126374441, 0.64874435357162], [-0.08750604656825, 0.97686944362527], [-0.96495267812511, -0.53960305946511], + [0.55526940659947, 0.78891523734774], [0.73538215752630, 0.96452072373404], [-0.30889773919437, -0.80664389776860], [0.03574995626194, -0.97325616900959], + [0.98720684660488, 0.48409133691962], [-0.81689296271203, -0.90827703628298], [0.67866860118215, 0.81284503870856], [-0.15808569732583, 0.85279555024382], + [0.80723395114371, -0.24717418514605], [0.47788757329038, -0.46333147839295], [0.96367554763201, 0.38486749303242], [-0.99143875716818, -0.24945277239809], + [0.83081876925833, -0.94780851414763], [-0.58753191905341, 0.01290772389163], [0.95538108220960, -0.85557052096538], [-0.96490920476211, -0.64020970923102], + [-0.97327101028521, 0.12378128133110], [0.91400366022124, 0.57972471346930], [-0.99925837363824, 0.71084847864067], [-0.86875903507313, -0.20291699203564], + [-0.26240034795124, -0.68264554369108], [-0.24664412953388, -0.87642273115183], [0.02416275806869, 0.27192914288905], [0.82068619590515, -0.85087787994476], + [0.88547373760759, -0.89636802901469], [-0.18173078152226, -0.26152145156800], [0.09355476558534, 0.54845123045604], [-0.54668414224090, 0.95980774020221], + [0.37050990604091, -0.59910140383171], [-0.70373594262891, 0.91227665827081], [-0.34600785879594, -0.99441426144200], [-0.68774481731008, -0.30238837956299], + [-0.26843291251234, 0.83115668004362], [0.49072334613242, -0.45359708737775], [0.38975993093975, 0.95515358099121], [-0.97757125224150, 0.05305894580606], + [-0.17325552859616, -0.92770672250494], [0.99948035025744, 0.58285545563426], [-0.64946246527458, 0.68645507104960], [-0.12016920576437, -0.57147322153312], + [-0.58947456517751, -0.34847132454388], [-0.41815140454465, 0.16276422358861], [0.99885650204884, 0.11136095490444], [-0.56649614128386, -0.90494866361587], + [0.94138021032330, 0.35281916733018], [-0.75725076534641, 0.53650549640587], [0.20541973692630, -0.94435144369918], [0.99980371023351, 0.79835913565599], + [0.29078277605775, 0.35393777921520], [-0.62858772103030, 0.38765693387102], [0.43440904467688, -0.98546330463232], [-0.98298583762390, 0.21021524625209], + [0.19513029146934, -0.94239832251867], [-0.95476662400101, 0.98364554179143], [0.93379635304810, -0.70881994583682], [-0.85235410573336, -0.08342347966410], + [-0.86425093011245, -0.45795025029466], [0.38879779059045, 0.97274429344593], [0.92045124735495, -0.62433652524220], [0.89162532251878, 0.54950955570563], + [-0.36834336949252, 0.96458298020975], [0.93891760988045, -0.89968353740388], [0.99267657565094, -0.03757034316958], [-0.94063471614176, 0.41332338538963], + [0.99740224117019, -0.16830494996370], [-0.35899413170555, -0.46633226649613], [0.05237237274947, -0.25640361602661], [0.36703583957424, -0.38653265641875], + [0.91653180367913, -0.30587628726597], [0.69000803499316, 0.90952171386132], [-0.38658751133527, 0.99501571208985], [-0.29250814029851, 0.37444994344615], + [-0.60182204677608, 0.86779651036123], [-0.97418588163217, 0.96468523666475], [0.88461574003963, 0.57508405276414], [0.05198933055162, 0.21269661669964], + [-0.53499621979720, 0.97241553731237], [-0.49429560226497, 0.98183865291903], [-0.98935142339139, -0.40249159006933], [-0.98081380091130, -0.72856895534041], + [-0.27338148835532, 0.99950922447209], [0.06310802338302, -0.54539587529618], [-0.20461677199539, -0.14209977628489], [0.66223843141647, 0.72528579940326], + [-0.84764345483665, 0.02372316801261], [-0.89039863483811, 0.88866581484602], [0.95903308477986, 0.76744927173873], [0.73504123909879, -0.03747203173192], + [-0.31744434966056, -0.36834111883652], [-0.34110827591623, 0.40211222807691], [0.47803883714199, -0.39423219786288], [0.98299195879514, 0.01989791390047], + [-0.30963073129751, -0.18076720599336], [0.99992588229018, -0.26281872094289], [-0.93149731080767, -0.98313162570490], [0.99923472302773, -0.80142993767554], + [-0.26024169633417, -0.75999759855752], [-0.35712514743563, 0.19298963768574], [-0.99899084509530, 0.74645156992493], [0.86557171579452, 0.55593866696299], + [0.33408042438752, 0.86185953874709], [0.99010736374716, 0.04602397576623], [-0.66694269691195, -0.91643611810148], [0.64016792079480, 0.15649530836856], + [0.99570534804836, 0.45844586038111], [-0.63431466947340, 0.21079116459234], [-0.07706847005931, -0.89581437101329], [0.98590090577724, 0.88241721133981], + [0.80099335254678, -0.36851896710853], [0.78368131392666, 0.45506999802597], [0.08707806671691, 0.80938994918745], [-0.86811883080712, 0.39347308654705], + [-0.39466529740375, -0.66809432114456], [0.97875325649683, -0.72467840967746], [-0.95038560288864, 0.89563219587625], [0.17005239424212, 0.54683053962658], + [-0.76910792026848, -0.96226617549298], [0.99743281016846, 0.42697157037567], [0.95437383549973, 0.97002324109952], [0.99578905365569, -0.54106826257356], + [0.28058259829990, -0.85361420634036], [0.85256524470573, -0.64567607735589], [-0.50608540105128, -0.65846015480300], [-0.97210735183243, -0.23095213067791], + [0.95424048234441, -0.99240147091219], [-0.96926570524023, 0.73775654896574], [0.30872163214726, 0.41514960556126], [-0.24523839572639, 0.63206633394807], + [-0.33813265086024, -0.38661779441897], [-0.05826828420146, -0.06940774188029], [-0.22898461455054, 0.97054853316316], [-0.18509915019881, 0.47565762892084], + [-0.10488238045009, -0.87769947402394], [-0.71886586182037, 0.78030982480538], [0.99793873738654, 0.90041310491497], [0.57563307626120, -0.91034337352097], + [0.28909646383717, 0.96307783970534], [0.42188998312520, 0.48148651230437], [0.93335049681047, -0.43537023883588], [-0.97087374418267, 0.86636445711364], + [0.36722871286923, 0.65291654172961], [-0.81093025665696, 0.08778370229363], [-0.26240603062237, -0.92774095379098], [0.83996497984604, 0.55839849139647], + [-0.99909615720225, -0.96024605713970], [0.74649464155061, 0.12144893606462], [-0.74774595569805, -0.26898062008959], [0.95781667469567, -0.79047927052628], + [0.95472308713099, -0.08588776019550], [0.48708332746299, 0.99999041579432], [0.46332038247497, 0.10964126185063], [-0.76497004940162, 0.89210929242238], + [0.57397389364339, 0.35289703373760], [0.75374316974495, 0.96705214651335], [-0.59174397685714, -0.89405370422752], [0.75087906691890, -0.29612672982396], + [-0.98607857336230, 0.25034911730023], [-0.40761056640505, -0.90045573444695], [0.66929266740477, 0.98629493401748], [-0.97463695257310, -0.00190223301301], + [0.90145509409859, 0.99781390365446], [-0.87259289048043, 0.99233587353666], [-0.91529461447692, -0.15698707534206], [-0.03305738840705, -0.37205262859764], + [0.07223051368337, -0.88805001733626], [0.99498012188353, 0.97094358113387], [-0.74904939500519, 0.99985483641521], [0.04585228574211, 0.99812337444082], + [-0.89054954257993, -0.31791913188064], [-0.83782144651251, 0.97637632547466], [0.33454804933804, -0.86231516800408], [-0.99707579362824, 0.93237990079441], + [-0.22827527843994, 0.18874759397997], [0.67248046289143, -0.03646211390569], [-0.05146538187944, -0.92599700120679], [0.99947295749905, 0.93625229707912], + [0.66951124390363, 0.98905825623893], [-0.99602956559179, -0.44654715757688], [0.82104905483590, 0.99540741724928], [0.99186510988782, 0.72023001312947], + [-0.65284592392918, 0.52186723253637], [0.93885443798188, -0.74895312615259], [0.96735248738388, 0.90891816978629], [-0.22225968841114, 0.57124029781228], + [-0.44132783753414, -0.92688840659280], [-0.85694974219574, 0.88844532719844], [0.91783042091762, -0.46356892383970], [0.72556974415690, -0.99899555770747], + [-0.99711581834508, 0.58211560180426], [0.77638976371966, 0.94321834873819], [0.07717324253925, 0.58638399856595], [-0.56049829194163, 0.82522301569036], + [0.98398893639988, 0.39467440420569], [0.47546946844938, 0.68613044836811], [0.65675089314631, 0.18331637134880], [0.03273375457980, -0.74933109564108], + [-0.38684144784738, 0.51337349030406], [-0.97346267944545, -0.96549364384098], [-0.53282156061942, -0.91423265091354], [0.99817310731176, 0.61133572482148], + [-0.50254500772635, -0.88829338134294], [0.01995873238855, 0.85223515096765], [0.99930381973804, 0.94578896296649], [0.82907767600783, -0.06323442598128], + [-0.58660709669728, 0.96840773806582], [-0.17573736667267, -0.48166920859485], [0.83434292401346, -0.13023450646997], [0.05946491307025, 0.20511047074866], + [0.81505484574602, -0.94685947861369], [-0.44976380954860, 0.40894572671545], [-0.89746474625671, 0.99846578838537], [0.39677256130792, -0.74854668609359], + [-0.07588948563079, 0.74096214084170], [0.76343198951445, 0.41746629422634], [-0.74490104699626, 0.94725911744610], [0.64880119792759, 0.41336660830571], + [0.62319537462542, -0.93098313552599], [0.42215817594807, -0.07712787385208], [0.02704554141885, -0.05417518053666], [0.80001773566818, 0.91542195141039], + [-0.79351832348816, -0.36208897989136], [0.63872359151636, 0.08128252493444], [0.52890520960295, 0.60048872455592], [0.74238552914587, 0.04491915291044], + [0.99096131449250, -0.19451182854402], [-0.80412329643109, -0.88513818199457], [-0.64612616129736, 0.72198674804544], [0.11657770663191, -0.83662833815041], + [-0.95053182488101, -0.96939905138082], [-0.62228872928622, 0.82767262846661], [0.03004475787316, -0.99738896333384], [-0.97987214341034, 0.36526129686425], + [-0.99986980746200, -0.36021610299715], [0.89110648599879, -0.97894250343044], [0.10407960510582, 0.77357793811619], [0.95964737821728, -0.35435818285502], + [0.50843233159162, 0.96107691266205], [0.17006334670615, -0.76854025314829], [0.25872675063360, 0.99893303933816], [-0.01115998681937, 0.98496019742444], + [-0.79598702973261, 0.97138411318894], [-0.99264708948101, -0.99542822402536], [-0.99829663752818, 0.01877138824311], [-0.70801016548184, 0.33680685948117], + [-0.70467057786826, 0.93272777501857], [0.99846021905254, -0.98725746254433], [-0.63364968534650, -0.16473594423746], [-0.16258217500792, -0.95939125400802], + [-0.43645594360633, -0.94805030113284], [-0.99848471702976, 0.96245166923809], [-0.16796458968998, -0.98987511890470], [-0.87979225745213, -0.71725725041680], + [0.44183099021786, -0.93568974498761], [0.93310180125532, -0.99913308068246], [-0.93941931782002, -0.56409379640356], [-0.88590003188677, 0.47624600491382], + [0.99971463703691, -0.83889954253462], [-0.75376385639978, 0.00814643438625], [0.93887685615875, -0.11284528204636], [0.85126435782309, 0.52349251543547], + [0.39701421446381, 0.81779634174316], [-0.37024464187437, -0.87071656222959], [-0.36024828242896, 0.34655735648287], [-0.93388812549209, -0.84476541096429], + [-0.65298804552119, -0.18439575450921], [0.11960319006843, 0.99899346780168], [0.94292565553160, 0.83163906518293], [0.75081145286948, -0.35533223142265], + [0.56721979748394, -0.24076836414499], [0.46857766746029, -0.30140233457198], [0.97312313923635, -0.99548191630031], [-0.38299976567017, 0.98516909715427], + [0.41025800019463, 0.02116736935734], [0.09638062008048, 0.04411984381457], [-0.85283249275397, 0.91475563922421], [0.88866808958124, -0.99735267083226], + [-0.48202429536989, -0.96805608884164], [0.27572582416567, 0.58634753335832], [-0.65889129659168, 0.58835634138583], [0.98838086953732, 0.99994349600236], + [-0.20651349620689, 0.54593044066355], [-0.62126416356920, -0.59893681700392], [0.20320105410437, -0.86879180355289], [-0.97790548600584, 0.96290806999242], + [0.11112534735126, 0.21484763313301], [-0.41368337314182, 0.28216837680365], [0.24133038992960, 0.51294362630238], [-0.66393410674885, -0.08249679629081], + [-0.53697829178752, -0.97649903936228], [-0.97224737889348, 0.22081333579837], [0.87392477144549, -0.12796173740361], [0.19050361015753, 0.01602615387195], + [-0.46353441212724, -0.95249041539006], [-0.07064096339021, -0.94479803205886], [-0.92444085484466, -0.10457590187436], [-0.83822593578728, -0.01695043208885], + [0.75214681811150, -0.99955681042665], [-0.42102998829339, 0.99720941999394], [-0.72094786237696, -0.35008961934255], [0.78843311019251, 0.52851398958271], + [0.97394027897442, -0.26695944086561], [0.99206463477946, -0.57010120849429], [0.76789609461795, -0.76519356730966], [-0.82002421836409, -0.73530179553767], + [0.81924990025724, 0.99698425250579], [-0.26719850873357, 0.68903369776193], [-0.43311260380975, 0.85321815947490], [0.99194979673836, 0.91876249766422], + [-0.80692001248487, -0.32627540663214], [0.43080003649976, -0.21919095636638], [0.67709491937357, -0.95478075822906], [0.56151770568316, -0.70693811747778], + [0.10831862810749, -0.08628837174592], [0.91229417540436, -0.65987351408410], [-0.48972893932274, 0.56289246362686], [-0.89033658689697, -0.71656563987082], + [0.65269447475094, 0.65916004833932], [0.67439478141121, -0.81684380846796], [-0.47770832416973, -0.16789556203025], [-0.99715979260878, -0.93565784007648], + [-0.90889593602546, 0.62034397054380], [-0.06618622548177, -0.23812217221359], [0.99430266919728, 0.18812555317553], [0.97686402381843, -0.28664534366620], + [0.94813650221268, -0.97506640027128], [-0.95434497492853, -0.79607978501983], [-0.49104783137150, 0.32895214359663], [0.99881175120751, 0.88993983831354], + [0.50449166760303, -0.85995072408434], [0.47162891065108, -0.18680204049569], [-0.62081581361840, 0.75000676218956], [-0.43867015250812, 0.99998069244322], + [0.98630563232075, -0.53578899600662], [-0.61510362277374, -0.89515019899997], [-0.03841517601843, -0.69888815681179], [-0.30102157304644, -0.07667808922205], + [0.41881284182683, 0.02188098922282], [-0.86135454941237, 0.98947480909359], [0.67226861393788, -0.13494389011014], [-0.70737398842068, -0.76547349325992], + [0.94044946687963, 0.09026201157416], [-0.82386352534327, 0.08924768823676], [-0.32070666698656, 0.50143421908753], [0.57593163224487, -0.98966422921509], + [-0.36326018419965, 0.07440243123228], [0.99979044674350, -0.14130287347405], [-0.92366023326932, -0.97979298068180], [-0.44607178518598, -0.54233252016394], + [0.44226800932956, 0.71326756742752], [0.03671907158312, 0.63606389366675], [0.52175424682195, -0.85396826735705], [-0.94701139690956, -0.01826348194255], + [-0.98759606946049, 0.82288714303073], [0.87434794743625, 0.89399495655433], [-0.93412041758744, 0.41374052024363], [0.96063943315511, 0.93116709541280], + [0.97534253457837, 0.86150930812689], [0.99642466504163, 0.70190043427512], [-0.94705089665984, -0.29580042814306], [0.91599807087376, -0.98147830385781], +]; + +/// `t_huffman_env_1_5dB` (`(codeword, length)` by index): envelope, time direction, 1.5 dB steps (LAV 60). +#[rustfmt::skip] +pub(crate) static T_ENV_1_5DB: [(u32, u8); 121] = [ + (0x3ffd6, 18), (0x3ffd7, 18), (0x3ffd8, 18), (0x3ffd9, 18), (0x3ffda, 18), (0x3ffdb, 18), (0x7ffb8, 19), (0x7ffb9, 19), + (0x7ffba, 19), (0x7ffbb, 19), (0x7ffbc, 19), (0x7ffbd, 19), (0x7ffbe, 19), (0x7ffbf, 19), (0x7ffc0, 19), (0x7ffc1, 19), + (0x7ffc2, 19), (0x7ffc3, 19), (0x7ffc4, 19), (0x7ffc5, 19), (0x7ffc6, 19), (0x7ffc7, 19), (0x7ffc8, 19), (0x7ffc9, 19), + (0x7ffca, 19), (0x7ffcb, 19), (0x7ffcc, 19), (0x7ffcd, 19), (0x7ffce, 19), (0x7ffcf, 19), (0x7ffd0, 19), (0x7ffd1, 19), + (0x7ffd2, 19), (0x7ffd3, 19), (0x1ffe6, 17), (0x3ffd4, 18), (0xfff0, 16), (0x1ffe9, 17), (0x3ffd5, 18), (0x1ffe7, 17), + (0xfff1, 16), (0xffec, 16), (0xffed, 16), (0xffee, 16), (0x7ff4, 15), (0x3ff9, 14), (0x3ff7, 14), (0x1ffa, 13), + (0x1ff9, 13), (0xffb, 12), (0x7fc, 11), (0x3fc, 10), (0x1fd, 9), (0xfd, 8), (0x7d, 7), (0x3d, 6), + (0x1d, 5), (0xd, 4), (0x5, 3), (0x1, 2), (0x0, 2), (0x4, 3), (0xc, 4), (0x1c, 5), + (0x3c, 6), (0x7c, 7), (0xfc, 8), (0x1fc, 9), (0x3fd, 10), (0xffa, 12), (0x1ff8, 13), (0x3ff6, 14), + (0x3ff8, 14), (0x7ff5, 15), (0xffef, 16), (0x1ffe8, 17), (0xfff2, 16), (0x7ffd4, 19), (0x7ffd5, 19), (0x7ffd6, 19), + (0x7ffd7, 19), (0x7ffd8, 19), (0x7ffd9, 19), (0x7ffda, 19), (0x7ffdb, 19), (0x7ffdc, 19), (0x7ffdd, 19), (0x7ffde, 19), + (0x7ffdf, 19), (0x7ffe0, 19), (0x7ffe1, 19), (0x7ffe2, 19), (0x7ffe3, 19), (0x7ffe4, 19), (0x7ffe5, 19), (0x7ffe6, 19), + (0x7ffe7, 19), (0x7ffe8, 19), (0x7ffe9, 19), (0x7ffea, 19), (0x7ffeb, 19), (0x7ffec, 19), (0x7ffed, 19), (0x7ffee, 19), + (0x7ffef, 19), (0x7fff0, 19), (0x7fff1, 19), (0x7fff2, 19), (0x7fff3, 19), (0x7fff4, 19), (0x7fff5, 19), (0x7fff6, 19), + (0x7fff7, 19), (0x7fff8, 19), (0x7fff9, 19), (0x7fffa, 19), (0x7fffb, 19), (0x7fffc, 19), (0x7fffd, 19), (0x7fffe, 19), + (0x7ffff, 19), +]; + +/// `f_huffman_env_1_5dB` (`(codeword, length)` by index): envelope, frequency direction, 1.5 dB steps (LAV 60). +#[rustfmt::skip] +pub(crate) static F_ENV_1_5DB: [(u32, u8); 121] = [ + (0x7ffe7, 19), (0x7ffe8, 19), (0xfffd2, 20), (0xfffd3, 20), (0xfffd4, 20), (0xfffd5, 20), (0xfffd6, 20), (0xfffd7, 20), + (0xfffd8, 20), (0x7ffda, 19), (0xfffd9, 20), (0xfffda, 20), (0xfffdb, 20), (0xfffdc, 20), (0x7ffdb, 19), (0xfffdd, 20), + (0x7ffdc, 19), (0x7ffdd, 19), (0xfffde, 20), (0x3ffe4, 18), (0xfffdf, 20), (0xfffe0, 20), (0xfffe1, 20), (0x7ffde, 19), + (0xfffe2, 20), (0xfffe3, 20), (0xfffe4, 20), (0x7ffdf, 19), (0xfffe5, 20), (0x7ffe0, 19), (0x3ffe8, 18), (0x7ffe1, 19), + (0x3ffe0, 18), (0x3ffe9, 18), (0x1ffef, 17), (0x3ffe5, 18), (0x1ffec, 17), (0x1ffed, 17), (0x1ffee, 17), (0xfff4, 16), + (0xfff3, 16), (0xfff0, 16), (0x7ff7, 15), (0x7ff6, 15), (0x3ffa, 14), (0x1ffa, 13), (0x1ff9, 13), (0xffa, 12), + (0xff8, 12), (0x7f9, 11), (0x3fb, 10), (0x1fc, 9), (0x1fa, 9), (0xfb, 8), (0x7c, 7), (0x3c, 6), + (0x1c, 5), (0xc, 4), (0x5, 3), (0x1, 2), (0x0, 2), (0x4, 3), (0xd, 4), (0x1d, 5), + (0x3d, 6), (0xfa, 8), (0xfc, 8), (0x1fb, 9), (0x3fa, 10), (0x7f8, 11), (0x7fa, 11), (0x7fb, 11), + (0xff9, 12), (0xffb, 12), (0x1ff8, 13), (0x1ffb, 13), (0x3ff8, 14), (0x3ff9, 14), (0xfff1, 16), (0xfff2, 16), + (0x1ffea, 17), (0x1ffeb, 17), (0x3ffe1, 18), (0x3ffe2, 18), (0x3ffea, 18), (0x3ffe3, 18), (0x3ffe6, 18), (0x3ffe7, 18), + (0x3ffeb, 18), (0xfffe6, 20), (0x7ffe2, 19), (0xfffe7, 20), (0xfffe8, 20), (0xfffe9, 20), (0xfffea, 20), (0xfffeb, 20), + (0xfffec, 20), (0x7ffe3, 19), (0xfffed, 20), (0xfffee, 20), (0xfffef, 20), (0xffff0, 20), (0x7ffe4, 19), (0xffff1, 20), + (0x3ffec, 18), (0xffff2, 20), (0xffff3, 20), (0x7ffe5, 19), (0x7ffe6, 19), (0xffff4, 20), (0xffff5, 20), (0xffff6, 20), + (0xffff7, 20), (0xffff8, 20), (0xffff9, 20), (0xffffa, 20), (0xffffb, 20), (0xffffc, 20), (0xffffd, 20), (0xffffe, 20), + (0xfffff, 20), +]; + +/// `t_huffman_env_bal_1_5dB` (`(codeword, length)` by index): envelope balance, time direction, 1.5 dB steps (LAV 24). +#[rustfmt::skip] +pub(crate) static T_ENV_BAL_1_5DB: [(u32, u8); 49] = [ + (0xffe4, 16), (0xffe5, 16), (0xffe6, 16), (0xffe7, 16), (0xffe8, 16), (0xffe9, 16), (0xffea, 16), (0xffeb, 16), + (0xffec, 16), (0xffed, 16), (0xffee, 16), (0xffef, 16), (0xfff0, 16), (0xfff1, 16), (0xfff2, 16), (0xfff3, 16), + (0xfff4, 16), (0xffe2, 16), (0xffc, 12), (0x7fc, 11), (0x1fe, 9), (0x7e, 7), (0x1e, 5), (0x6, 3), + (0x0, 1), (0x2, 2), (0xe, 4), (0x3e, 6), (0xfe, 8), (0x7fd, 11), (0xffd, 12), (0x7ff0, 15), + (0xffe3, 16), (0xfff5, 16), (0xfff6, 16), (0xfff7, 16), (0xfff8, 16), (0xfff9, 16), (0xfffa, 16), (0x1fff6, 17), + (0x1fff7, 17), (0x1fff8, 17), (0x1fff9, 17), (0x1fffa, 17), (0x1fffb, 17), (0x1fffc, 17), (0x1fffd, 17), (0x1fffe, 17), + (0x1ffff, 17), +]; + +/// `f_huffman_env_bal_1_5dB` (`(codeword, length)` by index): envelope balance, frequency direction, 1.5 dB steps (LAV 24). +#[rustfmt::skip] +pub(crate) static F_ENV_BAL_1_5DB: [(u32, u8); 49] = [ + (0x3ffe2, 18), (0x3ffe3, 18), (0x3ffe4, 18), (0x3ffe5, 18), (0x3ffe6, 18), (0x3ffe7, 18), (0x3ffe8, 18), (0x3ffe9, 18), + (0x3ffea, 18), (0x3ffeb, 18), (0x3ffec, 18), (0x3ffed, 18), (0x3ffee, 18), (0x3ffef, 18), (0x3fff0, 18), (0xfff7, 16), + (0x1fff0, 17), (0x3ffc, 14), (0x7fe, 11), (0x7fc, 11), (0xfe, 8), (0x7e, 7), (0xe, 4), (0x2, 2), + (0x0, 1), (0x6, 3), (0x1e, 5), (0x3e, 6), (0x1fe, 9), (0x7fd, 11), (0xffe, 12), (0x7ffa, 15), + (0xfff6, 16), (0x3fff1, 18), (0x3fff2, 18), (0x3fff3, 18), (0x3fff4, 18), (0x3fff5, 18), (0x3fff6, 18), (0x3fff7, 18), + (0x3fff8, 18), (0x3fff9, 18), (0x3fffa, 18), (0x3fffb, 18), (0x3fffc, 18), (0x3fffd, 18), (0x3fffe, 18), (0x7fffe, 19), + (0x7ffff, 19), +]; + +/// `t_huffman_env_3_0dB` (`(codeword, length)` by index): envelope, time direction, 3 dB steps (LAV 31). +#[rustfmt::skip] +pub(crate) static T_ENV_3_0DB: [(u32, u8); 63] = [ + (0x3ffed, 18), (0x3ffee, 18), (0x7ffde, 19), (0x7ffdf, 19), (0x7ffe0, 19), (0x7ffe1, 19), (0x7ffe2, 19), (0x7ffe3, 19), + (0x7ffe4, 19), (0x7ffe5, 19), (0x7ffe6, 19), (0x7ffe7, 19), (0x7ffe8, 19), (0x7ffe9, 19), (0x7ffea, 19), (0x7ffeb, 19), + (0x7ffec, 19), (0x1fff4, 17), (0xfff7, 16), (0xfff9, 16), (0xfff8, 16), (0x3ffb, 14), (0x3ffa, 14), (0x3ff8, 14), + (0x1ffa, 13), (0xffc, 12), (0x7fc, 11), (0xfe, 8), (0x3e, 6), (0xe, 4), (0x2, 2), (0x0, 1), + (0x6, 3), (0x1e, 5), (0x7e, 7), (0x1fe, 9), (0x7fd, 11), (0x1ffb, 13), (0x3ff9, 14), (0x3ffc, 14), + (0x7ffa, 15), (0xfff6, 16), (0x1fff5, 17), (0x3ffec, 18), (0x7ffed, 19), (0x7ffee, 19), (0x7ffef, 19), (0x7fff0, 19), + (0x7fff1, 19), (0x7fff2, 19), (0x7fff3, 19), (0x7fff4, 19), (0x7fff5, 19), (0x7fff6, 19), (0x7fff7, 19), (0x7fff8, 19), + (0x7fff9, 19), (0x7fffa, 19), (0x7fffb, 19), (0x7fffc, 19), (0x7fffd, 19), (0x7fffe, 19), (0x7ffff, 19), +]; + +/// `f_huffman_env_3_0dB` (`(codeword, length)` by index): envelope and noise floor, frequency direction, 3 dB steps (LAV 31). +#[rustfmt::skip] +pub(crate) static F_ENV_3_0DB: [(u32, u8); 63] = [ + (0xffff0, 20), (0xffff1, 20), (0xffff2, 20), (0xffff3, 20), (0xffff4, 20), (0xffff5, 20), (0xffff6, 20), (0x3fff3, 18), + (0x7fff5, 19), (0x7ffee, 19), (0x7ffef, 19), (0x7fff6, 19), (0x3fff4, 18), (0x3fff2, 18), (0xffff7, 20), (0x7fff0, 19), + (0x1fff5, 17), (0x3fff0, 18), (0x1fff4, 17), (0xfff7, 16), (0xfff6, 16), (0x7ff8, 15), (0x3ffb, 14), (0xffd, 12), + (0x7fd, 11), (0x3fd, 10), (0x1fd, 9), (0xfd, 8), (0x3e, 6), (0xe, 4), (0x2, 2), (0x0, 1), + (0x6, 3), (0x1e, 5), (0xfc, 8), (0x1fc, 9), (0x3fc, 10), (0x7fc, 11), (0xffc, 12), (0x1ffc, 13), + (0x3ffa, 14), (0x7ff9, 15), (0x7ffa, 15), (0xfff8, 16), (0xfff9, 16), (0x1fff6, 17), (0x1fff7, 17), (0x3fff5, 18), + (0x3fff6, 18), (0x3fff1, 18), (0xffff8, 20), (0x7fff1, 19), (0x7fff2, 19), (0x7fff3, 19), (0xffff9, 20), (0x7fff7, 19), + (0x7fff4, 19), (0xffffa, 20), (0xffffb, 20), (0xffffc, 20), (0xffffd, 20), (0xffffe, 20), (0xfffff, 20), +]; + +/// `t_huffman_env_bal_3_0dB` (`(codeword, length)` by index): envelope balance, time direction, 3 dB steps (LAV 12). +#[rustfmt::skip] +pub(crate) static T_ENV_BAL_3_0DB: [(u32, u8); 25] = [ + (0x1ff2, 13), (0x1ff3, 13), (0x1ff4, 13), (0x1ff5, 13), (0x1ff6, 13), (0x1ff7, 13), (0x1ff8, 13), (0xff8, 12), + (0xfe, 8), (0x7e, 7), (0xe, 4), (0x6, 3), (0x0, 1), (0x2, 2), (0x1e, 5), (0x3e, 6), + (0x1fe, 9), (0x1ff9, 13), (0x1ffa, 13), (0x1ffb, 13), (0x1ffc, 13), (0x1ffd, 13), (0x1ffe, 13), (0x3ffe, 14), + (0x3fff, 14), +]; + +/// `f_huffman_env_bal_3_0dB` (`(codeword, length)` by index): envelope and noise floor balance, frequency direction, 3 dB steps (LAV 12). +#[rustfmt::skip] +pub(crate) static F_ENV_BAL_3_0DB: [(u32, u8); 25] = [ + (0x1ff7, 13), (0x1ff8, 13), (0x1ff9, 13), (0x1ffa, 13), (0x1ffb, 13), (0x3ff8, 14), (0x3ff9, 14), (0x7fc, 11), + (0xfe, 8), (0x7e, 7), (0xe, 4), (0x2, 2), (0x0, 1), (0x6, 3), (0x1e, 5), (0x3e, 6), + (0x1fe, 9), (0xffa, 12), (0x1ff6, 13), (0x3ffa, 14), (0x3ffb, 14), (0x3ffc, 14), (0x3ffd, 14), (0x3ffe, 14), + (0x3fff, 14), +]; + +/// `t_huffman_noise_3_0dB` (`(codeword, length)` by index): noise floor, time direction (LAV 31). +#[rustfmt::skip] +pub(crate) static T_NOISE_3_0DB: [(u32, u8); 63] = [ + (0x1fce, 13), (0x1fcf, 13), (0x1fd0, 13), (0x1fd1, 13), (0x1fd2, 13), (0x1fd3, 13), (0x1fd4, 13), (0x1fd5, 13), + (0x1fd6, 13), (0x1fd7, 13), (0x1fd8, 13), (0x1fd9, 13), (0x1fda, 13), (0x1fdb, 13), (0x1fdc, 13), (0x1fdd, 13), + (0x1fde, 13), (0x1fdf, 13), (0x1fe0, 13), (0x1fe1, 13), (0x1fe2, 13), (0x1fe3, 13), (0x1fe4, 13), (0x1fe5, 13), + (0x1fe6, 13), (0x1fe7, 13), (0x7f2, 11), (0xfd, 8), (0x3e, 6), (0xe, 4), (0x6, 3), (0x0, 1), + (0x2, 2), (0x1e, 5), (0xfc, 8), (0x3f8, 10), (0x1fcc, 13), (0x1fe8, 13), (0x1fe9, 13), (0x1fea, 13), + (0x1feb, 13), (0x1fec, 13), (0x1fcd, 13), (0x1fed, 13), (0x1fee, 13), (0x1fef, 13), (0x1ff0, 13), (0x1ff1, 13), + (0x1ff2, 13), (0x1ff3, 13), (0x1ff4, 13), (0x1ff5, 13), (0x1ff6, 13), (0x1ff7, 13), (0x1ff8, 13), (0x1ff9, 13), + (0x1ffa, 13), (0x1ffb, 13), (0x1ffc, 13), (0x1ffd, 13), (0x1ffe, 13), (0x3ffe, 14), (0x3fff, 14), +]; + +/// `t_huffman_noise_bal_3_0dB` (`(codeword, length)` by index): noise floor balance, time direction (LAV 12). +#[rustfmt::skip] +pub(crate) static T_NOISE_BAL_3_0DB: [(u32, u8); 25] = [ + (0xec, 8), (0xed, 8), (0xee, 8), (0xef, 8), (0xf0, 8), (0xf1, 8), (0xf2, 8), (0xf3, 8), + (0xf4, 8), (0xf5, 8), (0x1c, 5), (0x2, 2), (0x0, 1), (0x6, 3), (0x3a, 6), (0xf6, 8), + (0xf7, 8), (0xf8, 8), (0xf9, 8), (0xfa, 8), (0xfb, 8), (0xfc, 8), (0xfd, 8), (0xfe, 8), + (0xff, 8), +]; diff --git a/crates/audio/src/track.rs b/crates/audio/src/track.rs index 920f571ae..e522e6f68 100644 --- a/crates/audio/src/track.rs +++ b/crates/audio/src/track.rs @@ -24,8 +24,10 @@ pub const LOOKAHEAD_PACKETS: usize = 8; enum Inner { #[cfg(feature = "aac-decode")] Aac { + /// The decoder, with SBR on for HE-AAC. dec: Box, - up: Option, + /// HE-AAC: 2048 samples per access unit, at twice the core rate. + sbr: bool, order: Option<&'static [usize]>, /// The format ADTS packets must keep, when their headers gave the configuration. adts_format: Option, @@ -74,7 +76,7 @@ pub(crate) type PacketFrames = fn(&[u8], u64) -> Option; /// /// It takes the track's [`CodecParams`] (from `isobmff::Track::codec_params`, /// `matroska::Track::codec_params`, `ogg::Stream::codec_params`, or built by hand for another -/// container) and decodes AAC (LC; HE-AAC as its upsampled LC core), ALAC, Opus, AC-3/E-AC-3, +/// container) and decodes AAC (LC, and HE-AAC with its SBR high band), ALAC, Opus, AC-3/E-AC-3, /// MP1/MP2, MP3 and PCM. Container timing (AAC priming, Opus pre-skip, end trimming) is the caller's: /// every decoded sample is returned. /// @@ -168,17 +170,18 @@ impl TrackDecoder { params.extradata.clone() }; let asc = asc.as_slice(); - let dec = aac::Decoder::new(asc)?; + let mut dec = aac::Decoder::new(asc)?; let out = aac::output_rate(asc, payloads.iter().copied()).unwrap_or(dec.sample_rate()); - let up = (dec.sample_rate().checked_mul(2) == Some(out)).then(|| aac::Upsample2x::new(dec.channels())); - let codec = if up.is_some() { CodecId::HeAac } else { CodecId::Aac }; - // Only twice the core rate is upsampled to; any other signalled rate plays the core. - let out = if up.is_some() { out } else { dec.sample_rate() }; + let sbr = dec.sample_rate().checked_mul(2) == Some(out); + let codec = if sbr { CodecId::HeAac } else { CodecId::Aac }; + // Only SBR at twice the core rate is decoded; any other signalled rate plays the core. + dec.set_sbr(sbr); + let out = dec.sample_rate(); // a program config element that spells out a configuration has its speakers let cfg = aac::speaker_config(dec.config()); let layout = aac_layout(cfg, dec.channels()); let order = layout.is_ordered().then(|| aac::wav_order(cfg)).flatten(); - (Inner::Aac { dec: Box::new(dec), up, order, adts_format }, codec, out, layout, SampleFormat::Compressed) + (Inner::Aac { dec: Box::new(dec), sbr, order, adts_format }, codec, out, layout, SampleFormat::Compressed) } #[cfg(feature = "alac-decode")] CodecId::Alac => { @@ -320,7 +323,7 @@ impl TrackDecoder { let mut partial = false; let channels = match &mut self.inner { #[cfg(feature = "aac-decode")] - Inner::Aac { dec, up, order, adts_format } => { + Inner::Aac { dec, order, adts_format, .. } => { let mut out: Vec> = Vec::new(); let units = match adts_frames(packet) { Some(frames) => adts_units(frames, *adts_format)?, @@ -328,17 +331,11 @@ impl TrackDecoder { }; // A failed frame leaves the decoder as it was, so the frames before it in the // packet must too: their audio is replaced by the packet's concealment. - let before = (units.len() > 1).then(|| (dec.clone(), up.clone())); - let decoded = units.into_iter().try_for_each(|unit| { - let mut block = dec.decode(unit)?; - if let Some(up) = up.as_mut() { - block = up.process(block); - } - append(&mut out, block) - }); + let before = (units.len() > 1).then(|| dec.clone()); + let decoded = units.into_iter().try_for_each(|unit| append(&mut out, dec.decode(unit)?)); if let Err(e) = decoded { if let Some(before) = before { - (*dec, *up) = before; + *dec = before; } return Err(e); } @@ -440,8 +437,8 @@ impl TrackDecoder { None } #[cfg(feature = "aac-decode")] - Inner::Aac { up, .. } => { - let per_unit = aac::FRAME_LEN * if up.is_some() { 2 } else { 1 }; + Inner::Aac { sbr, .. } => { + let per_unit = aac::FRAME_LEN * if *sbr { 2 } else { 1 }; match adts_frames(data) { Some(f) => Some(f.iter().map(|(h, _)| usize::from(h.raw_data_blocks)).sum::().min(MAX_ADTS_FRAMES_PER_PACKET) * per_unit), // A packet lost whole: as long as the last one, in a stream of multi-frame @@ -488,7 +485,7 @@ impl TrackDecoder { pub fn frames_per_packet(&self) -> Option { match &self.inner { #[cfg(feature = "aac-decode")] - Inner::Aac { up, .. } => Some(aac::FRAME_LEN * if up.is_some() { 2 } else { 1 }), + Inner::Aac { sbr, .. } => Some(aac::FRAME_LEN * if *sbr { 2 } else { 1 }), #[cfg(feature = "alac-decode")] Inner::Alac { .. } => None, #[cfg(feature = "ac3-decode")] @@ -544,7 +541,7 @@ impl TrackDecoder { pub fn preroll(&self) -> usize { match &self.inner { #[cfg(feature = "aac-decode")] - Inner::Aac { up, .. } => 2 * aac::FRAME_LEN * if up.is_some() { 2 } else { 1 }, + Inner::Aac { sbr, .. } => 2 * aac::FRAME_LEN * if *sbr { 2 } else { 1 }, #[cfg(feature = "alac-decode")] Inner::Alac { .. } => 0, #[cfg(feature = "ac3-decode")] @@ -570,12 +567,7 @@ impl TrackDecoder { self.last_frames = 0; match &mut self.inner { #[cfg(feature = "aac-decode")] - Inner::Aac { dec, up, .. } => { - dec.reset(); - if let Some(up) = up { - up.reset(); - } - } + Inner::Aac { dec, .. } => dec.reset(), #[cfg(feature = "alac-decode")] Inner::Alac { .. } => {} #[cfg(feature = "ac3-decode")] diff --git a/crates/audio/tests/codecs/aac/he_aac_oracle.rs b/crates/audio/tests/codecs/aac/he_aac_oracle.rs new file mode 100644 index 000000000..e41493e16 --- /dev/null +++ b/crates/audio/tests/codecs/aac/he_aac_oracle.rs @@ -0,0 +1,165 @@ +//! HE-AAC (SBR) decoding against ffmpeg's decoder, on streams from ffmpeg's AudioToolbox encoder +//! (oracle only; skipped without ffmpeg or without `aac_at`, which only macOS builds have). + +use std::process::Command; + +use craft_audio::aac::{Decoder, split_adts}; + +use super::common::*; + +/// ffmpeg, if it has the AudioToolbox AAC encoder. +fn ffmpeg_with_aac_at() -> Option { + let ff = ffmpeg()?; + let out = Command::new(&ff).args(["-hide_banner", "-encoders"]).output().ok()?; + if !String::from_utf8_lossy(&out.stdout).contains("aac_at") { + eprintln!("SKIPPED: ffmpeg has no aac_at encoder"); + return None; + } + Some(ff) +} + +fn input(rate: u32, channels: usize, secs: f32) -> Vec> { + let n = (rate as f32 * secs) as usize; + (0..channels) + .map(|c| { + let a = chords(rate, n, 0.004 * c as f32); + let s = speech(rate, n, 11 + c as u64); + let z = noise(n, 0.05, 3 + c as u64); + let k = clicks(rate, n); + a.iter().zip(&s).zip(&z).zip(&k).map(|(((a, s), z), k)| 0.4 * a + 0.4 * s * if c == 1 { -0.7 } else { 1.0 } + z + 0.3 * k).collect() + }) + .collect() +} + +/// ffmpeg's decode of a stream of `channels` channels. ffmpeg decodes mono HE-AAC as stereo (it +/// allows for parametric stereo the stream may hold), with the same signal in both channels when +/// there is none: of those, the first is taken (a downmix would scale it by -3 dB). +fn ffmpeg_decode_as(ff: &std::path::Path, file: &std::path::Path, channels: usize) -> Vec> { + let mut planar = ffmpeg_decode_raw(ff, file, channels.max(2)); + planar.truncate(channels); + planar +} + +fn ffmpeg_decode_raw(ff: &std::path::Path, file: &std::path::Path, channels: usize) -> Vec> { + let out = Command::new(ff).args(["-v", "error", "-i"]).arg(file).args(["-ac", &channels.to_string(), "-f", "f32le", "-"]).output().unwrap(); + assert!(out.status.success() && out.stderr.is_empty(), "{}", String::from_utf8_lossy(&out.stderr)); + let samples: Vec = out.stdout.as_chunks::<4>().0.iter().map(|&b| f32::from_le_bytes(b)).collect(); + (0..channels).map(|c| samples.iter().skip(c).step_by(channels).copied().collect()).collect() +} + +/// Our decode of an ADTS stream, SBR on. +fn decode_sbr(data: &[u8]) -> (Vec>, u32) { + let frames = split_adts(data).unwrap(); + let mut dec = Decoder::from_adts(&frames[0].0).unwrap(); + dec.set_sbr(true); + let mut out: Vec> = Vec::new(); + for (_, au) in frames { + let pcm = dec.decode(au).unwrap(); + out.resize(pcm.len(), Vec::new()); + for (o, p) in out.iter_mut().zip(pcm) { + o.extend(p); + } + } + (out, dec.sample_rate()) +} + +/// The lag of `b` behind `a` (within ±`range`) that correlates best. +fn best_lag(a: &[f32], b: &[f32], range: i64) -> i64 { + let n = a.len().min(b.len()) as i64; + let (from, to) = (range, n - range); + (-range..=range) + .max_by(|&x, &y| { + let c = |lag: i64| (from..to).step_by(3).map(|i| a[i as usize] as f64 * b[(i + lag) as usize] as f64).sum::(); + c(x).total_cmp(&c(y)) + }) + .unwrap() +} + +/// Energy in dB of `x[from..from + 2048]` within `lo..hi` Hz (Hann-windowed DFT). +fn band_db(x: &[f32], from: usize, rate: f32, lo: f32, hi: f32) -> f64 { + let n = 2048; + let seg = &x[from..from + n]; + let (b0, b1) = ((lo / rate * n as f32) as usize, (hi / rate * n as f32) as usize); + let mut e = 0f64; + for b in b0..b1.min(n / 2) { + let (mut re, mut im) = (0f64, 0f64); + for (i, &s) in seg.iter().enumerate() { + let w = 0.5 - 0.5 * (2.0 * std::f64::consts::PI * i as f64 / n as f64).cos(); + let ph = 2.0 * std::f64::consts::PI * (b * i) as f64 / n as f64; + re += s as f64 * w * ph.cos(); + im += s as f64 * w * ph.sin(); + } + e += re * re + im * im; + } + 10.0 * (e + 1e-12).log10() +} + +struct Case { + name: &'static str, + rate: u32, + channels: usize, + bitrate: &'static str, +} + +/// HE-AAC streams of 1 to 6 channels at 22.05 to 48 kHz decode as ffmpeg decodes them: the same +/// timing, the low band to 55 dB and the rebuilt high band's level to 0.1 dB. +#[test] +fn matches_ffmpeg_he_aac_decoder() { + let Some(ff) = ffmpeg_with_aac_at() else { return }; + let cases = [ + Case { name: "he_stereo_44k_48k", rate: 44_100, channels: 2, bitrate: "48k" }, + Case { name: "he_mono_44k_32k", rate: 44_100, channels: 1, bitrate: "32k" }, + Case { name: "he_stereo_48k_64k", rate: 48_000, channels: 2, bitrate: "64k" }, + Case { name: "he_stereo_32k_32k", rate: 32_000, channels: 2, bitrate: "32k" }, + Case { name: "he_stereo_44k_24k", rate: 44_100, channels: 2, bitrate: "24k" }, + Case { name: "he_stereo_22k_24k", rate: 22_050, channels: 2, bitrate: "24k" }, + Case { name: "he_51_48k_160k", rate: 48_000, channels: 6, bitrate: "160k" }, + ]; + for case in &cases { + let path = fixtures().join(format!("{}.aac", case.name)); + ffmpeg_encode(&ff, &input(case.rate, case.channels, 3.0), case.rate, &["-c:a", "aac_at", "-profile:a", "4", "-b:a", case.bitrate], &path); + let data = std::fs::read(&path).unwrap(); + let refr = ffmpeg_decode_as(&ff, &path, case.channels); + let (ours, rate) = decode_sbr(&data); + // ffmpeg puts surround in WAV order, the decoder in AAC element order + let ours: Vec> = match (case.channels == 6).then(|| craft_audio::aac::wav_order(6)).flatten() { + Some(order) => order.iter().map(|&c| ours[c].clone()).collect(), + None => ours, + }; + assert_eq!(rate, case.rate, "{}", case.name); + assert_eq!(ours.len(), case.channels); + // the same delay as ffmpeg's decoder: 962 samples + assert_eq!(best_lag(&refr[0], &ours[0], 2100), 0, "{}", case.name); + let core_nyquist = case.rate as f32 / 4.0; + for c in 0..case.channels { + let n = refr[c].len().min(ours[c].len()) - 4096; + let a: Vec = refr[c][2048..n].to_vec(); + let b: Vec = ours[c][2048..n].to_vec(); + let full = snr(&a, &b); + let (la, lb) = (lowpass(&a, core_nyquist * 0.8, case.rate as f32), lowpass(&b, core_nyquist * 0.8, case.rate as f32)); + let low = snr(&la, &lb); + let mut diffs = Vec::new(); + for from in (4096..a.len() - 4096).step_by(4096) { + for (lo, hi) in + [(core_nyquist * 1.05, core_nyquist * 1.3), (core_nyquist * 1.3, core_nyquist * 1.7), (core_nyquist * 1.7, core_nyquist * 2.0)] + { + let (ea, eb) = (band_db(&a, from, case.rate as f32, lo, hi), band_db(&b, from, case.rate as f32, lo, hi)); + if ea > 0.0 { + diffs.push(eb - ea); + } + } + } + let mean = diffs.iter().sum::() / diffs.len().max(1) as f64; + let mad = diffs.iter().map(|d| d.abs()).sum::() / diffs.len().max(1) as f64; + println!( + "{} ch{c}: full SNR {full:.1} dB, low band SNR {low:.1} dB, high band level {mean:+.2} dB (mean |Δ| {mad:.2} dB over {})", + case.name, + diffs.len() + ); + // The low band differs by the core decoders' rounding; the high band's noise comes + // from the same table, so whole frames match too. + assert!(full > 45.0 && low > 55.0, "{} ch{c}: SNR {full:.1} / {low:.1} dB", case.name); + assert!(mad < 0.1, "{} ch{c}: high band off by {mad:.2} dB", case.name); + } + } +} diff --git a/crates/audio/tests/codecs/aac/mod.rs b/crates/audio/tests/codecs/aac/mod.rs index 45e627242..7d55a7364 100644 --- a/crates/audio/tests/codecs/aac/mod.rs +++ b/crates/audio/tests/codecs/aac/mod.rs @@ -3,4 +3,5 @@ mod common; mod decoder_oracle; mod encoder_oracle; +mod he_aac_oracle; mod robustness; From fa9ca366973c0883c9c5750e2204903534fc04ef Mon Sep 17 00:00:00 2001 From: alehel Date: Thu, 12 Nov 2026 20:28:37 +0100 Subject: [PATCH 2/5] craft-audio: SBR review fixes - A VARFIX pointer of 1 marks no transient envelope, like 0. - A start at QMF channel 32 is valid; kx is checked once the crossover is known. - The noise and harmonic gains take q / (1 + q) first, so two clamped levels no longer overflow to infinity and NaN. - A header that runs past its fill element is refused before it replaces the one in use. - A new program config layout drops the SBR decoders, whose history was the old order's. - The ffmpeg oracle requires a high band to compare on every channel but the 5.1 LFE. Refs #1121 Co-Authored-By: Claude Opus 5.5 --- crates/audio/src/aac/decoder.rs | 11 +++- crates/audio/src/aac/sbr/bitstream.rs | 6 ++- crates/audio/src/aac/sbr/freq.rs | 13 ++++- crates/audio/src/aac/sbr/hf.rs | 7 +-- crates/audio/src/aac/sbr/mod.rs | 51 +++++++++++++++---- .../audio/tests/codecs/aac/he_aac_oracle.rs | 10 ++-- 6 files changed, 78 insertions(+), 20 deletions(-) diff --git a/crates/audio/src/aac/decoder.rs b/crates/audio/src/aac/decoder.rs index 1d8713f54..ec5dae9fc 100644 --- a/crates/audio/src/aac/decoder.rs +++ b/crates/audio/src/aac/decoder.rs @@ -135,6 +135,10 @@ impl Decoder { } self.layout = layout; self.offsets = offsets; + // the SBR decoders' history belongs to the old element order: `apply_sbr` rebuilds them + if let Some(e) = self.sbr_out.as_mut() { + e.clear(); + } self.channels = vec![ChannelState { overlap: vec![0.0; 1024], prev_shape: WindowShape::Sine }; n]; } @@ -700,7 +704,7 @@ mod tests { /// its SBR fill element describes is there; with SBR off, the core alone (silence). #[test] fn sbr_fill_elements_rebuild_the_high_band() { - let payload = sbr::test_payload(44_100, |_| 40, 0).unwrap(); + let payload = sbr::test_payload(44_100, |_| 40, |_| 0).unwrap(); let mut bw = BitWriter::new(); bw.write_bits(ID_SCE, 3); bw.write_bits(0, 4); @@ -736,6 +740,11 @@ mod tests { assert_eq!(out.len(), 4 * 2048); let rms = (out[2048..].iter().map(|s| s * s).sum::() / (out.len() - 2048) as f32).sqrt(); assert!(rms > 1e-4, "{rms}"); + // a new layout drops the SBR decoders (their history is the old element order's) and + // keeps SBR on + dec.set_layout(vec![(ElementType::Sce, 0)]); + assert_eq!(dec.sbr_out.as_ref().map(Vec::len), Some(0)); + assert_eq!(dec.decode(&au).unwrap()[0].len(), 2048); } /// An access unit whose elements are all empty: `elements` are (id, tag), then ID_END. diff --git a/crates/audio/src/aac/sbr/bitstream.rs b/crates/audio/src/aac/sbr/bitstream.rs index 6233504fa..1d343bf7b 100644 --- a/crates/audio/src/aac/sbr/bitstream.rs +++ b/crates/audio/src/aac/sbr/bitstream.rs @@ -204,8 +204,9 @@ impl Grid { if t[0] < 0 || t[..=n].windows(2).any(|w| w[0] >= w[1]) { return Err(bad()); } + // l_A (ISO/IEC 14496-3 §4.6.18.3.3): VARFIX pointer 1, like 0, marks no envelope let la = match (pointer, class) { - (0, _) => None, + (0, _) | (1, VARFIX) => None, (p, FIXVAR | VARVAR) => Some((n + 1).checked_sub(p).ok_or_else(bad)?), (p, _) => Some(p - 1), }; @@ -468,6 +469,9 @@ mod tests { // VARFIX: leading border 1, relative 2, pointer 2: l_A = 1 let g = grid(&[(VARFIX as u32, 2), (1, 2), (1, 2), (0, 2), (2, 2), (1, 1), (0, 1)]).unwrap(); assert_eq!((g.num_env, &g.t_env[..3], g.la, g.t_noise), (2, &[1, 3, 16][..], Some(1), [1, 3, 16])); + // the same with pointer 1: no transient envelope, the noise floors still split at 3 + let g = grid(&[(VARFIX as u32, 2), (1, 2), (1, 2), (0, 2), (1, 2), (1, 1), (0, 1)]).unwrap(); + assert_eq!((g.la, g.t_noise), (None, [1, 3, 16])); // VARVAR: 1 .. 17 with one border from each end let g = grid(&[(VARVAR as u32, 2), (1, 2), (1, 2), (1, 2), (1, 2), (0, 2), (0, 2), (0, 2), (0, 1), (0, 1), (0, 1)]).unwrap(); assert_eq!((g.num_env, &g.t_env[..4], g.la), (3, &[1, 3, 15, 17][..], None)); diff --git a/crates/audio/src/aac/sbr/freq.rs b/crates/audio/src/aac/sbr/freq.rs index dc2809c10..d12fbdc02 100644 --- a/crates/audio/src/aac/sbr/freq.rs +++ b/crates/audio/src/aac/sbr/freq.rs @@ -75,8 +75,9 @@ fn start_stop(h: &Header, fs: u32) -> Option<(usize, usize)> { } } .min(64); - // the low band reaches only the analysis bank's 32 channels - (1..32).contains(&k0).then_some(())?; + // the low band reaches only the analysis bank's 32 channels (`kx` is checked against them + // once the crossover is known) + (1..=32).contains(&k0).then_some(())?; (k2 > k0).then_some((k0 as usize, k2 as usize)) } @@ -341,6 +342,14 @@ mod tests { assert!(valid > 10_000, "{valid}"); } + /// The highest start a header can give, 32 at 24 kHz (start 15), is valid: with stop 14 + /// (twice the start) and linear bands it covers channels 32 to 64. + #[test] + fn a_start_at_channel_32_is_accepted() { + let t = FreqTables::new(&header(15, 14, 0, 0, false, 2), 24_000).unwrap(); + assert_eq!((t.k0, t.kx, t.m), (32, 32, 32)); + } + /// A typical 44.1 kHz header: start channel 12 + offset, stop near 64, three noise bands. #[test] fn a_44k_header_has_the_expected_layout() { diff --git a/crates/audio/src/aac/sbr/hf.rs b/crates/audio/src/aac/sbr/hf.rs index 43ae572cf..1c2fd81ac 100644 --- a/crates/audio/src/aac/sbr/hf.rs +++ b/crates/audio/src/aac/sbr/hf.rs @@ -201,13 +201,14 @@ fn envelope_gains(f: &Frame, l: usize, sine_index: &[bool; 64], delta: bool) -> } let mut out = EnvelopeGains { gain: [0.0; 64], noise: [0.0; 64], sine: [0.0; 64] }; for c in 0..m { + // `q / (1 + q)` first: energy and noise ratio are each clamped, their product is not let (eo, ec, q) = (e_orig[c], e_curr[c], q[c]); - out.noise[c] = (eo * q / (1.0 + q)).sqrt(); + let share = q / (1.0 + q); + out.noise[c] = (eo * share).sqrt(); if sine_index[c] { out.sine[c] = (eo / (1.0 + q)).sqrt(); } - out.gain[c] = - if s_band[c] { (eo / (EPS + ec) * q / (1.0 + q)).sqrt() } else { (eo / ((EPS + ec) * (1.0 + if delta { q } else { 0.0 }))).sqrt() }; + out.gain[c] = if s_band[c] { (eo / (EPS + ec) * share).sqrt() } else { (eo / ((EPS + ec) * (1.0 + if delta { q } else { 0.0 }))).sqrt() }; } // limit per limiter band, then boost back the energy the limiter took let lim_gain = LIMITER_GAINS[usize::from(hdr.limiter_gains & 3)]; diff --git a/crates/audio/src/aac/sbr/mod.rs b/crates/audio/src/aac/sbr/mod.rs index 32a6c55b0..752cd03a0 100644 --- a/crates/audio/src/aac/sbr/mod.rs +++ b/crates/audio/src/aac/sbr/mod.rs @@ -285,6 +285,8 @@ impl Element { } if br.read_flag().ok()? { let h = Header::read(&mut br).ok()?; + // a header that runs past its fill element is damaged: keep the one in use + (br.position() <= p.end).then_some(())?; if self.header != Some(h) { if self.header.is_none_or(|o| !o.same_tables(&h)) { for c in &mut self.channels { @@ -377,13 +379,13 @@ pub(crate) const TEST_HEADER: Header = Header { /// An SBR single channel element for tests at output rate `rate`: [`TEST_HEADER`] (3 dB steps, /// start 5, stop 9, the default extra fields), then one FIXFIX envelope at high resolution with -/// band `k` at `env(k)` (1.5 dB steps, as a single FIXFIX envelope has) and every noise floor -/// value `noise`. +/// band `k` at `env(k)` (1.5 dB steps, as a single FIXFIX envelope has) and noise band `k` at +/// `noise(k)`. #[cfg(test)] -pub(crate) fn test_payload(rate: u32, env: impl Fn(usize) -> i32, noise: u32) -> Option> { +pub(crate) fn test_payload(rate: u32, env: impl Fn(usize) -> i32, noise: impl Fn(usize) -> i32) -> Option> { use craft_media::bitstream::BitWriter; let t = FreqTables::new(&TEST_HEADER, rate)?; - let (env, n_q): (Vec, usize) = ((0..t.n_high()).map(env).collect(), t.n_q()); + let (env, noise): (Vec, Vec) = ((0..t.n_high()).map(env).collect(), (0..t.n_q()).map(noise).collect()); let mut bw = BitWriter::new(); bw.write_bits(1, 1); // bs_header_flag for (v, n) in [(1, 1), (5, 4), (9, 4), (0, 3), (0, 2), (0, 1), (0, 1)] { @@ -394,7 +396,7 @@ pub(crate) fn test_payload(rate: u32, env: impl Fn(usize) -> i32, noise: u32) -> bw.write_bits(0, 2); // one envelope bw.write_bits(1, 1); // high resolution bw.write_bits(0, 2); // frequency-direction envelope and noise floor - for _ in 0..n_q { + for _ in &noise { bw.write_bits(0, 2); // no inverse filtering } bw.write_bits(env[0] as u32, 7); @@ -402,9 +404,9 @@ pub(crate) fn test_payload(rate: u32, env: impl Fn(usize) -> i32, noise: u32) -> let (code, len) = tables::F_ENV_1_5DB[(w[1] - w[0] + 60) as usize]; bw.write_bits(code, u32::from(len)); } - bw.write_bits(noise, 5); - for _ in 1..n_q { - let (code, len) = tables::F_ENV_3_0DB[31]; + bw.write_bits(noise[0] as u32, 5); + for w in noise.windows(2) { + let (code, len) = tables::F_ENV_3_0DB[(w[1] - w[0] + 31) as usize]; bw.write_bits(code, u32::from(len)); } bw.write_bits(0, 2); // no sinusoids, no extended data @@ -460,7 +462,7 @@ mod tests { fn the_high_band_follows_the_envelope() { let t = tables(); let mid = t.n_high() / 2; - let data = test_payload(RATE, |k| if k < mid { 30 } else { 42 }, 0).unwrap(); + let data = test_payload(RATE, |k| if k < mid { 30 } else { 42 }, |_| 0).unwrap(); let mut e = Element::new(1, RATE); let out = run(&mut e, &data, 6); let tail = &out[out.len() - 4096..]; @@ -481,7 +483,7 @@ mod tests { /// finite and of full length. #[test] fn damaged_sbr_data_is_survived() { - let good = test_payload(RATE, |k| 20 + k as i32, 3).unwrap(); + let good = test_payload(RATE, |k| 20 + k as i32, |_| 3).unwrap(); let mut seed = 0x1234_5678u32; let mut next = || { seed ^= seed << 13; @@ -510,4 +512,33 @@ mod tests { assert!(out.iter().all(|c| c.len() == 2048 && c.iter().all(|s| s.is_finite()))); } } + + /// The largest envelope (127 steps of 1.5 dB) with the largest noise floor (each band 31 + /// steps below the last) keeps the output finite: the gains are formed so that no + /// intermediate product of two clamped levels overflows. + #[test] + fn extreme_levels_stay_finite() { + let rate = + [22_050, 24_000, 32_000, 44_100, 48_000].into_iter().find(|&r| FreqTables::new(&TEST_HEADER, r).is_some_and(|t| t.n_q() >= 3)).unwrap(); + let data = test_payload(rate, |_| 127, |k| -31 * k as i32).unwrap(); + let mut e = Element::new(1, rate); + run(&mut e, &data, 3); + } + + /// A header that runs past the end of its fill element is not taken: the one in use stays, + /// and the frame is dropped. + #[test] + fn a_header_cut_short_is_not_taken() { + let data = test_payload(RATE, |_| 30, |_| 0).unwrap(); + let mut e = Element::new(1, RATE); + run(&mut e, &data, 1); + assert_eq!(e.header, Some(TEST_HEADER)); + // another start frequency (bits 2..6), in an element that ends 6 bits in + let mut cut = data.clone(); + cut[0] ^= 0x04; + assert!(e.read(Payload { data: &cut, start: 0, end: 6, crc: false }).is_none()); + assert_eq!(e.header, Some(TEST_HEADER)); + assert!(e.read(Payload { data: &cut, start: 0, end: 8 * cut.len(), crc: false }).is_some()); + assert_ne!(e.header, Some(TEST_HEADER)); + } } diff --git a/crates/audio/tests/codecs/aac/he_aac_oracle.rs b/crates/audio/tests/codecs/aac/he_aac_oracle.rs index e41493e16..0eeba69c8 100644 --- a/crates/audio/tests/codecs/aac/he_aac_oracle.rs +++ b/crates/audio/tests/codecs/aac/he_aac_oracle.rs @@ -149,8 +149,12 @@ fn matches_ffmpeg_he_aac_decoder() { } } } - let mean = diffs.iter().sum::() / diffs.len().max(1) as f64; - let mad = diffs.iter().map(|d| d.abs()).sum::() / diffs.len().max(1) as f64; + // the LFE (WAV channel 3 of 5.1) has no SBR, so no high band; every other channel must + // have one to compare + let lfe = case.channels == 6 && c == 3; + assert!(lfe || !diffs.is_empty(), "{} ch{c}: no high band to compare", case.name); + let mean = diffs.iter().sum::() / diffs.len() as f64; + let mad = diffs.iter().map(|d| d.abs()).sum::() / diffs.len() as f64; println!( "{} ch{c}: full SNR {full:.1} dB, low band SNR {low:.1} dB, high band level {mean:+.2} dB (mean |Δ| {mad:.2} dB over {})", case.name, @@ -159,7 +163,7 @@ fn matches_ffmpeg_he_aac_decoder() { // The low band differs by the core decoders' rounding; the high band's noise comes // from the same table, so whole frames match too. assert!(full > 45.0 && low > 55.0, "{} ch{c}: SNR {full:.1} / {low:.1} dB", case.name); - assert!(mad < 0.1, "{} ch{c}: high band off by {mad:.2} dB", case.name); + assert!(lfe || mad < 0.1, "{} ch{c}: high band off by {mad:.2} dB", case.name); } } } From 48f5283ec899ec7eab328bc55acc9e7e50b0ca71 Mon Sep 17 00:00:00 2001 From: alehel Date: Thu, 12 Nov 2026 20:28:47 +0100 Subject: [PATCH 3/5] craft-audio: cite no section number for l_A Co-Authored-By: Claude Opus 5.5 --- crates/audio/src/aac/sbr/bitstream.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/audio/src/aac/sbr/bitstream.rs b/crates/audio/src/aac/sbr/bitstream.rs index 1d343bf7b..0e8e70c3a 100644 --- a/crates/audio/src/aac/sbr/bitstream.rs +++ b/crates/audio/src/aac/sbr/bitstream.rs @@ -204,7 +204,7 @@ impl Grid { if t[0] < 0 || t[..=n].windows(2).any(|w| w[0] >= w[1]) { return Err(bad()); } - // l_A (ISO/IEC 14496-3 §4.6.18.3.3): VARFIX pointer 1, like 0, marks no envelope + // l_A: in ISO/IEC 14496-3 a VARFIX pointer of 1, like 0, marks no transient envelope let la = match (pointer, class) { (0, _) | (1, VARFIX) => None, (p, FIXVAR | VARVAR) => Some((n + 1).checked_sub(p).ok_or_else(bad)?), From fe8c1f697a0fc04c8eabbffb526b5c9d51bfb9c6 Mon Sep 17 00:00:00 2001 From: alehel Date: Thu, 12 Nov 2026 20:45:15 +0100 Subject: [PATCH 4/5] craft-audio: keep the SBR history when a new program config fails The SBR decoders now record the layout they were built for and are rebuilt when the decoder's layout differs, instead of being cleared in set_layout. An access unit whose new program config fails puts the layout back, and with it the SBR decoders still match. Refs #1121 Co-Authored-By: Claude Opus 5.5 --- crates/audio/src/aac/decoder.rs | 97 ++++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 32 deletions(-) diff --git a/crates/audio/src/aac/decoder.rs b/crates/audio/src/aac/decoder.rs index ec5dae9fc..825ebb23a 100644 --- a/crates/audio/src/aac/decoder.rs +++ b/crates/audio/src/aac/decoder.rs @@ -85,6 +85,9 @@ pub struct Decoder { sbr_seen: bool, /// The SBR decoder of each layout entry, when output is at twice the core rate. sbr_out: Option>, + /// The layout `sbr_out` was built for. Kept apart from `layout`, so that an access unit whose + /// new program config fails (and whose layout is put back) leaves the SBR history alone. + sbr_layout: Vec<(ElementType, u8)>, } impl Decoder { @@ -121,6 +124,7 @@ impl Decoder { scratch: Scratch::new(), sbr_seen: false, sbr_out: None, + sbr_layout: Vec::new(), }; d.set_layout(layout); Ok(d) @@ -135,10 +139,6 @@ impl Decoder { } self.layout = layout; self.offsets = offsets; - // the SBR decoders' history belongs to the old element order: `apply_sbr` rebuilds them - if let Some(e) = self.sbr_out.as_mut() { - e.clear(); - } self.channels = vec![ChannelState { overlap: vec![0.0; 1024], prev_shape: WindowShape::Sine }; n]; } @@ -214,10 +214,11 @@ impl Decoder { let Some(elements) = self.sbr_out.as_mut() else { return pcm; }; - // a new layout (a program config element) starts the SBR decoders afresh - let fits = elements.len() == self.layout.len() && elements.iter().zip(&self.layout).all(|(e, l)| e.channels() == l.0.channels()); - if !fits { + // a new layout (a program config element) starts the SBR decoders afresh: their history + // belongs to the old element order, even where the channel counts match + if elements.len() != self.layout.len() || self.sbr_layout != self.layout { *elements = self.layout.iter().map(|l| sbr::Element::new(l.0.channels(), rate)).collect(); + self.sbr_layout.clone_from(&self.layout); } let mut out = Vec::with_capacity(pcm.len()); for (i, e) in elements.iter_mut().enumerate() { @@ -704,26 +705,7 @@ mod tests { /// its SBR fill element describes is there; with SBR off, the core alone (silence). #[test] fn sbr_fill_elements_rebuild_the_high_band() { - let payload = sbr::test_payload(44_100, |_| 40, |_| 0).unwrap(); - let mut bw = BitWriter::new(); - bw.write_bits(ID_SCE, 3); - bw.write_bits(0, 4); - bw.write_bits(0, 8 + 11 + 3); // global_gain, ics_info (max_sfb 0), no pulse/TNS/gain control - bw.write_bits(ID_FIL, 3); - let count = (4 + 8 * payload.len()).div_ceil(8); - if count < 15 { - bw.write_bits(count as u32, 4); - } else { - bw.write_bits(15, 4); - bw.write_bits((count - 14) as u32, 8); - } - bw.write_bits(EXT_SBR_DATA, 4); - for &b in &payload { - bw.write_bits(u32::from(b), 8); - } - bw.write_bits(0, 4); - bw.write_bits(ID_END, 3); - let au = bw.finish(); + let au = sbr_au(0); // AAC-LC, 22.05 kHz, mono let asc = AudioSpecificConfig::lc(7, 1); let mut dec = Decoder::from_config(asc.clone()).unwrap(); @@ -740,11 +722,62 @@ mod tests { assert_eq!(out.len(), 4 * 2048); let rms = (out[2048..].iter().map(|s| s * s).sum::() / (out.len() - 2048) as f32).sqrt(); assert!(rms > 1e-4, "{rms}"); - // a new layout drops the SBR decoders (their history is the old element order's) and - // keeps SBR on - dec.set_layout(vec![(ElementType::Sce, 0)]); - assert_eq!(dec.sbr_out.as_ref().map(Vec::len), Some(0)); - assert_eq!(dec.decode(&au).unwrap()[0].len(), 2048); + } + + /// A program config that changes the layout restarts the SBR decoders, even where the + /// channel counts match, and one in an access unit that fails leaves their history alone: the + /// access units after it decode as if it had not been there. + #[test] + fn a_new_layout_restarts_sbr_and_a_failed_one_keeps_it() { + let au = sbr_au(0); + let one = ProgramConfig { front: vec![(false, 0)], ..Default::default() }; + let six = ProgramConfig { front: vec![(false, 0), (true, 0)], back: vec![(true, 1)], lfe: vec![0], ..Default::default() }; + let decoder = || { + let mut d = Decoder::from_config(AudioSpecificConfig { pce: Some(one.clone()), ..AudioSpecificConfig::lc(7, 0) }).unwrap(); + d.set_sbr(true); + d + }; + let (mut kept, mut failed) = (decoder(), decoder()); + for _ in 0..3 { + assert_eq!(kept.decode(&au).unwrap(), failed.decode(&au).unwrap()); + } + // the new layout's elements are missing from this access unit + assert!(failed.decode(&silent_au(Some(&six), &[(ID_SCE, 0)])).is_err()); + for _ in 0..2 { + assert_eq!(kept.decode(&au).unwrap(), failed.decode(&au).unwrap()); + } + // the same channel count under another tag is a new layout: the SBR decoder starts over, + // as in a decoder that had that layout from the start + let tag1 = ProgramConfig { front: vec![(false, 1)], ..Default::default() }; + let mut fresh = Decoder::from_config(AudioSpecificConfig { pce: Some(tag1.clone()), ..AudioSpecificConfig::lc(7, 0) }).unwrap(); + fresh.set_sbr(true); + kept.set_layout(tag1.layout()); + assert_eq!(kept.decode(&sbr_au(1)).unwrap(), fresh.decode(&sbr_au(1)).unwrap()); + } + + /// One access unit of SCE `tag` with an SBR fill element of [`sbr::test_payload`] (44.1 kHz + /// output). + fn sbr_au(tag: u32) -> Vec { + let payload = sbr::test_payload(44_100, |_| 40, |_| 0).unwrap(); + let mut bw = BitWriter::new(); + bw.write_bits(ID_SCE, 3); + bw.write_bits(tag, 4); + bw.write_bits(0, 8 + 11 + 3); // global_gain, ics_info (max_sfb 0), no pulse/TNS/gain control + bw.write_bits(ID_FIL, 3); + let count = (4 + 8 * payload.len()).div_ceil(8); + if count < 15 { + bw.write_bits(count as u32, 4); + } else { + bw.write_bits(15, 4); + bw.write_bits((count - 14) as u32, 8); + } + bw.write_bits(EXT_SBR_DATA, 4); + for &b in &payload { + bw.write_bits(u32::from(b), 8); + } + bw.write_bits(0, 4); + bw.write_bits(ID_END, 3); + bw.finish() } /// An access unit whose elements are all empty: `elements` are (id, tag), then ID_END. From 1f3be111b8dc0b1d7d05195357df0889b5b507fe Mon Sep 17 00:00:00 2001 From: alehel Date: Fri, 13 Nov 2026 09:56:22 +0100 Subject: [PATCH 5/5] craft-audio: drop a short last SBR patch before the patch limit A header whose sixth patch is short (96 kHz, start 3, stop 9) was rejected, though that patch is dropped and five remain. Co-Authored-By: Claude Opus 5.5 --- crates/audio/src/aac/sbr/freq.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/crates/audio/src/aac/sbr/freq.rs b/crates/audio/src/aac/sbr/freq.rs index d12fbdc02..7bb372dc4 100644 --- a/crates/audio/src/aac/sbr/freq.rs +++ b/crates/audio/src/aac/sbr/freq.rs @@ -211,8 +211,9 @@ fn patches(master: &[usize], k0: usize, kx: usize, m: usize, fs: u32) -> Option< let (mut msb, mut usb) = (k0, kx); let mut out: Vec<(usize, usize)> = Vec::new(); // each pass either adds a patch or lowers `msb` to `kx`; a stream that does neither twice - // running cannot finish - for _ in 0..2 * (MAX_PATCHES + 1) { + // running cannot finish. One patch over the limit may still be a short last one that is + // dropped below. + for _ in 0..2 * (MAX_PATCHES + 2) { // the highest master border the patch can reach from below k0 let mut j = k + 1; let (sb, odd) = loop { @@ -227,7 +228,7 @@ fn patches(master: &[usize], k0: usize, kx: usize, m: usize, fs: u32) -> Option< if count > 0 { let start = (k0 - odd).checked_sub(count)?; out.push((start, count)); - if out.len() > MAX_PATCHES { + if out.len() > MAX_PATCHES + 1 { return None; } (usb, msb) = (sb, sb); @@ -241,7 +242,7 @@ fn patches(master: &[usize], k0: usize, kx: usize, m: usize, fs: u32) -> Option< if out.len() > 1 && out.last().is_some_and(|p| p.1 < 3) { out.pop(); } - return (!out.is_empty()).then_some(out); + return (!out.is_empty() && out.len() <= MAX_PATCHES).then_some(out); } } None @@ -350,6 +351,15 @@ mod tests { assert_eq!((t.k0, t.kx, t.m), (32, 32, 32)); } + /// At 96 kHz this header builds patches of 6, 6, 6, 6, 5 and 2 channels: the short sixth is + /// dropped, as any short last patch is, and the five left are within the limit. + #[test] + fn a_short_sixth_patch_is_dropped_before_the_limit() { + let t = FreqTables::new(&header(3, 9, 0, 0, false, 2), 96_000).unwrap(); + assert_eq!((t.k0, t.kx + t.m), (8, 39)); + assert_eq!(t.patches.iter().map(|p| p.1).collect::>(), [6, 6, 6, 6, 5]); + } + /// A typical 44.1 kHz header: start channel 12 + offset, stop near 64, three noise bands. #[test] fn a_44k_header_has_the_expected_layout() {