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..825ebb23a 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,11 @@ 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>, + /// 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 { @@ -110,6 +123,8 @@ impl Decoder { noise_state: NOISE_SEED, scratch: Scratch::new(), sbr_seen: false, + sbr_out: None, + sbr_layout: Vec::new(), }; d.set_layout(layout); Ok(d) @@ -131,16 +146,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 +184,61 @@ 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: 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() { + 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 +248,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 +258,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 +296,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 +315,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 +701,85 @@ 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 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(); + 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}"); + } + + /// 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. 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..0e8e70c3a --- /dev/null +++ b/crates/audio/src/aac/sbr/bitstream.rs @@ -0,0 +1,493 @@ +//! 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()); + } + // 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)?), + (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])); + // 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)); + } + + /// 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..7bb372dc4 --- /dev/null +++ b/crates/audio/src/aac/sbr/freq.rs @@ -0,0 +1,372 @@ +//! 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 (`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)) +} + +/// 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. 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 { + 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 + 1 { + 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() && out.len() <= MAX_PATCHES).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}"); + } + + /// 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)); + } + + /// 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() { + 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..1c2fd81ac --- /dev/null +++ b/crates/audio/src/aac/sbr/hf.rs @@ -0,0 +1,333 @@ +//! 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 { + // `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]); + 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) * 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)]; + 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..752cd03a0 --- /dev/null +++ b/crates/audio/src/aac/sbr/mod.rs @@ -0,0 +1,544 @@ +//! 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()?; + // 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 { + 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 noise band `k` at +/// `noise(k)`. +#[cfg(test)] +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, 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)] { + 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 &noise { + 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[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 + 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()))); + } + } + + /// 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/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..0eeba69c8 --- /dev/null +++ b/crates/audio/tests/codecs/aac/he_aac_oracle.rs @@ -0,0 +1,169 @@ +//! 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); + } + } + } + // 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, + 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!(lfe || 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;