diff --git a/crates/audio/Cargo.toml b/crates/audio/Cargo.toml index 9187edfc8..822c8a8ad 100644 --- a/crates/audio/Cargo.toml +++ b/crates/audio/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "craft-audio" -description = "Audio file I/O and codecs: WAV/AIFF read and write, FLAC encoder, AAC and ALAC decoders and encoders, AC-3/E-AC-3 decoder and encoder, MP3 encoder, Opus and MP1/MP2 decoders." +description = "Audio file I/O and codecs: WAV/AIFF read and write, FLAC encoder, AAC and ALAC decoders and encoders, AC-3/E-AC-3 decoder and encoder, MP3 decoder and encoder, Opus and MP1/MP2 decoders." version.workspace = true edition.workspace = true license.workspace = true @@ -14,12 +14,12 @@ all-features = true workspace = true [features] -# Probe and decode files: WAV and AIFF natively; AAC, ALAC, Opus, AC-3/E-AC-3, MP1/MP2 and PCM -# through craft-media's demuxers and this crate's decoders; MP3, FLAC, Vorbis, CAF, ADPCM, A-law -# and µ-law through symphonia. +# Probe and decode files: WAV and AIFF natively; AAC, ALAC, Opus, AC-3/E-AC-3, MP1/MP2/MP3 and +# PCM through craft-media's demuxers and this crate's decoders; FLAC, Vorbis, CAF, ADPCM, A-law, +# µ-law and free-format MP3 through symphonia. decode = ["decode-native", "symphonia"] # `decode` without symphonia: only the formats with a native decoder (others are Unsupported). -decode-native = ["aac-decode", "alac-decode", "opus-decode", "ac3-decode", "mpa-decode", "craft-media/isobmff", "craft-media/matroska", "craft-media/ogg", "dep:log"] +decode-native = ["aac-decode", "alac-decode", "opus-decode", "ac3-decode", "mpa-decode", "mp3-decode", "craft-media/isobmff", "craft-media/matroska", "craft-media/ogg", "dep:log"] # The symphonia fallback for the formats with no native decoder yet. symphonia = ["decode-native", "dep:symphonia"] # Every encoder: the lossless file writers and the AAC, ALAC, AC-3/E-AC-3 and MP3 encoders. @@ -36,6 +36,8 @@ aac-encode = ["craft-media/bitstream"] opus-decode = [] ac3-decode = ["craft-media/bitstream"] mpa-decode = ["craft-media/bitstream"] +# MPEG-1/2 Layer III (MP3) decoder (`mp3::Decoder`). +mp3-decode = ["mpa-decode"] # ALAC packet decoder and encoder (`alac`), ported from Apple's reference implementation. alac-decode = [] alac-encode = [] diff --git a/crates/audio/README.md b/crates/audio/README.md index ad0a41236..30f09ea57 100644 --- a/crates/audio/README.md +++ b/crates/audio/README.md @@ -1,7 +1,7 @@ # craft-audio Audio file I/O and codecs for the Craft apps. Native implementations: WAV and AIFF read/write, -FLAC encoder, AAC and ALAC decoders/encoders, AC-3/E-AC-3 decoder/encoder, MP3 encoder, Opus and MP1/MP2 decoders. +FLAC encoder, AAC and ALAC decoders/encoders, AC-3/E-AC-3 decoder/encoder, MP3 decoder/encoder, Opus and MP1/MP2 decoders. Formats without a native decoder are decoded by [symphonia](#symphonia), which is to be replaced by native decoders. @@ -26,7 +26,7 @@ No default features. The shared types (`AudioBuffer`, `AudioInfo`, `ChannelLayou | Feature | Provides | Pulls in | |---|---|---| | `decode` | `decode`, `probe`, `Decoder`, `tracks`, `TrackDecoder`; every format in [Format support](#format-support) | `decode-native`, `symphonia` | -| `decode-native` | the same, native formats only (others are `Unsupported`); `wav_pcm`, `aiff_pcm` (a file's integer PCM bit for bit, as `RawPcm`) | the five decoders below, craft-media `isobmff`/`matroska`/`ogg`, `log` | +| `decode-native` | the same, native formats only (others are `Unsupported`); `wav_pcm`, `aiff_pcm` (a file's integer PCM bit for bit, as `RawPcm`) | the six decoders below, craft-media `isobmff`/`matroska`/`ogg`, `log` | | `symphonia` | the fallback for formats with no native decoder; turning it on is the same as `decode` | `decode-native`, symphonia | | `encode` | every encoder: the WAV, AIFF and FLAC file writers, and the AAC, ALAC, AC-3/E-AC-3 and MP3 encoders | `lossless-encode`, `aac-encode`, `alac-encode`, `ac3-encode`, `eac3-encode`, `mp3-encode` | | `lossless-encode` | WAV, AIFF and FLAC writing | `pcm-encode`, `flac-encode` | @@ -39,12 +39,14 @@ No default features. The shared types (`AudioBuffer`, `AudioInfo`, `ChannelLayou | `ac3-decode` | `ac3::Decoder`, `ac3::ProgrammeDecoder` | craft-media `bitstream` | | `ac3-encode`, `eac3-encode` | `ac3::encoder::Encoder` | craft-media `bitstream` | | `mpa-decode` | `mpa::Decoder` | craft-media `bitstream` | +| `mp3-decode` | `mp3::Decoder`, `mp3::InfoTag` | `mpa-decode` | | `mp3-encode` | `mp3::Encoder` | craft-media `bitstream` | | `peaks` | `peaks::Peaks` | none | | `rayon` | parallel AAC encoding (with `aac-encode`) | `rayon` | | `serde` | `Serialize`/`Deserialize` on the shared types | `serde` | -Any one decoder feature (`aac-decode`, `alac-decode`, `opus-decode`, `ac3-decode`, `mpa-decode`) +Any one decoder feature (`aac-decode`, `alac-decode`, `opus-decode`, `ac3-decode`, `mpa-decode`, +`mp3-decode`) also gives `TrackDecoder`, for that codec and PCM. An app that decodes 32-bit FLAC with `decode` needs a profile override in its workspace; see @@ -310,18 +312,18 @@ match craft_audio::decode(&bytes, None) { | HE-AAC v1/v2 | native, core only (see [Limits](#limits)) | — | | ALAC | native | native, packets (`alac::Encoder`; no file writer) | | MP1, MP2 (MPEG audio Layer I/II) | native | — | -| MP3 (Layer III) | symphonia | native, frames (`mp3::Encoder`; the frames in a row are an `.mp3` file) | +| MP3 (Layer III) | native | native, frames (`mp3::Encoder`; the frames in a row are an `.mp3` file) | | Opus | native | — | | AC-3, E-AC-3 | native | native packet encoder | | Vorbis, ADPCM, A-law, µ-law | symphonia | — | | Container | Demuxer | Timing | |---|---|---| -| MP4, M4A, MOV | `craft_media::isobmff` for native codecs; symphonia for the others (MP3, FLAC) | edit lists (AAC priming, Opus pre-skip) | -| Matroska, WebM | `craft_media::matroska` for native codecs; symphonia for the others (Vorbis, FLAC, MP3) | `CodecDelay`, `DiscardPadding` | +| MP4, M4A, MOV | `craft_media::isobmff` for native codecs; symphonia for the others (FLAC) | edit lists (AAC priming, Opus pre-skip) | +| Matroska, WebM | `craft_media::matroska` for native codecs; symphonia for the others (Vorbis, FLAC) | `CodecDelay`, `DiscardPadding` | | Ogg | `craft_media::ogg` (Opus); symphonia (Vorbis, FLAC) | pre-skip, end trimming | | Raw ADTS AAC, AC-3, E-AC-3, MP1/MP2 | native frame sync | — | -| Raw MP3 | symphonia | — | +| Raw MP3 | native frame sync; symphonia for the free format | Xing / Info / VBRI frame skipped; LAME tag encoder delay and padding | | FLAC | symphonia | — | | CAF | symphonia | — | @@ -369,7 +371,7 @@ match craft_audio::decode(&bytes, None) { - `ChannelLayout::unordered` is written to FLAC as mask `0x0000`, as RFC 9639 §8.6.2 allows, but ffmpeg and `flac -d` ignore that mask and use their default layout for the channel count. - Not decoded at all: Speex. -- `TrackDecoder` returns `Unsupported` for MP3, FLAC and Vorbis; `decode` and `Decoder` read +- `TrackDecoder` returns `Unsupported` for FLAC and Vorbis; `decode` and `Decoder` read those through symphonia. ALAC in CAF still goes through symphonia (craft-media has no CAF demuxer). - ALAC: `EncodeFormat` has no ALAC entry; `alac::Encoder` writes packets and the configuration, @@ -415,6 +417,7 @@ Each codec module's rustdoc lists the coding tools it implements. | 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 | +| MP3 decoder (`mp3`) | ISO/IEC 11172-3, 13818-3 | Layer III, MPEG-1/2/2.5, 8–48 kHz, every bit rate, CBR and VBR; mono, stereo, dual channel, joint stereo (mid/side and intensity); long, short and mixed blocks; bit reservoir; CRC read, not checked; Xing / Info / VBRI and LAME tags; no free format (read through symphonia) | ffmpeg's float decoder: 127–131 dB SNR on LAME streams of every sampling frequency (CBR, VBR, CRC, no reservoir, transients with short blocks) and on this crate's encoder's; intensity stereo and mixed blocks through LAME frames edited to use them (112–131 dB) | | MP3 encoder (`mp3`) | ISO/IEC 11172-3, 13818-3 | Layer III, constant bit rate: 32–320 kbit/s at 32/44.1/48 kHz, 8–160 kbit/s at 16/22.05/24 kHz; mono, stereo, joint stereo (mid/side); bit reservoir; long blocks only; no Xing/Info tag, VBR, CRC or free format | ffmpeg decodes every Huffman code as intended; full encodes at every sampling frequency checked with ffprobe and by SNR of ffmpeg's decode | | ALAC (`alac`) | Apple's reference implementation | decode and encode, 1–8 channels, 16/20/24/32 bits (decode: any depth 1–32), fast mode, magic cookie in MP4/Matroska and CAF form | Apple's reference: same packets from the encoder, same samples from both decoders on 766 configurations; ffmpeg: bit-exact both ways | @@ -576,6 +579,7 @@ the full tests with ffmpeg and the vectors required. |---|---| | WAV, AIFF, `decode`/`probe`, peaks | SoundCraft `soundcraft-audio-io` [7dc56a4](https://github.com/storytold/soundcraft/commit/7dc56a4dbeebf57eacece09536726c57844fc59d) | | FLAC encoder, AAC, Opus decoder, AC-3, MP1/MP2, Ogg | FilmCraft [8fda2f2](https://github.com/storytold/filmcraft/commit/8fda2f232198c4c79ff9cfe5b6a347bb1375fa9e) | +| MP3 decoder | written here, from ISO/IEC 11172-3 and 13818-3; shares the encoder's tables and the MP1/MP2 synthesis filter | | MP3 encoder | FilmCraft `filmcraft-mp3enc` [4bec22b](https://github.com/storytold/filmcraft/commit/4bec22bd03bd1d995c9cf72e02a284f96d3d72cd) | | ALAC | Apple's reference implementation [macosforge/alac@c38887c](https://github.com/macosforge/alac/tree/c38887c5c5e64a4b31108733bd79ca9b2496d987/codec), Copyright © 2011 Apple Inc., Apache-2.0; ported to Rust and modified (see the `alac` module docs and [NOTICE](../../NOTICE)) | diff --git a/crates/audio/src/lib.rs b/crates/audio/src/lib.rs index 4964166a7..3ed684245 100644 --- a/crates/audio/src/lib.rs +++ b/crates/audio/src/lib.rs @@ -8,9 +8,9 @@ //! `wasm32-unknown-unknown`. //! //! - [`decode`], [`probe`], [`Decoder`] (streaming, seekable), [`tracks`], [`detect_container`] -//! (feature `decode`): WAV and AIFF are read natively; AAC, ALAC, Opus, AC-3/E-AC-3, MP1/MP2 and -//! PCM in MP4/MOV, Matroska/WebM, Ogg or raw streams go through craft-media's demuxers and -//! [`TrackDecoder`]; everything else (MP3, FLAC, Vorbis, CAF, ADPCM, A-law, µ-law) through +//! (feature `decode`): WAV and AIFF are read natively; AAC, ALAC, Opus, AC-3/E-AC-3, +//! MP1/MP2/MP3 and PCM in MP4/MOV, Matroska/WebM, Ogg or raw streams go through craft-media's +//! demuxers and [`TrackDecoder`]; everything else (FLAC, Vorbis, CAF, ADPCM, A-law, µ-law) through //! `symphonia`, which is to be replaced by native decoders. Feature `decode-native` is the //! same without symphonia. //! - [`TrackDecoder`] (any decoder feature): one track's packets from any demuxer to planar @@ -25,7 +25,7 @@ //! - [`peaks`] (feature `peaks`): min/max/RMS pyramid for waveform drawing. //! - Codec modules for packet-level use, each with its own features: [`aac`] (`aac-decode`, //! `aac-encode`), [`alac`] (`alac-decode`, `alac-encode`), [`opus`] (`opus-decode`), [`ac3`] -//! (`ac3-decode`), [`mpa`] (`mpa-decode`), [`mp3`] (`mp3-encode`: the Layer III encoder), +//! (`ac3-decode`), [`mpa`] (`mpa-decode`), [`mp3`] (`mp3-decode`, `mp3-encode`), //! [`flac`] (`flac-encode`: the packet encoder). //! //! The shared types below build with no features. @@ -38,7 +38,7 @@ pub mod ac3; pub mod alac; mod buffer; mod layout; -#[cfg(feature = "mp3-encode")] +#[cfg(any(feature = "mp3-encode", feature = "mp3-decode"))] pub mod mp3; #[cfg(feature = "mpa-decode")] pub mod mpa; diff --git a/crates/audio/src/mp3/decoder.rs b/crates/audio/src/mp3/decoder.rs new file mode 100644 index 000000000..1e388f801 --- /dev/null +++ b/crates/audio/src/mp3/decoder.rs @@ -0,0 +1,1097 @@ +//! The Layer III decoder: ISO/IEC 11172-3 §2.4.1.7 (side information, main data), §2.4.2.7 +//! (semantics) and §2.4.3.4 (decoding: Huffman, requantization, reordering, stereo, alias +//! reduction, IMDCT, frequency inversion, synthesis), with ISO/IEC 13818-3 §2.4.3.2 for the +//! scalefactors and intensity stereo of the lower sampling frequencies. + +use std::f64::consts::PI; +use std::sync::OnceLock; + +use craft_media::bitstream::BitReader; + +use super::huffman_tables::{LINBITS, QUAD_A_CODES, QUAD_A_LENS, pair_table}; +use super::tables; +use crate::mpa::{self, Error, Frame, Header, Mode, Result, Version}; + +/// Frequency lines of a granule. +const LINES: usize = 576; +/// The furthest back `main_data_begin` can reach: 9 bits (MPEG-1). +const MAX_BACK_MPEG1: usize = 511; +/// 8 bits for the lower sampling frequencies. +const MAX_BACK_LSF: usize = 255; +/// The scalefactor band boost of `preflag` (Table 3-B.6). +const PRETAB: [u8; 22] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 3, 2, 0]; + +/// Short-block scalefactor band widths (Table 3-B.8 of 11172-3, Table B.2 of 13818-3) at 44.1, +/// 48, 32, 22.05, 24, 16 (also 11.025 and 12) and 8 kHz. +const SHORT_WIDTHS: [[u16; 13]; 7] = [ + [4, 4, 4, 4, 6, 8, 10, 12, 14, 18, 22, 30, 56], + [4, 4, 4, 4, 6, 6, 10, 12, 14, 16, 20, 26, 66], + [4, 4, 4, 4, 6, 8, 12, 16, 20, 26, 34, 42, 12], + [4, 4, 4, 6, 6, 8, 10, 14, 18, 26, 32, 42, 18], + [4, 4, 4, 6, 8, 10, 12, 14, 18, 24, 32, 44, 12], + [4, 4, 4, 6, 8, 10, 12, 14, 18, 24, 30, 40, 18], + [8, 8, 8, 12, 16, 20, 24, 28, 36, 2, 2, 2, 26], +]; +/// Long-block band boundaries at 8 kHz (MPEG-2.5); 11.025 and 12 kHz use those of 16 kHz. +const LONG_8000: [u16; 23] = [0, 12, 24, 36, 48, 60, 72, 88, 108, 132, 160, 192, 232, 280, 336, 400, 476, 566, 568, 570, 572, 574, 576]; + +/// Scalefactor bands per partition for the lower sampling frequencies (13818-3 Table B.4, +/// `nr_of_sfb_block`), by table (0-2 without intensity stereo on the channel, 3-5 with) and block +/// kind (long, short, mixed). Short-block counts are of band × window entries. +const NR_OF_SFB: [[[usize; 4]; 3]; 6] = [ + [[6, 5, 5, 5], [9, 9, 9, 9], [6, 9, 9, 9]], + [[6, 5, 7, 3], [9, 9, 12, 6], [6, 9, 12, 6]], + [[11, 10, 0, 0], [18, 18, 0, 0], [15, 18, 0, 0]], + [[7, 7, 7, 0], [12, 12, 12, 0], [6, 15, 12, 0]], + [[6, 6, 6, 3], [12, 9, 9, 6], [6, 12, 9, 6]], + [[8, 8, 5, 0], [15, 12, 9, 0], [6, 18, 9, 0]], +]; + +/// The scalefactor bands of a sampling frequency, as line boundaries. +struct Bands { + long: [usize; 23], + /// Short bands, in lines of one window (`0..=192`). + short: [usize; 14], +} + +impl Bands { + fn of(sample_rate: u32) -> Bands { + let (long, short): (&[u16; 23], &[u16; 13]) = match sample_rate { + 44_100 => (&tables::SFB_MPEG1[0], &SHORT_WIDTHS[0]), + 48_000 => (&tables::SFB_MPEG1[1], &SHORT_WIDTHS[1]), + 32_000 => (&tables::SFB_MPEG1[2], &SHORT_WIDTHS[2]), + 22_050 => (&tables::SFB_LSF[0], &SHORT_WIDTHS[3]), + 24_000 => (&tables::SFB_LSF[1], &SHORT_WIDTHS[4]), + 8_000 => (&LONG_8000, &SHORT_WIDTHS[6]), + // 16, 12 and 11.025 kHz + _ => (&tables::SFB_LSF[2], &SHORT_WIDTHS[5]), + }; + let mut b = Bands { long: [0; 23], short: [0; 14] }; + for (o, &l) in b.long.iter_mut().zip(long) { + *o = usize::from(l).min(LINES); + } + let mut at = 0; + for (o, &w) in b.short.iter_mut().skip(1).zip(short) { + at += usize::from(w); + *o = at.min(LINES / 3); + } + b + } + + /// Lines `start..end` of long band `sfb` (empty past the last band). + fn long_band(&self, sfb: usize) -> (usize, usize) { + (self.long.get(sfb).copied().unwrap_or(LINES), self.long.get(sfb + 1).copied().unwrap_or(LINES)) + } + + /// Line of the first line of short band `sfb` (counting all three windows), and the band's + /// width in one window. + fn short_band(&self, sfb: usize) -> (usize, usize) { + let a = self.short.get(sfb).copied().unwrap_or(LINES / 3); + let b = self.short.get(sfb + 1).copied().unwrap_or(LINES / 3); + (3 * a, b.saturating_sub(a)) + } +} + +/// Side information of one granule of one channel (§2.4.1.7). +#[derive(Clone, Copy, Default)] +struct Granule { + part2_3_length: usize, + big_values: usize, + global_gain: i32, + scalefac_compress: u32, + window_switching: bool, + /// 0 normal, 1 start, 2 short, 3 stop. + block_type: u8, + /// Mixed block: the lowest two subbands are long (only with block type 2). + mixed: bool, + table_select: [usize; 3], + subblock_gain: [i32; 3], + region0_count: usize, + region1_count: usize, + preflag: bool, + scalefac_scale: bool, + count1_table_b: bool, +} + +impl Granule { + fn short(&self) -> bool { + self.block_type == 2 + } +} + +/// A frame's side information. +#[derive(Default)] +struct SideInfo { + main_data_begin: usize, + scfsi: [[bool; 4]; 2], + /// `[granule][channel]`. + granules: [[Granule; 2]; 2], +} + +/// Scalefactors of one granule of one channel, and the illegal intensity position of each band. +#[derive(Clone, Copy)] +struct Scalefactors { + long: [u8; 22], + short: [[u8; 3]; 13], + long_illegal: [u8; 22], + short_illegal: [[u8; 3]; 13], +} + +impl Scalefactors { + fn new(illegal: u8) -> Scalefactors { + Scalefactors { long: [0; 22], short: [[0; 3]; 13], long_illegal: [illegal; 22], short_illegal: [[illegal; 3]; 13] } + } + + /// Store the `k`-th scalefactor read for a granule: long bands first (up to `long_end`; all of + /// them for long blocks), then short bands from `short_start` window by window. + fn set(&mut self, g: &Granule, long_end: usize, k: usize, value: u8, illegal: u8) { + let (long, short_start) = match (g.short(), g.mixed) { + (false, _) => (usize::MAX, 0), + (true, true) => (long_end, 3), + (true, false) => (0, 0), + }; + if k < long { + if let (Some(v), Some(i)) = (self.long.get_mut(k), self.long_illegal.get_mut(k)) { + (*v, *i) = (value, illegal); + } + } else { + let s = k - long; + let (sfb, w) = (short_start + s / 3, s % 3); + if let (Some(v), Some(i)) = + (self.short.get_mut(sfb).and_then(|b| b.get_mut(w)), self.short_illegal.get_mut(sfb).and_then(|b| b.get_mut(w))) + { + (*v, *i) = (value, illegal); + } + } + } +} + +/// A Huffman code as a binary tree: `nodes[i][bit]` is a child node, or a symbol when it has +/// [`LEAF`] set; 0 is no child (the root is never a child). +struct Tree(Vec<[u16; 2]>); + +const LEAF: u16 = 0x8000; + +impl Tree { + /// The tree of the code `(code, length)` of each symbol in turn; `None` when two codes + /// collide (the tables are prefix-free: a unit test checks every one builds). + fn build(codes: impl Iterator) -> Option { + let mut nodes = vec![[0u16; 2]]; + for (sym, (code, len)) in codes.enumerate() { + if len == 0 || len > 24 || sym >= usize::from(LEAF) { + return None; + } + let mut n = 0usize; + for i in (0..len).rev() { + let bit = ((code >> i) & 1) as usize; + let child = *nodes.get(n)?.get(bit)?; + if i == 0 { + if child != 0 { + return None; + } + *nodes.get_mut(n)?.get_mut(bit)? = LEAF | sym as u16; + } else if child == 0 { + nodes.push([0, 0]); + let new = u16::try_from(nodes.len() - 1).ok().filter(|&x| x < LEAF)?; + *nodes.get_mut(n)?.get_mut(bit)? = new; + n = usize::from(new); + } else if child & LEAF != 0 { + return None; + } else { + n = usize::from(child); + } + } + } + Some(Tree(nodes)) + } + + fn decode(&self, r: &mut BitReader) -> Result { + let mut n = 0usize; + // no code is longer than 19 bits + for _ in 0..24 { + let bit = usize::from(r.read_bit().map_err(|_| overrun())?); + let child = self.0.get(n).and_then(|c| c.get(bit)).copied().unwrap_or(0); + if child & LEAF != 0 { + return Ok(usize::from(child & !LEAF)); + } + if child == 0 { + return Err(Error::Invalid("invalid Huffman code")); + } + n = usize::from(child); + } + Err(Error::Invalid("invalid Huffman code")) + } +} + +/// The Huffman trees: the pair tables by `table_select` (16-23 share one tree, as do 24-31) and +/// count1 table A. +struct Trees { + pairs: Vec>, + quad: Option, +} + +fn trees() -> &'static Trees { + static T: OnceLock = OnceLock::new(); + T.get_or_init(|| { + let pair = |select: usize| { + let t = pair_table(select)?; + Some((Tree::build(t.codes.iter().zip(t.lens).map(|(&c, &l)| (u32::from(c), l)))?, t.size)) + }; + let mut pairs: Vec> = (0..16).map(pair).collect(); + pairs.push(pair(16)); + pairs.push(pair(24)); + let quad = Tree::build(QUAD_A_CODES.iter().zip(QUAD_A_LENS).map(|(&c, l)| (u32::from(c), l))); + Trees { pairs, quad } + }) +} + +impl Trees { + fn pair(&self, select: usize) -> Option<&(Tree, usize)> { + let i = match select { + 16..=23 => 16, + 24..=31 => 17, + s => s, + }; + self.pairs.get(i)?.as_ref() + } +} + +fn overrun() -> Error { + Error::Invalid("Layer III data runs past the frame") +} + +fn bits(r: &mut BitReader, n: u32) -> Result { + if n == 0 { Ok(0) } else { r.read_bits(n).map_err(|_| overrun()) } +} + +fn flag(r: &mut BitReader) -> Result { + r.read_bit().map_err(|_| overrun()) +} + +/// Granules per frame: two in MPEG-1, one at the lower sampling frequencies. +fn granules(h: &Header) -> usize { + if h.version == Version::Mpeg1 { 2 } else { 1 } +} + +/// Bytes of side information (§2.4.1.7; 13818-3 §2.4.1.7). +fn side_info_len(h: &Header) -> usize { + match (h.version == Version::Mpeg1, h.channels()) { + (true, 1) => 17, + (true, _) => 32, + (false, 1) => 9, + (false, _) => 17, + } +} + +fn side_info(r: &mut BitReader, h: &Header) -> Result { + let lsf = h.version != Version::Mpeg1; + let nch = h.channels(); + let mut si = SideInfo { main_data_begin: bits(r, if lsf { 8 } else { 9 })? as usize, ..SideInfo::default() }; + // private bits + bits( + r, + match (lsf, nch) { + (false, 1) => 5, + (false, _) => 3, + (true, 1) => 1, + (true, _) => 2, + }, + )?; + if !lsf { + for ch in si.scfsi.iter_mut().take(nch) { + for band in ch.iter_mut() { + *band = flag(r)?; + } + } + } + for gr in si.granules.iter_mut().take(granules(h)) { + for g in gr.iter_mut().take(nch) { + g.part2_3_length = bits(r, 12)? as usize; + g.big_values = bits(r, 9)? as usize; + if g.big_values > LINES / 2 { + return Err(Error::Invalid("big_values above 288")); + } + g.global_gain = bits(r, 8)? as i32; + g.scalefac_compress = bits(r, if lsf { 9 } else { 4 })?; + g.window_switching = flag(r)?; + if g.window_switching { + g.block_type = bits(r, 2)? as u8; + if g.block_type == 0 { + return Err(Error::Invalid("window switching with the normal block type")); + } + g.mixed = flag(r)? && g.block_type == 2; + for t in g.table_select.iter_mut().take(2) { + *t = bits(r, 5)? as usize; + } + for s in g.subblock_gain.iter_mut() { + *s = bits(r, 3)? as i32; + } + } else { + for t in g.table_select.iter_mut() { + *t = bits(r, 5)? as usize; + } + g.region0_count = bits(r, 4)? as usize; + g.region1_count = bits(r, 3)? as usize; + } + if !lsf { + g.preflag = flag(r)?; + } + g.scalefac_scale = flag(r)?; + g.count1_table_b = flag(r)?; + } + } + Ok(si) +} + +/// MPEG-1 scalefactors (§2.4.1.7 `part2`, §2.4.2.7): `slen1` for the low bands, `slen2` for the +/// high ones; in the second granule a long block reuses the first granule's scalefactors of the +/// band groups `scfsi` marks. +fn scalefactors_mpeg1(r: &mut BitReader, g: &Granule, scfsi: &[bool; 4], second: bool, prev: &Scalefactors) -> Result { + let (slen1, slen2) = tables::SLEN.get(g.scalefac_compress as usize).copied().unwrap_or((0, 0)); + let mut sf = Scalefactors::new(7); + if g.short() { + // mixed: 8 long bands and short bands 3-11; short: bands 0-11. Bands below short band 6 + // (and the long ones) take slen1. + let (n, first) = if g.mixed { (8 + 27, 17) } else { (36, 18) }; + for k in 0..n { + let v = bits(r, if k < first { slen1 } else { slen2 })? as u8; + sf.set(g, 8, k, v, 7); + } + } else { + const GROUPS: [(usize, usize); 4] = [(0, 6), (6, 11), (11, 16), (16, 21)]; + for (i, &(a, b)) in GROUPS.iter().enumerate() { + let len = if i < 2 { slen1 } else { slen2 }; + let reuse = second && scfsi.get(i).copied().unwrap_or(false); + for band in a..b { + let v = if reuse { prev.long.get(band).copied().unwrap_or(0) } else { bits(r, len)? as u8 }; + if let Some(s) = sf.long.get_mut(band) { + *s = v; + } + } + } + } + Ok(sf) +} + +/// Lower-sampling-frequency scalefactors (13818-3 §2.4.3.2): `scalefac_compress` gives four +/// partitions' lengths; on the right channel of intensity stereo it is read differently, and +/// the largest value of each band marks an illegal intensity position. Returns the scalefactors +/// and `preflag`. +fn scalefactors_lsf(r: &mut BitReader, g: &Granule, intensity_right: bool) -> Result<(Scalefactors, bool)> { + let sfc = g.scalefac_compress; + let (slen, table, preflag) = if intensity_right { + let i = sfc >> 1; + if i < 180 { + ([i / 36, (i % 36) / 6, (i % 36) % 6, 0], 3, false) + } else if i < 244 { + let i = i - 180; + ([(i & 63) >> 4, (i & 15) >> 2, i & 3, 0], 4, false) + } else { + let i = i - 244; + ([i / 3, i % 3, 0, 0], 5, false) + } + } else if sfc < 400 { + ([(sfc >> 4) / 5, (sfc >> 4) % 5, (sfc & 15) >> 2, sfc & 3], 0, false) + } else if sfc < 500 { + let s = sfc - 400; + ([(s >> 2) / 5, (s >> 2) % 5, s & 3, 0], 1, false) + } else { + let s = sfc - 500; + ([s / 3, s % 3, 0, 0], 2, true) + }; + let kind = match (g.short(), g.mixed) { + (false, _) => 0, + (true, false) => 1, + (true, true) => 2, + }; + let counts = NR_OF_SFB.get(table).and_then(|t| t.get(kind)).copied().unwrap_or_default(); + let mut sf = Scalefactors::new(u8::MAX); + let mut k = 0; + for (n, len) in counts.into_iter().zip(slen) { + // slen is at most 4 bits for every scalefac_compress + let len = len.min(4); + let illegal = ((1u32 << len) - 1) as u8; + for _ in 0..n { + let v = bits(r, len)? as u8; + sf.set(g, 6, k, v, illegal); + k += 1; + } + } + Ok((sf, preflag)) +} + +/// The Huffman-coded lines of a granule (§2.4.2.7 `huffmancodebits`) into `out`, reading up to +/// bit `end` of the main data. Bits past `end` belong to the next granule: in a damaged granule +/// no big-value pair starts at or past it (a pair that starts before it is kept whole, as +/// ffmpeg does), and a count1 quadruple that crosses it is dropped. Lines not coded stay 0. +fn huffman(r: &mut BitReader, end: usize, g: &Granule, bands: &Bands, out: &mut [i32; LINES]) -> Result<()> { + let big = (g.big_values * 2).min(LINES); + // region boundaries: with window switching, region 1 starts at short band 3 (3 × 12 = 36 + // lines, but 72 at 8 kHz) for short blocks, else at long band 8; region 2 is empty + let (r1, r2) = if g.window_switching { + (if g.short() { bands.short_band(3).0 } else { bands.long[8] }, LINES) + } else { + let a = g.region0_count + 1; + let b = a + g.region1_count + 1; + (bands.long.get(a).copied().unwrap_or(LINES), bands.long.get(b).copied().unwrap_or(LINES)) + }; + let r1 = r1.min(big); + let r2 = r2.clamp(r1, big); + let trees = trees(); + let mut i = 0; + for (region_end, &select) in [r1, r2, big].into_iter().zip(&g.table_select) { + if select == 0 { + i = region_end.max(i); + continue; + } + let (tree, size) = trees.pair(select).ok_or(Error::Invalid("Huffman table 4 or 14"))?; + let linbits = LINBITS.get(select).copied().unwrap_or(0); + while i + 2 <= region_end { + if r.position() >= end { + return Ok(()); + } + let sym = tree.decode(r)?; + let mut pair = [(sym / size) as i32, (sym % size) as i32]; + for v in pair.iter_mut() { + if linbits > 0 && *v == 15 { + *v += bits(r, linbits)? as i32; + } + if *v != 0 && flag(r)? { + *v = -*v; + } + } + if let Some(o) = out.get_mut(i..i + 2) { + o.copy_from_slice(&pair); + } + i += 2; + } + } + let quad = trees.quad.as_ref().ok_or(Error::Invalid("count1 table"))?; + while i + 4 <= LINES && r.position() < end { + let q = if g.count1_table_b { 15 - bits(r, 4)? as usize } else { quad.decode(r)? }; + let mut v = [(q >> 3) & 1, (q >> 2) & 1, (q >> 1) & 1, q & 1].map(|b| b as i32); + for x in v.iter_mut() { + if *x != 0 && flag(r)? { + *x = -1; + } + } + if r.position() > end { + break; + } + if let Some(o) = out.get_mut(i..i + 4) { + o.copy_from_slice(&v); + } + i += 4; + } + Ok(()) +} + +/// Requantize (§2.4.3.4.7) into `xr`, in coded order: short-block bands window by window. +fn requantize(ints: &[i32; LINES], g: &Granule, sf: &Scalefactors, preflag: bool, lsf: bool, bands: &Bands, xr: &mut [f32; LINES]) { + let p43 = tables::pow43(); + let shift = if g.scalefac_scale { 1.0 } else { 0.5 }; + let gain = 0.25 * (g.global_gain - 210) as f32; + let value = |v: i32, exp: f32| { + let m = p43.get(v.unsigned_abs() as usize).copied().unwrap_or(0.0) * exp.exp2(); + if v < 0 { -m } else { m } + }; + *xr = [0.0; LINES]; + let (long_end, short_start) = match (g.short(), g.mixed) { + (false, _) => (22, 13), + (true, true) => (if lsf { 6 } else { 8 }, 3), + (true, false) => (0, 0), + }; + for sfb in 0..long_end { + let (a, b) = bands.long_band(sfb); + let boost = if preflag { PRETAB.get(sfb).copied().unwrap_or(0) } else { 0 }; + let exp = gain - shift * f32::from(sf.long.get(sfb).copied().unwrap_or(0) + boost); + for (x, &q) in xr.get_mut(a..b).unwrap_or_default().iter_mut().zip(ints.get(a..b).unwrap_or_default()) { + *x = value(q, exp); + } + } + if !g.short() { + return; + } + for sfb in short_start..13 { + let (start, width) = bands.short_band(sfb); + for w in 0..3 { + let scf = sf.short.get(sfb).and_then(|s| s.get(w)).copied().unwrap_or(0); + let exp = gain - 2.0 * g.subblock_gain[w] as f32 - shift * f32::from(scf); + let at = start + w * width; + for (x, &q) in xr.get_mut(at..at + width).unwrap_or_default().iter_mut().zip(ints.get(at..at + width).unwrap_or_default()) { + *x = value(q, exp); + } + } + } +} + +/// Reorder a short block's lines (§2.4.3.4.8) from window by window within each band to the +/// IMDCT's order: each subband's 18 lines become 6 frequencies × 3 windows. Mixed blocks keep +/// their long bands as they are. +fn reorder(xr: &mut [f32; LINES], g: &Granule, bands: &Bands) { + if !g.short() { + return; + } + let coded = *xr; + for sfb in if g.mixed { 3 } else { 0 }..13 { + let (start, width) = bands.short_band(sfb); + for w in 0..3 { + for i in 0..width { + if let (Some(&v), Some(x)) = (coded.get(start + w * width + i), xr.get_mut(start + 3 * i + w)) { + *x = v; + } + } + } + } +} +/// Intensity-stereo ratios `(left, right)` for MPEG-1 positions 0-6: `tan(pos·π/12)` split as +/// `(r / (1 + r), 1 / (1 + r))` (§2.4.3.4.9.3). +fn is_ratio_mpeg1(pos: u8) -> (f32, f32) { + let a = f64::from(pos) * PI / 12.0; + let (s, c) = a.sin_cos(); + ((s / (s + c)) as f32, (c / (s + c)) as f32) +} + +/// Lower-sampling-frequency intensity ratios (13818-3 §2.4.3.2). +fn is_ratio_lsf(pos: u8, intensity_scale: bool) -> (f32, f32) { + let io: f32 = if intensity_scale { 0.5f32.sqrt() } else { 0.5f32.powf(0.25) }; + match pos { + 0 => (1.0, 1.0), + p if p % 2 == 1 => (io.powi(i32::from(p).div_euclid(2) + 1), 1.0), + p => (1.0, io.powi(i32::from(p) / 2)), + } +} + +/// Joint stereo (§2.4.3.4.9): intensity stereo above the right channel's last non-zero band +/// (per window for short blocks), mid/side elsewhere. The right channel's block layout decides +/// the bands, as both channels must share it. The lines are in coded order (before +/// [`reorder`]), as ffmpeg and libmad process them: where the channels' layouts differ, against +/// the specification, each channel is then reordered by its own. +fn stereo(xr: &mut [[f32; LINES]; 2], mode_extension: u8, g: &Granule, sf: &Scalefactors, lsf: bool, bands: &Bands) { + let ms = mode_extension & 2 != 0; + let intensity = mode_extension & 1 != 0; + // per line: the intensity ratios, when the line is in an intensity band with a legal position + let mut ratios: Vec> = vec![None; LINES]; + if intensity { + let ratio = |pos: u8, illegal: u8| -> Option<(f32, f32)> { + if pos >= illegal { + None + } else if lsf { + Some(is_ratio_lsf(pos, g.scalefac_compress & 1 == 1)) + } else { + Some(is_ratio_mpeg1(pos)) + } + }; + let right = &xr[1]; + let mut set = |line: usize, r: Option<(f32, f32)>| { + if let Some(x) = ratios.get_mut(line) { + *x = r; + } + }; + let long_end = match (g.short(), g.mixed) { + (false, _) => 22, + (true, true) => { + if lsf { + 6 + } else { + 8 + } + } + (true, false) => 0, + }; + let mut nonzero_short = false; + if g.short() { + let short_start = if g.mixed { 3 } else { 0 }; + for w in 0..3 { + for sfb in (short_start..13).rev() { + let (start, width) = bands.short_band(sfb); + let lines = (0..width).map(|i| start + w * width + i); + if lines.clone().any(|l| right.get(l).is_some_and(|&v| v != 0.0)) { + nonzero_short = true; + break; + } + let k = sfb.min(11); + let pos = sf.short.get(k).and_then(|s| s.get(w)).copied().unwrap_or(0); + let illegal = sf.short_illegal.get(k).and_then(|s| s.get(w)).copied().unwrap_or(0); + let r = ratio(pos, illegal); + lines.for_each(|l| set(l, r)); + } + } + } + if !nonzero_short { + for sfb in (0..long_end).rev() { + let (a, b) = bands.long_band(sfb); + if right.get(a..b).unwrap_or_default().iter().any(|&v| v != 0.0) { + break; + } + let k = sfb.min(20); + let r = ratio(sf.long.get(k).copied().unwrap_or(0), sf.long_illegal.get(k).copied().unwrap_or(0)); + (a..b).for_each(|l| set(l, r)); + } + } + } + let [left, right] = xr; + let inv_sqrt2 = std::f32::consts::FRAC_1_SQRT_2; + for ((l, r), ratio) in left.iter_mut().zip(right.iter_mut()).zip(&ratios) { + match ratio { + Some((kl, kr)) => { + let v = *l; + *l = v * kl; + *r = v * kr; + } + None if ms => { + let (m, s) = (*l, *r); + *l = (m + s) * inv_sqrt2; + *r = (m - s) * inv_sqrt2; + } + None => {} + } + } +} + +/// Alias reduction butterflies (§2.4.3.4.10.1) between the long-block subbands. +fn antialias(xr: &mut [f32; LINES], g: &Granule) { + let boundaries = match (g.short(), g.mixed) { + (false, _) => 31, + (true, true) => 1, + (true, false) => 0, + }; + let coefficients = tables::alias_coefficients(); + for sb in 1..=boundaries { + for (i, &(cs, ca)) in coefficients.iter().enumerate() { + let (up, dn) = (18 * sb - 1 - i, 18 * sb + i); + if let (Some(&a), Some(&b)) = (xr.get(up), xr.get(dn)) { + xr[up] = a * cs - b * ca; + xr[dn] = b * cs + a * ca; + } + } + } +} + +/// IMDCT kernels and windows (§2.4.3.4.10.2). +struct Imdct { + /// `cos(π/72 · (2i + 1 + 18)(2k + 1))`, `[i][k]`. + long: [[f32; 18]; 36], + /// The windows of block types 0, 1 and 3 (index 2 unused). + windows: [[f32; 36]; 4], + /// `cos(π/24 · (2i + 1 + 6)(2k + 1))`, `[i][k]`. + short: [[f32; 6]; 12], + short_window: [f32; 12], +} + +fn imdct() -> &'static Imdct { + static I: OnceLock = OnceLock::new(); + I.get_or_init(|| { + let mut t = Imdct { long: [[0.0; 18]; 36], windows: [[0.0; 36]; 4], short: [[0.0; 6]; 12], short_window: [0.0; 12] }; + for (i, row) in t.long.iter_mut().enumerate() { + for (k, c) in row.iter_mut().enumerate() { + *c = (PI / 72.0 * (2 * i + 1 + 18) as f64 * (2 * k + 1) as f64).cos() as f32; + } + } + for (i, row) in t.short.iter_mut().enumerate() { + for (k, c) in row.iter_mut().enumerate() { + *c = (PI / 24.0 * (2 * i + 1 + 6) as f64 * (2 * k + 1) as f64).cos() as f32; + } + } + let sin36 = |i: usize| (PI / 36.0 * (i as f64 + 0.5)).sin() as f32; + let sin12 = |i: usize| (PI / 12.0 * (i as f64 + 0.5)).sin() as f32; + for i in 0..36 { + t.windows[0][i] = sin36(i); + t.windows[1][i] = match i { + 0..18 => sin36(i), + 18..24 => 1.0, + 24..30 => sin12(i - 18), + _ => 0.0, + }; + t.windows[3][i] = match i { + 0..6 => 0.0, + 6..12 => sin12(i - 6), + 12..18 => 1.0, + _ => sin36(i), + }; + } + for (i, w) in t.short_window.iter_mut().enumerate() { + *w = sin12(i); + } + t + }) +} + +/// The 36 windowed IMDCT outputs of one subband's 18 lines. +fn imdct_subband(x: &[f32; 18], short: bool, block_type: u8, out: &mut [f32; 36]) { + let t = imdct(); + *out = [0.0; 36]; + if short { + for w in 0..3 { + for (i, (row, win)) in t.short.iter().zip(&t.short_window).enumerate() { + let s: f32 = row.iter().enumerate().map(|(k, c)| x[3 * k + w] * c).sum(); + out[6 + 6 * w + i] += s * win; + } + } + } else { + let win = &t.windows[usize::from(block_type & 3)]; + for ((o, row), w) in out.iter_mut().zip(&t.long).zip(win) { + let s: f32 = row.iter().zip(x).map(|(c, v)| c * v).sum(); + *o = s * w; + } + } +} + +/// What a Xing / Info or VBRI frame at the start of a stream says. That frame carries no +/// audio: a decoder skips it. +#[non_exhaustive] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct InfoTag { + /// Audio frames in the stream, the tag's frame not counted, when the tag says. + pub frames: Option, + /// The LAME tag's encoder delay and padding, in samples, when there is one: the stream's + /// audio starts `delay + 529` samples into the decoded output (529 being the decoder's own + /// delay) and the last `padding - 529` decoded samples are not audio. + pub delay_padding: Option<(u32, u32)>, +} + +impl InfoTag { + /// The tag in the Layer III frame at the start of `frame`, if it is a Xing / Info or VBRI + /// frame. + pub fn parse(frame: &[u8]) -> Option { + let h = mpa::parse_header(frame).ok().filter(|h| h.layer == 3)?; + let frame = frame.get(..h.frame_bytes.max(4)).unwrap_or(frame); + let be32 = |at: usize| frame.get(at..at + 4).and_then(|b| b.try_into().ok()).map(u32::from_be_bytes); + if frame.get(36..40) == Some(b"VBRI") { + // VBRI: version (2), delay (2), quality (2), bytes (4), frames (4) + return Some(InfoTag { frames: be32(36 + 14), delay_padding: None }); + } + let at = 4 + side_info_len(&h); + let id = frame.get(at..at + 4)?; + if id != b"Xing" && id != b"Info" { + return None; + } + let flags = be32(at + 4)?; + let mut p = at + 8; + let mut tag = InfoTag::default(); + if flags & 1 != 0 { + tag.frames = be32(p); + p += 4; + } + if flags & 2 != 0 { + p += 4; + } + if flags & 4 != 0 { + p += 100; + } + if flags & 8 != 0 { + p += 4; + } + // the LAME extension: a 9-byte encoder string, then delay and padding 12 bits each at + // byte 21 + let encoder = frame.get(p..p + 4); + if matches!(encoder, Some(b"LAME" | b"Lavf" | b"Lavc")) + && let Some(&[a, b, c]) = frame.get(p + 21..p + 24) + { + let delay = (u32::from(a) << 4) | (u32::from(b) >> 4); + let padding = (u32::from(b & 15) << 8) | u32::from(c); + tag.delay_padding = Some((delay, padding)); + } + Some(tag) + } +} + +/// A Layer III (MP3) decoder: MPEG-1, MPEG-2 lower sampling frequencies and MPEG-2.5; mono, +/// stereo, dual channel and joint stereo (mid/side and intensity); long, short and mixed blocks. +/// Output is planar f32 at full scale ±1, 1152 samples per channel per MPEG-1 frame and 576 per +/// MPEG-2 / 2.5 frame. +/// +/// Frames share the bit reservoir and the filter banks' state, so feed them in order and call +/// [`Decoder::reset`] before decoding from another position. A frame whose main data starts in +/// frames this decoder has not seen (the first frames after a seek) decodes as silence. Errors +/// are [`mpa::Error`](crate::mpa::Error), as for the other MPEG audio layers; a damaged frame +/// still updates the reservoir, so the frames after it decode. +/// +/// Not decoded: the free format (bit rate index 0) and the CRC (read, not checked). A Xing / Info +/// frame decodes like any other (to silence); [`InfoTag::parse`] tells it apart. +/// +/// ``` +/// use craft_audio::mp3::Decoder; +/// +/// // three silent frames: MPEG-1 Layer III, 128 kbit/s, 44.1 kHz, stereo +/// let mut frame = vec![0u8; 417]; +/// frame[..4].copy_from_slice(&[0xFF, 0xFB, 0x90, 0x00]); +/// let stream = frame.repeat(3); +/// +/// let mut dec = Decoder::new(); +/// let mut left = Vec::new(); +/// let mut at = 0; +/// while at < stream.len() { +/// let frame = dec.decode(&stream[at..])?; +/// at += frame.header.frame_bytes; +/// left.extend_from_slice(&frame.channels[0]); +/// } +/// assert_eq!(left.len(), 3 * 1152); +/// # Ok::<(), Box>(()) +/// ``` +pub struct Decoder { + /// Main data of the frames before, for `main_data_begin` (at most 511 bytes). + reservoir: Vec, + /// The second half of each subband's last IMDCT output per channel, `[ch][sb * 18 + i]`. + overlap: Box<[[f32; LINES]; 2]>, + /// The synthesis filter's `V` ring per channel and where it starts (see `mpa`). + v: Box<[[f32; 1024]; 2]>, + pos: [usize; 2], + /// The last frame's header, for [`Decoder::preroll`]. + last: Option
, +} + +impl Default for Decoder { + fn default() -> Self { + Self::new() + } +} + +impl Decoder { + /// A decoder with an empty reservoir and filter state. + pub fn new() -> Self { + Decoder { reservoir: Vec::new(), overlap: Box::new([[0.0; LINES]; 2]), v: Box::new([[0.0; 1024]; 2]), pos: [0; 2], last: None } + } + + /// Forget the reservoir and the filter state. + pub fn reset(&mut self) { + self.reservoir.clear(); + *self.overlap = [[0.0; LINES]; 2]; + *self.v = [[0.0; 1024]; 2]; + self.pos = [0; 2]; + } + + /// Samples per channel to decode before a seek target, so that its frame's main data and + /// the filter state are complete: enough of the smallest frames the stream can hold to span + /// the furthest reach of `main_data_begin`, and two more for the IMDCT overlap and synthesis + /// filter. A VBR stream's frames before the target may be of the lowest bit rate whatever the + /// last frame was; the version, sampling frequency, channel mode and CRC do not change within + /// a stream. `None` before the first frame. + pub fn preroll(&self) -> Option { + let h = self.last?; + // the lowest bit rate: 32 kbit/s in MPEG-1, 8 kbit/s at the lower sampling frequencies + let (slots, kbps, back) = if h.version == Version::Mpeg1 { (144, 32, MAX_BACK_MPEG1) } else { (72, 8, MAX_BACK_LSF) }; + let smallest = slots * kbps * 1000 / (h.sample_rate as usize).max(1); + let main = smallest.saturating_sub(4 + if h.crc { 2 } else { 0 } + side_info_len(&h)).max(1); + Some((back.div_ceil(main) + 2) * h.samples()) + } + + /// Take the stream's format from `header` (its first frame) before any frame is decoded, so + /// that [`Decoder::preroll`] knows it. + pub(crate) fn prime(&mut self, header: Header) { + self.last.get_or_insert(header); + } + + /// Decode the frame at the start of `frame` (which may run on into the next frames; only + /// [`Header::frame_bytes`](crate::mpa::Header::frame_bytes) are read). + pub fn decode(&mut self, frame: &[u8]) -> Result { + let header = mpa::parse_header(frame)?; + if header.layer != 3 { + return Err(Error::Unsupported("not Layer III")); + } + if header.frame_bytes == 0 { + return Err(Error::Unsupported("free-format bit rate")); + } + let data = frame.get(..header.frame_bytes).ok_or(Error::Invalid("truncated frame"))?; + let si_start = 4 + if header.crc { 2 } else { 0 }; + let si_end = si_start + side_info_len(&header); + let si_bytes = data.get(si_start..si_end).ok_or(Error::Invalid("frame shorter than its side information"))?; + let main = data.get(si_end..).unwrap_or_default(); + let mut r = BitReader::new(si_bytes); + let si = side_info(&mut r, &header); + // The main data runs from main_data_begin bytes before this frame's into it; the bytes + // of this frame join the reservoir whether or not it decodes. + let begin = si.as_ref().map_or(0, |si| si.main_data_begin); + let have = begin <= self.reservoir.len(); + let mut buf = Vec::with_capacity(begin + main.len()); + if have { + buf.extend_from_slice(self.reservoir.get(self.reservoir.len() - begin..).unwrap_or_default()); + buf.extend_from_slice(main); + } + self.reservoir.extend_from_slice(main); + let excess = self.reservoir.len().saturating_sub(MAX_BACK_MPEG1); + self.reservoir.drain(..excess); + let si = si?; + self.last = Some(header); + + let nch = header.channels(); + let lsf = header.version != Version::Mpeg1; + let bands = Bands::of(header.sample_rate); + let joint = header.mode == Mode::JointStereo && nch == 2; + let mut out = vec![vec![0f32; header.samples()]; nch]; + let mut r = BitReader::new(&buf); + let mut at = 0usize; + let mut prev = [Scalefactors::new(7); 2]; + let mut damaged = false; + for gr in 0..granules(&header) { + let mut xr = [[0f32; LINES]; 2]; + let mut sfs = [Scalefactors::new(7); 2]; + for ch in 0..nch { + let g = &si.granules[gr][ch]; + let start = at; + at += g.part2_3_length; + if !have { + continue; + } + let end = at; + r.seek_bits(start.min(buf.len() * 8)); + let mut ints = [0i32; LINES]; + let decoded = (|| -> Result<(Scalefactors, bool)> { + if end > buf.len() * 8 { + return Err(overrun()); + } + let (sf, preflag) = if lsf { + scalefactors_lsf(&mut r, g, joint && ch == 1 && header.mode_extension & 1 != 0)? + } else { + (scalefactors_mpeg1(&mut r, g, &si.scfsi[ch], gr == 1, &prev[ch])?, g.preflag) + }; + huffman(&mut r, end, g, &bands, &mut ints)?; + Ok((sf, preflag)) + })(); + match decoded { + Ok((sf, preflag)) => { + requantize(&ints, g, &sf, preflag, lsf, &bands, &mut xr[ch]); + sfs[ch] = sf; + } + Err(_) => damaged = true, + } + } + if joint { + stereo(&mut xr, header.mode_extension, &si.granules[gr][1], &sfs[1], lsf, &bands); + } + prev = sfs; + for ch in 0..nch { + let g = &si.granules[gr][ch]; + reorder(&mut xr[ch], g, &bands); + antialias(&mut xr[ch], g); + let pcm = out.get_mut(ch).and_then(|o| o.get_mut(gr * LINES..(gr + 1) * LINES)); + if let Some(pcm) = pcm { + self.hybrid_synthesis(ch, &xr[ch], g, pcm); + } + } + } + if damaged { + return Err(Error::Invalid("damaged Layer III main data")); + } + Ok(Frame { header, channels: out }) + } + + /// IMDCT, overlap-add, frequency inversion and polyphase synthesis of one granule of one + /// channel into its 576 samples. + fn hybrid_synthesis(&mut self, ch: usize, xr: &[f32; LINES], g: &Granule, pcm: &mut [f32]) { + let mut sub = [[0f32; 32]; 18]; + let overlap = &mut self.overlap[ch & 1]; + let mut y = [0f32; 36]; + for (sb, (x, prev)) in xr.as_chunks::<18>().0.iter().zip(overlap.as_chunks_mut::<18>().0).enumerate() { + let short = g.short() && !(g.mixed && sb < 2); + let block_type = if g.short() { 0 } else { g.block_type }; + imdct_subband(x, short, block_type, &mut y); + for (t, (p, (&a, &b))) in prev.iter_mut().zip(y.iter().zip(&y[18..])).enumerate() { + let v = a + *p; + *p = b; + sub[t][sb] = if sb % 2 == 1 && t % 2 == 1 { -v } else { v }; + } + } + let window = mpa::tables::window(); + let matrix = mpa::tables::matrix_t(); + let (v, pos) = (&mut self.v[ch & 1], &mut self.pos[ch & 1]); + for (slot, o) in sub.iter().zip(pcm.as_chunks_mut::<32>().0) { + mpa::synthesize(v, pos, matrix, window, slot, o); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// A damaged granule whose big values run past its `part2_3_length` stops at its end: the + /// bits after it are the next granule's. + #[test] + fn big_values_stop_at_the_granule_end() { + // table 1 decodes each run of zero bits as (1, 1) with two positive signs: 5 bits a pair + let data = [0u8; 200]; + let mut r = BitReader::new(&data); + let g = Granule { big_values: 288, table_select: [1, 1, 1], region0_count: 15, region1_count: 7, ..Granule::default() }; + let mut out = [0i32; LINES]; + huffman(&mut r, 10, &g, &Bands::of(44_100), &mut out).unwrap(); + assert_eq!(out[..4], [1, 1, 1, 1]); + assert!(out[4..].iter().all(|&v| v == 0), "lines past the granule's end were decoded"); + } + + /// The seek pre-roll covers the smallest frames the stream may hold, not the last frame's + /// size: a 320 kbit/s frame at 44.1 kHz holds 1008 bytes of main data, a 32 kbit/s one 68, + /// so the 511 bytes of reservoir span 8 small frames. + #[test] + fn preroll_assumes_the_smallest_frames() { + let mut frame = vec![0u8; 1044]; + frame[..4].copy_from_slice(&[0xFF, 0xFB, 0xE0, 0x00]); // MPEG-1 Layer III, 320 kbit/s, 44.1 kHz, stereo + let mut dec = Decoder::new(); + assert_eq!(dec.preroll(), None); + dec.decode(&frame).unwrap(); + assert_eq!(dec.preroll(), Some((8 + 2) * 1152)); + // 8 kbit/s at 24 kHz, stereo: 24-byte frames with 3 bytes of main data each + let mut dec = Decoder::new(); + dec.prime(mpa::parse_header(&[0xFF, 0xF3, 0x14, 0x00]).unwrap()); + assert_eq!(dec.preroll(), Some((85 + 2) * 576)); + } + + #[test] + fn every_huffman_table_is_a_complete_prefix_code() { + let t = trees(); + for select in (1..32).filter(|s| *s != 4 && *s != 14) { + let Some(p) = pair_table(select) else { panic!("table {select}") }; + assert!(t.pair(select).is_some(), "table {select} does not build"); + // Kraft sum of exactly 1: no code is missing + let kraft: f64 = p.lens.iter().map(|&l| 0.5f64.powi(i32::from(l))).sum(); + assert!((kraft - 1.0).abs() < 1e-12, "table {select}: {kraft}"); + } + assert!(t.pair(4).is_none() && t.pair(14).is_none()); + assert!(t.quad.is_some()); + } + + #[test] + fn band_tables_cover_the_granule() { + for rate in [44_100, 48_000, 32_000, 22_050, 24_000, 16_000, 12_000, 11_025, 8_000] { + let b = Bands::of(rate); + assert_eq!(b.long[22], LINES, "{rate}"); + assert_eq!(b.short[13] * 3, LINES, "{rate}"); + assert!(b.long.windows(2).all(|w| w[0] < w[1]), "{rate}"); + } + } + + #[test] + fn intensity_ratios() { + assert_eq!(is_ratio_mpeg1(0), (0.0, 1.0)); + let (l, r) = is_ratio_mpeg1(3); + assert!((l - 0.5).abs() < 1e-6 && (r - 0.5).abs() < 1e-6); + assert_eq!(is_ratio_lsf(0, false), (1.0, 1.0)); + let (l, r) = is_ratio_lsf(1, true); + assert!((l - 0.5f32.sqrt()).abs() < 1e-6 && r == 1.0); + let (l, r) = is_ratio_lsf(4, true); + assert!(l == 1.0 && (r - 0.5).abs() < 1e-6); + } + + /// Hostile frames: every header combination with random side information and main data + /// decodes or fails without panicking. + #[test] + fn random_frames_do_not_panic() { + let mut seed = 0x1234_5678_u32; + let mut rnd = || { + seed ^= seed << 13; + seed ^= seed >> 17; + seed ^= seed << 5; + seed + }; + let mut dec = Decoder::new(); + for i in 0..20_000 { + let mut f: Vec = (0..1500).map(|_| rnd() as u8).collect(); + f[0] = 0xFF; + f[1] = 0xE2 | ((i as u8 & 3) << 3) | (rnd() as u8 & 1); + let _ = dec.decode(&f); + if i % 1000 == 0 { + dec.reset(); + } + } + } +} diff --git a/crates/audio/src/mp3/encoder.rs b/crates/audio/src/mp3/encoder.rs new file mode 100644 index 000000000..25664d361 --- /dev/null +++ b/crates/audio/src/mp3/encoder.rs @@ -0,0 +1,479 @@ +//! The Layer III encoder; the module docs are in `mp3/mod.rs`. + +// Index loops follow the specification's pseudo-code (FilmCraft allows this lint workspace-wide). +#![allow(clippy::needless_range_loop)] + +use std::collections::VecDeque; + +use craft_media::bitstream::BitWriter; + +use super::filterbank::{Analysis, GRANULE}; +use super::quantize::{Granule, Model}; +use super::tables; + +/// Settings an [`Encoder`] refuses. +#[non_exhaustive] +#[derive(Debug, thiserror::Error, PartialEq, Eq)] +pub enum Error { + /// A sample rate outside [`SAMPLE_RATES`]. + #[error("MP3: unsupported sample rate {0} Hz (32, 44.1, 48 kHz, or 16, 22.05, 24 kHz)")] + SampleRate(u32), + /// A bit rate that is not one of [`bitrates`] for the sample rate. + #[error("MP3: unsupported bit rate {kbps} kbit/s at {rate} Hz")] + Bitrate { + /// The bit rate asked for, kbit/s. + kbps: u32, + /// The sample rate, Hz. + rate: u32, + }, + /// More than two channels, or none. + #[error("MP3: unsupported channel count {0} (1 or 2)")] + Channels(usize), +} + +/// Result with this module's [`Error`]. +pub type Result = std::result::Result; + +/// How two channels are coded. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum StereoMode { + /// Left and right coded independently (`mode` 00). + Stereo, + /// Joint stereo (`mode` 01): each frame chooses left / right or mid / side. + #[default] + JointStereo, +} + +/// Encoder settings. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Config { + /// One of [`SAMPLE_RATES`]. + pub sample_rate: u32, + /// 1 or 2. + pub channels: usize, + /// One of [`bitrates`] for the sample rate, kbit/s. + pub bitrate_kbps: u32, + /// How two channels are coded (ignored for mono). + pub stereo: StereoMode, +} + +impl Config { + /// `channels` at `sample_rate`, constant `bitrate_kbps`, joint stereo. + pub fn cbr(sample_rate: u32, channels: usize, bitrate_kbps: u32) -> Config { + Config { sample_rate, channels, bitrate_kbps, stereo: StereoMode::JointStereo } + } +} + +/// Whether the sample rate needs MPEG-2's lower sampling frequencies. +fn is_lsf(rate: u32) -> Option { + if tables::RATES_MPEG1.contains(&rate) { + Some(false) + } else if tables::RATES_LSF.contains(&rate) { + Some(true) + } else { + None + } +} + +/// The sample rates the encoder accepts. +pub const SAMPLE_RATES: [u32; 6] = [32_000, 44_100, 48_000, 16_000, 22_050, 24_000]; + +/// The bit rates (kbit/s) a stream at `sample_rate` can use; empty for an unsupported rate. +pub fn bitrates(sample_rate: u32) -> &'static [u32] { + match is_lsf(sample_rate) { + Some(false) => &tables::BITRATES_MPEG1, + Some(true) => &tables::BITRATES_LSF, + None => &[], + } +} + +/// The highest bit rate at `sample_rate` not above `kbps` (the lowest one when `kbps` is below +/// all of them); `None` for an unsupported rate. +pub fn nearest_bitrate(sample_rate: u32, kbps: u32) -> Option { + let rates = bitrates(sample_rate); + rates.iter().rev().find(|&&r| r <= kbps).or(rates.first()).copied() +} + +/// The supported sample rate an encoder should use for audio at `rate`: `rate` itself when +/// supported, else the closest supported one at or above it (48 kHz above 48 kHz). +pub fn nearest_sample_rate(rate: u32) -> u32 { + let mut sorted = SAMPLE_RATES; + sorted.sort_unstable(); + sorted.iter().copied().find(|&r| r >= rate).unwrap_or(48_000) +} + +/// Energy of the frequency lines of a full-scale sine (amplitude 1) over one granule. +const FULL_SCALE_TONE_ENERGY: f32 = 1.0; +/// Playback level of a full-scale sine assumed by the threshold in quiet, dB SPL. +const FULL_SCALE_SPL: f64 = 96.0; +/// Samples by which a standard decoder's output lags the input: the polyphase analysis and +/// synthesis filters (481) and the MDCT overlap of one granule (576). +pub const DELAY: u32 = 1057; + +/// A Layer III encoder. Feed planar `f32` samples (full scale ±1) to [`Encoder::encode`]; it +/// returns whole frames as they are completed. [`Encoder::flush`] pads the end with silence and +/// returns the rest. +/// +/// ``` +/// use craft_audio::mp3::{Config, DELAY, Encoder}; +/// +/// let left: Vec = (0..48_000).map(|i| (i as f32 * 0.05).sin() * 0.5).collect(); +/// let right = left.clone(); +/// let mut enc = Encoder::new(Config::cbr(48_000, 2, 192))?; +/// let mut mp3 = enc.encode(&[&left, &right]); // whole frames as they complete +/// mp3.extend(enc.flush()); // pads with silence to the last input sample +/// assert_eq!(mp3.len() % 576, 0); // 192 kbit/s at 48 kHz: 576 bytes per frame +/// assert_eq!(DELAY, 1057); // a decoder's output lags the input by this many samples +/// # Ok::<(), craft_audio::mp3::Error>(()) +/// ``` +pub struct Encoder { + cfg: Config, + lsf: bool, + sr_index: u32, + br_index: u32, + granules: usize, + analysis: Vec, + model: Model, + /// Buffered input per channel, and how much of it is consumed. + input: Vec>, + consumed: usize, + /// Frames whose main-data slot is not yet filled: header + side information, slot bytes. + pending: VecDeque<(Vec, usize)>, + /// Main data from the first pending frame's slot on. + main: Vec, + /// Slot bytes of the pending frames. + slot_total: usize, + /// Largest `main_data_begin`, bytes. + max_reservoir: usize, + /// Remainder accumulator for the padding slot (44.1 / 22.05 kHz). + pad_acc: u64, + samples_in: u64, + frames: u64, +} + +impl Encoder { + /// An encoder for `cfg`, or the setting it cannot carry. + pub fn new(cfg: Config) -> Result { + let lsf = is_lsf(cfg.sample_rate).ok_or(Error::SampleRate(cfg.sample_rate))?; + if !(1..=2).contains(&cfg.channels) { + return Err(Error::Channels(cfg.channels)); + } + let rates = if lsf { &tables::RATES_LSF } else { &tables::RATES_MPEG1 }; + let sr_index = rates.iter().position(|&r| r == cfg.sample_rate).ok_or(Error::SampleRate(cfg.sample_rate))?; + let br_index = bitrates(cfg.sample_rate) + .iter() + .position(|&b| b == cfg.bitrate_kbps) + .ok_or(Error::Bitrate { kbps: cfg.bitrate_kbps, rate: cfg.sample_rate })?; + let bands = if lsf { tables::SFB_LSF[sr_index] } else { tables::SFB_MPEG1[sr_index] }; + let line_hz = cfg.sample_rate as f64 / 2.0 / GRANULE as f64; + let mut ath = [0f32; 22]; + for (b, a) in ath.iter_mut().enumerate() { + let (lo, hi) = (usize::from(bands[b]), usize::from(bands[b + 1])); + let db = (lo..hi).map(|i| tables::ath_db((i as f64 + 0.5) * line_hz)).fold(f64::MAX, f64::min); + *a = (f64::from(FULL_SCALE_TONE_ENERGY) * 10f64.powf((db - FULL_SCALE_SPL) / 10.0)) as f32; + } + let cutoff_hz = cutoff_hz(cfg.bitrate_kbps, cfg.channels).min(cfg.sample_rate as f64 * 0.5); + let cutoff = ((cutoff_hz / line_hz).ceil() as usize).min(GRANULE); + let frame_bits = u64::from(cfg.bitrate_kbps) * 1000 * if lsf { 576 } else { 1152 } / u64::from(cfg.sample_rate); + // reservoir and frame must fit the decoder's 7680-bit input buffer + let max_reservoir = (7680u64.saturating_sub(frame_bits) / 8).min(if lsf { 255 } else { 511 }) as usize; + Ok(Encoder { + cfg, + lsf, + sr_index: sr_index as u32, + br_index: br_index as u32 + 1, + granules: if lsf { 1 } else { 2 }, + analysis: vec![Analysis::new(); cfg.channels], + model: Model::new(lsf, bands, ath, cutoff), + input: vec![Vec::new(); cfg.channels], + consumed: 0, + pending: VecDeque::new(), + main: Vec::new(), + slot_total: 0, + max_reservoir, + pad_acc: 0, + samples_in: 0, + frames: 0, + }) + } + + /// The settings. + pub fn config(&self) -> &Config { + &self.cfg + } + + /// Samples per channel in a frame: 1152 (MPEG-1) or 576 (MPEG-2 LSF). + pub fn frame_samples(&self) -> usize { + GRANULE * self.granules + } + + /// Samples by which a standard decoder's output lags the input. + pub fn delay(&self) -> u32 { + DELAY + } + + /// Frames written so far. + pub fn frames(&self) -> u64 { + self.frames + } + + /// Encode planar samples (one slice per channel; a missing channel is silence, extra ones + /// are ignored, every channel takes the first channel's length). Returns the completed frames. + pub fn encode(&mut self, planar: &[&[f32]]) -> Vec { + let n = planar.first().map_or(0, |p| p.len()); + for (c, buf) in self.input.iter_mut().enumerate() { + let src = planar.get(c).copied().unwrap_or(&[]); + buf.extend((0..n).map(|i| src.get(i).copied().filter(|v| v.is_finite()).unwrap_or(0.0).clamp(-4.0, 4.0))); + } + self.samples_in = self.samples_in.saturating_add(n as u64); + let mut out = Vec::new(); + self.drain(&mut out); + out + } + + /// Encode the buffered rest padded with silence, plus enough silence that a decoder outputs + /// every input sample, and return the remaining frames. + pub fn flush(&mut self) -> Vec { + let fs = self.frame_samples() as u64; + let needed = if self.samples_in == 0 { self.frames } else { self.samples_in.saturating_add(u64::from(DELAY)).div_ceil(fs) }; + let have = self.input.first().map_or(0, |b| b.len().saturating_sub(self.consumed)); + let want = (needed.saturating_sub(self.frames) as usize).saturating_mul(fs as usize); + if want > have { + for buf in &mut self.input { + buf.resize(self.consumed + want, 0.0); + } + } + let mut out = Vec::new(); + self.drain(&mut out); + // fill the last frames' slots and write them out + self.main.resize(self.slot_total.max(self.main.len()), 0); + self.emit(&mut out); + self.input.iter_mut().for_each(Vec::clear); + self.consumed = 0; + out + } + + /// Encode every whole frame of buffered input. + fn drain(&mut self, out: &mut Vec) { + let fs = self.frame_samples(); + let mut pcm = vec![[0f32; GRANULE]; self.cfg.channels * self.granules]; + while self.input.first().is_some_and(|b| b.len() - self.consumed >= fs) { + for (c, buf) in self.input.iter().enumerate() { + for gr in 0..self.granules { + let start = self.consumed + gr * GRANULE; + if let (Some(dst), Some(src)) = (pcm.get_mut(gr * self.cfg.channels + c), buf.get(start..start + GRANULE)) { + dst.copy_from_slice(src); + } + } + } + self.consumed += fs; + self.encode_frame(&pcm); + self.emit(out); + } + if self.consumed > 0 { + let k = self.consumed; + self.input.iter_mut().for_each(|b| { + b.drain(..k.min(b.len())); + }); + self.consumed = 0; + } + } + + /// Bytes of the next frame (with its padding slot). + fn next_frame_len(&mut self) -> usize { + let per = if self.lsf { 72_000 } else { 144_000 }; + let num = per * u64::from(self.cfg.bitrate_kbps); + let sr = u64::from(self.cfg.sample_rate); + self.pad_acc += num % sr; + let pad = self.pad_acc >= sr; + if pad { + self.pad_acc -= sr; + } + (num / sr) as usize + usize::from(pad) + } + + fn side_info_len(&self) -> usize { + match (self.lsf, self.cfg.channels) { + (false, 1) => 17, + (false, _) => 32, + (true, 1) => 9, + (true, _) => 17, + } + } + + /// Analyse, quantize and assemble one frame (`pcm[gr * channels + ch]`). + fn encode_frame(&mut self, pcm: &[[f32; GRANULE]]) { + let nch = self.cfg.channels; + let ngr = self.granules; + let mut xr = vec![[0f32; GRANULE]; nch * ngr]; + for gr in 0..ngr { + for ch in 0..nch { + let k = gr * nch + ch; + if let (Some(a), Some(p), Some(x)) = (self.analysis.get_mut(ch), pcm.get(k), xr.get_mut(k)) { + a.granule(p, x); + x.iter_mut().skip(self.model.cutoff).for_each(|v| *v = 0.0); + } + } + } + let mut xmin: Vec<[f32; 22]> = xr.iter().map(|x| self.model.thresholds(x)).collect(); + let ms = nch == 2 && self.cfg.stereo == StereoMode::JointStereo && use_mid_side(&xr); + if ms { + for gr in 0..ngr { + let (l, r) = xr.split_at_mut(gr * 2 + 1); + if let (Some(l), Some(r)) = (l.last_mut(), r.first_mut()) { + for (a, b) in l.iter_mut().zip(r.iter_mut()) { + let (m, s) = ((*a + *b) * std::f32::consts::FRAC_1_SQRT_2, (*a - *b) * std::f32::consts::FRAC_1_SQRT_2); + *a = m; + *b = s; + } + } + let t: [f32; 22] = std::array::from_fn(|b| xmin[gr * 2][b].min(xmin[gr * 2 + 1][b])); + xmin[gr * 2] = t; + xmin[gr * 2 + 1] = t; + } + } + // the reservoir: free bytes in the pending frames' slots, stuffed down to the maximum + let free = self.slot_total.saturating_sub(self.main.len()); + if free > self.max_reservoir { + self.main.resize(self.slot_total - self.max_reservoir, 0); + } + let main_data_begin = free.min(self.max_reservoir); + let frame_len = self.next_frame_len(); + let slot = frame_len.saturating_sub(4 + self.side_info_len()); + let budget = 8 * (main_data_begin + slot); + let n = nch * ngr; + let mean = 8 * slot / n; + let max_reservoir_bits = 8 * self.max_reservoir; + let mut used = 0usize; + let mut coded: Vec = Vec::with_capacity(n); + for (k, (x, t)) in xr.iter().zip(&xmin).enumerate() { + let left = n - k; + let remaining = budget.saturating_sub(used); + let surplus = remaining.saturating_sub(mean * left); + let max_bits = (mean + surplus * 3 / 5 / left).min(remaining).min(4095); + let room = max_reservoir_bits.saturating_sub(surplus); + let min_bits = mean.saturating_sub(room / left); + let g = self.model.encode(x, t, max_bits, min_bits); + used += g.bits(); + coded.push(g); + } + let mut md = BitWriter::new(); + for g in &coded { + g.write(&mut md, &self.model); + } + let mut main = md.finish(); + if main.len() > main_data_begin + slot || coded.iter().any(|g| g.bits() > 4095) { + // never expected: fall back to silence rather than write a broken frame + coded = (0..n).map(|_| self.model.encode(&[0.0; GRANULE], &[f32::MAX; 22], 0, 0)).collect(); + main = Vec::new(); + } + let head = self.header_and_side_info(frame_len, ms, main_data_begin, &coded); + self.pending.push_back((head, slot)); + self.slot_total += slot; + self.main.extend_from_slice(&main); + self.frames += 1; + } + + /// Write out the pending frames whose slots are full. + fn emit(&mut self, out: &mut Vec) { + while let Some((_, slot)) = self.pending.front() { + let slot = *slot; + if self.main.len() < slot { + break; + } + if let Some((head, _)) = self.pending.pop_front() { + out.extend_from_slice(&head); + out.extend(self.main.drain(..slot)); + self.slot_total -= slot; + } + } + } + + fn header_and_side_info(&self, frame_len: usize, ms: bool, main_data_begin: usize, coded: &[Granule]) -> Vec { + let nch = self.cfg.channels; + let padding = frame_len > (if self.lsf { 72_000 } else { 144_000 } * self.cfg.bitrate_kbps / self.cfg.sample_rate) as usize; + let mode = match (nch, self.cfg.stereo) { + (1, _) => 3, + (_, StereoMode::Stereo) => 0, + (_, StereoMode::JointStereo) => 1, + }; + let mut w = BitWriter::new(); + w.write_bits(0x7FF, 11); + w.write_bits(if self.lsf { 2 } else { 3 }, 2); // ID: MPEG-1 = 11, MPEG-2 = 10 + w.write_bits(1, 2); // Layer III + w.write_bit(true); // protection_bit: no CRC + w.write_bits(self.br_index, 4); + w.write_bits(self.sr_index, 2); + w.write_bit(padding); + w.write_bit(false); // private_bit + w.write_bits(mode, 2); + w.write_bits(if ms { 2 } else { 0 }, 2); // mode_extension: mid/side, no intensity + w.write_bit(false); // copyright + w.write_bit(true); // original + w.write_bits(0, 2); // emphasis + if self.lsf { + w.write_bits(main_data_begin as u32, 8); + w.write_bits(0, if nch == 1 { 1 } else { 2 }); + } else { + w.write_bits(main_data_begin as u32, 9); + w.write_bits(0, if nch == 1 { 5 } else { 3 }); + w.write_bits(0, 4 * nch as u32); // scfsi + } + for g in coded { + let c = &g.coding; + w.write_bits(g.bits() as u32, 12); + w.write_bits(c.big_values as u32, 9); + w.write_bits(g.global_gain, 8); + w.write_bits(g.scalefac_compress, if self.lsf { 9 } else { 4 }); + w.write_bit(false); // window_switching_flag + for t in c.table_select { + w.write_bits(t as u32, 5); + } + w.write_bits(c.region0_count as u32, 4); + w.write_bits(c.region1_count as u32, 3); + if !self.lsf { + w.write_bit(false); // preflag + } + w.write_bit(g.scalefac_scale); + w.write_bits(c.count1_table as u32, 1); + } + w.finish() + } +} + +/// Mid/side for a frame when the side signal is much weaker than the mid signal. +fn use_mid_side(xr: &[[f32; GRANULE]]) -> bool { + let (mut m, mut s) = (0f64, 0f64); + for pair in xr.as_chunks::<2>().0 { + for (l, r) in pair[0].iter().zip(pair[1].iter()) { + let (a, b) = (f64::from(*l), f64::from(*r)); + m += (a + b) * (a + b); + s += (a - b) * (a - b); + } + } + s < 0.25 * m +} + +/// The low-pass for a bit rate: the band the bits can carry (joint stereo counted as 1.6 +/// channels' worth). +fn cutoff_hz(kbps: u32, channels: usize) -> f64 { + let per = f64::from(kbps) / if channels > 1 { 1.6 } else { 1.0 }; + const STEPS: [(f64, f64); 11] = [ + (140.0, 20_000.0), + (120.0, 19_000.0), + (100.0, 18_000.0), + (80.0, 16_500.0), + (64.0, 15_000.0), + (56.0, 13_500.0), + (48.0, 12_000.0), + (40.0, 10_000.0), + (32.0, 8_000.0), + (24.0, 6_500.0), + (16.0, 5_000.0), + ]; + STEPS.iter().find(|(k, _)| per >= *k).map_or(3_500.0, |s| s.1) +} + +#[cfg(test)] +#[path = "tests.rs"] +mod tests; diff --git a/crates/audio/src/mp3/huffman_tables.rs b/crates/audio/src/mp3/huffman_tables.rs index b98290e0a..801329a11 100644 --- a/crates/audio/src/mp3/huffman_tables.rs +++ b/crates/audio/src/mp3/huffman_tables.rs @@ -338,4 +338,5 @@ pub(crate) const LINBITS: [u32; 32] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, pub(crate) const QUAD_A_CODES: [u8; 16] = [1, 5, 4, 5, 6, 5, 4, 4, 7, 3, 6, 0, 7, 2, 3, 1]; pub(crate) const QUAD_A_LENS: [u8; 16] = [1, 4, 4, 5, 4, 6, 5, 6, 4, 5, 5, 6, 5, 6, 6, 6]; /// Count1 table B: four bits, the inverted quadruple. +#[cfg(feature = "mp3-encode")] pub(crate) const QUAD_B_CODES: [u8; 16] = [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]; diff --git a/crates/audio/src/mp3/mod.rs b/crates/audio/src/mp3/mod.rs index dd7591d62..d28607110 100644 --- a/crates/audio/src/mp3/mod.rs +++ b/crates/audio/src/mp3/mod.rs @@ -1,4 +1,19 @@ -//! Clean-room MPEG-1 / MPEG-2 Audio Layer III (MP3) encoder, written from ISO/IEC 11172-3:1993 +//! MPEG-1 / MPEG-2 Audio Layer III (MP3): a clean-room decoder ([`Decoder`], feature +//! `mp3-decode`) and encoder ([`Encoder`], feature `mp3-encode`), written from ISO/IEC 11172-3 +//! and ISO/IEC 13818-3. They share the Huffman code tables and scalefactor bands. +//! +//! # Decoder (feature `mp3-decode`) +//! +//! [`Decoder`] decodes Layer III frames one at a time: MPEG-1, the MPEG-2 lower sampling +//! frequencies and MPEG-2.5 (8-48 kHz), every bit rate (CBR or VBR), mono, stereo, dual channel +//! and joint stereo (mid/side and intensity), long, short and mixed blocks, across the bit +//! reservoir. [`InfoTag`] reads the Xing / Info or VBRI frame at the start of a file and the +//! LAME tag's encoder delay and padding. `craft_audio::decode` (feature `decode-native`) reads +//! `.mp3` files and MP3 in MP4 and Matroska through them, gapless. +//! +//! # Encoder (feature `mp3-encode`) +//! +//! A clean-room encoder, written from ISO/IEC 11172-3:1993 //! (MPEG-1 Audio: §2.4.1 bit stream syntax, §2.4.2 semantics, Annex B tables, Annex C encoding //! process) and ISO/IEC 13818-3:1998 (the lower sampling frequencies). //! @@ -13,490 +28,26 @@ //! - Output: a plain stream of frames, no ID3 or Xing / Info tag, no CRC. The frames written one //! after another are a playable `.mp3` file. //! -//! No decoder here: [`mpa`](crate::mpa) decodes Layers I and II, and files with Layer III are read -//! through `symphonia` (feature `decode`). -//! -//! ``` -//! use craft_audio::mp3::{Config, DELAY, Encoder}; -//! -//! let left: Vec = (0..48_000).map(|i| (i as f32 * 0.05).sin() * 0.5).collect(); -//! let right = left.clone(); -//! let mut enc = Encoder::new(Config::cbr(48_000, 2, 192))?; -//! let mut mp3 = enc.encode(&[&left, &right]); // whole frames as they complete -//! mp3.extend(enc.flush()); // pads with silence to the last input sample -//! assert_eq!(mp3.len() % 576, 0); // 192 kbit/s at 48 kHz: 576 bytes per frame -//! assert_eq!(DELAY, 1057); // a decoder's output lags the input by this many samples -//! # Ok::<(), craft_audio::mp3::Error>(()) -//! ``` -//! //! From FilmCraft's `filmcraft-mp3enc` (storytold/filmcraft@4bec22b). Public items keep their //! names, so FilmCraft can re-export this module. // Index loops follow the specification's pseudo-code (FilmCraft allows this lint workspace-wide). #![allow(clippy::needless_range_loop)] +#[cfg(feature = "mp3-decode")] +mod decoder; +#[cfg(feature = "mp3-encode")] +mod encoder; +#[cfg(feature = "mp3-encode")] mod filterbank; +#[cfg(feature = "mp3-encode")] mod huffman; mod huffman_tables; +#[cfg(feature = "mp3-encode")] mod quantize; mod tables; -use std::collections::VecDeque; - -use craft_media::bitstream::BitWriter; - -use filterbank::{Analysis, GRANULE}; -use quantize::{Granule, Model}; - -/// Settings an [`Encoder`] refuses. -#[non_exhaustive] -#[derive(Debug, thiserror::Error, PartialEq, Eq)] -pub enum Error { - /// A sample rate outside [`SAMPLE_RATES`]. - #[error("MP3: unsupported sample rate {0} Hz (32, 44.1, 48 kHz, or 16, 22.05, 24 kHz)")] - SampleRate(u32), - /// A bit rate that is not one of [`bitrates`] for the sample rate. - #[error("MP3: unsupported bit rate {kbps} kbit/s at {rate} Hz")] - Bitrate { - /// The bit rate asked for, kbit/s. - kbps: u32, - /// The sample rate, Hz. - rate: u32, - }, - /// More than two channels, or none. - #[error("MP3: unsupported channel count {0} (1 or 2)")] - Channels(usize), -} - -/// Result with this module's [`Error`]. -pub type Result = std::result::Result; - -/// How two channels are coded. -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -pub enum StereoMode { - /// Left and right coded independently (`mode` 00). - Stereo, - /// Joint stereo (`mode` 01): each frame chooses left / right or mid / side. - #[default] - JointStereo, -} - -/// Encoder settings. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct Config { - /// One of [`SAMPLE_RATES`]. - pub sample_rate: u32, - /// 1 or 2. - pub channels: usize, - /// One of [`bitrates`] for the sample rate, kbit/s. - pub bitrate_kbps: u32, - /// How two channels are coded (ignored for mono). - pub stereo: StereoMode, -} - -impl Config { - /// `channels` at `sample_rate`, constant `bitrate_kbps`, joint stereo. - pub fn cbr(sample_rate: u32, channels: usize, bitrate_kbps: u32) -> Config { - Config { sample_rate, channels, bitrate_kbps, stereo: StereoMode::JointStereo } - } -} - -/// Whether the sample rate needs MPEG-2's lower sampling frequencies. -fn is_lsf(rate: u32) -> Option { - if tables::RATES_MPEG1.contains(&rate) { - Some(false) - } else if tables::RATES_LSF.contains(&rate) { - Some(true) - } else { - None - } -} - -/// The sample rates the encoder accepts. -pub const SAMPLE_RATES: [u32; 6] = [32_000, 44_100, 48_000, 16_000, 22_050, 24_000]; - -/// The bit rates (kbit/s) a stream at `sample_rate` can use; empty for an unsupported rate. -pub fn bitrates(sample_rate: u32) -> &'static [u32] { - match is_lsf(sample_rate) { - Some(false) => &tables::BITRATES_MPEG1, - Some(true) => &tables::BITRATES_LSF, - None => &[], - } -} - -/// The highest bit rate at `sample_rate` not above `kbps` (the lowest one when `kbps` is below -/// all of them); `None` for an unsupported rate. -pub fn nearest_bitrate(sample_rate: u32, kbps: u32) -> Option { - let rates = bitrates(sample_rate); - rates.iter().rev().find(|&&r| r <= kbps).or(rates.first()).copied() -} - -/// The supported sample rate an encoder should use for audio at `rate`: `rate` itself when -/// supported, else the closest supported one at or above it (48 kHz above 48 kHz). -pub fn nearest_sample_rate(rate: u32) -> u32 { - let mut sorted = SAMPLE_RATES; - sorted.sort_unstable(); - sorted.iter().copied().find(|&r| r >= rate).unwrap_or(48_000) -} - -/// Energy of the frequency lines of a full-scale sine (amplitude 1) over one granule. -const FULL_SCALE_TONE_ENERGY: f32 = 1.0; -/// Playback level of a full-scale sine assumed by the threshold in quiet, dB SPL. -const FULL_SCALE_SPL: f64 = 96.0; -/// Samples by which a standard decoder's output lags the input: the polyphase analysis and -/// synthesis filters (481) and the MDCT overlap of one granule (576). -pub const DELAY: u32 = 1057; - -/// A Layer III encoder. Feed planar `f32` samples (full scale ±1) to [`Encoder::encode`]; it -/// returns whole frames as they are completed. [`Encoder::flush`] pads the end with silence and -/// returns the rest. -pub struct Encoder { - cfg: Config, - lsf: bool, - sr_index: u32, - br_index: u32, - granules: usize, - analysis: Vec, - model: Model, - /// Buffered input per channel, and how much of it is consumed. - input: Vec>, - consumed: usize, - /// Frames whose main-data slot is not yet filled: header + side information, slot bytes. - pending: VecDeque<(Vec, usize)>, - /// Main data from the first pending frame's slot on. - main: Vec, - /// Slot bytes of the pending frames. - slot_total: usize, - /// Largest `main_data_begin`, bytes. - max_reservoir: usize, - /// Remainder accumulator for the padding slot (44.1 / 22.05 kHz). - pad_acc: u64, - samples_in: u64, - frames: u64, -} - -impl Encoder { - /// An encoder for `cfg`, or the setting it cannot carry. - pub fn new(cfg: Config) -> Result { - let lsf = is_lsf(cfg.sample_rate).ok_or(Error::SampleRate(cfg.sample_rate))?; - if !(1..=2).contains(&cfg.channels) { - return Err(Error::Channels(cfg.channels)); - } - let rates = if lsf { &tables::RATES_LSF } else { &tables::RATES_MPEG1 }; - let sr_index = rates.iter().position(|&r| r == cfg.sample_rate).ok_or(Error::SampleRate(cfg.sample_rate))?; - let br_index = bitrates(cfg.sample_rate) - .iter() - .position(|&b| b == cfg.bitrate_kbps) - .ok_or(Error::Bitrate { kbps: cfg.bitrate_kbps, rate: cfg.sample_rate })?; - let bands = if lsf { tables::SFB_LSF[sr_index] } else { tables::SFB_MPEG1[sr_index] }; - let line_hz = cfg.sample_rate as f64 / 2.0 / GRANULE as f64; - let mut ath = [0f32; 22]; - for (b, a) in ath.iter_mut().enumerate() { - let (lo, hi) = (usize::from(bands[b]), usize::from(bands[b + 1])); - let db = (lo..hi).map(|i| tables::ath_db((i as f64 + 0.5) * line_hz)).fold(f64::MAX, f64::min); - *a = (f64::from(FULL_SCALE_TONE_ENERGY) * 10f64.powf((db - FULL_SCALE_SPL) / 10.0)) as f32; - } - let cutoff_hz = cutoff_hz(cfg.bitrate_kbps, cfg.channels).min(cfg.sample_rate as f64 * 0.5); - let cutoff = ((cutoff_hz / line_hz).ceil() as usize).min(GRANULE); - let frame_bits = u64::from(cfg.bitrate_kbps) * 1000 * if lsf { 576 } else { 1152 } / u64::from(cfg.sample_rate); - // reservoir and frame must fit the decoder's 7680-bit input buffer - let max_reservoir = (7680u64.saturating_sub(frame_bits) / 8).min(if lsf { 255 } else { 511 }) as usize; - Ok(Encoder { - cfg, - lsf, - sr_index: sr_index as u32, - br_index: br_index as u32 + 1, - granules: if lsf { 1 } else { 2 }, - analysis: vec![Analysis::new(); cfg.channels], - model: Model::new(lsf, bands, ath, cutoff), - input: vec![Vec::new(); cfg.channels], - consumed: 0, - pending: VecDeque::new(), - main: Vec::new(), - slot_total: 0, - max_reservoir, - pad_acc: 0, - samples_in: 0, - frames: 0, - }) - } - - /// The settings. - pub fn config(&self) -> &Config { - &self.cfg - } - - /// Samples per channel in a frame: 1152 (MPEG-1) or 576 (MPEG-2 LSF). - pub fn frame_samples(&self) -> usize { - GRANULE * self.granules - } - - /// Samples by which a standard decoder's output lags the input. - pub fn delay(&self) -> u32 { - DELAY - } - - /// Frames written so far. - pub fn frames(&self) -> u64 { - self.frames - } - - /// Encode planar samples (one slice per channel; a missing channel is silence, extra ones - /// are ignored, every channel takes the first channel's length). Returns the completed frames. - pub fn encode(&mut self, planar: &[&[f32]]) -> Vec { - let n = planar.first().map_or(0, |p| p.len()); - for (c, buf) in self.input.iter_mut().enumerate() { - let src = planar.get(c).copied().unwrap_or(&[]); - buf.extend((0..n).map(|i| src.get(i).copied().filter(|v| v.is_finite()).unwrap_or(0.0).clamp(-4.0, 4.0))); - } - self.samples_in = self.samples_in.saturating_add(n as u64); - let mut out = Vec::new(); - self.drain(&mut out); - out - } - - /// Encode the buffered rest padded with silence, plus enough silence that a decoder outputs - /// every input sample, and return the remaining frames. - pub fn flush(&mut self) -> Vec { - let fs = self.frame_samples() as u64; - let needed = if self.samples_in == 0 { self.frames } else { self.samples_in.saturating_add(u64::from(DELAY)).div_ceil(fs) }; - let have = self.input.first().map_or(0, |b| b.len().saturating_sub(self.consumed)); - let want = (needed.saturating_sub(self.frames) as usize).saturating_mul(fs as usize); - if want > have { - for buf in &mut self.input { - buf.resize(self.consumed + want, 0.0); - } - } - let mut out = Vec::new(); - self.drain(&mut out); - // fill the last frames' slots and write them out - self.main.resize(self.slot_total.max(self.main.len()), 0); - self.emit(&mut out); - self.input.iter_mut().for_each(Vec::clear); - self.consumed = 0; - out - } - - /// Encode every whole frame of buffered input. - fn drain(&mut self, out: &mut Vec) { - let fs = self.frame_samples(); - let mut pcm = vec![[0f32; GRANULE]; self.cfg.channels * self.granules]; - while self.input.first().is_some_and(|b| b.len() - self.consumed >= fs) { - for (c, buf) in self.input.iter().enumerate() { - for gr in 0..self.granules { - let start = self.consumed + gr * GRANULE; - if let (Some(dst), Some(src)) = (pcm.get_mut(gr * self.cfg.channels + c), buf.get(start..start + GRANULE)) { - dst.copy_from_slice(src); - } - } - } - self.consumed += fs; - self.encode_frame(&pcm); - self.emit(out); - } - if self.consumed > 0 { - let k = self.consumed; - self.input.iter_mut().for_each(|b| { - b.drain(..k.min(b.len())); - }); - self.consumed = 0; - } - } - - /// Bytes of the next frame (with its padding slot). - fn next_frame_len(&mut self) -> usize { - let per = if self.lsf { 72_000 } else { 144_000 }; - let num = per * u64::from(self.cfg.bitrate_kbps); - let sr = u64::from(self.cfg.sample_rate); - self.pad_acc += num % sr; - let pad = self.pad_acc >= sr; - if pad { - self.pad_acc -= sr; - } - (num / sr) as usize + usize::from(pad) - } - - fn side_info_len(&self) -> usize { - match (self.lsf, self.cfg.channels) { - (false, 1) => 17, - (false, _) => 32, - (true, 1) => 9, - (true, _) => 17, - } - } - - /// Analyse, quantize and assemble one frame (`pcm[gr * channels + ch]`). - fn encode_frame(&mut self, pcm: &[[f32; GRANULE]]) { - let nch = self.cfg.channels; - let ngr = self.granules; - let mut xr = vec![[0f32; GRANULE]; nch * ngr]; - for gr in 0..ngr { - for ch in 0..nch { - let k = gr * nch + ch; - if let (Some(a), Some(p), Some(x)) = (self.analysis.get_mut(ch), pcm.get(k), xr.get_mut(k)) { - a.granule(p, x); - x.iter_mut().skip(self.model.cutoff).for_each(|v| *v = 0.0); - } - } - } - let mut xmin: Vec<[f32; 22]> = xr.iter().map(|x| self.model.thresholds(x)).collect(); - let ms = nch == 2 && self.cfg.stereo == StereoMode::JointStereo && use_mid_side(&xr); - if ms { - for gr in 0..ngr { - let (l, r) = xr.split_at_mut(gr * 2 + 1); - if let (Some(l), Some(r)) = (l.last_mut(), r.first_mut()) { - for (a, b) in l.iter_mut().zip(r.iter_mut()) { - let (m, s) = ((*a + *b) * std::f32::consts::FRAC_1_SQRT_2, (*a - *b) * std::f32::consts::FRAC_1_SQRT_2); - *a = m; - *b = s; - } - } - let t: [f32; 22] = std::array::from_fn(|b| xmin[gr * 2][b].min(xmin[gr * 2 + 1][b])); - xmin[gr * 2] = t; - xmin[gr * 2 + 1] = t; - } - } - // the reservoir: free bytes in the pending frames' slots, stuffed down to the maximum - let free = self.slot_total.saturating_sub(self.main.len()); - if free > self.max_reservoir { - self.main.resize(self.slot_total - self.max_reservoir, 0); - } - let main_data_begin = free.min(self.max_reservoir); - let frame_len = self.next_frame_len(); - let slot = frame_len.saturating_sub(4 + self.side_info_len()); - let budget = 8 * (main_data_begin + slot); - let n = nch * ngr; - let mean = 8 * slot / n; - let max_reservoir_bits = 8 * self.max_reservoir; - let mut used = 0usize; - let mut coded: Vec = Vec::with_capacity(n); - for (k, (x, t)) in xr.iter().zip(&xmin).enumerate() { - let left = n - k; - let remaining = budget.saturating_sub(used); - let surplus = remaining.saturating_sub(mean * left); - let max_bits = (mean + surplus * 3 / 5 / left).min(remaining).min(4095); - let room = max_reservoir_bits.saturating_sub(surplus); - let min_bits = mean.saturating_sub(room / left); - let g = self.model.encode(x, t, max_bits, min_bits); - used += g.bits(); - coded.push(g); - } - let mut md = BitWriter::new(); - for g in &coded { - g.write(&mut md, &self.model); - } - let mut main = md.finish(); - if main.len() > main_data_begin + slot || coded.iter().any(|g| g.bits() > 4095) { - // never expected: fall back to silence rather than write a broken frame - coded = (0..n).map(|_| self.model.encode(&[0.0; GRANULE], &[f32::MAX; 22], 0, 0)).collect(); - main = Vec::new(); - } - let head = self.header_and_side_info(frame_len, ms, main_data_begin, &coded); - self.pending.push_back((head, slot)); - self.slot_total += slot; - self.main.extend_from_slice(&main); - self.frames += 1; - } - - /// Write out the pending frames whose slots are full. - fn emit(&mut self, out: &mut Vec) { - while let Some((_, slot)) = self.pending.front() { - let slot = *slot; - if self.main.len() < slot { - break; - } - if let Some((head, _)) = self.pending.pop_front() { - out.extend_from_slice(&head); - out.extend(self.main.drain(..slot)); - self.slot_total -= slot; - } - } - } - - fn header_and_side_info(&self, frame_len: usize, ms: bool, main_data_begin: usize, coded: &[Granule]) -> Vec { - let nch = self.cfg.channels; - let padding = frame_len > (if self.lsf { 72_000 } else { 144_000 } * self.cfg.bitrate_kbps / self.cfg.sample_rate) as usize; - let mode = match (nch, self.cfg.stereo) { - (1, _) => 3, - (_, StereoMode::Stereo) => 0, - (_, StereoMode::JointStereo) => 1, - }; - let mut w = BitWriter::new(); - w.write_bits(0x7FF, 11); - w.write_bits(if self.lsf { 2 } else { 3 }, 2); // ID: MPEG-1 = 11, MPEG-2 = 10 - w.write_bits(1, 2); // Layer III - w.write_bit(true); // protection_bit: no CRC - w.write_bits(self.br_index, 4); - w.write_bits(self.sr_index, 2); - w.write_bit(padding); - w.write_bit(false); // private_bit - w.write_bits(mode, 2); - w.write_bits(if ms { 2 } else { 0 }, 2); // mode_extension: mid/side, no intensity - w.write_bit(false); // copyright - w.write_bit(true); // original - w.write_bits(0, 2); // emphasis - if self.lsf { - w.write_bits(main_data_begin as u32, 8); - w.write_bits(0, if nch == 1 { 1 } else { 2 }); - } else { - w.write_bits(main_data_begin as u32, 9); - w.write_bits(0, if nch == 1 { 5 } else { 3 }); - w.write_bits(0, 4 * nch as u32); // scfsi - } - for g in coded { - let c = &g.coding; - w.write_bits(g.bits() as u32, 12); - w.write_bits(c.big_values as u32, 9); - w.write_bits(g.global_gain, 8); - w.write_bits(g.scalefac_compress, if self.lsf { 9 } else { 4 }); - w.write_bit(false); // window_switching_flag - for t in c.table_select { - w.write_bits(t as u32, 5); - } - w.write_bits(c.region0_count as u32, 4); - w.write_bits(c.region1_count as u32, 3); - if !self.lsf { - w.write_bit(false); // preflag - } - w.write_bit(g.scalefac_scale); - w.write_bits(c.count1_table as u32, 1); - } - w.finish() - } -} - -/// Mid/side for a frame when the side signal is much weaker than the mid signal. -fn use_mid_side(xr: &[[f32; GRANULE]]) -> bool { - let (mut m, mut s) = (0f64, 0f64); - for pair in xr.as_chunks::<2>().0 { - for (l, r) in pair[0].iter().zip(pair[1].iter()) { - let (a, b) = (f64::from(*l), f64::from(*r)); - m += (a + b) * (a + b); - s += (a - b) * (a - b); - } - } - s < 0.25 * m -} - -/// The low-pass for a bit rate: the band the bits can carry (joint stereo counted as 1.6 -/// channels' worth). -fn cutoff_hz(kbps: u32, channels: usize) -> f64 { - let per = f64::from(kbps) / if channels > 1 { 1.6 } else { 1.0 }; - const STEPS: [(f64, f64); 11] = [ - (140.0, 20_000.0), - (120.0, 19_000.0), - (100.0, 18_000.0), - (80.0, 16_500.0), - (64.0, 15_000.0), - (56.0, 13_500.0), - (48.0, 12_000.0), - (40.0, 10_000.0), - (32.0, 8_000.0), - (24.0, 6_500.0), - (16.0, 5_000.0), - ]; - STEPS.iter().find(|(k, _)| per >= *k).map_or(3_500.0, |s| s.1) -} - -#[cfg(test)] -mod tests; +#[cfg(feature = "mp3-decode")] +pub use decoder::{Decoder, InfoTag}; +#[cfg(feature = "mp3-encode")] +pub use encoder::{Config, DELAY, Encoder, Error, Result, SAMPLE_RATES, StereoMode, bitrates, nearest_bitrate, nearest_sample_rate}; diff --git a/crates/audio/src/mp3/tables.rs b/crates/audio/src/mp3/tables.rs index 054df5136..8711cdf04 100644 --- a/crates/audio/src/mp3/tables.rs +++ b/crates/audio/src/mp3/tables.rs @@ -4,12 +4,16 @@ use std::sync::OnceLock; /// Layer III bit rates in kbit/s by `bitrate_index` 1-14 (§2.4.2.3); index 0 (free format) and /// 15 (forbidden) are never written. +#[cfg(feature = "mp3-encode")] pub(crate) const BITRATES_MPEG1: [u32; 14] = [32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320]; /// Layer III bit rates of the lower sampling frequencies (ISO/IEC 13818-3 §2.4.2.3). +#[cfg(feature = "mp3-encode")] pub(crate) const BITRATES_LSF: [u32; 14] = [8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160]; /// Sampling frequencies by `sampling_frequency` (0-2) of MPEG-1 and of MPEG-2 LSF. +#[cfg(feature = "mp3-encode")] pub(crate) const RATES_MPEG1: [u32; 3] = [44_100, 48_000, 32_000]; +#[cfg(feature = "mp3-encode")] pub(crate) const RATES_LSF: [u32; 3] = [22_050, 24_000, 16_000]; /// Long-block scalefactor band boundaries (Table 3-B.8 of 11172-3; Table B.2 of 13818-3), by @@ -31,11 +35,13 @@ pub(crate) const SLEN: [(u32, u32); 16] = /// Scalefactor bands per partition for long blocks without intensity stereo when MPEG-2 /// `scalefac_compress` < 400 (13818-3 §2.4.3.2, `nr_of_sfb_block[0][0]`). +#[cfg(feature = "mp3-encode")] pub(crate) const LSF_PARTITIONS: [usize; 4] = [6, 5, 5, 5]; /// Coefficients `D[0..=256]` of the synthesis window (Table 3-B.3), in units of 2⁻¹⁶: the table's /// nine-decimal values are exact multiples of 1/65536. The analysis window of Table 3-C.1 is the /// same prototype divided by 32: `C[i] = D[i] / 32`. +#[cfg(feature = "mp3-encode")] const WINDOW_HALF: [i32; 257] = [ 0, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -3, -3, -4, -4, -5, // -5, -6, -7, -7, -8, -9, -10, -11, -13, -14, -16, -17, -19, -21, -24, -26, // @@ -59,6 +65,7 @@ const WINDOW_HALF: [i32; 257] = [ /// The 512 synthesis window coefficients `D[i]` (Table 3-B.3). The table stores the symmetric /// prototype `h[i] = h[512 - i]` with every other block of 64 negated, so for `i > 256` /// `D[i] = -D[512 - i]`, except at multiples of 64 where both halves share the block parity. +#[cfg(feature = "mp3-encode")] pub(crate) fn synthesis_window() -> &'static [f32; 512] { static W: OnceLock<[f32; 512]> = OnceLock::new(); W.get_or_init(|| { @@ -72,6 +79,7 @@ pub(crate) fn synthesis_window() -> &'static [f32; 512] { } /// The analysis window `C[i]` (Table 3-C.1): `D[i] / 32`. +#[cfg(feature = "mp3-encode")] pub(crate) fn analysis_window() -> &'static [f32; 512] { static C: OnceLock<[f32; 512]> = OnceLock::new(); C.get_or_init(|| { @@ -83,6 +91,7 @@ pub(crate) fn analysis_window() -> &'static [f32; 512] { /// The analysis matrixing coefficients `M[i][k] = cos((2i + 1)(k - 16)π / 64)` (Annex C.1.3), /// `i` in `0..32`, `k` in `0..64`. +#[cfg(feature = "mp3-encode")] pub(crate) fn analysis_matrix() -> &'static [[f32; 64]; 32] { static M: OnceLock<[[f32; 64]; 32]> = OnceLock::new(); M.get_or_init(|| { @@ -119,6 +128,7 @@ pub(crate) fn pow43() -> &'static [f32] { /// The absolute threshold of hearing in dB SPL at `f` Hz (Terhardt's approximation, as in the /// psychoacoustic models of 11172-3 Annex D). +#[cfg(feature = "mp3-encode")] pub(crate) fn ath_db(f: f64) -> f64 { let k = (f.max(20.0)) / 1000.0; 3.64 * k.powf(-0.8) - 6.5 * (-0.6 * (k - 3.3) * (k - 3.3)).exp() + 0.001 * k.powi(4) diff --git a/crates/audio/src/mp3/tests.rs b/crates/audio/src/mp3/tests.rs index a4160f0ab..121e8389a 100644 --- a/crates/audio/src/mp3/tests.rs +++ b/crates/audio/src/mp3/tests.rs @@ -1,6 +1,7 @@ -use super::filterbank::Analysis; -use super::tables::{alias_coefficients, synthesis_window}; use super::*; +use crate::mp3::filterbank::Analysis; +use crate::mp3::tables::{alias_coefficients, synthesis_window}; +use crate::mp3::{huffman, huffman_tables}; /// The decoder side of the hybrid filterbank (§2.4.3.4.10: alias butterflies, IMDCT with the /// normal window and overlap-add, frequency inversion, polyphase synthesis), for checking the diff --git a/crates/audio/src/mpa/mod.rs b/crates/audio/src/mpa/mod.rs index 7ba224d7e..f1d5b6378 100644 --- a/crates/audio/src/mpa/mod.rs +++ b/crates/audio/src/mpa/mod.rs @@ -19,7 +19,7 @@ // Index loops follow the specification's pseudo-code (FilmCraft allows this lint workspace-wide). #![allow(clippy::needless_range_loop)] -mod tables; +pub(crate) mod tables; use craft_media::bitstream::BitReader; use tables::AllocTable; @@ -418,7 +418,7 @@ fn layer2(r: &mut BitReader, h: &Header, out: &mut Subbands) -> Result<()> { /// The synthesis subband filter (§2.4.3.2, Figure A.2) for one time slot: 32 subband samples in, /// 32 PCM samples out. -fn synthesize(v: &mut [f32; 1024], pos: &mut usize, n: &[[f32; 36]; 32], d: &[f32; 512], s: &[f32; 32], out: &mut [f32; 32]) { +pub(crate) fn synthesize(v: &mut [f32; 1024], pos: &mut usize, n: &[[f32; 36]; 32], d: &[f32; 512], s: &[f32; 32], out: &mut [f32; 32]) { // shift V by 64: move the ring's start back instead of copying 960 values *pos = (*pos + 1024 - 64) % 1024; let p = *pos; diff --git a/crates/audio/src/read/mod.rs b/crates/audio/src/read/mod.rs index 35a220767..5d78af9c1 100644 --- a/crates/audio/src/read/mod.rs +++ b/crates/audio/src/read/mod.rs @@ -95,7 +95,7 @@ fn detect_by_magic(b: &[u8], depth: u32) -> Option { /// The file extensions of the formats [`decode`] reads, lower case and without the dot: what a /// file picker or a browser filters by. Each is a hint [`detect_container`] knows; a file's /// contents still decide what it is. Without the `symphonia` feature the formats only symphonia -/// decodes (FLAC, MP3, CAF) are left out. +/// decodes (FLAC, CAF) are left out. #[cfg(feature = "symphonia")] pub const DECODE_EXTENSIONS: &[&str] = &[ "wav", "wave", "bwf", "rf64", "aif", "aiff", "aifc", "flac", "mp3", "mp2", "mp1", "mpa", "ogg", "oga", "opus", "m4a", "m4b", "mp4", "mov", "aac", @@ -104,11 +104,11 @@ pub const DECODE_EXTENSIONS: &[&str] = &[ /// The file extensions of the formats [`decode`] reads, lower case and without the dot: what a /// file picker or a browser filters by. Each is a hint [`detect_container`] knows; a file's /// contents still decide what it is. Without the `symphonia` feature the formats only symphonia -/// decodes (FLAC, MP3, CAF) are left out. +/// decodes (FLAC, CAF) are left out. #[cfg(not(feature = "symphonia"))] pub const DECODE_EXTENSIONS: &[&str] = &[ - "wav", "wave", "bwf", "rf64", "aif", "aiff", "aifc", "mp2", "mp1", "mpa", "ogg", "oga", "opus", "m4a", "m4b", "mp4", "mov", "aac", "adts", "ac3", - "eac3", "ec3", "mkv", "mka", "webm", + "wav", "wave", "bwf", "rf64", "aif", "aiff", "aifc", "mp3", "mp2", "mp1", "mpa", "ogg", "oga", "opus", "m4a", "m4b", "mp4", "mov", "aac", "adts", + "ac3", "eac3", "ec3", "mkv", "mka", "webm", ]; fn detect_by_ext(ext: &str) -> Option { diff --git a/crates/audio/src/read/native.rs b/crates/audio/src/read/native.rs index 5d61b0f92..cc0fac4c4 100644 --- a/crates/audio/src/read/native.rs +++ b/crates/audio/src/read/native.rs @@ -1,12 +1,13 @@ //! The native decode path: craft-media's demuxers (ISO BMFF / QuickTime, Matroska / WebM), raw //! ADTS, AC-3 and MPEG audio streams, and WAV / AIFF PCM, feeding [`TrackDecoder`]. -//! [`Native::open`] answers `None` for a stream with no native codec (MP3, FLAC, ALAC, Vorbis, -//! ...) or a container these demuxers do not read; the caller then uses symphonia. +//! [`Native::open`] answers `None` for a stream with no native codec (FLAC, Vorbis, ...) or a +//! container these demuxers do not read; the caller then uses symphonia. //! //! Every container is a [`Packets`] source over a [`Src`], read packet by packet, so a decode //! streams. Timing follows the containers: MP4 edit lists (AAC priming, Opus pre-skip), Matroska //! `CodecDelay` and per-block `DiscardPadding`, and Ogg Opus pre-skip and end trimming (RFC 7845 -//! §4) are applied, so a file decodes to exactly its presentation length. +//! §4) are applied, and so are an MP3's encoder delay and padding from its LAME tag, so a file +//! decodes to exactly its presentation length. use super::aiff::{self, AiffParse}; use super::id3v2_len; @@ -716,15 +717,41 @@ fn open_adts<'a>(src: &Src<'a>, at: u64) -> Option>> { Some(Ok(n)) } -/// A raw MPEG audio layer I or II stream. +/// The decoder's own delay that a LAME tag's encoder delay leaves out: decoded audio lags the +/// encoder's input by the delay plus these 528 + 1 samples. +#[cfg(feature = "mp3-decode")] +const MP3_DECODER_DELAY: u64 = 529; + +/// A raw MPEG audio stream: layer I or II, and layer III with `mp3-decode`. A Xing / Info or +/// VBRI frame at the start carries no audio and is not a packet; its LAME tag's encoder delay +/// and padding trim the decoded audio to what was encoded. fn open_mpa<'a>(src: &Src<'a>, at: u64) -> Option>> { - let frames = match raw_frames(src, at, |rest| { - mpa::parse_header(rest).ok().filter(|h| h.layer == 1 || h.layer == 2).map(|h| (h.frame_bytes, h.samples() as u32)) - }) { - Ok(frames) => frames?, - Err(e) => return Some(Err(e)), + let layer_ok = |layer: u8| layer == 1 || layer == 2 || (cfg!(feature = "mp3-decode") && layer == 3); + #[cfg_attr(not(feature = "mp3-decode"), allow(unused_mut))] + let mut frames = + match raw_frames(src, at, |rest| mpa::parse_header(rest).ok().filter(|h| layer_ok(h.layer)).map(|h| (h.frame_bytes, h.samples() as u32))) { + Ok(frames) => frames?, + Err(e) => return Some(Err(e)), + }; + #[cfg_attr(not(feature = "mp3-decode"), allow(unused_mut))] + let mut gapless: Option<(u32, u32)> = None; + #[cfg(feature = "mp3-decode")] + if let Some(&(p, n, _)) = frames.first() { + let first = match src.read(p, n as usize) { + Ok(first) => first, + Err(e) => return Some(Err(e)), + }; + if let Some(tag) = crate::mp3::InfoTag::parse(&first) { + frames.remove(0); + gapless = tag.delay_padding; + } + } + let total: u64 = frames.iter().map(|&(_, _, f)| u64::from(f)).sum(); + let (skip, limit) = match gapless { + #[cfg(feature = "mp3-decode")] + Some((delay, padding)) => (u64::from(delay) + MP3_DECODER_DELAY, Some(total.saturating_sub(u64::from(delay) + u64::from(padding)))), + _ => (0, None), }; - let total = frames.iter().map(|&(_, _, f)| u64::from(f)).sum(); let lookahead = match raw_lookahead(src, &frames, 1) { Ok(lookahead) => lookahead, Err(e) => return Some(Err(e)), @@ -734,9 +761,9 @@ fn open_mpa<'a>(src: &Src<'a>, at: u64) -> Option>> { Packets::Raw { frames, src: src.clone(), next: 0 }, &CodecParams::new(CodecId::Mp2), &lookahead, - 0, - None, - Some(total), + skip, + limit, + Some(limit.unwrap_or(total)), ) .map(Ok) } diff --git a/crates/audio/src/track.rs b/crates/audio/src/track.rs index af984d718..920f571ae 100644 --- a/crates/audio/src/track.rs +++ b/crates/audio/src/track.rs @@ -8,6 +8,8 @@ use crate::aac; use crate::ac3; #[cfg(feature = "alac-decode")] use crate::alac; +#[cfg(feature = "mp3-decode")] +use crate::mp3; #[cfg(feature = "mpa-decode")] use crate::mpa; #[cfg(feature = "opus-decode")] @@ -43,8 +45,20 @@ enum Inner { dec: Box, order: Option<&'static [usize]>, }, + /// The decoder, and whether a packet has held several frames. #[cfg(feature = "mpa-decode")] - Mpa(Box), + Mpa { + dec: Box, + several: bool, + }, + /// The Layer III decoder, the samples per channel of a frame (1152, or 576 at the lower + /// sampling frequencies), and whether a packet has held several frames. + #[cfg(feature = "mp3-decode")] + Mp3 { + dec: Box, + samples: usize, + several: bool, + }, Pcm { kind: PcmKind, channels: usize, @@ -61,7 +75,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, -/// MP1/MP2 and PCM. Container timing (AAC priming, Opus pre-skip, end trimming) is the caller's: +/// MP1/MP2, MP3 and PCM. Container timing (AAC priming, Opus pre-skip, end trimming) is the caller's: /// every decoded sample is returned. /// /// ```no_run @@ -116,7 +130,7 @@ impl TrackDecoder { /// /// ALAC needs its magic cookie in `extradata`. /// - /// Codecs without a native packet decoder yet (MP3, FLAC, Vorbis) are + /// Codecs without a native packet decoder yet (FLAC, Vorbis) are /// [`AudioError::Unsupported`]; `craft_audio::decode` decodes them through symphonia when the /// `symphonia` feature is on. pub fn new(params: &CodecParams, lookahead: &[&[u8]]) -> Result { @@ -128,6 +142,10 @@ impl TrackDecoder { // Opus needs none. #[cfg_attr(not(any(feature = "ac3-decode", feature = "mpa-decode")), allow(unused_variables))] let first = lookahead.first().copied().unwrap_or_default(); + // MP4 and Matroska packets may hold several MPEG audio frames: then they are not all + // one frame long. + #[cfg(feature = "mpa-decode")] + let several = lookahead.iter().any(|p| mpa_packet_frames(p).0 > 1); let (inner, codec, sample_rate, layout, sample_format) = match params.codec { #[cfg(feature = "aac-decode")] CodecId::Aac | CodecId::HeAac => { @@ -220,15 +238,32 @@ impl TrackDecoder { } #[cfg(feature = "mpa-decode")] CodecId::Mp1 | CodecId::Mp2 | CodecId::Mp3 => match mpa::parse_header(first) { + #[cfg(feature = "mp3-decode")] + Ok(h) if h.layer == 3 => { + let mut dec = Box::::default(); + dec.prime(h); + (Inner::Mp3 { dec, samples: h.samples(), several }, CodecId::Mp3, h.sample_rate, mpa_layout(&h), SampleFormat::Compressed) + } + #[cfg(not(feature = "mp3-decode"))] Ok(h) if h.layer == 3 => return Err(unsupported(CodecId::Mp3)), Ok(h) => { let codec = if h.layer == 1 { CodecId::Mp1 } else { CodecId::Mp2 }; - (Inner::Mpa(Box::default()), codec, h.sample_rate, mpa_layout(&h), SampleFormat::Compressed) + (Inner::Mpa { dec: Box::default(), several }, codec, h.sample_rate, mpa_layout(&h), SampleFormat::Compressed) } - // Without a first frame, a container that names layer III is left to symphonia. + // Without a first frame, the container's rate and channels stand in until the + // first frame is decoded. + #[cfg(feature = "mp3-decode")] + Err(_) if params.codec == CodecId::Mp3 => ( + Inner::Mp3 { dec: Box::default(), samples: if rate >= 32_000 { 1152 } else { 576 }, several }, + CodecId::Mp3, + rate, + ChannelLayout::default_for(crate::layout::count(channels)), + SampleFormat::Compressed, + ), + #[cfg(not(feature = "mp3-decode"))] Err(_) if params.codec == CodecId::Mp3 => return Err(unsupported(CodecId::Mp3)), Err(_) => ( - Inner::Mpa(Box::default()), + Inner::Mpa { dec: Box::default(), several }, params.codec, rate, ChannelLayout::default_for(crate::layout::count(channels)), @@ -281,7 +316,7 @@ impl TrackDecoder { /// its timing. pub fn decode(&mut self, packet: &[u8]) -> Result { // A packet decoded only in part says nothing of how long packets are. - #[cfg_attr(not(feature = "ac3-decode"), allow(unused_mut))] + #[cfg_attr(not(any(feature = "ac3-decode", feature = "mpa-decode")), allow(unused_mut))] let mut partial = false; let channels = match &mut self.inner { #[cfg(feature = "aac-decode")] @@ -316,21 +351,27 @@ impl TrackDecoder { reorder(floats, *order) } #[cfg(feature = "mpa-decode")] - Inner::Mpa(d) => { - let mut out: Vec> = Vec::new(); - let mut p = 0; - while let Some(rest) = packet.get(p..).filter(|r| !r.is_empty()) { - let f = match d.decode(rest) { - Ok(f) => f, - Err(e) if out.is_empty() => return Err(e.into()), - Err(_) => break, - }; - p += f.header.frame_bytes.max(1); + Inner::Mpa { dec, several } => { + let (out, first, count, cut) = mpa_frames(packet, |f| dec.decode(f))?; + *several |= count > 1; + partial = cut; + if let Some(h) = first.filter(|_| self.last_frames == 0) { + self.sample_rate = h.sample_rate; + self.layout = mpa_layout(&h); + } + out + } + #[cfg(feature = "mp3-decode")] + Inner::Mp3 { dec, samples, several } => { + let (out, first, count, cut) = mpa_frames(packet, |f| dec.decode(f))?; + *several |= count > 1; + partial = cut; + if let Some(h) = first { + *samples = h.samples(); if self.last_frames == 0 { - self.sample_rate = f.header.sample_rate; - self.layout = mpa_layout(&f.header); + self.sample_rate = h.sample_rate; + self.layout = mpa_layout(&h); } - append(&mut out, f.channels)?; } out } @@ -418,7 +459,19 @@ impl TrackDecoder { #[cfg(feature = "ac3-decode")] Inner::Ac3(..) => Some(ac3_packet_frames(data)), #[cfg(feature = "mpa-decode")] - Inner::Mpa(_) => mpa::parse_header(data).ok().map(|h| h.samples()), + // a packet lost before any has decoded is a frame + Inner::Mpa { .. } => Some(match mpa_packet_frames(data) { + (0, _) if self.last_frames == 0 && self.codec == CodecId::Mp1 => 384, + (0, _) if self.last_frames == 0 => 1152, + (_, n) => n, + }), + // a packet lost whole is as long as the last one, a frame at the start + #[cfg(feature = "mp3-decode")] + Inner::Mp3 { samples, .. } => Some(match mpa_packet_frames(data) { + (0, _) if self.last_frames > 0 => self.last_frames, + (0, _) => *samples, + (_, n) => n, + }), Inner::Pcm { kind, channels } => Some(data.len() / (kind.bytes() * *channels).max(1)), }; let frames = frames.filter(|&n| n > 0).unwrap_or(self.last_frames); @@ -428,9 +481,10 @@ impl TrackDecoder { /// The frames every packet decodes to, for codecs whose packets all have one length: AAC /// (1024, 2048 for HE-AAC; a packet of several ADTS frames decodes to that many times this) and /// AC-3 / E-AC-3 (the frames of the last decoded packet, else of the first packet's primary - /// syncframes, else 1536) and MP1/MP2 (one frame per packet); `None` for Opus, PCM and ALAC - /// (whose last packet is shorter; the container's timing says), and for AC-3 / E-AC-3 whose - /// packets read so far differ in length. + /// syncframes, else 1536) and MP1/MP2/MP3 (one frame per packet; 576 for MP3 at the lower + /// sampling frequencies); `None` for Opus, PCM and ALAC (whose last packet is shorter; the + /// container's timing says), for AC-3 / E-AC-3 whose packets read so far differ in length, + /// and for MPEG audio once a packet (in the lookahead or decoded) has held several frames. pub fn frames_per_packet(&self) -> Option { match &self.inner { #[cfg(feature = "aac-decode")] @@ -447,7 +501,13 @@ impl TrackDecoder { #[cfg(feature = "opus-decode")] Inner::Opus { .. } => None, #[cfg(feature = "mpa-decode")] - Inner::Mpa(_) => Some(if self.codec == CodecId::Mp1 { 384 } else { 1152 }), + Inner::Mpa { several: true, .. } => None, + #[cfg(feature = "mpa-decode")] + Inner::Mpa { .. } => Some(if self.codec == CodecId::Mp1 { 384 } else { 1152 }), + #[cfg(feature = "mp3-decode")] + Inner::Mp3 { several: true, .. } => None, + #[cfg(feature = "mp3-decode")] + Inner::Mp3 { samples, .. } => Some(*samples), Inner::Pcm { .. } => None, } } @@ -476,8 +536,9 @@ impl TrackDecoder { } /// Frames to decode before a seek target and drop, so the decoder has settled when the target - /// is reached: two AAC frames, 80 ms of Opus (RFC 7845 §4.6), one AC-3 or MPEG audio frame - /// (their filter banks overlap one frame), none for ALAC (its packets decode independently) + /// is reached: two AAC frames, 80 ms of Opus (RFC 7845 §4.6), one AC-3 or MP1/MP2 frame + /// (their filter banks overlap one frame), for MP3 the frames its bit reservoir can reach back + /// over and two more, none for ALAC (its packets decode independently) /// and PCM. E-AC-3 syncframes of fewer than six blocks may reuse exponents from any syncframe /// before them, so once one has been read the stream decodes from its start (`usize::MAX`). pub fn preroll(&self) -> usize { @@ -493,7 +554,10 @@ impl TrackDecoder { #[cfg(feature = "opus-decode")] Inner::Opus { .. } => 3840, #[cfg(feature = "mpa-decode")] - Inner::Mpa(_) => self.last_frames.max(1152), + Inner::Mpa { .. } => self.last_frames.max(1152), + // the bit reservoir reaches up to 511 bytes back: as many frames as that takes + #[cfg(feature = "mp3-decode")] + Inner::Mp3 { dec, samples, .. } => dec.preroll().unwrap_or(MP3_PREROLL_FRAMES * *samples), Inner::Pcm { .. } => 0, } } @@ -519,7 +583,9 @@ impl TrackDecoder { #[cfg(feature = "opus-decode")] Inner::Opus { dec, .. } => dec.reset(), #[cfg(feature = "mpa-decode")] - Inner::Mpa(d) => d.reset(), + Inner::Mpa { dec, .. } => dec.reset(), + #[cfg(feature = "mp3-decode")] + Inner::Mp3 { dec, .. } => dec.reset(), Inner::Pcm { .. } => {} } } @@ -532,12 +598,13 @@ fn unsupported(codec: CodecId) -> AudioError { CodecId::Ac3 | CodecId::Eac3 => Some("ac3-decode"), CodecId::Opus => Some("opus-decode"), CodecId::Mp1 | CodecId::Mp2 => Some("mpa-decode"), + CodecId::Mp3 => Some("mp3-decode"), _ => None, }; match feature { // A codec this crate decodes, in a build without its feature. Some(f) => AudioError::Unsupported(format!("decoding {} needs the `{f}` feature", codec.name())), - None if cfg!(feature = "symphonia") && matches!(codec, CodecId::Mp3 | CodecId::Flac | CodecId::Vorbis) => { + None if cfg!(feature = "symphonia") && matches!(codec, CodecId::Flac | CodecId::Vorbis) => { AudioError::Unsupported(format!("no native {} packet decoder yet (craft_audio::decode reads it through symphonia)", codec.name())) } None => AudioError::Unsupported(format!("no native {} packet decoder yet", codec.name())), @@ -554,6 +621,54 @@ fn reorder(mut channels: Vec>, order: Option<&[usize]>) -> Vec } } +/// Frames of an MP3 stream decoded before a seek target when neither a lookahead nor a decoded +/// frame has said what the stream is: the most any stream needs. The smallest frames (8 kbit/s at +/// 24 kHz, stereo, with CRC) hold 1 byte of main data, so 255 bytes of reservoir span 255 of them, +/// and two more settle the filter banks. +#[cfg(feature = "mp3-decode")] +const MP3_PREROLL_FRAMES: usize = 257; + +/// The MPEG audio frames of a packet (an MP4 or Matroska packet may hold several), decoded by +/// `decode`, the first frame's header, how many frames decoded, and whether a frame after the +/// first failed, which ends the packet (then it decoded only in part). +#[cfg(feature = "mpa-decode")] +#[allow(clippy::type_complexity)] +fn mpa_frames(packet: &[u8], mut decode: impl FnMut(&[u8]) -> mpa::Result) -> Result<(Vec>, Option, usize, bool)> { + let mut out: Vec> = Vec::new(); + let mut first = None; + let mut count = 0; + let mut p = 0; + while let Some(rest) = packet.get(p..).filter(|r| !r.is_empty()) { + let f = match decode(rest) { + Ok(f) => f, + Err(e) if out.is_empty() => return Err(e.into()), + Err(_) => return Ok((out, first, count, true)), + }; + p += f.header.frame_bytes.max(1); + first.get_or_insert(f.header); + count += 1; + append(&mut out, f.channels)?; + } + Ok((out, first, count, false)) +} + +/// The MPEG audio frames a packet holds by their headers, and the samples per channel they +/// decode to: frames are counted while their headers parse, the last one even when the packet +/// ends inside it. +#[cfg(feature = "mpa-decode")] +fn mpa_packet_frames(packet: &[u8]) -> (usize, usize) { + let (mut frames, mut samples, mut p) = (0, 0, 0); + while let Some(h) = packet.get(p..).and_then(|rest| mpa::parse_header(rest).ok()) { + frames += 1; + samples += h.samples(); + if h.frame_bytes == 0 { + break; + } + p += h.frame_bytes; + } + (frames, samples) +} + /// Append `more` to `out` channel by channel. A packet whose frames differ in channel count is /// malformed: one buffer cannot hold them. #[cfg(any(feature = "aac-decode", feature = "ac3-decode", feature = "mpa-decode"))] @@ -781,6 +896,69 @@ mod tests { use super::*; use crate::PcmFormat; + /// An MP3 frame of silence: MPEG-1 Layer III, 128 kbit/s, 44.1 kHz, stereo, 417 bytes. + #[cfg(feature = "mp3-decode")] + fn mp3_frame() -> Vec { + let mut f = vec![0u8; 417]; + f[..4].copy_from_slice(&[0xFF, 0xFB, 0x90, 0x00]); + f + } + + /// An MP2 frame of silence: MPEG-1 Layer II, 192 kbit/s, 48 kHz, stereo, 576 bytes. + #[cfg(feature = "mpa-decode")] + fn mp2_frame() -> Vec { + let mut f = vec![0u8; 576]; + f[..4].copy_from_slice(&[0xFF, 0xFD, 0xA4, 0x00]); + f + } + + /// An MP4 or Matroska packet may hold several MPEG audio frames: then packets are not one + /// frame long (an MP4 track's length is not capped at a frame per packet), and a lost packet + /// is as long as the frames it held. + #[cfg(feature = "mp3-decode")] + #[test] + fn mp3_packets_of_several_frames_have_their_length() { + let one = mp3_frame(); + let two = [one.clone(), one.clone()].concat(); + let params = CodecParams::new(CodecId::Mp3); + let mut dec = TrackDecoder::new(¶ms, &[&one]).unwrap(); + assert_eq!(dec.frames_per_packet(), Some(1152)); + assert_eq!(dec.conceal(None).unwrap().frames(), 1152, "a packet lost before any is a frame"); + assert_eq!(dec.decode(&two).unwrap().frames(), 2304); + assert_eq!(dec.frames_per_packet(), None); + assert_eq!(dec.conceal(None).unwrap().frames(), 2304, "a packet lost whole is as long as the last"); + assert_eq!(dec.conceal(Some(&[two.clone(), one.clone()].concat())).unwrap().frames(), 3456); + // several frames in the lookahead say so before anything is decoded + assert_eq!(TrackDecoder::new(¶ms, &[&one, &two]).unwrap().frames_per_packet(), None); + // a packet decoded only in part (its second frame is cut short) keeps the length of the + // last whole packet for the next lost one + let cut = [one.clone(), one[..100].to_vec()].concat(); + assert_eq!(dec.decode(&cut).unwrap().frames(), 1152); + assert_eq!(dec.conceal(None).unwrap().frames(), 2304); + } + + #[cfg(feature = "mpa-decode")] + #[test] + fn mp2_packets_of_several_frames_have_their_length() { + let one = mp2_frame(); + let two = [one.clone(), one.clone()].concat(); + let params = CodecParams::new(CodecId::Mp2); + let mut dec = TrackDecoder::new(¶ms, &[&one]).unwrap(); + assert_eq!(dec.frames_per_packet(), Some(1152)); + assert_eq!(dec.decode(&two).unwrap().frames(), 2304); + assert_eq!(dec.frames_per_packet(), None); + assert_eq!(dec.conceal(Some(&two)).unwrap().frames(), 2304); + assert_eq!(TrackDecoder::new(¶ms, &[&two]).unwrap().frames_per_packet(), None); + // a packet lost before any has decoded is a frame: 1152 samples, 384 for Layer I + let stereo = |codec| CodecParams::new(codec).with_sample_rate(48_000).with_channels(2); + assert_eq!(TrackDecoder::new(&stereo(CodecId::Mp2), &[]).unwrap().conceal(None).unwrap().frames(), 1152); + assert_eq!(TrackDecoder::new(&stereo(CodecId::Mp1), &[]).unwrap().conceal(None).unwrap().frames(), 384); + let mut dec = TrackDecoder::new(¶ms, &[&one]).unwrap(); + dec.decode(&one).unwrap(); + dec.reset(); + assert_eq!(dec.conceal(None).unwrap().frames(), 1152, "after a seek"); + } + #[test] fn pcm_tracks_decode_in_wav_order() { let params = CodecParams::new(CodecId::Pcm).with_sample_rate(48_000).with_channels(2).with_pcm(PcmFormat::int(16, true, false)); @@ -1145,7 +1323,7 @@ mod tests { #[test] fn codecs_without_a_packet_decoder_are_unsupported() { - for codec in [CodecId::Flac, CodecId::Vorbis, CodecId::Mp3, CodecId::H264] { + for codec in [CodecId::Flac, CodecId::Vorbis, CodecId::H264] { assert!(matches!(TrackDecoder::new(&CodecParams::new(codec), &[]), Err(AudioError::Unsupported(_))), "{codec:?}"); } // A codec whose feature is off names the feature. @@ -1155,6 +1333,8 @@ mod tests { assert!(matches!(TrackDecoder::new(&CodecParams::new(CodecId::Alac), &[]), Err(AudioError::Unsupported(m)) if m.contains("alac-decode"))); #[cfg(not(feature = "opus-decode"))] assert!(matches!(TrackDecoder::new(&CodecParams::new(CodecId::Opus), &[]), Err(AudioError::Unsupported(m)) if m.contains("opus-decode"))); + #[cfg(not(feature = "mp3-decode"))] + assert!(matches!(TrackDecoder::new(&CodecParams::new(CodecId::Mp3), &[]), Err(AudioError::Unsupported(m)) if m.contains("mp3-decode"))); // PCM needs its sample format. assert!(TrackDecoder::new(&CodecParams::new(CodecId::Pcm).with_channels(2).with_sample_rate(48_000), &[]).is_err()); } diff --git a/crates/audio/tests/audio/malformed.rs b/crates/audio/tests/audio/malformed.rs index 035b6ccc1..9d48d8068 100644 --- a/crates/audio/tests/audio/malformed.rs +++ b/crates/audio/tests/audio/malformed.rs @@ -424,23 +424,26 @@ fn a_valid_mp3_with_incidental_container_magic_still_probes() { assert_eq!((info.codec, info.sample_rate, info.channels), (CodecId::Mp3, 44_100, 2)); } -/// Symphonia 0.6's CAF/FLAC/MP4 `Scoreable` implementations are unconditional -/// (`Supported(255)`), so a container magic before the first audio frame *is* the reader the probe -/// selects; there is no API to ask the probe to skip it. The preflight must therefore keep -/// refusing an oversized CAF cookie instead of ignoring the marker and playing the MPEG frames -/// behind it (which would also let a real malformed container bypass the size guard). +/// MP3 frames are read natively (as MP1 and MP2 are), so a container marker in junk before the +/// first frame does not hide the frames: the stream plays, and the bytes before it never reach +/// symphonia, whose CAF, FLAC and MP4 probes would claim the stream at the marker. A file that +/// starts with the container is read as that container whatever the hint, so its size checks +/// still refuse an oversized CAF cookie. #[test] -fn a_container_marker_before_the_first_audio_frame_is_the_probed_candidate() { +fn a_container_marker_before_the_first_audio_frame_does_not_hide_the_frames() { let mut frame = vec![0u8; 417]; frame[..4].copy_from_slice(&[0xFF, 0xFB, 0x90, 0x00]); // MPEG-1 Layer III, 128 kbps, 44.1 kHz, stereo let mp3: Vec = (0..8).flat_map(|_| frame.clone()).collect(); let malformed = caf(&[caf_desc(b"aac ", 0, 1024, 2, 0), (b"kuki", 1i64 << 50, vec![0; 8])]); for f in [[vec![0u8], malformed.clone(), mp3.clone()].concat(), [vec![0u8; 32], malformed.clone(), mp3.clone()].concat()] { - // The hint still names the raw stream, but the probe sees the CAF marker first. assert_eq!(detect_container(&f, Some("mp3")), Some(Container::MpegAudio)); - assert!(matches!(probe(&f, Some("mp3")), Err(AudioError::Malformed(m)) if m.contains("larger than the file"))); - assert!(matches!(decode(&f, Some("mp3")), Err(AudioError::Malformed(m)) if m.contains("larger than the file"))); + let info = probe(&f, Some("mp3")).expect("MP3 frames behind a container marker"); + assert_eq!((info.codec, info.sample_rate, info.channels, info.frames), (CodecId::Mp3, 44_100, 2, 8 * 1152)); + assert_eq!(decode(&f, Some("mp3")).expect("decode them").1.frames(), 8 * 1152); } + let f = [malformed, mp3.clone()].concat(); + assert!(matches!(probe(&f, Some("mp3")), Err(AudioError::Malformed(m)) if m.contains("larger than the file"))); + assert!(matches!(decode(&f, Some("mp3")), Err(AudioError::Malformed(m)) if m.contains("larger than the file"))); // Plain leading junk without format magic is tolerated, as before. let f = [vec![0u8; 32], mp3].concat(); assert_eq!(detect_container(&f, Some("mp3")), Some(Container::MpegAudio)); @@ -874,10 +877,10 @@ fn damaged_mp4_and_matroska_are_malformed() { } } -/// MP3 in MP4 is decoded by symphonia (PCM, AAC, ALAC, Opus and AC-3 are native), so it reaches -/// the checks in `read/mp4.rs`. -fn mp3_mp4(frames: u32, swap: impl Fn(&[u8; 4], Vec) -> Vec) -> Vec { - mp4(frames, &sample_entry(b".mp3", 0, &[], &[]), swap) +/// FLAC in MP4 is decoded by symphonia (PCM, AAC, ALAC, Opus, AC-3, MPEG audio are native), so +/// it reaches the checks in `read/mp4.rs`. +fn symphonia_mp4(frames: u32, swap: impl Fn(&[u8; 4], Vec) -> Vec) -> Vec { + mp4(frames, &sample_entry(b"fLaC", 0, &[], &[]), swap) } /// An `stts` atom of `n` one-sample runs. @@ -890,11 +893,11 @@ fn one_sample_runs(n: u32) -> Vec { #[test] fn an_audio_timing_table_with_a_run_per_sample_is_bounded() { let frames = 200_000; - let hostile = mp3_mp4(frames, |k, a| if k == b"stts" { one_sample_runs(frames) } else { a }); + let hostile = symphonia_mp4(frames, |k, a| if k == b"stts" { one_sample_runs(frames) } else { a }); let r = decode(&hostile, None); assert!(matches!(&r, Err(AudioError::Malformed(m)) if m.contains("timing table")), "{:?}", r.map(|x| x.0)); // a table of many runs over few packets is cheap to scan, so it is not refused for its size - let few = mp3_mp4(100, |k, a| if k == b"stts" { one_sample_runs(100_000) } else { a }); + let few = symphonia_mp4(100, |k, a| if k == b"stts" { one_sample_runs(100_000) } else { a }); assert!(!matches!(decode(&few, None), Err(AudioError::Malformed(m)) if m.contains("timing table"))); } @@ -903,7 +906,7 @@ fn an_audio_timing_table_with_a_run_per_sample_is_bounded() { fn the_audio_timing_bound_follows_the_sample_entry_not_the_handler() { let video_handler = atom(b"hdlr", &[0u32, 0, u32::from_be_bytes(*b"vide"), 0, 0, 0, 0].map(u32::to_be_bytes).concat()); let frames = 200_000; - let f = mp3_mp4(frames, |k, a| match k { + let f = symphonia_mp4(frames, |k, a| match k { b"stts" => one_sample_runs(frames), b"hdlr" => video_handler.clone(), _ => a, diff --git a/crates/audio/tests/codecs/main.rs b/crates/audio/tests/codecs/main.rs index 0fafa5dd2..5db6af4b0 100644 --- a/crates/audio/tests/codecs/main.rs +++ b/crates/audio/tests/codecs/main.rs @@ -12,6 +12,7 @@ mod ac3_encoder_oracle; mod ac3_oracle; mod alac; mod concealment; +mod mp3_decoder_oracle; mod mp3_encoder_oracle; mod mpa_oracle; mod opus; diff --git a/crates/audio/tests/codecs/mp3_decoder_oracle.rs b/crates/audio/tests/codecs/mp3_decoder_oracle.rs new file mode 100644 index 000000000..951a9b129 --- /dev/null +++ b/crates/audio/tests/codecs/mp3_decoder_oracle.rs @@ -0,0 +1,397 @@ +//! The Layer III decoder against ffmpeg's float MP3 decoder as the reference, sample for sample. +//! +//! - LAME through ffmpeg (`libmp3lame`): MPEG-1, MPEG-2 and MPEG-2.5 sampling frequencies, mono, +//! stereo and joint stereo, CBR from 8 to 320 kbit/s and VBR, transients (block switching). +//! - The `lame` command, when installed: CRC-protected frames, forced mid / side and no bit +//! reservoir. +//! - This crate's own encoder. +//! - What no encoder at hand writes, made by editing LAME's frames so that they stay valid: +//! intensity stereo (the `mode_extension` bit turned on, which MPEG-1 scalefactors do not +//! depend on) and mixed blocks (`mixed_block_flag` set on short blocks whose scalefactors take +//! no bits, so the granule's `part2_3_length` still holds). +//! +//! Each stream's side information is read here as well, to prove what it covers (short blocks, +//! mid / side, CRC). +//! +//! Streams are written without a Xing / Info frame, so ffmpeg decodes every frame and trims +//! nothing: both decoders' outputs line up from the first sample. + +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; + +use craft_audio::mp3::{Config, Decoder, Encoder, InfoTag, StereoMode}; +use craft_audio::mpa::{Mode, parse_header}; + +const TONES: &str = "aevalsrc=exprs=0.3*sin(2*PI*441*t)+0.1*sin(2*PI*3100*t)|0.25*sin(2*PI*660*t)+0.1*sin(2*PI*(500+4000*t)*t)"; +/// Clicks over a quiet tone: an encoder switches to short blocks around each click. +const CLICKS: &str = "aevalsrc=exprs=if(lt(mod(t\\,0.2)\\,0.004)\\,0.9*sin(2*PI*2500*t)\\,0.05*sin(2*PI*330*t))|if(lt(mod(t+0.1\\,0.2)\\,0.004)\\,0.8*sin(2*PI*1800*t)\\,0.04*sin(2*PI*220*t))"; +const NOISE: &str = "anoisesrc=color=pink:amplitude=0.4:seed=7"; +/// Two nearly equal channels: an encoder codes them as mid / side. +const CORRELATED: &str = "aevalsrc=exprs=0.3*sin(2*PI*441*t)+0.05*sin(2*PI*5000*t)|0.28*sin(2*PI*441*t+0.1)+0.05*sin(2*PI*5000*t)"; + +/// A stream from ffmpeg's LAME encoder. +struct Lame { + name: &'static str, + input: &'static str, + rate: u32, + channels: usize, + /// Encoder arguments after `-c:a libmp3lame`. + enc: &'static [&'static str], +} + +const LAME: &[Lame] = &[ + Lame { name: "joint_44k_128k", input: TONES, rate: 44_100, channels: 2, enc: &["-b:a", "128k"] }, + Lame { name: "joint_44k_correlated", input: CORRELATED, rate: 44_100, channels: 2, enc: &["-b:a", "96k"] }, + Lame { name: "joint_44k_clicks", input: CLICKS, rate: 44_100, channels: 2, enc: &["-b:a", "160k"] }, + Lame { name: "stereo_48k_320k", input: NOISE, rate: 48_000, channels: 2, enc: &["-b:a", "320k", "-joint_stereo", "0"] }, + Lame { name: "mono_32k_48k", input: CLICKS, rate: 32_000, channels: 1, enc: &["-b:a", "48k"] }, + Lame { name: "vbr_44k_noise", input: NOISE, rate: 44_100, channels: 2, enc: &["-q:a", "2"] }, + Lame { name: "lsf_22k_64k", input: CLICKS, rate: 22_050, channels: 2, enc: &["-b:a", "64k"] }, + Lame { name: "lsf_24k_mono_32k", input: TONES, rate: 24_000, channels: 1, enc: &["-b:a", "32k"] }, + Lame { name: "lsf_16k_vbr", input: NOISE, rate: 16_000, channels: 2, enc: &["-q:a", "4"] }, + Lame { name: "mpeg25_12k_24k", input: CLICKS, rate: 12_000, channels: 2, enc: &["-b:a", "24k"] }, + Lame { name: "mpeg25_11k_mono", input: TONES, rate: 11_025, channels: 1, enc: &["-b:a", "16k"] }, + Lame { name: "mpeg25_8k_8k", input: CLICKS, rate: 8_000, channels: 1, enc: &["-b:a", "8k"] }, + // Low bit rates: many short blocks with scalefac_compress 0, for the mixed block edits. + Lame { name: "mixed_44k_32k", input: CLICKS, rate: 44_100, channels: 2, enc: &["-b:a", "32k"] }, + Lame { name: "mixed_32k_mono_32k", input: CLICKS, rate: 32_000, channels: 1, enc: &["-b:a", "32k"] }, + Lame { name: "mixed_lsf_22k_32k", input: CLICKS, rate: 22_050, channels: 2, enc: &["-b:a", "32k"] }, + // 16 kHz has the band tables of MPEG-2.5's 11.025 and 12 kHz, whose streams give LAME few + // short blocks to edit. + Lame { name: "mixed_lsf_16k_32k", input: CLICKS, rate: 16_000, channels: 2, enc: &["-b:a", "32k"] }, +]; + +/// The minimum SNR against ffmpeg. Both decoders compute in f32 with different IMDCT and +/// synthesis factorizations: they agree to 125-132 dB on encoded and edited streams. A wrong +/// table entry, band boundary or stereo factor costs far more: the 8 kHz short-block region +/// boundary at 36 lines instead of 72 measured -5 dB. +const MIN_SNR: f64 = 100.0; + +fn generate(out: &Path, cmd: impl FnOnce(&Path) -> Command) -> Option { + crate::testkit::fixtures::generate(out, |tmp| cmd(tmp).stdin(Stdio::null()).status().is_ok_and(|s| s.success())) +} + +/// The LAME stream for `s`, or `None` (after saying so) when this ffmpeg has no `libmp3lame`; a +/// failure with `CRAFT_MEDIA_REQUIRE_ORACLES=1`. +fn lame_stream(ff: &Path, s: &Lame) -> Option { + let out = crate::testkit::fixtures_dir("mp3dec").join(format!("{}.mp3", s.name)); + generate(&out, |tmp| { + let mut c = Command::new(ff); + c.args(["-hide_banner", "-v", "error", "-y", "-f", "lavfi", "-i"]) + .arg(format!("{}:sample_rate={}", s.input, s.rate)) + .args(["-t", "2", "-ac", &s.channels.to_string(), "-c:a", "libmp3lame"]) + .args(s.enc) + .args(["-write_xing", "0", "-id3v2_version", "0", "-f", "mp3"]) + .arg(tmp); + c + }) + .or_else(|| { + assert!(!craft_media_testkit::oracles_required(), "could not generate {} (ffmpeg without libmp3lame?)", s.name); + eprintln!("SKIPPED {}: ffmpeg could not encode it (no libmp3lame?)", s.name); + None + }) +} + +/// The frames of a raw stream. +fn frames(data: &[u8]) -> Vec<&[u8]> { + let mut out = Vec::new(); + let mut p = 0; + while let Some(h) = data.get(p..).and_then(|b| parse_header(b).ok()) { + let Some(f) = data.get(p..p + h.frame_bytes).filter(|_| h.frame_bytes > 0) else { break }; + out.push(f); + p += h.frame_bytes; + } + out +} + +/// ffmpeg's float decode of `file`, interleaved. +fn ffmpeg_decode(ff: &Path, file: &Path) -> Vec { + let o = Command::new(ff).args(["-v", "error", "-c:a", "mp3float", "-f", "mp3", "-i"]).arg(file).args(["-f", "f32le", "-"]).output().unwrap(); + assert!(o.status.success(), "{}: {}", file.display(), String::from_utf8_lossy(&o.stderr)); + o.stdout.as_chunks::<4>().0.iter().map(|b| f32::from_le_bytes(*b)).collect() +} + +/// Our decode of every frame of `data`, planar. +fn decode_all(data: &[u8], rate: u32, channels: usize) -> Vec> { + let mut dec = Decoder::new(); + let mut out: Vec> = vec![Vec::new(); channels]; + for f in frames(data) { + let d = dec.decode(f).unwrap_or_else(|e| panic!("frame {}: {e}", out[0].len())); + assert_eq!((d.header.sample_rate, d.channels.len()), (rate, channels)); + for (o, c) in out.iter_mut().zip(d.channels) { + o.extend(c); + } + } + out +} + +/// The worst channel's SNR of `ours` against ffmpeg's decode, which must be as long. +fn snr(ours: &[Vec], want: &[f32], what: &str) -> f64 { + let channels = ours.len(); + assert_eq!(want.len() / channels, ours[0].len(), "{what}: sample count"); + let mut worst = f64::INFINITY; + for (c, ours) in ours.iter().enumerate() { + let (mut sig, mut err) = (0f64, 0f64); + for (i, &a) in ours.iter().enumerate() { + let b = f64::from(want[i * channels + c]); + sig += b * b; + err += (f64::from(a) - b).powi(2); + } + worst = worst.min(10.0 * (sig / err.max(1e-30)).log10()); + } + worst +} + +fn check(ff: &Path, file: &Path, rate: u32, channels: usize, what: &str) { + let data = std::fs::read(file).unwrap(); + assert!(frames(&data).len() > 10, "{what}: too few frames"); + let ours = decode_all(&data, rate, channels); + let snr = snr(&ours, &ffmpeg_decode(ff, file), what); + println!("{what}: SNR vs ffmpeg {snr:.1} dB"); + assert!(snr >= MIN_SNR, "{what}: SNR {snr:.1} dB < {MIN_SNR}"); +} + +#[test] +fn lame_streams_match_ffmpeg() { + let ff = require_ffmpeg!(); + for s in LAME { + let Some(f) = lame_stream(&ff, s) else { continue }; + check(&ff, &f, s.rate, s.channels, s.name); + } +} + +/// The `lame` command's options ffmpeg does not reach: CRC, forced mid / side, no reservoir. +#[test] +fn lame_cli_streams_match_ffmpeg() { + let ff = require_ffmpeg!(); + let Some(lame) = crate::testkit::oracle::find_tool("lame") else { + eprintln!("lame not installed, skipped"); + return; + }; + let cases: &[(&str, &[&str], u32, usize)] = &[ + ("crc_48k_mono", &["-p", "-b", "96", "-m", "m"], 48_000, 1), + ("nores_32k", &["--nores", "-b", "80"], 32_000, 2), + ("forced_ms_44k", &["-m", "f", "-b", "96"], 44_100, 2), + ]; + for &(name, args, rate, channels) in cases { + let dir = crate::testkit::fixtures_dir("mp3dec"); + let wav = dir.join(format!("{name}.wav")); + let input = if channels == 1 { "aevalsrc=exprs=if(lt(mod(t\\,0.2)\\,0.004)\\,0.9\\,0.05)*sin(2*PI*700*t)" } else { CLICKS }; + generate(&wav, |tmp| { + let mut c = Command::new(&ff); + c.args(["-hide_banner", "-v", "error", "-y", "-f", "lavfi", "-i"]) + .arg(format!("{input}:sample_rate={rate}")) + .args(["-t", "2", "-c:a", "pcm_s16le", "-f", "wav"]) + .arg(tmp); + c + }) + .unwrap(); + let out = dir.join(format!("{name}.mp3")); + let f = generate(&out, |tmp| { + let mut c = Command::new(&lame); + c.args(["--quiet", "-t", "--resample"]).arg(format!("{}", f64::from(rate) / 1000.0)).args(args).arg(&wav).arg(tmp); + c + }) + .unwrap_or_else(|| panic!("lame could not encode {name}")); + if name.starts_with("crc") { + assert!(parse_header(&std::fs::read(&f).unwrap()).unwrap().crc, "{name}: no CRC"); + } + check(&ff, &f, rate, channels, name); + } +} + +#[test] +fn our_encoder_streams_match_ffmpeg() { + let ff = require_ffmpeg!(); + let dir = crate::testkit::fixtures_dir("mp3dec"); + for (rate, channels, kbps, stereo) in [ + (44_100, 2, 192, StereoMode::JointStereo), + (48_000, 2, 128, StereoMode::Stereo), + (32_000, 1, 64, StereoMode::Stereo), + (24_000, 2, 64, StereoMode::JointStereo), + ] { + let input: Vec> = (0..channels) + .map(|c| { + (0..rate as usize).map(|i| 0.4 * (i as f32 * (0.03 + 0.02 * c as f32)).sin() * (1.0 + (i as f32 * 0.0007).sin()) / 2.0).collect() + }) + .collect(); + let refs: Vec<&[f32]> = input.iter().map(Vec::as_slice).collect(); + let mut enc = Encoder::new(Config { stereo, ..Config::cbr(rate, channels, kbps) }).unwrap(); + let mut data = enc.encode(&refs); + data.extend(enc.flush()); + let path = dir.join(format!("ours_{rate}_{channels}ch_{kbps}k.mp3")); + std::fs::write(&path, &data).unwrap(); + check(&ff, &path, rate, channels, &format!("our encoder {rate} Hz {channels} ch {kbps} kbit/s")); + } +} + +/// The streams cover what they are meant to: short blocks (LAME switches on the clicks, at +/// every sampling frequency family), mid / side and CRC. +#[test] +fn streams_cover_short_blocks_and_mid_side() { + let ff = require_ffmpeg!(); + for s in LAME.iter().filter(|s| s.input == CLICKS) { + let Some(f) = lame_stream(&ff, s) else { continue }; + let data = std::fs::read(f).unwrap(); + let short = frames(&data).iter().flat_map(|f| granules(f)).filter(|g| g.block_type == 2).count(); + assert!(short > 5, "{}: {short} short-block granules", s.name); + } + let s = LAME.iter().find(|s| s.name == "joint_44k_correlated").unwrap(); + let Some(f) = lame_stream(&ff, s) else { return }; + let data = std::fs::read(f).unwrap(); + let ms = frames(&data).iter().filter(|f| parse_header(f).is_ok_and(|h| h.mode == Mode::JointStereo && h.mode_extension & 2 != 0)).count(); + assert!(ms > 10, "{ms} mid/side frames"); +} + +/// What the side information says of one granule of one channel, and where its +/// `mixed_block_flag` is (a bit offset in the frame) when it switches windows. +struct GranuleSide { + block_type: u8, + scalefac_compress: u32, + mixed_bit: Option, +} + +/// Read the side information of a Layer III frame (ISO/IEC 11172-3 §2.4.1.7): each granule and +/// channel is 59 bits in MPEG-1, 63 in MPEG-2. +fn granules(frame: &[u8]) -> Vec { + let h = parse_header(frame).unwrap(); + let lsf = h.sample_rate < 32_000; + let nch = h.channels(); + let bit = |p: usize| (frame[p / 8] >> (7 - p % 8)) & 1; + let field = |p: usize, n: usize| (p..p + n).fold(0u32, |v, q| v << 1 | u32::from(bit(q))); + let mut p = 32 + if h.crc { 16 } else { 0 }; + p += if lsf { 8 + if nch == 1 { 1 } else { 2 } } else { 9 + if nch == 1 { 5 } else { 3 } + 4 * nch }; + let mut out = Vec::new(); + for _ in 0..if lsf { 1 } else { 2 } { + for _ in 0..nch { + let scalefac_compress = field(p + 29, if lsf { 9 } else { 4 }); + let ws = p + if lsf { 38 } else { 33 }; + let (block_type, mixed_bit) = if bit(ws) == 1 { (field(ws + 1, 2) as u8, Some(ws + 3)) } else { (0, None) }; + out.push(GranuleSide { block_type, scalefac_compress, mixed_bit }); + p += if lsf { 63 } else { 59 }; + } + } + out +} + +/// A granule the mixed block test may edit: its frame, where its `mixed_block_flag` is, whether +/// it is a short block, and whether its scalefactors take no bits. +#[derive(Clone, Copy)] +struct Candidate { + frame: usize, + bit: Option, + short: bool, + editable: bool, +} + +/// Write `frames` (edited copies) as a stream and compare it with ffmpeg. +fn check_edited(ff: &Path, frames: Vec>, rate: u32, channels: usize, name: &str) { + let path = crate::testkit::fixtures_dir("mp3dec").join(format!("{name}.mp3")); + std::fs::write(&path, frames.concat()).unwrap(); + check(ff, &path, rate, channels, name); +} + +/// Intensity stereo: LAME's MPEG-1 joint stereo frames with the intensity bit of +/// `mode_extension` set. The right channel's zero bands above its last non-zero line become +/// intensity coded with the positions its scalefactors give, in long and short blocks. +#[test] +fn intensity_stereo_matches_ffmpeg() { + let ff = require_ffmpeg!(); + for name in ["joint_44k_clicks", "joint_44k_correlated", "joint_44k_128k"] { + let s = LAME.iter().find(|s| s.name == name).unwrap(); + let Some(f) = lame_stream(&ff, s) else { continue }; + let data = std::fs::read(f).unwrap(); + let mut edited = 0; + let fr: Vec> = frames(&data) + .into_iter() + .map(|f| { + let mut f = f.to_vec(); + if parse_header(&f).unwrap().mode == Mode::JointStereo { + f[3] |= 0x10; + edited += 1; + } + f + }) + .collect(); + assert!(edited > 10, "{name}: {edited} joint stereo frames"); + check_edited(&ff, fr, s.rate, s.channels, &format!("{name}_intensity")); + } +} + +/// Mixed blocks: LAME's short-block granules with `mixed_block_flag` set, at MPEG-1 and MPEG-2 +/// sampling frequencies (16 kHz shares its band tables with MPEG-2.5's 11.025 and 12 kHz). +/// +/// - Only granules whose scalefactors take no bits (`scalefac_compress` 0) are edited: the mixed +/// layout reads the same zero bits, so the stream stays valid. Editing others would shift every +/// later field of the granule past its `part2_3_length`, and decoders differ on such damage. +/// - Not a granule whose channel's next granule is a short block that stays unmixed. ffmpeg's +/// short-block IMDCT drops the last 6 overlap values of each subband, which is exact after a +/// start or short window (whose tails are zero) but not after a mixed block's long subbands +/// (the normal window's tail is not); ISO/IEC 11172-3 adds the whole overlap, as this decoder +/// does. +/// - Not at 8 kHz, where ffmpeg does not implement mixed blocks. +#[test] +fn mixed_blocks_match_ffmpeg() { + let ff = require_ffmpeg!(); + for name in ["mixed_44k_32k", "mixed_32k_mono_32k", "mixed_lsf_22k_32k", "mixed_lsf_16k_32k"] { + let s = LAME.iter().find(|s| s.name == name).unwrap(); + let Some(f) = lame_stream(&ff, s) else { continue }; + let data = std::fs::read(f).unwrap(); + let mut fr: Vec> = frames(&data).into_iter().map(<[u8]>::to_vec).collect(); + // each channel's granules in order + let mut channels: Vec> = vec![Vec::new(); s.channels]; + for (frame, f) in fr.iter().enumerate() { + for (i, g) in granules(f).into_iter().enumerate() { + let short = g.block_type == 2; + let editable = short && g.scalefac_compress == 0; + channels[i % s.channels].push(Candidate { frame, bit: g.mixed_bit, short, editable }); + } + } + let mut mixed = 0; + for ch in &channels { + // from the last granule back, so whether the next one is mixed is already decided + let mut next_pure_short = false; + for c in ch.iter().rev() { + let edit = c.editable && !next_pure_short; + if let (true, Some(p)) = (edit, c.bit) { + fr[c.frame][p / 8] |= 0x80 >> (p % 8); + mixed += 1; + } + next_pure_short = c.short && !edit; + } + } + println!("{name}: {mixed} granules made mixed"); + assert!(mixed > 5, "{name}: {mixed} mixed granules"); + check_edited(&ff, fr, s.rate, s.channels, &format!("{name}_mixed")); + } +} + +/// A Xing / Info frame with a LAME tag gives the frame count, encoder delay and padding. +#[test] +fn info_tag_of_a_lame_stream() { + let ff = require_ffmpeg!(); + let out = crate::testkit::fixtures_dir("mp3dec").join("tagged_44k.mp3"); + let f = generate(&out, |tmp| { + let mut c = Command::new(&ff); + c.args(["-hide_banner", "-v", "error", "-y", "-f", "lavfi", "-i", "sine=frequency=500:sample_rate=44100", "-t", "1"]) + .args(["-c:a", "libmp3lame", "-b:a", "128k", "-id3v2_version", "0", "-f", "mp3"]) + .arg(tmp); + c + }) + .or_else(|| { + assert!(!craft_media_testkit::oracles_required(), "could not write a LAME-tagged stream (ffmpeg without libmp3lame?)"); + None + }); + let Some(f) = f else { + eprintln!("SKIPPED: ffmpeg could not encode a LAME-tagged stream (no libmp3lame?)"); + return; + }; + let data = std::fs::read(f).unwrap(); + let fr = frames(&data); + let tag = InfoTag::parse(fr[0]).expect("Info frame"); + assert_eq!(tag.frames, Some(fr.len() as u32 - 1)); + let (delay, padding) = tag.delay_padding.expect("LAME tag"); + // 1 s at 44.1 kHz: the frames hold the delay, the audio and the padding + assert_eq!(u64::from(delay) + 44_100 + u64::from(padding), (fr.len() as u64 - 1) * 1152); + assert!(InfoTag::parse(fr[1]).is_none()); +} diff --git a/crates/audio/tests/codecs/routing_oracle.rs b/crates/audio/tests/codecs/routing_oracle.rs index 6472979b3..88cf9beec 100644 --- a/crates/audio/tests/codecs/routing_oracle.rs +++ b/crates/audio/tests/codecs/routing_oracle.rs @@ -267,6 +267,58 @@ const CASES: &[Case] = &[ frames: Some(240_000), min_snr: 80.0, }, + Case { + name: "mp3.mp3", + surround: false, + mono: false, + rate: 48_000, + args: &["-c:a", "libmp3lame", "-b:a", "192k"], + container: Container::MpegAudio, + codec: CodecId::Mp3, + layout: ChannelLayout::STEREO, + channels: 2, + frames: Some(240_000), + min_snr: 100.0, + }, + Case { + name: "mp3-lsf-mono.mp3", + surround: false, + mono: true, + rate: 24_000, + args: &["-c:a", "libmp3lame", "-b:a", "32k"], + container: Container::MpegAudio, + codec: CodecId::Mp3, + layout: ChannelLayout::MONO, + channels: 1, + frames: Some(120_000), + min_snr: 100.0, + }, + Case { + name: "mp3.mkv", + surround: false, + mono: false, + rate: 48_000, + args: &["-c:a", "libmp3lame", "-b:a", "128k"], + container: Container::Matroska, + codec: CodecId::Mp3, + layout: ChannelLayout::STEREO, + channels: 2, + frames: Some(240_000), + min_snr: 100.0, + }, + Case { + name: "mp3.mp4", + surround: false, + mono: false, + rate: 48_000, + args: &["-c:a", "libmp3lame", "-b:a", "128k"], + container: Container::Mp4, + codec: CodecId::Mp3, + layout: ChannelLayout::STEREO, + channels: 2, + frames: Some(240_000), + min_snr: 100.0, + }, Case { name: "pcm-s24be.mov", surround: false, @@ -342,6 +394,8 @@ fn reference_lead(name: &str) -> usize { "aac.mkv" => 1024, "eac3-5.1.mkv" => 256, "mp2.mkv" => 481, + // LAME's encoder delay and the decoder's, the Matroska CodecDelay + "mp3.mkv" => 1105, _ => 0, } } @@ -493,7 +547,7 @@ fn track_decoder_on_demuxed_packets_matches_decode() { use craft_audio::TrackDecoder; use craft_media::isobmff; let ff = require_ffmpeg!(); - for name in ["aac.m4a", "aac-5.1.m4a", "opus.mp4", "mp2.mp4", "pcm-s24be.mov"] { + for name in ["aac.m4a", "aac-5.1.m4a", "opus.mp4", "mp2.mp4", "mp3.mp4", "pcm-s24be.mov"] { let c = CASES.iter().find(|c| c.name == name).unwrap(); let Some(path) = fixture(&ff, c) else { continue }; let bytes = std::fs::read(&path).unwrap();