From 35166b384184199bbc8883c873e87bbca2edf62d Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Wed, 11 Nov 2026 14:47:18 -0500 Subject: [PATCH] Media: Opus, Ogg, MP4 and Matroska come from craft-libs (#2047) DeckCraft kept its own Opus decoder (crates/opus) and Ogg demuxer (crates/ogg), and two crates that only re-exported craft-media (crates/isobmff, crates/matroska). craft-libs has all four: craft-audio's opus module and craft-media's ogg, isobmff and matroska modules. The four crates are removed and crates/media depends on craft-audio and craft-media directly. craft-media and craft-audio are pinned at 113f2360, the head of craft-libs #810, which carries the two Ogg fixes DeckCraft made to its own copy (51d19baa, 9d36b593). The pin moves to craft-libs main once #810 merges. craft-pdf, craft-update and craft-ui keep their pins. One API difference: craft-media's OpusTiming::of saturates on a hostile granule position instead of returning an error. The end-trim product in the Ogg Opus decode now saturates as well; a test with a three-channel file and the largest granule panicked on it without that. crates/bitstream stays: it also holds the video decoders' memory budget, which craft-libs does not have. --- Cargo.lock | 39 +- Cargo.toml | 10 +- NOTICE | 5 +- ROADMAP.md | 4 +- crates/isobmff/Cargo.toml | 13 - crates/isobmff/src/lib.rs | 6 - crates/matroska/Cargo.toml | 13 - crates/matroska/src/lib.rs | 6 - crates/media/Cargo.toml | 7 +- crates/media/src/audio.rs | 17 +- crates/media/src/probe.rs | 16 +- crates/media/src/video.rs | 18 +- crates/media/tests/it/decode.rs | 46 ++ crates/ogg/Cargo.toml | 12 - crates/ogg/src/lib.rs | 581 ----------------- crates/ogg/src/tests.rs | 307 --------- crates/opus/Cargo.toml | 12 - crates/opus/src/celt.rs | 678 ------------------- crates/opus/src/celt/bands.rs | 1013 ----------------------------- crates/opus/src/celt/energy.rs | 171 ----- crates/opus/src/celt/mdct.rs | 205 ------ crates/opus/src/celt/rate.rs | 584 ----------------- crates/opus/src/celt/tables.rs | 130 ---- crates/opus/src/decoder.rs | 477 -------------- crates/opus/src/header.rs | 196 ------ crates/opus/src/lib.rs | 46 -- crates/opus/src/multistream.rs | 198 ------ crates/opus/src/packet.rs | 340 ---------- crates/opus/src/range.rs | 497 -------------- crates/opus/src/silk.rs | 912 -------------------------- crates/opus/src/silk/lpc.rs | 377 ----------- crates/opus/src/silk/resampler.rs | 205 ------ crates/opus/src/silk/tables.rs | 540 --------------- docs/architecture.md | 9 +- docs/file-format-parity.md | 2 +- xtask/src/layers.rs | 9 +- 36 files changed, 106 insertions(+), 7595 deletions(-) delete mode 100644 crates/isobmff/Cargo.toml delete mode 100644 crates/isobmff/src/lib.rs delete mode 100644 crates/matroska/Cargo.toml delete mode 100644 crates/matroska/src/lib.rs delete mode 100644 crates/ogg/Cargo.toml delete mode 100644 crates/ogg/src/lib.rs delete mode 100644 crates/ogg/src/tests.rs delete mode 100644 crates/opus/Cargo.toml delete mode 100644 crates/opus/src/celt.rs delete mode 100644 crates/opus/src/celt/bands.rs delete mode 100644 crates/opus/src/celt/energy.rs delete mode 100644 crates/opus/src/celt/mdct.rs delete mode 100644 crates/opus/src/celt/rate.rs delete mode 100644 crates/opus/src/celt/tables.rs delete mode 100644 crates/opus/src/decoder.rs delete mode 100644 crates/opus/src/header.rs delete mode 100644 crates/opus/src/lib.rs delete mode 100644 crates/opus/src/multistream.rs delete mode 100644 crates/opus/src/packet.rs delete mode 100644 crates/opus/src/range.rs delete mode 100644 crates/opus/src/silk.rs delete mode 100644 crates/opus/src/silk/lpc.rs delete mode 100644 crates/opus/src/silk/resampler.rs delete mode 100644 crates/opus/src/silk/tables.rs diff --git a/Cargo.lock b/Cargo.lock index 4cf447655..7ca1c0bd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -844,6 +844,15 @@ dependencies = [ "libc", ] +[[package]] +name = "craft-audio" +version = "0.1.0" +source = "git+https://github.com/storytold/craft-libs?rev=75895c0321d8cb6e175a42466cba050352b117c5#75895c0321d8cb6e175a42466cba050352b117c5" +dependencies = [ + "craft-media", + "thiserror 2.0.21", +] + [[package]] name = "craft-fonts-build" version = "0.1.0" @@ -875,7 +884,7 @@ dependencies = [ [[package]] name = "craft-media" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=6e03f65bf45181db77ba6229716e6624db9eb25b#6e03f65bf45181db77ba6229716e6624db9eb25b" +source = "git+https://github.com/storytold/craft-libs?rev=75895c0321d8cb6e175a42466cba050352b117c5#75895c0321d8cb6e175a42466cba050352b117c5" dependencies = [ "thiserror 2.0.21", ] @@ -1166,20 +1175,6 @@ dependencies = [ "thiserror 2.0.21", ] -[[package]] -name = "deckcraft-isobmff" -version = "0.18.0" -dependencies = [ - "craft-media", -] - -[[package]] -name = "deckcraft-matroska" -version = "0.18.0" -dependencies = [ - "craft-media", -] - [[package]] name = "deckcraft-mcp" version = "0.18.0" @@ -1194,13 +1189,11 @@ dependencies = [ name = "deckcraft-media" version = "0.18.0" dependencies = [ + "craft-audio", + "craft-media", "deckcraft-av1", "deckcraft-h264", "deckcraft-hevc", - "deckcraft-isobmff", - "deckcraft-matroska", - "deckcraft-ogg", - "deckcraft-opus", "deckcraft-vp9", "image", "log", @@ -1229,14 +1222,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "deckcraft-ogg" -version = "0.18.0" - -[[package]] -name = "deckcraft-opus" -version = "0.18.0" - [[package]] name = "deckcraft-pdf" version = "0.18.0" diff --git a/Cargo.toml b/Cargo.toml index c12ae375f..3524658a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,16 +27,12 @@ deckcraft-engine = { path = "crates/engine" } deckcraft-mcp = { path = "crates/mcp" } deckcraft-ui-egui = { path = "crates/ui-egui" } deckcraft-media = { path = "crates/media" } -# codecs (ported from FilmCraft, same owner and licence) +# video codecs (ported from FilmCraft, same owner and licence) deckcraft-bitstream = { path = "crates/bitstream" } deckcraft-h264 = { path = "crates/h264" } deckcraft-hevc = { path = "crates/hevc" } deckcraft-vp9 = { path = "crates/vp9" } deckcraft-av1 = { path = "crates/av1" } -deckcraft-isobmff = { path = "crates/isobmff" } -deckcraft-matroska = { path = "crates/matroska" } -deckcraft-ogg = { path = "crates/ogg" } -deckcraft-opus = { path = "crates/opus" } # foundation kurbo = { version = "0.13", features = ["serde"] } @@ -59,7 +55,9 @@ harfrust = "0.13" vello_cpu = "0.2" vello_common = { version = "0.2", default-features = false } image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp", "gif", "tiff", "bmp"] } -craft-media = { git = "https://github.com/storytold/craft-libs", rev = "6e03f65bf45181db77ba6229716e6624db9eb25b", version = "=0.1.0", default-features = false } +# craft-media and craft-audio share types and stay on one rev. +craft-media = { git = "https://github.com/storytold/craft-libs", rev = "75895c0321d8cb6e175a42466cba050352b117c5", version = "=0.1.0", default-features = false } +craft-audio = { git = "https://github.com/storytold/craft-libs", rev = "75895c0321d8cb6e175a42466cba050352b117c5", version = "=0.1.0", default-features = false, features = ["opus-decode"] } craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "6e03f65bf45181db77ba6229716e6624db9eb25b", version = "=0.1.0", default-features = false, features = ["write", "write-simple-text"] } # craft-update: app-specific pretend versions apply to startup and manual checks. craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "6e03f65bf45181db77ba6229716e6624db9eb25b", features = ["egui"] } diff --git a/NOTICE b/NOTICE index 438030f2d..874a0e584 100644 --- a/NOTICE +++ b/NOTICE @@ -76,8 +76,9 @@ Code dependencies - symphonia (audio decoding for MP3, AAC, ALAC, FLAC, Vorbis, WAV, AIFF, CAF) is used unmodified under the Mozilla Public License 2.0; its source is available at https://github.com/pdeljanov/Symphonia. -- The H.264, HEVC, VP9, AV1, Opus, MP4, Matroska and Ogg crates under crates/ are ported from - FilmCraft (same owner, MIT OR Apache-2.0). +- The H.264, HEVC, VP9 and AV1 crates under crates/ are ported from FilmCraft (same owner, + MIT OR Apache-2.0). The Opus decoder (craft-audio) and the MP4, Matroska and Ogg demuxers + (craft-media) come from craft-libs (same owner, MIT OR Apache-2.0). - Krilla 0.8.2 (https://github.com/LaurenzV/krilla) is used through craft-pdf under MIT OR Apache-2.0. craft-libs vendors that version with page/surface unwind recovery diff --git a/ROADMAP.md b/ROADMAP.md index edd316276..34d6567da 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **Stage: alpha** · next: beta, ~20 points and ~120–210 h away -> **Last reviewed:** 2026-11-04 · **Last updated:** 2026-11-04 · **Change:** trivial (Ogg Opus recovery includes trailing packets without granules in its reported duration; parity scores unchanged) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365) +> **Last reviewed:** 2026-11-04 · **Last updated:** 2026-11-11 · **Change:** trivial (the Opus decoder and the Ogg, MP4 and Matroska demuxers come from craft-libs; parity scores unchanged) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365) > **Last reviewed:** 2026-11-02 · **Last updated:** 2026-11-03 · **Change:** trivial (progress log: web `?host=parent` bridge opens and saves through a same-origin framing page; scores unchanged) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365) > **Last reviewed:** 2026-11-02 · **Last updated:** 2026-11-03 · **Change:** trivial (community PR wave 10: PPTX and native saves keep table cell centred anchoring, text overflow, compatible line spacing, normalised character height, Thai distributed alignment, RTL table direction, alternate run language and reflection transforms; imported effects placed by parent time-node delays; Reuse Slides fits the destination size and brings custom table styles; deleted slides leave custom shows; merged shapes drop their animations; Compress Pictures measures grouped pictures at displayed size; parity scores unchanged) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365) @@ -154,6 +154,7 @@ app's JSON control channel (`deckcraft --control PORT`). | Date | What landed | |---|---| +| 2026-11-11 | Media: the Opus decoder and the Ogg, MP4/MOV and Matroska demuxers are craft-audio and craft-media from craft-libs; DeckCraft's own copies and re-export crates are removed | | 2026-11-04 | Ogg Opus recovery keeps trailing packets without granules in the total duration, matching their recovered positions; valid end trimming stays covered | | 2026-11-03 | Web: the `?host=parent` bridge (as in GridCraft, WordCraft and PdfCraft) lets a same-origin page that frames DeckCraft send a presentation and store its saves; Save writes an opened .pptx back as .pptx under its own name, as on the desktop | | 2026-11-02 | Slide show on Linux and the BSDs: Presenter View stays off when winit lists exactly one display, as in PowerPoint; if it lists none, the setting is kept. On one display the presenter window covered the show, and the hidden window stalled every frame: slide changes took up to ~1.7 s under Hyprland (#1156) | @@ -340,6 +341,7 @@ app's JSON control channel (`deckcraft --control PORT`). | Date | Change | Summary | |---|---|---| +| 2026-11-11 | trivial | Opus and container code moved to craft-libs crates; parity scores unchanged | | 2026-11-04 | trivial | Ogg Opus recovery duration includes its recovered tail; parity scores unchanged | | 2026-11-02 | trivial | Progress log: run alternate-language metadata round-trips through native and PPTX formats; product parity scores unchanged | | 2026-11-03 | trivial | Reuse Slides brings referenced custom table styles along; scores unchanged | diff --git a/crates/isobmff/Cargo.toml b/crates/isobmff/Cargo.toml deleted file mode 100644 index 7bc074b26..000000000 --- a/crates/isobmff/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "deckcraft-isobmff" -description = "Clean-room ISO BMFF (MP4) and QuickTime (MOV) demuxer and muxer" -version.workspace = true -edition.workspace = true -license.workspace = true -rust-version.workspace = true - -[lints] -workspace = true - -[dependencies] -craft-media = { workspace = true, features = ["isobmff"] } diff --git a/crates/isobmff/src/lib.rs b/crates/isobmff/src/lib.rs deleted file mode 100644 index d51006b9d..000000000 --- a/crates/isobmff/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! ISO BMFF (MP4) and QuickTime (MOV) demuxer and muxer: a re-export of [`craft_media::isobmff`] -//! from craft-libs. Supported boxes and limitations are in the craft-media README. - -#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable))] - -pub use craft_media::isobmff::*; diff --git a/crates/matroska/Cargo.toml b/crates/matroska/Cargo.toml deleted file mode 100644 index cc6d71161..000000000 --- a/crates/matroska/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "deckcraft-matroska" -description = "Clean-room Matroska (MKV) and WebM demuxer and muxer" -version.workspace = true -edition.workspace = true -license.workspace = true -rust-version.workspace = true - -[lints] -workspace = true - -[dependencies] -craft-media = { workspace = true, features = ["matroska"] } diff --git a/crates/matroska/src/lib.rs b/crates/matroska/src/lib.rs deleted file mode 100644 index 94428f619..000000000 --- a/crates/matroska/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! Matroska (MKV) and WebM demuxer and muxer: a re-export of [`craft_media::matroska`] from -//! craft-libs. Supported elements and limitations are in the craft-media README. - -#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable))] - -pub use craft_media::matroska::*; diff --git a/crates/media/Cargo.toml b/crates/media/Cargo.toml index e785f369c..4bb66486c 100644 --- a/crates/media/Cargo.toml +++ b/crates/media/Cargo.toml @@ -9,10 +9,9 @@ description = "DeckCraft media: probing, audio decoding (MP3, AAC/M4A, WAV, AIFF workspace = true [dependencies] -deckcraft-isobmff = { workspace = true } -deckcraft-matroska = { workspace = true } -deckcraft-ogg = { workspace = true } -deckcraft-opus = { workspace = true } +# Containers (MP4/MOV, Matroska/WebM, Ogg) and the Opus decoder, shared with the other Craft apps. +craft-media = { workspace = true, features = ["isobmff", "matroska", "ogg"] } +craft-audio = { workspace = true } deckcraft-h264 = { workspace = true } deckcraft-hevc = { workspace = true } deckcraft-vp9 = { workspace = true } diff --git a/crates/media/src/audio.rs b/crates/media/src/audio.rs index 09f9efa37..9f4ab3275 100644 --- a/crates/media/src/audio.rs +++ b/crates/media/src/audio.rs @@ -91,15 +91,15 @@ pub fn decode(bytes: &Bytes) -> Result { /// Ogg Opus with our decoder; `None` when the file holds no Opus stream. fn decode_ogg_opus(bytes: &Bytes) -> Option> { let src: &[u8] = bytes; - let file = deckcraft_ogg::open(src).ok()?; - let s = file.stream_of(deckcraft_ogg::Codec::Opus)?; + let file = craft_media::ogg::open(src).ok()?; + let s = file.stream_of(craft_media::ogg::Codec::Opus)?; let stream = file.streams.get(s)?; Some((|| { let head = stream.headers.first().ok_or(MediaError::Corrupt("Opus header missing".into()))?; - let mut dec = deckcraft_opus::Decoder::new(head).map_err(|e| MediaError::Corrupt(e.to_string()))?; + let mut dec = craft_audio::opus::Decoder::new(head).map_err(|e| MediaError::Corrupt(e.to_string()))?; dec.set_trim_pre_skip(true); let channels = dec.channels() as u16; - let timing = deckcraft_ogg::OpusTiming::of(stream, dec.pre_skip() as u32).map_err(|e| MediaError::Corrupt(e.to_string()))?; + let timing = craft_media::ogg::OpusTiming::of(stream, dec.pre_skip() as u32); let mut out = Vec::new(); for i in 0..stream.packets.len() { if MAX_PCM_SAMPLES.saturating_sub(out.len()) < channels.max(1) as usize { @@ -115,7 +115,8 @@ fn decode_ogg_opus(bytes: &Bytes) -> Option> { } } // End trimming (RFC 7845 §4.5). - let total = timing.total.max(0) as usize * channels.max(1) as usize; + // the timing saturates on a hostile granule, so this product must too + let total = usize::try_from(timing.total).unwrap_or(0).saturating_mul(channels.max(1) as usize); if total > 0 && total < out.len() { out.truncate(total); } @@ -126,13 +127,13 @@ fn decode_ogg_opus(bytes: &Bytes) -> Option> { /// WebM / Matroska Opus with our decoder; `None` when there is no Opus track. fn decode_mkv_opus(bytes: &Bytes) -> Option> { let src: &[u8] = bytes; - let file = deckcraft_matroska::open(src).ok()?; + let file = craft_media::matroska::open(src).ok()?; let (ti, head) = file.tracks.iter().enumerate().find_map(|(i, t)| match &t.codec { - deckcraft_matroska::Codec::Opus { head } => Some((i, head.clone())), + craft_media::matroska::Codec::Opus { head } => Some((i, head.clone())), _ => None, })?; Some((|| { - let mut dec = deckcraft_opus::Decoder::new(&head).map_err(|e| MediaError::Corrupt(e.to_string()))?; + let mut dec = craft_audio::opus::Decoder::new(&head).map_err(|e| MediaError::Corrupt(e.to_string()))?; dec.set_trim_pre_skip(true); let channels = dec.channels() as u16; let mut out = Vec::new(); diff --git a/crates/media/src/probe.rs b/crates/media/src/probe.rs index 4e8e845c5..5a7da2aee 100644 --- a/crates/media/src/probe.rs +++ b/crates/media/src/probe.rs @@ -106,9 +106,9 @@ fn codec_label(short: &str) -> String { } fn probe_mp4(bytes: &Bytes) -> Result { - use deckcraft_isobmff::{CodecConfig, TrackKind}; + use craft_media::isobmff::{CodecConfig, TrackKind}; let src: &[u8] = bytes; - let f = deckcraft_isobmff::open(src).map_err(|e| MediaError::Corrupt(e.to_string()))?; + let f = craft_media::isobmff::open(src).map_err(|e| MediaError::Corrupt(e.to_string()))?; let mut info = MediaInfo { container: if f.is_quicktime { "mov" } else { "mp4" }, ..Default::default() }; let mut dur = if f.timescale > 0 { f.duration as f64 / f.timescale as f64 } else { 0.0 }; if let Some(ti) = f.track_of_kind(TrackKind::Video) { @@ -146,9 +146,9 @@ fn probe_mp4(bytes: &Bytes) -> Result { } fn probe_mkv(bytes: &Bytes) -> Result { - use deckcraft_matroska::{Codec, TrackKind}; + use craft_media::matroska::{Codec, TrackKind}; let src: &[u8] = bytes; - let f = deckcraft_matroska::open(src).map_err(|e| MediaError::Corrupt(e.to_string()))?; + let f = craft_media::matroska::open(src).map_err(|e| MediaError::Corrupt(e.to_string()))?; let mut info = MediaInfo { container: if f.is_webm() { "webm" } else { "matroska" }, ..Default::default() }; let dur = f.duration_ns().map(|n| n as f64 / 1e9).unwrap_or(0.0); info.duration_ms = ms(dur); @@ -179,11 +179,11 @@ fn probe_mkv(bytes: &Bytes) -> Result { fn probe_ogg(bytes: &Bytes) -> Option { let src: &[u8] = bytes; - let f = deckcraft_ogg::open(src).ok()?; - let s = f.stream_of(deckcraft_ogg::Codec::Opus)?; + let f = craft_media::ogg::open(src).ok()?; + let s = f.stream_of(craft_media::ogg::Codec::Opus)?; let st = f.streams.get(s)?; - let head = deckcraft_opus::OpusHead::parse(st.headers.first()?).ok()?; - let timing = deckcraft_ogg::OpusTiming::of(st, head.pre_skip as u32).ok()?; + let head = craft_audio::opus::OpusHead::parse(st.headers.first()?).ok()?; + let timing = craft_media::ogg::OpusTiming::of(st, head.pre_skip as u32); Some(MediaInfo { container: "ogg", duration_ms: ms(timing.total as f64 / 48_000.0), diff --git a/crates/media/src/video.rs b/crates/media/src/video.rs index 5670817ee..56474c6af 100644 --- a/crates/media/src/video.rs +++ b/crates/media/src/video.rs @@ -3,8 +3,8 @@ use std::collections::VecDeque; -use deckcraft_isobmff::CodecConfig; -use deckcraft_matroska::Codec as MkvCodec; +use craft_media::isobmff::CodecConfig; +use craft_media::matroska::Codec as MkvCodec; use crate::yuv::{self, Matrix, Planes}; use crate::{Bytes, Container, MediaError, Result}; @@ -257,8 +257,8 @@ struct Track { } enum Demux { - Mp4(Box, usize), - Mkv(Box, usize), + Mp4(Box, usize), + Mkv(Box, usize), } impl Demux { @@ -273,8 +273,8 @@ impl Demux { fn open_track(bytes: &[u8]) -> Result<(Demux, Track, Box)> { match Container::sniff(bytes) { Container::Mp4 => { - let f = deckcraft_isobmff::open(bytes).map_err(dec_err)?; - let ti = f.track_of_kind(deckcraft_isobmff::TrackKind::Video).ok_or(MediaError::Missing("video track"))?; + let f = craft_media::isobmff::open(bytes).map_err(dec_err)?; + let ti = f.track_of_kind(craft_media::isobmff::TrackKind::Video).ok_or(MediaError::Missing("video track"))?; let t = &f.tracks[ti]; let entry = t.entries.first().ok_or(MediaError::Missing("video track"))?; let codec: Box = match &entry.codec { @@ -290,11 +290,11 @@ fn open_track(bytes: &[u8]) -> Result<(Demux, Track, Box)> { Ok((Demux::Mp4(Box::new(f), ti), track, codec)) } Container::Matroska => { - let f = deckcraft_matroska::open(bytes).map_err(dec_err)?; + let f = craft_media::matroska::open(bytes).map_err(dec_err)?; let ti = f .tracks .iter() - .position(|t| t.kind == deckcraft_matroska::TrackKind::Video && !t.samples.is_empty()) + .position(|t| t.kind == craft_media::matroska::TrackKind::Video && !t.samples.is_empty()) .ok_or(MediaError::Missing("video track"))?; let t = &f.tracks[ti]; let codec: Box = match &t.codec { @@ -303,7 +303,7 @@ fn open_track(bytes: &[u8]) -> Result<(Demux, Track, Box)> { MkvCodec::Vp9 { .. } => Box::new(Vp9 { dec: deckcraft_vp9::Decoder::new() }), MkvCodec::Av1 { av1c } => Box::new(Av1 { dec: deckcraft_av1::Decoder::new(), - config_obus: deckcraft_isobmff::Av1Config::parse(av1c).map(|c| c.config_obus).unwrap_or_default(), + config_obus: craft_media::isobmff::Av1Config::parse(av1c).map(|c| c.config_obus).unwrap_or_default(), primed: false, }), other => return Err(MediaError::Unsupported(format!("{} video", mkv_codec_name(other)))), diff --git a/crates/media/tests/it/decode.rs b/crates/media/tests/it/decode.rs index 8393caa16..0a3cd9e00 100644 --- a/crates/media/tests/it/decode.rs +++ b/crates/media/tests/it/decode.rs @@ -166,6 +166,52 @@ fn ogg_opus() { check_tone("opus", &fixture("tone.opus"), 48_000, 1); } +/// Ogg Opus timing saturates on a hostile granule position instead of failing, so the end-trim +/// arithmetic on its total must not overflow either: a three-channel file that ends with a page +/// carrying the largest granule still probes and decodes. +#[test] +fn ogg_opus_with_a_huge_granule_does_not_overflow() { + fn seal(mut page: Vec) -> Vec { + page[22..26].fill(0); + let crc = craft_media::ogg::crc32(&page); + page[22..26].copy_from_slice(&crc.to_le_bytes()); + page + } + let f = fixture("tone.opus"); + let mut pages: Vec> = Vec::new(); + let mut at = 0; + while at + 27 <= f.len() && &f[at..at + 4] == b"OggS" { + let nseg = f[at + 26] as usize; + let end = at + 27 + nseg + f[at + 27..at + 27 + nseg].iter().map(|&b| b as usize).sum::(); + pages.push(f[at..end].to_vec()); + at = end; + } + assert_eq!(at, f.len(), "the fixture is whole pages"); + // an OpusHead for three channels (mapping family 1), each playing the one mono stream + let head = pages[0][28..].to_vec(); + let mut first = pages[0][..26].to_vec(); + first.extend_from_slice(&[1, 24]); + first.extend_from_slice(&head[..9]); + first.push(3); + first.extend_from_slice(&head[10..18]); + first.extend_from_slice(&[1, 1, 0, 0, 0, 0]); + pages[0] = seal(first); + // the last page once more, as the next page of the stream, with the largest granule + let last = pages.len() - 1; + let mut extra = pages[last].clone(); + pages[last][5] &= !0x04; + pages[last] = seal(pages[last].clone()); + extra[6..14].copy_from_slice(&i64::MAX.to_le_bytes()); + let seq = u32::from_le_bytes([extra[18], extra[19], extra[20], extra[21]]) + 1; + extra[18..22].copy_from_slice(&seq.to_le_bytes()); + pages.push(seal(extra)); + let b: Bytes = Arc::new(pages.concat()); + assert!(probe(&b).is_ok_and(|i| i.duration_ms > 1 << 40), "the granule is taken at its word"); + let p = audio::decode(&b).expect("decode"); + assert_eq!(p.channels, 3); + assert!(!p.samples.is_empty()); +} + #[test] fn wma_is_probed_but_not_decoded() { let b = fixture("tone.wma"); diff --git a/crates/ogg/Cargo.toml b/crates/ogg/Cargo.toml deleted file mode 100644 index 9e3888319..000000000 --- a/crates/ogg/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "deckcraft-ogg" -description = "Clean-room Ogg (RFC 3533) demuxer with Ogg Opus (RFC 7845) and Ogg Vorbis stream mapping" -version.workspace = true -edition.workspace = true -license.workspace = true -rust-version.workspace = true - -[lints] -workspace = true - -[dependencies] diff --git a/crates/ogg/src/lib.rs b/crates/ogg/src/lib.rs deleted file mode 100644 index 0261ee0e6..000000000 --- a/crates/ogg/src/lib.rs +++ /dev/null @@ -1,581 +0,0 @@ -//! Clean-room Ogg demuxer, written from RFC 3533 (the Ogg encapsulation format), RFC 7845 (Ogg -//! Opus) and the Vorbis I specification's Ogg mapping (section A). -//! -//! [`open`] reads every page (capture pattern, header, lacing values, CRC-32 checked; damaged -//! regions are skipped by searching for the next `OggS`), groups them by logical bitstream and -//! assembles packets — including packets that continue across pages — as lists of byte ranges, -//! with the granule position of the page each packet completes on. Codec headers (Opus: -//! `OpusHead` + `OpusTags`; Vorbis: identification, comment, setup) are collected separately from -//! the audio packets. -//! -//! [`OpusTiming`] implements RFC 7845 §4: packet start positions from granule positions and -//! TOC-derived durations, the pre-skip, and end trimming of the final page, so a player can seek -//! to any 48 kHz sample. -//! -//! Layer L0: no dependencies beyond `std`, no `unsafe`, builds for `wasm32-unknown-unknown`. - -#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable))] - -use std::collections::HashMap; -use std::fmt; -use std::io; - -/// Random-access, read-only byte source (same shape as the other FilmCraft demuxers'). -pub trait ByteSource { - fn len(&self) -> u64; - fn read_at(&self, offset: u64, buf: &mut [u8]) -> io::Result<()>; - fn is_empty(&self) -> bool { - self.len() == 0 - } -} - -impl ByteSource for [u8] { - fn len(&self) -> u64 { - <[u8]>::len(self) as u64 - } - fn read_at(&self, offset: u64, buf: &mut [u8]) -> io::Result<()> { - let eof = || io::Error::new(io::ErrorKind::UnexpectedEof, "read past end"); - let a = usize::try_from(offset).map_err(|_| eof())?; - buf.copy_from_slice(self.get(a..a.checked_add(buf.len()).ok_or_else(eof)?).ok_or_else(eof)?); - Ok(()) - } -} - -impl ByteSource for Vec { - fn len(&self) -> u64 { - self.as_slice().len() as u64 - } - fn read_at(&self, offset: u64, buf: &mut [u8]) -> io::Result<()> { - self.as_slice().read_at(offset, buf) - } -} - -impl ByteSource for &T { - fn len(&self) -> u64 { - (**self).len() - } - fn read_at(&self, offset: u64, buf: &mut [u8]) -> io::Result<()> { - (**self).read_at(offset, buf) - } -} - -#[derive(Debug)] -pub enum Error { - Io(io::Error), - /// No Ogg page found. - NotOgg, - Invalid(String), -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Error::Io(e) => write!(f, "I/O error: {e}"), - Error::NotOgg => write!(f, "not an Ogg file"), - Error::Invalid(s) => write!(f, "invalid Ogg: {s}"), - } - } -} - -impl std::error::Error for Error {} - -impl From for Error { - fn from(e: io::Error) -> Self { - Error::Io(e) - } -} - -pub type Result = std::result::Result; - -/// The capture pattern at the start of a file. -pub fn sniff(head: &[u8]) -> bool { - head.len() >= 27 && &head[..4] == b"OggS" && head[4] == 0 -} - -/// CRC-32 of RFC 3533 §6: polynomial 0x04C11DB7, initial value 0, no reflection, no final XOR. -pub fn crc32(data: &[u8]) -> u32 { - static TABLE: std::sync::OnceLock<[u32; 256]> = std::sync::OnceLock::new(); - let t = TABLE.get_or_init(|| { - let mut t = [0u32; 256]; - for (i, e) in t.iter_mut().enumerate() { - let mut r = (i as u32) << 24; - for _ in 0..8 { - r = if r & 0x8000_0000 != 0 { (r << 1) ^ 0x04C1_1DB7 } else { r << 1 }; - } - *e = r; - } - t - }); - data.iter().fold(0u32, |c, &b| (c << 8) ^ t[((c >> 24) as u8 ^ b) as usize]) -} - -/// The codec of a logical bitstream (from its first packet). -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Codec { - Opus, - Vorbis, - Flac, - Theora, - Speex, - Unknown, -} - -impl Codec { - fn of(first: &[u8]) -> Codec { - if first.starts_with(b"OpusHead") { - Codec::Opus - } else if first.starts_with(b"\x01vorbis") { - Codec::Vorbis - } else if first.starts_with(b"\x7fFLAC") { - Codec::Flac - } else if first.starts_with(b"\x80theora") { - Codec::Theora - } else if first.starts_with(b"Speex ") { - Codec::Speex - } else { - Codec::Unknown - } - } - /// Header packets before the audio packets. - fn header_count(self) -> usize { - match self { - Codec::Opus => 2, - Codec::Vorbis | Codec::Theora => 3, - Codec::Speex => 2, - Codec::Flac | Codec::Unknown => 1, - } - } - pub fn name(self) -> &'static str { - match self { - Codec::Opus => "Opus", - Codec::Vorbis => "Vorbis", - Codec::Flac => "FLAC", - Codec::Theora => "Theora", - Codec::Speex => "Speex", - Codec::Unknown => "unknown", - } - } -} - -/// A packet: its byte ranges (a packet may span pages) and the granule position of the page it -/// completes on when it is that page's last completed packet. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Packet { - pub parts: Vec<(u64, u32)>, - pub size: u32, - /// Granule position of the completing page if this is the last packet completed on it. - pub granule: Option, - /// Index of the page (within the stream) the packet completes on. - pub page: u32, - /// The packet completes on the stream's end-of-stream page. - pub eos: bool, - /// The first bytes of the packet (TOC for Opus, packet type for Vorbis). - pub prefix: [u8; 2], -} - -/// A logical bitstream. -#[derive(Clone, Debug)] -pub struct Stream { - pub serial: u32, - pub codec: Codec, - /// Codec header packets (contents). - pub headers: Vec>, - /// Data packets after the headers. - pub packets: Vec, - pub pages: u32, - /// Granule position of the last page with one. - pub last_granule: Option, - pub saw_eos: bool, -} - -/// An opened Ogg file. -#[derive(Clone, Debug)] -pub struct OggFile { - pub streams: Vec, - /// Pages dropped for a bad CRC, lost sync, truncation… - pub warnings: Vec, -} - -/// Source bytes damage recovery may read per file: the windows the scan for the next capture -/// pattern fills and the pages read only to find their CRC wrong. -const RECOVERY_BUDGET: u64 = 32 << 20; -/// Warnings kept per file (not counting the one about the budget); the rest are counted in one last entry. -const MAX_WARNINGS: usize = 256; - -/// Record a warning, keeping at most `MAX_WARNINGS` and one entry saying the rest were dropped. -fn warn(warnings: &mut Vec, msg: String) { - match warnings.len().cmp(&MAX_WARNINGS) { - std::cmp::Ordering::Less => warnings.push(msg), - std::cmp::Ordering::Equal => warnings.push("further warnings omitted".into()), - std::cmp::Ordering::Greater => {} - } -} - -struct Partial { - parts: Vec<(u64, u32)>, - size: u32, - prefix: Vec, -} - -/// Read every page and assemble the packets of every logical bitstream. -pub fn open(src: &(impl ByteSource + ?Sized)) -> Result { - let len = src.len(); - let mut streams: Vec = Vec::new(); - let mut by_serial: HashMap = HashMap::new(); - let mut partial: HashMap = HashMap::new(); - let mut warnings = Vec::new(); - let mut pos = 0u64; - let mut found_any = false; - let mut hdr = [0u8; 27 + 255]; - let mut scanner = CaptureScanner::new(); - while pos + 27 <= len { - src.read_at(pos, &mut hdr[..27])?; - if &hdr[..4] != b"OggS" || hdr[4] != 0 { - match scanner.find(src, pos + 1, len)? { - Some(p) => { - if found_any { - warn(&mut warnings, format!("lost sync at byte {pos}; resumed at {p}")); - } - pos = p; - continue; - } - None => break, - } - } - let nseg = hdr[26] as usize; - if pos + 27 + nseg as u64 > len { - warn(&mut warnings, format!("truncated page header at byte {pos}")); - break; - } - src.read_at(pos + 27, &mut hdr[27..27 + nseg])?; - let body_len: u64 = hdr[27..27 + nseg].iter().map(|&b| b as u64).sum(); - let page_len = 27 + nseg as u64 + body_len; - if pos + page_len > len { - warn(&mut warnings, format!("truncated page at byte {pos}")); - break; - } - let mut page = vec![0u8; page_len as usize]; - src.read_at(pos, &mut page)?; - let stored_crc = u32::from_le_bytes([page[22], page[23], page[24], page[25]]); - page[22..26].fill(0); - if crc32(&page) != stored_crc { - warn(&mut warnings, format!("bad CRC on page at byte {pos}")); - // a page read only to find it damaged is recovery work, charged like the scan's windows - if scanner.charge(page_len) { - break; - } - pos += 1; - continue; - } - found_any = true; - let flags = page[5]; - let granule = i64::from_le_bytes(page[6..14].try_into().unwrap_or([0; 8])); - let serial = u32::from_le_bytes([page[14], page[15], page[16], page[17]]); - let lacing = page[27..27 + nseg].to_vec(); - let body_off = pos + 27 + nseg as u64; - let si = *by_serial.entry(serial).or_insert_with(|| { - streams.push(Stream { - serial, - codec: Codec::Unknown, - headers: Vec::new(), - packets: Vec::new(), - pages: 0, - last_granule: None, - saw_eos: false, - }); - streams.len() - 1 - }); - let st = &mut streams[si]; - let page_index = st.pages; - st.pages += 1; - if flags & 0x04 != 0 { - st.saw_eos = true; - } - // a continued page without a pending packet (lost a page): drop the fragment - let continued = flags & 0x01 != 0; - if !continued && partial.remove(&serial).is_some() { - warn(&mut warnings, format!("incomplete packet before page at byte {pos}")); - } - let mut completed: Vec = Vec::new(); - let mut off = 0u64; - let mut seg_start = 0u64; - let mut skip_fragment = continued && !partial.contains_key(&serial); - for (k, &l) in lacing.iter().enumerate() { - off += l as u64; - if l < 255 || k + 1 == nseg { - let (a, n) = (seg_start, off - seg_start); - seg_start = off; - if skip_fragment { - if l < 255 { - skip_fragment = false; - } - continue; - } - let p = partial.entry(serial).or_insert_with(|| Partial { parts: Vec::new(), size: 0, prefix: Vec::new() }); - if n > 0 { - p.parts.push((body_off + a, n as u32)); - p.size += n as u32; - let s = (body_off - pos + a) as usize; - let need = 2usize.saturating_sub(p.prefix.len()).min(n as usize); - p.prefix.extend_from_slice(&page[s..s + need]); - } - if l < 255 - && let Some(p) = partial.remove(&serial) - { - let mut prefix = [0u8; 2]; - prefix[..p.prefix.len().min(2)].copy_from_slice(&p.prefix[..p.prefix.len().min(2)]); - completed.push(Packet { parts: p.parts, size: p.size, granule: None, page: page_index, eos: flags & 0x04 != 0, prefix }); - } - } - } - if granule != -1 { - st.last_granule = Some(granule); - if let Some(last) = completed.last_mut() { - last.granule = Some(granule); - } - } - for pk in completed { - if st.headers.is_empty() && st.packets.is_empty() { - let data = read_parts(src, &pk.parts)?; - st.codec = Codec::of(&data); - } - if st.headers.len() < st.codec.header_count() && st.packets.is_empty() { - st.headers.push(read_parts(src, &pk.parts)?); - } else { - st.packets.push(pk); - } - } - pos += page_len; - } - if !found_any { - return Err(Error::NotOgg); - } - if !partial.is_empty() { - warn(&mut warnings, "file ends inside a packet".into()); - } - if scanner.spent() { - // once per file and last, so it is not subject to the cap on the others - warnings.push("recovery budget spent; the rest of the damaged data was not searched".into()); - } - Ok(OggFile { streams, warnings }) -} - -struct CaptureScanner { - buf: Vec, - buf_start: u64, - budget: u64, -} - -impl CaptureScanner { - fn new() -> Self { - Self { buf: Vec::new(), buf_start: 0, budget: RECOVERY_BUDGET } - } - - /// Charge `bytes` of recovery work; true once the budget is spent. - fn charge(&mut self, bytes: u64) -> bool { - self.budget = self.budget.saturating_sub(bytes); - self.spent() - } - - /// Nothing left to search with: fewer bytes than a capture pattern can still be charged for. - fn spent(&self) -> bool { - self.budget < 4 - } - - fn find(&mut self, src: &(impl ByteSource + ?Sized), from: u64, len: u64) -> Result> { - let mut at = from; - while at + 4 <= len { - let buf_end = self.buf_start + self.buf.len() as u64; - if at >= self.buf_start && at + 4 <= buf_end { - let off = (at - self.buf_start) as usize; - if let Some(i) = self.buf[off..].windows(4).position(|w| w == b"OggS") { - return Ok(Some(at + i as u64)); - } - at = buf_end.saturating_sub(3); - continue; - } - if self.spent() { - return Ok(None); - } - let n = ((len - at) as usize).min(64 * 1024).min(usize::try_from(self.budget).unwrap_or(usize::MAX)); - if n < 4 { - break; - } - self.buf.resize(n, 0); - src.read_at(at, &mut self.buf)?; - self.buf_start = at; - self.budget = self.budget.saturating_sub(n as u64); - if let Some(i) = self.buf.windows(4).position(|w| w == b"OggS") { - return Ok(Some(at + i as u64)); - } - at += (n - 3) as u64; - } - Ok(None) - } -} - -fn read_parts(src: &(impl ByteSource + ?Sized), parts: &[(u64, u32)]) -> Result> { - // Ranges come from a scan of some source; check them against this one before sizing a buffer. - let len = src.len(); - if parts.iter().any(|&(o, n)| o.checked_add(u64::from(n)).is_none_or(|end| end > len)) { - return Err(Error::Invalid("packet range lies outside the source".into())); - } - let mut v = Vec::with_capacity(parts.iter().map(|p| p.1 as usize).sum()); - for &(o, n) in parts { - let a = v.len(); - v.resize(a + n as usize, 0); - src.read_at(o, &mut v[a..])?; - } - Ok(v) -} - -impl OggFile { - /// The first stream of `codec`. - pub fn stream_of(&self, codec: Codec) -> Option { - self.streams.iter().position(|s| s.codec == codec) - } - /// The bytes of packet `i` of stream `s`. - pub fn read_packet(&self, src: &(impl ByteSource + ?Sized), s: usize, i: usize) -> Result> { - let p = self.streams.get(s).and_then(|st| st.packets.get(i)).ok_or_else(|| Error::Invalid(format!("no packet {i} in stream {s}")))?; - read_parts(src, &p.parts) - } -} - -/// Duration of an Opus packet in 48 kHz samples from its TOC (RFC 6716 §3.1); `None` if invalid. -pub fn opus_packet_samples(prefix: [u8; 2], size: u32) -> Option { - if size == 0 { - return None; - } - let toc = prefix[0]; - let config = toc >> 3; - let frame = match config { - 0..=11 => [480, 960, 1920, 2880][(config & 3) as usize], - 12..=15 => [480, 960][(config & 1) as usize], - _ => [120, 240, 480, 960][(config & 3) as usize], - }; - let frames = match toc & 3 { - 0 => 1, - 1 | 2 => 2, - _ => { - if size < 2 { - return None; - } - (prefix[1] & 0x3F) as u32 - } - }; - let n = frame * frames; - (n > 0 && n <= 5760).then_some(n) -} - -/// RFC 7845 §4 timing of an Opus stream. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct OpusTiming { - pub pre_skip: u32, - /// Start of each packet's decoded output in output samples (48 kHz): 0 is the first sample - /// after the pre-skip; the pre-skip samples have negative positions. - pub starts: Vec, - /// Decoded samples of each packet. - pub durations: Vec, - /// Playable samples (end trimming applied). - pub total: i64, -} - -fn timing_add(a: i64, b: i64) -> Result { - a.checked_add(b).ok_or_else(|| Error::Invalid("opus timing overflow".into())) -} - -fn timing_sub(a: i64, b: i64) -> Result { - a.checked_sub(b).ok_or_else(|| Error::Invalid("opus timing overflow".into())) -} - -impl OpusTiming { - /// Timing of the Opus stream `s` (`pre_skip` from its `OpusHead`). - /// - /// A granule or a run of packet durations that does not fit in `i64` is an error. Ordinary - /// files, including a truncated tail and end trimming, are unchanged. - pub fn of(stream: &Stream, pre_skip: u32) -> Result { - let durations: Vec = stream.packets.iter().map(|p| opus_packet_samples(p.prefix, p.size).unwrap_or(0)).collect(); - let n = durations.len(); - let mut ends: Vec> = vec![None; n]; - let mut i = 0; - let mut cursor: Option = None; - let mut trimmed_end: Option = None; - while i < n { - // packets completing on the same page: i..j, the last carries the page granule - let mut j = i; - while j < n && stream.packets[j].granule.is_none() { - j += 1; - } - if j == n { - // trailing packets without a granule (truncated file): continue from the cursor - let mut c = cursor.unwrap_or(0); - for k in i..n { - c = timing_add(c, i64::from(durations[k]))?; - ends[k] = Some(c); - } - cursor = Some(c); - break; - } - let Some(g) = stream.packets[j].granule else { break }; - let sum = durations[i..=j].iter().try_fold(0i64, |a, &d| timing_add(a, i64::from(d)))?; - let forward = match cursor { - Some(c) => Some(timing_add(c, sum)?), - None => None, - }; - let end = match forward { - // end trimming on the last page (RFC 7845 §4.5): keep the forward positions - Some(f) if stream.packets[j].eos && g < f => { - trimmed_end = Some(g); - f - } - Some(f) if f == g => f, - // first page, or a discontinuity: trust the granule - _ => g, - }; - let mut e = end; - for k in (i..=j).rev() { - ends[k] = Some(e); - e = timing_sub(e, i64::from(durations[k]))?; - } - cursor = Some(end); - i = j + 1; - } - let mut raw_starts = Vec::with_capacity(n); - for (e, &d) in ends.iter().zip(&durations) { - raw_starts.push(timing_sub(e.unwrap_or(0), i64::from(d))?); - } - let base = raw_starts.first().copied().unwrap_or(0).max(0); - let shift = timing_add(base, i64::from(pre_skip))?; - let mut starts = Vec::with_capacity(n); - for s in &raw_starts { - starts.push(timing_sub(*s, shift)?); - } - let end_all = trimmed_end.or(cursor).unwrap_or(0); - let total = timing_sub(end_all, shift)?.max(0); - Ok(OpusTiming { pre_skip, starts, durations, total }) - } -} - -/// Vorbis identification header fields (Vorbis I §4.2.2). -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct VorbisInfo { - pub channels: u8, - pub sample_rate: u32, - pub bitrate_nominal: i32, -} - -impl VorbisInfo { - pub fn parse(h: &[u8]) -> Option { - if h.len() < 30 || !h.starts_with(b"\x01vorbis") { - return None; - } - Some(VorbisInfo { - channels: h[11], - sample_rate: u32::from_le_bytes([h[12], h[13], h[14], h[15]]), - bitrate_nominal: i32::from_le_bytes([h[20], h[21], h[22], h[23]]), - }) - } -} - -#[cfg(test)] -mod tests; diff --git a/crates/ogg/src/tests.rs b/crates/ogg/src/tests.rs deleted file mode 100644 index 728e618bd..000000000 --- a/crates/ogg/src/tests.rs +++ /dev/null @@ -1,307 +0,0 @@ -//! Synthetic Ogg streams: a small page writer (RFC 3533) and Opus-like packets. - -use std::cell::Cell; -use std::io; - -use super::{ByteSource, CaptureScanner, Codec, Error, OpusTiming, Packet, Stream, crc32, open, opus_packet_samples, sniff}; - -/// Write one page holding `segments` (lacing already computed by `lace`). -fn page(out: &mut Vec, flags: u8, granule: i64, serial: u32, seq: u32, lacing: &[u8], body: &[u8]) { - let start = out.len(); - out.extend_from_slice(b"OggS"); - out.push(0); - out.push(flags); - out.extend_from_slice(&granule.to_le_bytes()); - out.extend_from_slice(&serial.to_le_bytes()); - out.extend_from_slice(&seq.to_le_bytes()); - out.extend_from_slice(&[0; 4]); - out.push(lacing.len() as u8); - out.extend_from_slice(lacing); - out.extend_from_slice(body); - let crc = crc32(&out[start..]); - out[start + 22..start + 26].copy_from_slice(&crc.to_le_bytes()); -} - -fn lace(packets: &[&[u8]], last_continues: bool) -> (Vec, Vec) { - let mut l = Vec::new(); - let mut b = Vec::new(); - for (i, p) in packets.iter().enumerate() { - let mut n = p.len(); - while n >= 255 { - l.push(255); - n -= 255; - } - if !(last_continues && i + 1 == packets.len()) { - l.push(n as u8); - } - b.extend_from_slice(p); - } - (l, b) -} - -fn opus_head(pre_skip: u16) -> Vec { - let mut h = b"OpusHead".to_vec(); - h.push(1); - h.push(2); - h.extend_from_slice(&pre_skip.to_le_bytes()); - h.extend_from_slice(&48_000u32.to_le_bytes()); - h.extend_from_slice(&0i16.to_le_bytes()); - h.push(0); - h -} - -/// A CELT 20 ms packet (960 samples): TOC config 31, code 0. -fn pkt(i: usize, len: usize) -> Vec { - let mut p = vec![0xF8]; - p.extend((0..len.saturating_sub(1)).map(|k| (i * 7 + k) as u8)); - p -} - -/// Header pages, then pages of 3 packets each; the last page's granule trims `trim` samples. -fn opus_file(npk: usize, pre_skip: u16, trim: i64) -> Vec { - let mut f = Vec::new(); - let head = opus_head(pre_skip); - let (l, b) = lace(&[&head], false); - page(&mut f, 0x02, 0, 9, 0, &l, &b); - let tags = b"OpusTags\x00\x00\x00\x00\x00\x00\x00\x00".to_vec(); - let (l, b) = lace(&[&tags], false); - page(&mut f, 0, 0, 9, 1, &l, &b); - let pkts: Vec> = (0..npk).map(|i| pkt(i, 40 + i % 5)).collect(); - let mut done = 0; - for (seq, chunk) in (2..).zip(pkts.chunks(3)) { - done += chunk.len(); - let last = done == npk; - let mut g = done as i64 * 960; - if last { - g -= trim; - } - let refs: Vec<&[u8]> = chunk.iter().map(Vec::as_slice).collect(); - let (l, b) = lace(&refs, false); - page(&mut f, if last { 0x04 } else { 0 }, g, 9, seq, &l, &b); - } - f -} - -#[test] -fn crc_matches_known_value() { - // CRC-32/CKSUM parameters without its final XOR (check value 0x765E7680 ^ 0xFFFFFFFF) - assert_eq!(crc32(b"123456789"), 0x89A1_897F); - assert_eq!(crc32(b""), 0); - assert_ne!(crc32(b"OggS"), crc32(b"OggT")); -} - -#[test] -fn opus_pages_packets_and_timing() { - let f = opus_file(10, 312, 500); - assert!(sniff(&f)); - let o = open(&f).unwrap(); - assert!(o.warnings.is_empty(), "{:?}", o.warnings); - let s = o.stream_of(Codec::Opus).unwrap(); - let st = &o.streams[s]; - assert_eq!(st.headers.len(), 2); - assert_eq!(st.packets.len(), 10); - assert!(st.saw_eos); - assert_eq!(o.read_packet(&f, s, 4).unwrap(), pkt(4, 44)); - let t = OpusTiming::of(st, 312).unwrap(); - assert_eq!(t.durations, vec![960; 10]); - assert_eq!(t.starts[0], -312); - assert_eq!(t.starts[9], 9 * 960 - 312); - // end trimming: the last page granule is 500 short - assert_eq!(t.total, 10 * 960 - 500 - 312); -} - -#[test] -fn packets_spanning_pages() { - let mut f = Vec::new(); - let head = opus_head(0); - let (l, b) = lace(&[&head], false); - page(&mut f, 0x02, 0, 1, 0, &l, &b); - let (l, b) = lace(&[b"OpusTags"], false); - page(&mut f, 0, 0, 1, 1, &l, &b); - let big = pkt(0, 700); - // first 510 bytes on one page (two 255 lacing values, packet continues), rest on the next - let (l, b) = (vec![255, 255], big[..510].to_vec()); - page(&mut f, 0, -1, 1, 2, &l, &b); - let rest = &big[510..]; - let small = pkt(1, 30); - let (l, b) = lace(&[rest, &small], false); - page(&mut f, 0x01 | 0x04, 1920, 1, 3, &l, &b); - let o = open(&f).unwrap(); - let st = &o.streams[0]; - assert_eq!(st.packets.len(), 2); - assert_eq!(st.packets[0].parts.len(), 2); - assert_eq!(o.read_packet(&f, 0, 0).unwrap(), big); - assert_eq!(o.read_packet(&f, 0, 1).unwrap(), small); - let t = OpusTiming::of(st, 0).unwrap(); - assert_eq!(t.starts, vec![0, 960]); - assert_eq!(t.total, 1920); -} - -#[test] -fn corrupt_pages_are_skipped_and_truncation_is_safe() { - let f = opus_file(12, 312, 0); - // flip a byte inside the third data page's body: that page fails its CRC and is dropped - let mut g = f.clone(); - let third = g.windows(4).enumerate().filter(|(_, w)| *w == b"OggS").map(|(i, _)| i).nth(4).unwrap(); - g[third + 40] ^= 0xFF; - let o = open(&g).unwrap(); - assert!(o.warnings.iter().any(|w| w.contains("CRC")), "{:?}", o.warnings); - assert_eq!(o.streams[0].packets.len(), 9); - let t = OpusTiming::of(&o.streams[0], 312).unwrap(); - // the granules after the gap put the later packets at their true positions - assert_eq!(*t.starts.last().unwrap(), 11 * 960 - 312); - for cut in 0..f.len() { - if let Ok(o) = open(&f[..cut].to_vec()) { - for st in &o.streams { - let t = OpusTiming::of(st, 312).unwrap(); - assert_eq!(t.starts.len(), st.packets.len()); - } - } - } - let mut seed = 99u64; - for _ in 0..300 { - let mut g = f.clone(); - seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407); - let at = (seed >> 33) as usize % g.len(); - g[at] = (seed >> 20) as u8; - if let Ok(o) = open(&g) { - for st in &o.streams { - let _ = OpusTiming::of(st, 312); - } - } - } - assert!(matches!(open(&b"not ogg at all, no capture pattern".to_vec()), Err(Error::NotOgg))); -} - -#[test] -fn opus_toc_durations() { - assert_eq!(opus_packet_samples([0x00, 0], 10), Some(480)); // SILK NB 10 ms - assert_eq!(opus_packet_samples([0x18, 0], 10), Some(2880)); // SILK NB 60 ms - assert_eq!(opus_packet_samples([0x78 | 1, 0], 10), Some(1920)); // hybrid 20 ms × 2 - assert_eq!(opus_packet_samples([0x80 | 3, 6], 10), Some(720)); // CELT 2.5 ms × 6 - assert_eq!(opus_packet_samples([0xF8 | 3, 7], 10), None); // 140 ms: invalid - assert_eq!(opus_packet_samples([0xF8, 0], 0), None); -} - -#[test] -fn opus_timing_includes_packets_after_the_last_known_granule() { - let f = opus_file(6, 312, 0); - let o = open(&f).unwrap(); - let mut stream = o.streams[0].clone(); - // Missing granules on complete-packet pages are non-conforming. The existing recovery - // fallback still assigns their packet positions; total must include the same samples. - stream.packets.last_mut().unwrap().granule = None; - let timing = OpusTiming::of(&stream, 312).unwrap(); - assert_eq!(timing.starts, vec![-312, 648, 1608, 2568, 3528, 4488]); - assert_eq!(timing.total, 6 * 960 - 312); - - // With no usable granule, every complete packet still contributes its TOC duration. - for packet in &mut stream.packets { - packet.granule = None; - } - let timing = OpusTiming::of(&stream, 312).unwrap(); - assert_eq!(timing.starts, vec![-312, 648, 1608, 2568, 3528, 4488]); - assert_eq!(timing.total, 6 * 960 - 312); -} - -#[test] -fn opus_timing_rejects_a_granule_that_overflows() { - let packet = |granule: i64| Stream { - serial: 1, - codec: Codec::Opus, - headers: vec![], - packets: vec![Packet { parts: vec![], size: 10, granule: Some(granule), page: 0, eos: true, prefix: [0x00, 0] }], - pages: 1, - last_granule: Some(granule), - saw_eos: true, - }; - let ok = OpusTiming::of(&packet(480), 0).unwrap(); - assert_eq!(ok.starts, vec![0]); - assert_eq!(ok.total, 480); - assert!(OpusTiming::of(&packet(i64::MIN), 0).is_err()); -} - -/// A source that counts the bytes read from it. -struct Counting<'a> { - data: &'a [u8], - read: Cell, -} - -impl ByteSource for Counting<'_> { - fn len(&self) -> u64 { - self.data.len() as u64 - } - fn read_at(&self, offset: u64, buf: &mut [u8]) -> io::Result<()> { - self.read.set(self.read.get() + buf.len() as u64); - self.data.read_at(offset, buf) - } -} - -/// A valid file followed by `len` bytes of 0xFF with a capture pattern every 64 bytes. Each one -/// claims 255 full lacing values, so it reads as a page of about 64 KiB that fails its CRC. -fn dense_fake_pages(len: usize) -> Vec { - let mut f = opus_file(6, 312, 0); - let start = f.len(); - f.resize(start + len, 0xFF); - for at in (start..start + len - 64).step_by(64) { - f[at..at + 5].copy_from_slice(b"OggS\0"); - f[at + 26] = 255; - } - f -} - -#[test] -fn recovery_reads_a_bounded_amount_of_a_damaged_file() { - let f = dense_fake_pages(1 << 20); - let src = Counting { data: &f, read: Cell::new(0) }; - let o = open(&src).unwrap(); - assert_eq!(o.streams[0].packets.len(), 6, "the good pages are kept"); - let read = src.read.get(); - assert!(read <= 40 << 20, "read {read} bytes of a {} byte file", f.len()); - assert!(o.warnings.iter().any(|w| w.contains("recovery budget")), "{:?}", o.warnings); -} - -#[test] -fn warnings_are_capped() { - // 100,000 pages with no segments and a wrong CRC, 27 bytes each - let mut f = opus_file(6, 312, 0); - for _ in 0..100_000 { - let at = f.len(); - f.extend_from_slice(b"OggS\0\0"); - f.resize(at + 27, 0); - f[at + 22] = 1; - } - let o = open(&f).unwrap(); - assert!(o.warnings.len() <= 257, "{} warnings", o.warnings.len()); - assert!(o.warnings.iter().any(|w| w.contains("omitted")), "{:?}", o.warnings.last()); -} - -#[test] -fn a_file_of_good_pages_is_not_charged_for_recovery() { - let f = opus_file(2000, 312, 0); - let src = Counting { data: &f, read: Cell::new(0) }; - let o = open(&src).unwrap(); - assert_eq!(o.streams[0].packets.len(), 2000); - assert!(o.warnings.is_empty(), "{:?}", o.warnings); -} - -#[test] -fn a_budget_too_small_to_search_is_spent() { - // 1 to 3 bytes left cannot hold a capture pattern: the scan would stop without saying why - let mut scanner = CaptureScanner::new(); - assert!(!scanner.spent()); - assert!(scanner.charge(super::RECOVERY_BUDGET - 3)); - assert!(scanner.spent()); - let f = opus_file(6, 312, 0); - assert_eq!(scanner.find(&f, 0, f.len() as u64).unwrap(), None); -} - -#[test] -fn read_packet_checks_its_ranges_against_the_source_before_allocating() { - let f = opus_file(10, 312, 500); - let o = open(&f).unwrap(); - let s = o.stream_of(Codec::Opus).unwrap(); - // The last packet lies past the end of a source cut short: an error, not a read of missing bytes. - let short = &f[..f.len() / 2]; - assert!(matches!(o.read_packet(&short, s, 9), Err(Error::Invalid(_)))); -} diff --git a/crates/opus/Cargo.toml b/crates/opus/Cargo.toml deleted file mode 100644 index 257cd81dc..000000000 --- a/crates/opus/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "deckcraft-opus" -description = "Clean-room pure-Rust Opus decoder (RFC 6716 / RFC 8251; OpusHead, multistream surround)" -version.workspace = true -edition.workspace = true -license.workspace = true -rust-version.workspace = true - -[lints] -workspace = true - -[dependencies] diff --git a/crates/opus/src/celt.rs b/crates/opus/src/celt.rs deleted file mode 100644 index fdd3f6940..000000000 --- a/crates/opus/src/celt.rs +++ /dev/null @@ -1,678 +0,0 @@ -//! CELT layer decoder (RFC 6716 §4.3). - -pub(crate) mod bands; -pub(crate) mod energy; -pub(crate) mod mdct; -pub(crate) mod rate; -pub(crate) mod tables; - -use std::sync::OnceLock; - -use bands::{anti_collapse, denormalise_bands, quant_all_bands, renormalise_vector}; -use energy::{unquant_coarse_energy, unquant_energy_finalise, unquant_fine_energy}; -use mdct::ImdctScratch; -use rate::{Mode, compute_allocation}; -use tables::{COMB_GAINS, EBANDS, MAX_LM, NB_EBANDS, OVERLAP, PREEMPH, SHORT_MDCT, SPREAD_ICDF, TAPSET_ICDF, TF_SELECT_TABLE, TRIM_ICDF}; - -use crate::range::{BITRES, RangeDecoder}; - -const DECODE_BUFFER_SIZE: usize = 2048; -const COMBFILTER_MINPERIOD: i32 = 15; -const PLC_PITCH_LAG_MAX: usize = 720; -const PLC_PITCH_LAG_MIN: usize = 100; - -pub(crate) fn mode() -> &'static Mode { - static MODE: OnceLock = OnceLock::new(); - MODE.get_or_init(Mode::new) -} - -/// CELT decoder state for one (mono or stereo) stream. -pub struct CeltDecoder { - m: &'static Mode, - /// Output channels. - channels: usize, - /// Channels coded in the stream. - pub stream_channels: usize, - downsample: usize, - pub start: usize, - pub end: usize, - pub disable_inv: bool, - pub rng: u32, - loss_count: u32, - skip_plc: bool, - postfilter_period: i32, - postfilter_period_old: i32, - postfilter_gain: f32, - postfilter_gain_old: f32, - postfilter_tapset: usize, - postfilter_tapset_old: usize, - preemph_mem: [f32; 2], - decode_mem: Vec, - old_band_e: [f32; 2 * NB_EBANDS], - old_log_e: [f32; 2 * NB_EBANDS], - old_log_e2: [f32; 2 * NB_EBANDS], - background_log_e: [f32; 2 * NB_EBANDS], - // scratch - x: Vec, - freq: Vec, - imdct: ImdctScratch, - pcm_scratch: Vec, -} - -/// Detects the silence flag, advancing the decoder to the end of the frame when set. Returns the flag and the new bit position. -fn read_silence(dec: &mut RangeDecoder, total_bits_raw: i32) -> (bool, i32) { - let tell = dec.tell(); - let silence = tell >= total_bits_raw || (tell == 1 && dec.bit_logp(15)); - if silence { - // Pretend we've read all the remaining bits. - dec.skip_to_end(total_bits_raw); - return (true, total_bits_raw); - } - (false, tell) -} - -struct PostfilterParams { - gain: f32, - pitch: i32, - tapset: usize, - tell: i32, -} - -/// Reads the optional post-filter header (pitch, gain, tapset) at the start of a frame. -fn read_postfilter_params(dec: &mut RangeDecoder, start: usize, tell: i32, total_bits_raw: i32) -> PostfilterParams { - let mut p = PostfilterParams { gain: 0.0, pitch: 0, tapset: 0, tell }; - if start == 0 && tell + 16 <= total_bits_raw { - if dec.bit_logp(1) { - let octave = dec.uint(6); - p.pitch = ((16 << octave) + dec.bits(4 + octave)) as i32 - 1; - let qg = dec.bits(3); - if dec.tell() + 2 <= total_bits_raw { - p.tapset = dec.icdf(&TAPSET_ICDF, 2); - } - p.gain = 0.09375 * (qg + 1) as f32; - } - p.tell = dec.tell(); - } - p -} - -/// Decodes per-band dynamic allocation boosts. Returns the offsets, the fractional bit position and the remaining bit budget. -fn decode_dynalloc( - dec: &mut RangeDecoder, - start: usize, - end: usize, - c: usize, - lm: usize, - cap: &[i32], - total_bits_raw: i32, -) -> ([i32; NB_EBANDS], i32, i32) { - let mut offsets = [0i32; NB_EBANDS]; - let mut dynalloc_logp = 6; - let mut total_bits = total_bits_raw << BITRES; - let mut tellf = dec.tell_frac() as i32; - for i in start..end { - let width = (c as i32 * (EBANDS[i + 1] - EBANDS[i])) << lm; - let quanta = (width << BITRES).min((6 << BITRES).max(width)); - let mut loop_logp = dynalloc_logp; - let mut boost = 0; - while tellf + (loop_logp << BITRES) < total_bits && boost < cap[i] { - let flag = dec.bit_logp(loop_logp as u32); - tellf = dec.tell_frac() as i32; - if !flag { - break; - } - boost += quanta; - total_bits -= quanta; - loop_logp = 1; - } - offsets[i] = boost; - if boost > 0 { - dynalloc_logp = 2.max(dynalloc_logp - 1); - } - } - (offsets, tellf, total_bits) -} - -impl CeltDecoder { - /// `sample_rate` is the output rate (48000, 24000, 16000, 12000 or 8000). - pub fn new(sample_rate: u32, channels: usize) -> CeltDecoder { - let downsample = (48000 / sample_rate) as usize; - let mut d = CeltDecoder { - m: mode(), - channels, - stream_channels: channels, - downsample, - start: 0, - end: NB_EBANDS, - disable_inv: channels == 1, - rng: 0, - loss_count: 0, - skip_plc: true, - postfilter_period: 0, - postfilter_period_old: 0, - postfilter_gain: 0.0, - postfilter_gain_old: 0.0, - postfilter_tapset: 0, - postfilter_tapset_old: 0, - preemph_mem: [0.0; 2], - decode_mem: vec![0.0; channels * (DECODE_BUFFER_SIZE + OVERLAP)], - old_band_e: [0.0; 2 * NB_EBANDS], - old_log_e: [0.0; 2 * NB_EBANDS], - old_log_e2: [0.0; 2 * NB_EBANDS], - background_log_e: [0.0; 2 * NB_EBANDS], - x: Vec::new(), - freq: Vec::new(), - imdct: ImdctScratch::default(), - pcm_scratch: Vec::new(), - }; - d.reset(); - d - } - - /// `OPUS_RESET_STATE`. - pub fn reset(&mut self) { - self.rng = 0; - self.loss_count = 0; - self.skip_plc = true; - self.postfilter_period = 0; - self.postfilter_period_old = 0; - self.postfilter_gain = 0.0; - self.postfilter_gain_old = 0.0; - self.postfilter_tapset = 0; - self.postfilter_tapset_old = 0; - self.preemph_mem = [0.0; 2]; - self.decode_mem.fill(0.0); - self.old_band_e = [0.0; 2 * NB_EBANDS]; - self.old_log_e = [-28.0; 2 * NB_EBANDS]; - self.old_log_e2 = [-28.0; 2 * NB_EBANDS]; - self.background_log_e = [0.0; 2 * NB_EBANDS]; - } - - fn mem_len(&self) -> usize { - DECODE_BUFFER_SIZE + OVERLAP - } - - /// Decodes one CELT frame of `frame_size` samples (at the output rate) into interleaved `pcm` - /// (`channels` wide). With `accum`, the output is added to `pcm`. `data = None` (or fewer than - /// two bytes) runs the packet-loss concealment. - pub fn decode( - &mut self, - data: Option<&[u8]>, - frame_size: usize, - pcm: &mut [f32], - accum: bool, - dec: Option<&mut RangeDecoder>, - ) -> Result { - let n_full = frame_size * self.downsample; - let lm = match n_full { - 120 => 0, - 240 => 1, - 480 => 2, - 960 => 3, - _ => return Err(crate::Error::InvalidArgument("CELT frame size")), - }; - let data = match data { - Some(d) if d.len() > 1 => d, - _ => { - self.decode_lost(n_full, lm); - self.deemphasis(n_full, pcm, accum); - return Ok(frame_size); - } - }; - if data.len() > 1275 { - return Err(crate::Error::InvalidPacket("CELT frame too long")); - } - match dec { - Some(d) => self.decode_frame(d, data.len(), n_full, lm), - None => { - let mut own = RangeDecoder::new(data); - self.decode_frame(&mut own, data.len(), n_full, lm) - } - } - self.deemphasis(n_full, pcm, accum); - Ok(frame_size) - } - - fn decode_frame(&mut self, dec: &mut RangeDecoder, len: usize, n: usize, lm: usize) { - let cc = self.channels; - let c = self.stream_channels; - let m = self.m; - let mm = 1usize << lm; - let start = self.start; - let end = self.end; - let eff_end = end.min(NB_EBANDS); - if self.loss_count == 0 { - self.skip_plc = false; - } - if c == 1 { - for i in 0..NB_EBANDS { - self.old_band_e[i] = self.old_band_e[i].max(self.old_band_e[NB_EBANDS + i]); - } - } - let total_bits_raw = len as i32 * 8; - let (silence, mut tell) = read_silence(dec, total_bits_raw); - let pf = read_postfilter_params(dec, start, tell, total_bits_raw); - tell = pf.tell; - let (postfilter_gain, postfilter_pitch, postfilter_tapset) = (pf.gain, pf.pitch, pf.tapset); - let is_transient = if lm > 0 && tell + 3 <= total_bits_raw { - let t = dec.bit_logp(3); - tell = dec.tell(); - t - } else { - false - }; - let short_blocks = is_transient; - let intra = if tell + 3 <= total_bits_raw { dec.bit_logp(3) } else { false }; - unquant_coarse_energy(start, end, &mut self.old_band_e, intra, dec, c, lm); - let mut tf_res = [0i32; NB_EBANDS]; - tf_decode(start, end, is_transient, &mut tf_res, lm, dec); - tell = dec.tell(); - let mut spread = 2usize; - if tell + 4 <= total_bits_raw { - spread = dec.icdf(&SPREAD_ICDF, 5); - } - let cap = m.init_caps(lm, c); - let (offsets, tellf, total_bits) = decode_dynalloc(dec, start, end, c, lm, &cap, total_bits_raw); - let alloc_trim = if tellf + (6 << BITRES) <= total_bits { dec.icdf(&TRIM_ICDF, 7) as i32 } else { 5 }; - let mut bits = ((len as i32 * 8) << BITRES) - dec.tell_frac() as i32 - 1; - let anti_collapse_rsv = if is_transient && lm >= 2 && bits >= ((lm as i32 + 2) << BITRES) { 1 << BITRES } else { 0 }; - bits -= anti_collapse_rsv; - let alloc = compute_allocation(m, start, end, &offsets, &cap, alloc_trim, bits, c, lm, dec); - unquant_fine_energy(start, end, &mut self.old_band_e, &alloc.fine_quant, dec, c); - - // Shift the decode memory by one frame. - let ml = self.mem_len(); - for ch in 0..cc { - let mem = &mut self.decode_mem[ch * ml..(ch + 1) * ml]; - mem.copy_within(n..DECODE_BUFFER_SIZE + OVERLAP / 2, 0); - } - - let mut collapse_masks = vec![0u32; c * NB_EBANDS]; - let mut x = std::mem::take(&mut self.x); - x.clear(); - x.resize(c * n, 0.0); - { - let (xa, xb) = x.split_at_mut(n); - let yb = if c == 2 { Some(xb) } else { None }; - let mut seed = self.rng; - quant_all_bands( - m, - start, - end, - xa, - yb, - &mut collapse_masks, - &alloc.pulses, - short_blocks, - spread, - alloc.dual_stereo, - alloc.intensity, - &tf_res, - ((len as i32) * (8 << BITRES)) - anti_collapse_rsv, - alloc.balance, - dec, - lm, - alloc.coded_bands, - &mut seed, - self.disable_inv, - ); - self.rng = seed; - } - let mut anti_collapse_on = false; - if anti_collapse_rsv > 0 { - anti_collapse_on = dec.bits(1) != 0; - } - let bits_left = len as i32 * 8 - dec.tell(); - unquant_energy_finalise(start, end, &mut self.old_band_e, &alloc.fine_quant, &alloc.fine_priority, bits_left, dec, c); - if anti_collapse_on { - anti_collapse( - &mut x, - &collapse_masks, - lm, - c, - n, - start, - end, - &self.old_band_e, - &self.old_log_e, - &self.old_log_e2, - &alloc.pulses, - self.rng, - ); - } - if silence { - for v in self.old_band_e[..c * NB_EBANDS].iter_mut() { - *v = -28.0; - } - } - self.synthesis(&x, n, start, eff_end, c, is_transient, lm, silence); - self.x = x; - - // Post-filter. - for ch in 0..cc { - self.postfilter_period = self.postfilter_period.max(COMBFILTER_MINPERIOD); - self.postfilter_period_old = self.postfilter_period_old.max(COMBFILTER_MINPERIOD); - let mem = &mut self.decode_mem[ch * ml..(ch + 1) * ml]; - let off = DECODE_BUFFER_SIZE - n; - comb_filter( - mem, - off, - self.postfilter_period_old, - self.postfilter_period, - SHORT_MDCT, - self.postfilter_gain_old, - self.postfilter_gain, - self.postfilter_tapset_old, - self.postfilter_tapset, - &m.window, - ); - if lm != 0 { - comb_filter( - mem, - off + SHORT_MDCT, - self.postfilter_period, - postfilter_pitch, - n - SHORT_MDCT, - self.postfilter_gain, - postfilter_gain, - self.postfilter_tapset, - postfilter_tapset, - &m.window, - ); - } - } - self.postfilter_period_old = self.postfilter_period; - self.postfilter_gain_old = self.postfilter_gain; - self.postfilter_tapset_old = self.postfilter_tapset; - self.postfilter_period = postfilter_pitch; - self.postfilter_gain = postfilter_gain; - self.postfilter_tapset = postfilter_tapset; - if lm != 0 { - self.postfilter_period_old = self.postfilter_period; - self.postfilter_gain_old = self.postfilter_gain; - self.postfilter_tapset_old = self.postfilter_tapset; - } - - if c == 1 { - let (a, b) = self.old_band_e.split_at_mut(NB_EBANDS); - b.copy_from_slice(a); - } - if !is_transient { - self.old_log_e2 = self.old_log_e; - self.old_log_e = self.old_band_e; - for i in 0..2 * NB_EBANDS { - self.background_log_e[i] = (self.background_log_e[i] + mm as f32 * 0.001).min(self.old_band_e[i]); - } - } else { - for i in 0..2 * NB_EBANDS { - self.old_log_e[i] = self.old_log_e[i].min(self.old_band_e[i]); - } - } - for ch in 0..2 { - for i in (0..start).chain(end..NB_EBANDS) { - self.old_band_e[ch * NB_EBANDS + i] = 0.0; - self.old_log_e[ch * NB_EBANDS + i] = -28.0; - self.old_log_e2[ch * NB_EBANDS + i] = -28.0; - } - } - self.rng = dec.rng(); - self.loss_count = 0; - } - - /// Denormalisation + IMDCT into the decode memory (`celt_synthesis`). - #[allow(clippy::too_many_arguments)] - fn synthesis(&mut self, x: &[f32], n: usize, start: usize, eff_end: usize, c: usize, is_transient: bool, lm: usize, silence: bool) { - let cc = self.channels; - let m = self.m; - let mm = 1usize << lm; - let (bcount, nb, shift) = if is_transient { (mm, SHORT_MDCT, MAX_LM) } else { (1, SHORT_MDCT << lm, MAX_LM - lm) }; - let ml = self.mem_len(); - let mut freq = std::mem::take(&mut self.freq); - freq.clear(); - freq.resize(2 * n, 0.0); - let (f0, f1) = freq.split_at_mut(n); - let out_off = DECODE_BUFFER_SIZE - n; - if cc == 2 && c == 1 { - denormalise_bands(x, f0, &self.old_band_e, start, eff_end, mm, self.downsample, silence); - f1.copy_from_slice(f0); - for (ch, f) in [(0usize, &*f1), (1usize, &*f0)] { - let mem = &mut self.decode_mem[ch * ml..(ch + 1) * ml]; - for b in 0..bcount { - m.mdct[shift].backward(&f[b..], bcount, &mut mem[out_off + nb * b..], &m.window, OVERLAP, &mut self.imdct); - } - } - } else if cc == 1 && c == 2 { - denormalise_bands(x, f0, &self.old_band_e, start, eff_end, mm, self.downsample, silence); - denormalise_bands(&x[n..], f1, &self.old_band_e[NB_EBANDS..], start, eff_end, mm, self.downsample, silence); - for i in 0..n { - f0[i] = 0.5 * (f0[i] + f1[i]); - } - let mem = &mut self.decode_mem[..ml]; - for b in 0..bcount { - m.mdct[shift].backward(&f0[b..], bcount, &mut mem[out_off + nb * b..], &m.window, OVERLAP, &mut self.imdct); - } - } else { - for ch in 0..cc { - denormalise_bands(&x[ch * n..], f0, &self.old_band_e[ch * NB_EBANDS..], start, eff_end, mm, self.downsample, silence); - let mem = &mut self.decode_mem[ch * ml..(ch + 1) * ml]; - for b in 0..bcount { - m.mdct[shift].backward(&f0[b..], bcount, &mut mem[out_off + nb * b..], &m.window, OVERLAP, &mut self.imdct); - } - } - } - self.freq = freq; - } - - fn deemphasis(&mut self, n: usize, pcm: &mut [f32], accum: bool) { - let cc = self.channels; - let ml = self.mem_len(); - let ds = self.downsample; - let out_off = DECODE_BUFFER_SIZE - n; - let mut scratch = std::mem::take(&mut self.pcm_scratch); - for ch in 0..cc { - let mem = &self.decode_mem[ch * ml + out_off..ch * ml + out_off + n]; - let mut mstate = self.preemph_mem[ch]; - scratch.clear(); - for &v in mem { - let tmp = v + 1e-30 + mstate; - mstate = PREEMPH * tmp; - scratch.push(tmp); - } - self.preemph_mem[ch] = mstate; - for j in 0..n / ds { - let v = scratch[j * ds] * (1.0 / 32768.0); - let o = &mut pcm[j * cc + ch]; - if accum { - *o += v; - } else { - *o = v; - } - } - } - self.pcm_scratch = scratch; - } - - /// Packet-loss concealment: pitch-periodic extension for the first lost frames, then - /// decaying shaped noise. - fn decode_lost(&mut self, n: usize, lm: usize) { - let cc = self.channels; - let ml = self.mem_len(); - let m = self.m; - let pitch_based = self.loss_count < 5 && self.start == 0 && !self.skip_plc; - if !pitch_based { - let decay = if self.loss_count == 0 { 1.5 } else { 0.5 }; - for ch in 0..cc { - for i in self.start..self.end { - let k = ch * NB_EBANDS + i; - self.old_band_e[k] = self.background_log_e[k].max(self.old_band_e[k] - decay); - } - } - let mut seed = self.rng; - let mut x = std::mem::take(&mut self.x); - x.clear(); - x.resize(cc * n, 0.0); - let eff_end = self.end.min(NB_EBANDS); - for ch in 0..cc { - for i in self.start..eff_end { - let off = n * ch + ((EBANDS[i] as usize) << lm); - let blen = ((EBANDS[i + 1] - EBANDS[i]) as usize) << lm; - for v in x[off..off + blen].iter_mut() { - seed = bands::lcg_rand(seed); - *v = ((seed as i32) >> 20) as f32; - } - renormalise_vector(&mut x[off..off + blen], 1.0); - } - } - self.rng = seed; - for ch in 0..cc { - let mem = &mut self.decode_mem[ch * ml..(ch + 1) * ml]; - mem.copy_within(n..DECODE_BUFFER_SIZE + OVERLAP / 2, 0); - } - let saved_sc = self.stream_channels; - self.stream_channels = cc; - self.synthesis(&x, n, self.start, eff_end, cc, false, lm, false); - self.stream_channels = saved_sc; - self.x = x; - } else { - // Pitch search on the (mono-mixed) history. - let hist_len = 1024usize; - let base = DECODE_BUFFER_SIZE - hist_len; - let mut mono = vec![0f32; hist_len]; - for ch in 0..cc { - for (i, v) in mono.iter_mut().enumerate() { - *v += self.decode_mem[ch * ml + base + i]; - } - } - let period = if self.loss_count == 0 { find_pitch(&mono) } else { self.postfilter_period_old.max(PLC_PITCH_LAG_MIN as i32) as usize }; - let period = period.clamp(PLC_PITCH_LAG_MIN, PLC_PITCH_LAG_MAX); - // Remember the period for subsequent losses in the (otherwise unused) old period slot. - self.postfilter_period_old = period as i32; - let fade = 0.8f32.powi(self.loss_count as i32 + 1); - let mut ext = vec![0f32; n + OVERLAP]; - for ch in 0..cc { - let mem = &mut self.decode_mem[ch * ml..(ch + 1) * ml]; - // Energy decay across the last two periods. - let e1: f32 = mem[DECODE_BUFFER_SIZE - period..DECODE_BUFFER_SIZE].iter().map(|v| v * v).sum(); - let e0: f32 = mem[DECODE_BUFFER_SIZE - 2 * period..DECODE_BUFFER_SIZE - period].iter().map(|v| v * v).sum(); - let decay = if e0 > 0.0 { (e1 / e0).sqrt().min(1.0) } else { 0.0 }; - let mut g = 1.0f32; - for (i, e) in ext.iter_mut().enumerate() { - if i > 0 && i % period == 0 { - g *= decay; - } - let src = DECODE_BUFFER_SIZE - period + (i % period); - *e = mem[src] * g * fade; - } - mem.copy_within(n..DECODE_BUFFER_SIZE + OVERLAP / 2, 0); - let out_off = DECODE_BUFFER_SIZE - n; - // Cross-fade the start with the previous overlap so the transition is smooth. - mem[out_off..out_off + n].copy_from_slice(&ext[..n]); - let tail = &ext[n..n + OVERLAP]; - for i in 0..OVERLAP / 2 { - mem[DECODE_BUFFER_SIZE + i] = m.window[i] * tail[OVERLAP - 1 - i] + m.window[OVERLAP - 1 - i] * tail[i]; - } - } - } - self.loss_count += 1; - } -} - -fn find_pitch(x: &[f32]) -> usize { - let n = x.len(); - let win = 512.min(n - PLC_PITCH_LAG_MAX); - let tgt = &x[n - win..]; - let mut best = PLC_PITCH_LAG_MIN; - let mut best_score = f32::MIN; - let e_t: f32 = tgt.iter().map(|v| v * v).sum::() + 1e-9; - for lag in PLC_PITCH_LAG_MIN..=PLC_PITCH_LAG_MAX { - let src = &x[n - win - lag..n - lag]; - let mut xy = 0f32; - let mut yy = 1e-9f32; - for (a, b) in tgt.iter().zip(src) { - xy += a * b; - yy += b * b; - } - let score = xy / (e_t * yy).sqrt(); - if score > best_score { - best_score = score; - best = lag; - } - } - best -} - -fn tf_decode(start: usize, end: usize, is_transient: bool, tf_res: &mut [i32; NB_EBANDS], lm: usize, dec: &mut RangeDecoder) { - let mut budget = dec.storage() as i32 * 8; - let mut tell = dec.tell(); - let mut logp: u32 = if is_transient { 2 } else { 4 }; - let tf_select_rsv = lm > 0 && tell + (logp as i32) < budget; - budget -= tf_select_rsv as i32; - let mut tf_changed = 0; - let mut curr = 0; - for r in tf_res.iter_mut().take(end).skip(start) { - if tell + logp as i32 <= budget { - curr ^= dec.bit_logp(logp) as i32; - tell = dec.tell(); - tf_changed |= curr; - } - *r = curr; - logp = if is_transient { 4 } else { 5 }; - } - let mut tf_select = 0; - let ti = 4 * is_transient as usize; - if tf_select_rsv && TF_SELECT_TABLE[lm][ti + tf_changed as usize] != TF_SELECT_TABLE[lm][ti + 2 + tf_changed as usize] { - tf_select = dec.bit_logp(1) as usize; - } - for r in tf_res.iter_mut().take(end).skip(start) { - *r = TF_SELECT_TABLE[lm][ti + 2 * tf_select + *r as usize] as i32; - } -} - -/// Pitch post-filter (in place on `buf[off .. off + n]`, reading history before `off`). -#[allow(clippy::too_many_arguments)] -fn comb_filter(buf: &mut [f32], off: usize, t0: i32, t1: i32, n: usize, g0: f32, g1: f32, tapset0: usize, tapset1: usize, window: &[f32; OVERLAP]) { - if g0 == 0.0 && g1 == 0.0 { - return; - } - let t0 = t0.max(COMBFILTER_MINPERIOD) as usize; - let t1 = t1.max(COMBFILTER_MINPERIOD) as usize; - let g00 = g0 * COMB_GAINS[tapset0][0]; - let g01 = g0 * COMB_GAINS[tapset0][1]; - let g02 = g0 * COMB_GAINS[tapset0][2]; - let g10 = g1 * COMB_GAINS[tapset1][0]; - let g11 = g1 * COMB_GAINS[tapset1][1]; - let g12 = g1 * COMB_GAINS[tapset1][2]; - let mut x1 = buf[off + 1 - t1]; - let mut x2 = buf[off - t1]; - let mut x3 = buf[off - t1 - 1]; - let mut x4 = buf[off - t1 - 2]; - let overlap = if g0 == g1 && t0 == t1 && tapset0 == tapset1 { 0 } else { OVERLAP.min(n) }; - let mut i = 0; - while i < overlap { - let p = off + i; - let x0 = buf[p + 2 - t1]; - let f = window[i] * window[i]; - let a = 1.0 - f; - buf[p] = buf[p] - + a * g00 * buf[p - t0] - + a * g01 * (buf[p + 1 - t0] + buf[p - t0 - 1]) - + a * g02 * (buf[p + 2 - t0] + buf[p - t0 - 2]) - + f * g10 * x2 - + f * g11 * (x1 + x3) - + f * g12 * (x0 + x4); - x4 = x3; - x3 = x2; - x2 = x1; - x1 = x0; - i += 1; - } - if g1 == 0.0 { - return; - } - // Constant filter for the rest of the block. - let t = t1; - for i in overlap..n { - let p = off + i; - buf[p] = buf[p] + g10 * buf[p - t] + g11 * (buf[p + 1 - t] + buf[p - t - 1]) + g12 * (buf[p + 2 - t] + buf[p - t - 2]); - } -} diff --git a/crates/opus/src/celt/bands.rs b/crates/opus/src/celt/bands.rs deleted file mode 100644 index fa04b3c5f..000000000 --- a/crates/opus/src/celt/bands.rs +++ /dev/null @@ -1,1013 +0,0 @@ -//! Band shape decoding: PVQ, spreading, TF changes, band splitting, stereo, folding -//! (RFC 6716 §4.3.4 – §4.3.6). - -use super::rate::{Mode, QTHETA_OFFSET, QTHETA_OFFSET_TWOPHASE, get_pulses}; -use super::tables::{E_MEANS, EBANDS, NB_EBANDS, ORDERY, SHORT_MDCT}; -use crate::range::{BITRES, RangeDecoder, ilog}; - -pub const SPREAD_NONE: usize = 0; -pub const SPREAD_AGGRESSIVE: usize = 3; - -#[inline] -pub fn lcg_rand(seed: u32) -> u32 { - seed.wrapping_mul(1_664_525).wrapping_add(1_013_904_223) -} - -/// Decodes a PVQ codeword of `k` pulses in `n` dimensions; returns the sum of squares. -fn decode_pulses(y: &mut [i32], n: usize, k: usize, dec: &mut RangeDecoder, v: &mut Vec) -> f32 { - // Table of V(nn, kk) for nn in 0..=n, kk in 0..=k (saturating). - let w = k + 1; - v.clear(); - v.resize((n + 1) * w, 0); - v[0] = 1; - for nn in 1..=n { - v[nn * w] = 1; - for kk in 1..=k { - let a = v[(nn - 1) * w + kk]; - let b = v[nn * w + kk - 1]; - let c = v[(nn - 1) * w + kk - 1]; - v[nn * w + kk] = a.saturating_add(b).saturating_add(c); - } - } - let total = v[n * w + k]; - let ft = total.min(u32::MAX as u64) as u32; - let mut i = if ft >= 2 { dec.uint(ft) as u64 } else { 0 }; - let mut kk = k; - let mut ryy = 0f32; - for j in 0..n { - let rem = n - j; - if kk == 0 { - y[j] = 0; - continue; - } - let vv = |nn: usize, kx: usize| v[nn * w + kx]; - let mut p = (vv(rem - 1, kk) + vv(rem, kk)) / 2; - let neg = i >= p; - if neg { - i -= p; - } - let k0 = kk; - p -= vv(rem - 1, kk); - while p > i && kk > 0 { - kk -= 1; - p -= vv(rem - 1, kk); - } - let mag = (k0 - kk) as i32; - y[j] = if neg { -mag } else { mag }; - i -= p.min(i); - ryy += (mag * mag) as f32; - } - ryy -} - -fn exp_rotation1(x: &mut [f32], len: usize, stride: usize, c: f32, s: f32) { - let ms = -s; - if len <= stride { - return; - } - for i in 0..len - stride { - let x1 = x[i]; - let x2 = x[i + stride]; - x[i + stride] = c * x2 + s * x1; - x[i] = c * x1 + ms * x2; - } - if len > 2 * stride { - for i in (0..=len - 2 * stride - 1).rev() { - let x1 = x[i]; - let x2 = x[i + stride]; - x[i + stride] = c * x2 + s * x1; - x[i] = c * x1 + ms * x2; - } - } -} - -/// Inverse spreading rotation (decoder direction). -fn exp_rotation(x: &mut [f32], len: usize, stride: usize, k: usize, spread: usize) { - const SPREAD_FACTOR: [usize; 3] = [15, 10, 5]; - if 2 * k >= len || spread == SPREAD_NONE { - return; - } - let factor = SPREAD_FACTOR[spread - 1]; - let gain = len as f32 / (len + factor * k) as f32; - let theta = 0.5 * gain * gain; - let c = (0.5 * std::f32::consts::PI * theta).cos(); - let s = (0.5 * std::f32::consts::PI * (1.0 - theta)).cos(); - let mut stride2 = 0; - if len >= 8 * stride { - stride2 = 1; - while (stride2 * stride2 + stride2) * stride + (stride >> 2) < len { - stride2 += 1; - } - } - let sub = len / stride; - for i in 0..stride { - let xs = &mut x[i * sub..(i + 1) * sub]; - if stride2 != 0 { - exp_rotation1(xs, sub, stride2, s, c); - } - exp_rotation1(xs, sub, 1, c, s); - } -} - -fn extract_collapse_mask(iy: &[i32], n: usize, b: usize) -> u32 { - if b <= 1 { - return 1; - } - let n0 = n / b; - let mut mask = 0u32; - for i in 0..b { - if iy[i * n0..(i + 1) * n0].iter().any(|&v| v != 0) { - mask |= 1 << i; - } - } - mask -} - -pub fn renormalise_vector(x: &mut [f32], gain: f32) { - let e: f32 = 1e-15 + x.iter().map(|v| v * v).sum::(); - let g = gain / e.sqrt(); - for v in x.iter_mut() { - *v *= g; - } -} - -fn haar1(x: &mut [f32], n0: usize, stride: usize) { - let n0 = n0 >> 1; - const S: f32 = std::f32::consts::FRAC_1_SQRT_2; - for i in 0..stride { - for j in 0..n0 { - let a = x[stride * 2 * j + i] * S; - let b = x[stride * (2 * j + 1) + i] * S; - x[stride * 2 * j + i] = a + b; - x[stride * (2 * j + 1) + i] = a - b; - } - } -} - -fn ordery(stride: usize) -> &'static [usize] { - &ORDERY[stride - 2..2 * stride - 2] -} - -fn deinterleave_hadamard(x: &mut [f32], n0: usize, stride: usize, hadamard: bool, tmp: &mut Vec) { - let n = n0 * stride; - tmp.clear(); - tmp.resize(n, 0.0); - if hadamard { - let ord = ordery(stride); - for i in 0..stride { - for j in 0..n0 { - tmp[ord[i] * n0 + j] = x[j * stride + i]; - } - } - } else { - for i in 0..stride { - for j in 0..n0 { - tmp[i * n0 + j] = x[j * stride + i]; - } - } - } - x[..n].copy_from_slice(tmp); -} - -fn interleave_hadamard(x: &mut [f32], n0: usize, stride: usize, hadamard: bool, tmp: &mut Vec) { - let n = n0 * stride; - tmp.clear(); - tmp.resize(n, 0.0); - if hadamard { - let ord = ordery(stride); - for i in 0..stride { - for j in 0..n0 { - tmp[j * stride + i] = x[ord[i] * n0 + j]; - } - } - } else { - for i in 0..stride { - for j in 0..n0 { - tmp[j * stride + i] = x[i * n0 + j]; - } - } - } - x[..n].copy_from_slice(tmp); -} - -#[inline] -fn frac_mul16(a: i32, b: i32) -> i32 { - (16384 + (a as i16 as i32) * (b as i16 as i32)) >> 15 -} - -fn bitexact_cos(x: i32) -> i32 { - let tmp = (4096 + x * x) >> 13; - let x2 = tmp; - let x2 = (32767 - x2) + frac_mul16(x2, -7651 + frac_mul16(x2, 8277 + frac_mul16(-626, x2))); - 1 + x2 -} - -fn bitexact_log2tan(isin: i32, icos: i32) -> i32 { - let lc = ilog(icos as u32); - let ls = ilog(isin as u32); - let icos = icos << (15 - lc); - let isin = isin << (15 - ls); - (ls - lc) * (1 << 11) + frac_mul16(isin, frac_mul16(isin, -2597) + 7932) - frac_mul16(icos, frac_mul16(icos, -2597) + 7932) -} - -fn isqrt32(v: u32) -> u32 { - let mut r = (v as f64).sqrt() as u32; - while (r as u64) * (r as u64) > v as u64 { - r -= 1; - } - while ((r + 1) as u64) * ((r + 1) as u64) <= v as u64 { - r += 1; - } - r -} - -fn compute_qn(n: i32, b: i32, offset: i32, pulse_cap: i32, stereo: bool) -> i32 { - const EXP2_TABLE8: [i32; 8] = [16384, 17866, 19483, 21247, 23170, 25267, 27554, 30048]; - let mut n2 = 2 * n - 1; - if stereo && n == 2 { - n2 -= 1; - } - let mut qb = sdiv(b + n2 * offset, n2); - qb = qb.min(b - pulse_cap - (4 << BITRES)); - qb = qb.min(8 << BITRES); - if qb < (1 << BITRES >> 1) { - 1 - } else { - let qn = EXP2_TABLE8[(qb & 0x7) as usize] >> (14 - (qb >> BITRES)); - (qn + 1) >> 1 << 1 - } -} - -/// C-style truncating signed division. -#[inline] -fn sdiv(a: i32, b: i32) -> i32 { - a / b -} - -pub struct BandCtx<'m, 'd, 'b> { - pub m: &'m Mode, - pub dec: &'d mut RangeDecoder<'b>, - pub i: usize, - pub intensity: usize, - pub spread: usize, - pub tf_change: i32, - pub remaining_bits: i32, - pub seed: u32, - pub disable_inv: bool, - pub avoid_split_noise: bool, - pub pvq_v: Vec, - pub iy: Vec, - pub tmp: Vec, -} - -struct Split { - inv: bool, - imid: i32, - iside: i32, - delta: i32, - itheta: i32, - qalloc: i32, -} - -/// Block geometry for one theta decode: `n` samples, `bb` blocks per band, `b0` initial block count. -#[derive(Clone, Copy)] -struct ThetaShape { - n: i32, - bb: usize, - b0: usize, - lm: i32, - stereo: bool, -} - -/// Mid/side gains and delta for a decoded angle; also narrows `fill` to the blocks that stay coded. -fn split_gains(itheta: i32, n: i32, bb: usize, fill: &mut u32) -> (i32, i32, i32) { - if itheta == 0 { - *fill &= (1 << bb) - 1; - (32767, 0, -16384) - } else if itheta == 16384 { - *fill &= ((1 << bb) - 1) << bb; - (0, 32767, 16384) - } else { - let imid = bitexact_cos(itheta); - let iside = bitexact_cos(16384 - itheta); - (imid, iside, frac_mul16((n - 1) << 7, bitexact_log2tan(iside, imid))) - } -} - -impl BandCtx<'_, '_, '_> { - fn compute_theta(&mut self, shape: ThetaShape, b: &mut i32, fill: &mut u32) -> Split { - let ThetaShape { n, bb, b0, lm, stereo } = shape; - let i = self.i; - let pulse_cap = self.m.log_n[i] + lm * (1 << BITRES); - let offset = (pulse_cap >> 1) - if stereo && n == 2 { QTHETA_OFFSET_TWOPHASE } else { QTHETA_OFFSET }; - let mut qn = compute_qn(n, *b, offset, pulse_cap, stereo); - if stereo && i >= self.intensity { - qn = 1; - } - let tell = self.dec.tell_frac() as i32; - let mut itheta = 0i32; - let mut inv = false; - if qn != 1 { - itheta = self.decode_itheta(qn, n, b0, stereo); - } else if stereo { - inv = self.decode_inversion(*b); - } - let qalloc = self.dec.tell_frac() as i32 - tell; - *b -= qalloc; - let (imid, iside, delta) = split_gains(itheta, n, bb, fill); - Split { inv, imid, iside, delta, itheta, qalloc } - } - - /// Decodes the quantised stereo/band angle for `qn > 1` and scales it to the 0..=16384 range. - fn decode_itheta(&mut self, qn: i32, n: i32, b0: usize, stereo: bool) -> i32 { - let itheta = if stereo && n > 2 { - self.decode_itheta_stereo_triangle(qn) - } else if b0 > 1 || stereo { - self.dec.uint(qn as u32 + 1) as i32 - } else { - self.decode_itheta_triangle(qn) - }; - ((itheta as u32 * 16384) / qn as u32) as i32 - } - - fn decode_itheta_stereo_triangle(&mut self, qn: i32) -> i32 { - let p0 = 3u32; - let x0 = (qn / 2) as u32; - let ft = p0 * (x0 + 1) + x0; - let fs = self.dec.decode(ft); - let x = if fs < (x0 + 1) * p0 { fs / p0 } else { x0 + 1 + (fs - (x0 + 1) * p0) }; - let (fl, fh) = if x <= x0 { (p0 * x, p0 * (x + 1)) } else { ((x - 1 - x0) + (x0 + 1) * p0, (x - x0) + (x0 + 1) * p0) }; - self.dec.update(fl, fh, ft); - x as i32 - } - - fn decode_itheta_triangle(&mut self, qn: i32) -> i32 { - let half = (qn >> 1) as u32; - let ft = (half + 1) * (half + 1); - let fm = self.dec.decode(ft); - let (fl, fs, itheta); - if fm < (half * (half + 1)) >> 1 { - let it = (isqrt32(8 * fm + 1) - 1) >> 1; - fs = it + 1; - fl = it * (it + 1) >> 1; - itheta = it as i32; - } else { - let q = qn as u32; - let it = (2 * (q + 1) - isqrt32(8 * (ft - fm - 1) + 1)) >> 1; - fs = q + 1 - it; - fl = ft - ((q + 1 - it) * (q + 2 - it) >> 1); - itheta = it as i32; - } - self.dec.update(fl, fl + fs, ft); - itheta - } - - /// Reads the stereo inversion flag when there are enough bits left (never when inversion is disabled). - fn decode_inversion(&mut self, b: i32) -> bool { - let inv = b > 2 << BITRES && self.remaining_bits > 2 << BITRES && self.dec.bit_logp(2); - inv && !self.disable_inv - } - - fn quant_band_n1(&mut self, x: &mut [f32], y: Option<&mut [f32]>, lowband_out: Option<&mut [f32]>) -> u32 { - let dec_sign = |ctx: &mut Self| -> f32 { - let mut sign = 0; - if ctx.remaining_bits >= 1 << BITRES { - sign = ctx.dec.bits(1); - ctx.remaining_bits -= 1 << BITRES; - } - if sign != 0 { -1.0 } else { 1.0 } - }; - x[0] = dec_sign(self); - if let Some(y) = y { - y[0] = dec_sign(self); - } - if let Some(lo) = lowband_out { - lo[0] = x[0]; - } - 1 - } - - #[allow(clippy::too_many_arguments)] - fn quant_partition( - &mut self, - x: &mut [f32], - n: usize, - mut b: i32, - mut bb: usize, - lowband: Option<&[f32]>, - mut lm: i32, - gain: f32, - mut fill: u32, - ) -> u32 { - let b0 = bb; - let i = self.i; - let cache = self.m.cache(lm, i); - let cache_max = cache[cache[0] as usize] as i32; - if lm != -1 && b > cache_max + 12 && n > 2 { - let n = n >> 1; - let (xa, ya) = x.split_at_mut(n); - lm -= 1; - if bb == 1 { - fill = (fill & 1) | (fill << 1); - } - bb = (bb + 1) >> 1; - let sp = self.compute_theta(ThetaShape { n: n as i32, bb, b0, lm, stereo: false }, &mut b, &mut fill); - let mid = sp.imid as f32 / 32768.0; - let side = sp.iside as f32 / 32768.0; - let mut delta = sp.delta; - if b0 > 1 && (sp.itheta & 0x3fff) != 0 { - if sp.itheta > 8192 { - delta -= delta >> (4 - lm); - } else { - delta = 0.min(delta + ((n as i32) << BITRES >> (5 - lm))); - } - } - let mut mbits = 0.max(b.min((b - delta) / 2)); - let mut sbits = b - mbits; - self.remaining_bits -= sp.qalloc; - let next_lowband2 = lowband.map(|l| &l[n..]); - let mut rebalance = self.remaining_bits; - let mut cm; - if mbits >= sbits { - cm = self.quant_partition(xa, n, mbits, bb, lowband, lm, gain * mid, fill); - rebalance = mbits - (rebalance - self.remaining_bits); - if rebalance > 3 << BITRES && sp.itheta != 0 { - sbits += rebalance - (3 << BITRES); - } - cm |= self.quant_partition(ya, n, sbits, bb, next_lowband2, lm, gain * side, fill >> bb) << (b0 >> 1); - } else { - cm = self.quant_partition(ya, n, sbits, bb, next_lowband2, lm, gain * side, fill >> bb) << (b0 >> 1); - rebalance = sbits - (rebalance - self.remaining_bits); - if rebalance > 3 << BITRES && sp.itheta != 16384 { - mbits += rebalance - (3 << BITRES); - } - cm |= self.quant_partition(xa, n, mbits, bb, lowband, lm, gain * mid, fill); - } - return cm; - } - // No split. - let m = self.m; - let mut q = m.bits2pulses(i, lm, b); - let mut curr_bits = m.pulses2bits(i, lm, q); - self.remaining_bits -= curr_bits; - while self.remaining_bits < 0 && q > 0 { - self.remaining_bits += curr_bits; - q -= 1; - curr_bits = m.pulses2bits(i, lm, q); - self.remaining_bits -= curr_bits; - } - if q != 0 { - let k = get_pulses(q) as usize; - let mut iy = std::mem::take(&mut self.iy); - iy.clear(); - iy.resize(n, 0); - let ryy = decode_pulses(&mut iy, n, k, self.dec, &mut self.pvq_v); - let g = gain / ryy.sqrt(); - for (xv, &yv) in x[..n].iter_mut().zip(iy.iter()) { - *xv = g * yv as f32; - } - exp_rotation(&mut x[..n], n, bb, k, self.spread); - let cm = extract_collapse_mask(&iy, n, bb); - self.iy = iy; - cm - } else { - let cm_mask = (1u32 << bb) - 1; - fill &= cm_mask; - if fill == 0 { - x[..n].fill(0.0); - 0 - } else { - let cm = match lowband { - None => { - for v in x[..n].iter_mut() { - self.seed = lcg_rand(self.seed); - *v = ((self.seed as i32) >> 20) as f32; - } - cm_mask - } - Some(lb) => { - for j in 0..n { - self.seed = lcg_rand(self.seed); - let tmp = if self.seed & 0x8000 != 0 { 1.0 / 256.0 } else { -1.0 / 256.0 }; - x[j] = lb[j] + tmp; - } - fill - } - }; - renormalise_vector(&mut x[..n], gain); - cm - } - } - } - - #[allow(clippy::too_many_arguments)] - pub fn quant_band( - &mut self, - x: &mut [f32], - n: usize, - b: i32, - mut bb: usize, - mut lowband: Option<&mut [f32]>, - lm: i32, - lowband_out: Option<&mut [f32]>, - gain: f32, - mut fill: u32, - ) -> u32 { - let n0 = n; - let mut n_b = n; - let b0_orig = bb; - let mut time_divide = 0; - let mut recombine = 0; - let long_blocks = b0_orig == 1; - let mut tf_change = self.tf_change; - n_b /= bb; - if n == 1 { - return self.quant_band_n1(x, None, lowband_out); - } - if tf_change > 0 { - recombine = tf_change; - } - const BIT_INTERLEAVE: [u32; 16] = [0, 1, 1, 1, 2, 3, 3, 3, 2, 3, 3, 3, 2, 3, 3, 3]; - for k in 0..recombine { - if let Some(lb) = lowband.as_deref_mut() { - haar1(lb, n >> k, 1 << k); - } - fill = BIT_INTERLEAVE[(fill & 0xF) as usize] | (BIT_INTERLEAVE[(fill >> 4) as usize] << 2); - } - bb >>= recombine; - n_b <<= recombine; - while (n_b & 1) == 0 && tf_change < 0 { - if let Some(lb) = lowband.as_deref_mut() { - haar1(lb, n_b, bb); - } - fill |= fill << bb; - bb <<= 1; - n_b >>= 1; - time_divide += 1; - tf_change += 1; - } - let b0 = bb; - let n_b0 = n_b; - let mut tmp = std::mem::take(&mut self.tmp); - if b0 > 1 - && let Some(lb) = lowband.as_deref_mut() - { - deinterleave_hadamard(lb, n_b >> recombine, b0 << recombine, long_blocks, &mut tmp); - } - let mut cm = self.quant_partition(x, n, b, bb, lowband.as_deref(), lm, gain, fill); - if b0 > 1 { - interleave_hadamard(x, n_b >> recombine, b0 << recombine, long_blocks, &mut tmp); - } - self.tmp = tmp; - let mut n_b = n_b0; - bb = b0; - for _ in 0..time_divide { - bb >>= 1; - n_b <<= 1; - cm |= cm >> bb; - haar1(x, n_b, bb); - } - const BIT_DEINTERLEAVE: [u32; 16] = [0x00, 0x03, 0x0C, 0x0F, 0x30, 0x33, 0x3C, 0x3F, 0xC0, 0xC3, 0xCC, 0xCF, 0xF0, 0xF3, 0xFC, 0xFF]; - for k in 0..recombine { - cm = BIT_DEINTERLEAVE[(cm & 0xF) as usize]; - haar1(x, n0 >> k, 1 << k); - } - bb <<= recombine; - if let Some(lo) = lowband_out { - let s = (n0 as f32).sqrt(); - for j in 0..n0 { - lo[j] = s * x[j]; - } - } - cm & ((1u32 << bb) - 1) - } - - #[allow(clippy::too_many_arguments)] - pub fn quant_band_stereo( - &mut self, - x: &mut [f32], - y: &mut [f32], - n: usize, - mut b: i32, - bb: usize, - lowband: Option<&mut [f32]>, - lm: i32, - lowband_out: Option<&mut [f32]>, - mut fill: u32, - ) -> u32 { - let orig_fill = fill; - if n == 1 { - return self.quant_band_n1(x, Some(y), lowband_out); - } - let sp = self.compute_theta(ThetaShape { n: n as i32, bb, b0: bb, lm, stereo: true }, &mut b, &mut fill); - let mid = sp.imid as f32 / 32768.0; - let side = sp.iside as f32 / 32768.0; - let mut cm; - if n == 2 { - let mut mbits = b; - let mut sbits = 0; - if sp.itheta != 0 && sp.itheta != 16384 { - sbits = 1 << BITRES; - } - mbits -= sbits; - let c = sp.itheta > 8192; - self.remaining_bits -= sp.qalloc + sbits; - let mut sign = 0; - if sbits != 0 { - sign = self.dec.bits(1) as i32; - } - let sign = (1 - 2 * sign) as f32; - if c { - cm = self.quant_band(y, n, mbits, bb, lowband, lm, lowband_out, 1.0, orig_fill); - x[0] = -sign * y[1]; - x[1] = sign * y[0]; - } else { - cm = self.quant_band(x, n, mbits, bb, lowband, lm, lowband_out, 1.0, orig_fill); - y[0] = -sign * x[1]; - y[1] = sign * x[0]; - } - x[0] *= mid; - x[1] *= mid; - y[0] *= side; - y[1] *= side; - let t = x[0]; - x[0] = t - y[0]; - y[0] += t; - let t = x[1]; - x[1] = t - y[1]; - y[1] += t; - } else { - let mut mbits = 0.max(b.min((b - sp.delta) / 2)); - let mut sbits = b - mbits; - self.remaining_bits -= sp.qalloc; - let mut rebalance = self.remaining_bits; - if mbits >= sbits { - cm = self.quant_band(x, n, mbits, bb, lowband, lm, lowband_out, 1.0, fill); - rebalance = mbits - (rebalance - self.remaining_bits); - if rebalance > 3 << BITRES && sp.itheta != 0 { - sbits += rebalance - (3 << BITRES); - } - cm |= self.quant_band(y, n, sbits, bb, None, lm, None, side, fill >> bb); - } else { - cm = self.quant_band(y, n, sbits, bb, None, lm, None, side, fill >> bb); - rebalance = sbits - (rebalance - self.remaining_bits); - if rebalance > 3 << BITRES && sp.itheta != 16384 { - mbits += rebalance - (3 << BITRES); - } - cm |= self.quant_band(x, n, mbits, bb, lowband, lm, lowband_out, 1.0, fill); - } - } - if n != 2 { - stereo_merge(&mut x[..n], &mut y[..n], mid); - } - if sp.inv { - for v in y[..n].iter_mut() { - *v = -*v; - } - } - cm - } -} - -fn stereo_merge(x: &mut [f32], y: &mut [f32], mid: f32) { - let mut xp = 0f32; - let mut side = 0f32; - for (a, b) in x.iter().zip(y.iter()) { - xp += a * b; - side += b * b; - } - xp *= mid; - let mid2 = mid; - let el = mid2 * mid2 + side - 2.0 * xp; - let er = mid2 * mid2 + side + 2.0 * xp; - if er < 6e-4 || el < 6e-4 { - y.copy_from_slice(x); - return; - } - let lgain = 1.0 / el.sqrt(); - let rgain = 1.0 / er.sqrt(); - for (a, b) in x.iter_mut().zip(y.iter_mut()) { - let l = mid * *a; - let r = *b; - *a = lgain * (l - r); - *b = rgain * (l + r); - } -} - -/// Decodes all band shapes (`quant_all_bands`, decoder side). -#[allow(clippy::too_many_arguments)] -pub fn quant_all_bands( - m: &Mode, - start: usize, - end: usize, - x_buf: &mut [f32], - y_buf: Option<&mut [f32]>, - collapse_masks: &mut [u32], - pulses: &[i32; NB_EBANDS], - short_blocks: bool, - spread: usize, - mut dual_stereo: bool, - intensity: usize, - tf_res: &[i32; NB_EBANDS], - total_bits: i32, - mut balance: i32, - dec: &mut RangeDecoder, - lm: usize, - coded_bands: usize, - seed: &mut u32, - disable_inv: bool, -) { - let mm = 1usize << lm; - let bb = if short_blocks { mm } else { 1 }; - let norm_offset = mm * EBANDS[start] as usize; - let norm_len = mm * EBANDS[NB_EBANDS - 1] as usize - norm_offset; - let c = if y_buf.is_some() { 2 } else { 1 }; - let mut norm = vec![0f32; norm_len]; - let mut norm2 = vec![0f32; if c == 2 { norm_len } else { 0 }]; - let mut scratch = vec![0f32; mm * (EBANDS[NB_EBANDS] - EBANDS[NB_EBANDS - 1]) as usize]; - let mut scratch2 = scratch.clone(); - let mut y_buf = y_buf; - let mut lowband_offset = 0usize; - let mut update_lowband = true; - let mut ctx = BandCtx { - m, - dec, - i: 0, - intensity, - spread, - tf_change: 0, - remaining_bits: 0, - seed: *seed, - disable_inv, - avoid_split_noise: bb > 1, - pvq_v: Vec::new(), - iy: Vec::new(), - tmp: Vec::new(), - }; - for i in start..end { - ctx.i = i; - let last = i == end - 1; - let xo = mm * EBANDS[i] as usize; - let n = mm * EBANDS[i + 1] as usize - xo; - let tell = ctx.dec.tell_frac() as i32; - if i != start { - balance -= tell; - } - let remaining_bits = total_bits - tell - 1; - ctx.remaining_bits = remaining_bits; - let b = if i < coded_bands { - let curr_balance = sdiv(balance, 3.min(coded_bands as i32 - i as i32)); - 0.max(16383.min((remaining_bits + 1).min(pulses[i] + curr_balance))) - } else { - 0 - }; - if (mm * EBANDS[i] as usize >= n + norm_offset || i == start + 1) && (update_lowband || lowband_offset == 0) { - lowband_offset = i; - } - if i == start + 1 { - let n1 = mm * (EBANDS[start + 1] - EBANDS[start]) as usize; - let n2 = mm * (EBANDS[start + 2] - EBANDS[start + 1]) as usize; - if n2 > n1 { - norm.copy_within(2 * n1 - n2..n1, n1); - if c == 2 { - norm2.copy_within(2 * n1 - n2..n1, n1); - } - } - } - let tf_change = tf_res[i]; - ctx.tf_change = tf_change; - let mut effective_lowband: Option = None; - let (mut x_cm, mut y_cm); - if lowband_offset != 0 && (spread != SPREAD_AGGRESSIVE || bb > 1 || tf_change < 0) { - let eff = (mm * EBANDS[lowband_offset] as usize).saturating_sub(norm_offset + n); - effective_lowband = Some(eff); - let mut fold_start = lowband_offset; - loop { - fold_start -= 1; - if mm * (EBANDS[fold_start] as usize) <= eff + norm_offset { - break; - } - } - let mut fold_end = lowband_offset - 1; - loop { - fold_end += 1; - if !(fold_end < i && mm * (EBANDS[fold_end] as usize) < eff + norm_offset + n) { - break; - } - } - x_cm = 0; - y_cm = 0; - let mut fi = fold_start; - loop { - x_cm |= collapse_masks[fi * c]; - y_cm |= collapse_masks[fi * c + c - 1]; - fi += 1; - if fi >= fold_end { - break; - } - } - } else { - x_cm = (1u32 << bb) - 1; - y_cm = x_cm; - } - if dual_stereo && i == intensity { - dual_stereo = false; - for j in 0..(mm * EBANDS[i] as usize - norm_offset) { - norm[j] = 0.5 * (norm[j] + norm2[j]); - } - } - let out_off = xo - norm_offset; - let x = &mut x_buf[xo..xo + n]; - if dual_stereo && let Some(y_buf) = y_buf.as_deref_mut() { - let y = &mut y_buf[xo..xo + n]; - let lb = effective_lowband.map(|e| { - scratch[..n].copy_from_slice(&norm[e..e + n]); - &mut scratch[..n] - }); - let lo = if last { None } else { Some(&mut norm[out_off..out_off + n]) }; - x_cm = ctx.quant_band(x, n, b / 2, bb, lb, lm as i32, lo, 1.0, x_cm); - let lb2 = effective_lowband.map(|e| { - scratch2[..n].copy_from_slice(&norm2[e..e + n]); - &mut scratch2[..n] - }); - let lo2 = if last { None } else { Some(&mut norm2[out_off..out_off + n]) }; - y_cm = ctx.quant_band(y, n, b / 2, bb, lb2, lm as i32, lo2, 1.0, y_cm); - } else { - let lb = effective_lowband.map(|e| { - scratch[..n].copy_from_slice(&norm[e..e + n]); - &mut scratch[..n] - }); - let lo = if last { None } else { Some(&mut norm[out_off..out_off + n]) }; - if let Some(yb) = y_buf.as_deref_mut() { - let y = &mut yb[xo..xo + n]; - x_cm = ctx.quant_band_stereo(x, y, n, b, bb, lb, lm as i32, lo, x_cm | y_cm); - } else { - x_cm = ctx.quant_band(x, n, b, bb, lb, lm as i32, lo, 1.0, x_cm | y_cm); - } - y_cm = x_cm; - } - collapse_masks[i * c] = x_cm; - collapse_masks[i * c + c - 1] = y_cm; - balance += pulses[i] + tell; - update_lowband = b > (n as i32) << BITRES; - ctx.avoid_split_noise = false; - } - *seed = ctx.seed; -} - -/// Anti-collapse noise injection (RFC 6716 §4.3.5). -#[allow(clippy::too_many_arguments)] -pub fn anti_collapse( - x_buf: &mut [f32], - collapse_masks: &[u32], - lm: usize, - c: usize, - size: usize, - start: usize, - end: usize, - log_e: &[f32], - prev1: &[f32], - prev2: &[f32], - pulses: &[i32; NB_EBANDS], - mut seed: u32, -) { - for i in start..end { - let n0 = (EBANDS[i + 1] - EBANDS[i]) as usize; - let depth = ((1 + pulses[i]) as u32 / n0 as u32) >> lm; - let thresh = 0.5 * (-0.125 * depth as f32).exp2(); - let sqrt_1 = 1.0 / ((n0 << lm) as f32).sqrt(); - for ch in 0..c { - let mut p1 = prev1[ch * NB_EBANDS + i]; - let mut p2 = prev2[ch * NB_EBANDS + i]; - if c == 1 { - p1 = p1.max(prev1[NB_EBANDS + i]); - p2 = p2.max(prev2[NB_EBANDS + i]); - } - let ediff = (log_e[ch * NB_EBANDS + i] - p1.min(p2)).max(0.0); - let mut r = 2.0 * (-ediff).exp2(); - if lm == 3 { - r *= std::f32::consts::SQRT_2; - } - r = r.min(thresh); - r *= sqrt_1; - let off = ch * size + ((EBANDS[i] as usize) << lm); - let xb = &mut x_buf[off..off + (n0 << lm)]; - let mut renorm = false; - for k in 0..(1usize << lm) { - if collapse_masks[i * c + ch] & (1 << k) == 0 { - for j in 0..n0 { - seed = lcg_rand(seed); - xb[(j << lm) + k] = if seed & 0x8000 != 0 { r } else { -r }; - } - renorm = true; - } - } - if renorm { - renormalise_vector(xb, 1.0); - } - } - } -} - -/// Multiplies the normalised shapes by the band energies (`denormalise_bands`). -#[allow(clippy::too_many_arguments)] -pub fn denormalise_bands( - x: &[f32], - freq: &mut [f32], - band_log_e: &[f32], - mut start: usize, - mut end: usize, - mm: usize, - downsample: usize, - silence: bool, -) { - let n = mm * SHORT_MDCT; - let mut bound = mm * EBANDS[end] as usize; - if downsample != 1 { - bound = bound.min(n / downsample); - } - if silence { - bound = 0; - start = 0; - end = 0; - } - let s0 = mm * EBANDS[start] as usize; - freq[..s0].fill(0.0); - for i in start..end { - let lo = mm * EBANDS[i] as usize; - let hi = mm * EBANDS[i + 1] as usize; - let lg = band_log_e[i] + E_MEANS[i]; - let g = lg.min(32.0).exp2(); - for j in lo..hi { - freq[j] = x[j] * g; - } - } - let from = bound; - if from < n { - freq[from..n].fill(0.0); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn bitexact_trig_endpoints() { - assert_eq!(bitexact_cos(0), 32768); - - let mid = bitexact_cos(8192); - assert!((mid - 23170).abs() < 20, "{mid}"); - assert_eq!(bitexact_log2tan(16384, 16384), 0); - } - - #[test] - fn isqrt_exact() { - for v in [0u32, 1, 2, 3, 4, 15, 16, 17, 99, 100, 1 << 20, u32::MAX] { - let r = isqrt32(v) as u64; - assert!(r * r <= v as u64 && (r + 1) * (r + 1) > v as u64); - } - } - - #[test] - fn pvq_roundtrip_all_codewords_small() { - use crate::range::enc::RangeEncoder; - // Enumerate every index for small N,K, decode, and check uniqueness + sum |y| == K. - for n in 1..6usize { - for k in 1..5usize { - let total = super::super::rate::pvq_v(n, k) as u32; - let mut seen = std::collections::HashSet::new(); - for idx in 0..total { - let mut e = RangeEncoder::new(16); - e.uint(idx, total); - let buf = e.done(); - let mut d = RangeDecoder::new(&buf); - let mut y = vec![0i32; n]; - let mut v = Vec::new(); - let ryy = decode_pulses(&mut y, n, k, &mut d, &mut v); - assert_eq!(y.iter().map(|v| v.unsigned_abs() as usize).sum::(), k); - assert_eq!(ryy as i32, y.iter().map(|v| v * v).sum::()); - assert!(seen.insert(y.clone()), "duplicate codeword n={n} k={k} idx={idx}"); - } - } - } - } - - #[test] - fn haar_is_involution() { - let mut x: Vec = (0..16).map(|i| i as f32).collect(); - let orig = x.clone(); - haar1(&mut x, 16, 1); - haar1(&mut x, 16, 1); - for (a, b) in x.iter().zip(&orig) { - assert!((a - b).abs() < 1e-4); - } - let mut tmp = Vec::new(); - for &h in &[false, true] { - let mut y = orig.clone(); - deinterleave_hadamard(&mut y, 4, 4, h, &mut tmp); - interleave_hadamard(&mut y, 4, 4, h, &mut tmp); - assert_eq!(y, orig); - } - } -} diff --git a/crates/opus/src/celt/energy.rs b/crates/opus/src/celt/energy.rs deleted file mode 100644 index 3ac9453b1..000000000 --- a/crates/opus/src/celt/energy.rs +++ /dev/null @@ -1,171 +0,0 @@ -//! Band energy decoding: Laplace-coded coarse energy, fine energy and the final refinement -//! (RFC 6716 §4.3.2). - -use super::rate::MAX_FINE_BITS; -use super::tables::{BETA_COEF, BETA_INTRA, E_PROB_MODEL, NB_EBANDS, PRED_COEF, SMALL_ENERGY_ICDF}; -use crate::range::RangeDecoder; - -const LAPLACE_MINP: u32 = 1; -const LAPLACE_NMIN: u32 = 16; - -fn laplace_get_freq1(fs0: u32, decay: u32) -> u32 { - let ft = 32768 - LAPLACE_MINP * (2 * LAPLACE_NMIN) - fs0; - (ft * (16384 - decay)) >> 15 -} - -/// Decodes a Laplace-distributed integer (`fs` = probability of 0 in Q15, `decay` in Q14). -pub fn laplace_decode(dec: &mut RangeDecoder, mut fs: u32, decay: u32) -> i32 { - let mut val = 0i32; - let fm = dec.decode_bin(15); - let mut fl = 0u32; - if fm >= fs { - val += 1; - fl = fs; - fs = laplace_get_freq1(fs, decay) + LAPLACE_MINP; - while fs > LAPLACE_MINP && fm >= fl + 2 * fs { - fs *= 2; - fl += fs; - fs = (((fs - 2 * LAPLACE_MINP) * decay) >> 15) + LAPLACE_MINP; - val += 1; - } - if fs <= LAPLACE_MINP { - let di = (fm - fl) >> 1; - val += di as i32; - fl += 2 * di * LAPLACE_MINP; - } - if fm < fl + fs { - val = -val; - } else { - fl += fs; - } - } - dec.update_bin(fl, (fl + fs).min(32768), 15); - val -} - -/// `unquant_coarse_energy`. -#[allow(clippy::too_many_arguments)] -pub fn unquant_coarse_energy(start: usize, end: usize, old_e: &mut [f32], intra: bool, dec: &mut RangeDecoder, c: usize, lm: usize) { - let prob = &E_PROB_MODEL[lm][intra as usize]; - let (coef, beta) = if intra { (0.0, BETA_INTRA) } else { (PRED_COEF[lm], BETA_COEF[lm]) }; - let budget = dec.storage() as i32 * 8; - let mut prev = [0f32; 2]; - for i in start..end { - for ch in 0..c { - let tell = dec.tell(); - let qi = if budget - tell >= 15 { - let pi = 2 * i.min(20); - laplace_decode(dec, (prob[pi] as u32) << 7, (prob[pi + 1] as u32) << 6) - } else if budget - tell >= 2 { - let q = dec.icdf(&SMALL_ENERGY_ICDF, 2) as i32; - (q >> 1) ^ -(q & 1) - } else if budget - tell >= 1 { - -(dec.bit_logp(1) as i32) - } else { - -1 - }; - let q = qi as f32; - let idx = i + ch * NB_EBANDS; - old_e[idx] = old_e[idx].max(-9.0); - let tmp = coef * old_e[idx] + prev[ch] + q; - old_e[idx] = tmp; - prev[ch] = prev[ch] + q - beta * q; - } - } -} - -pub fn unquant_fine_energy(start: usize, end: usize, old_e: &mut [f32], fine_quant: &[i32; NB_EBANDS], dec: &mut RangeDecoder, c: usize) { - for i in start..end { - let fq = fine_quant[i]; - if fq <= 0 { - continue; - } - for ch in 0..c { - let q2 = dec.bits(fq as u32); - let offset = (q2 as f32 + 0.5) * (1 << (14 - fq)) as f32 / 16384.0 - 0.5; - old_e[i + ch * NB_EBANDS] += offset; - } - } -} - -#[allow(clippy::too_many_arguments)] -pub fn unquant_energy_finalise( - start: usize, - end: usize, - old_e: &mut [f32], - fine_quant: &[i32; NB_EBANDS], - fine_priority: &[i32; NB_EBANDS], - mut bits_left: i32, - dec: &mut RangeDecoder, - c: usize, -) { - for prio in 0..2 { - let mut i = start; - while i < end && bits_left >= c as i32 { - if fine_quant[i] >= MAX_FINE_BITS || fine_priority[i] != prio { - i += 1; - continue; - } - for ch in 0..c { - let q2 = dec.bits(1); - let offset = (q2 as f32 - 0.5) * (1 << (14 - fine_quant[i] - 1)) as f32 / 16384.0; - old_e[i + ch * NB_EBANDS] += offset; - bits_left -= 1; - } - i += 1; - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::range::enc::RangeEncoder; - - fn laplace_encode(e: &mut RangeEncoder, value: i32, mut fs: u32, decay: u32) { - let mut fl = 0u32; - let mut val = value; - if val != 0 { - let s: i32 = if val < 0 { -1 } else { 0 }; - val = val.abs(); - fl = fs; - fs = laplace_get_freq1(fs, decay); - let mut i = 1; - while fs > 0 && i < val { - fs *= 2; - fl += fs + 2 * LAPLACE_MINP; - fs = (fs * decay) >> 15; - i += 1; - } - if fs == 0 { - let ndi_max = (32768 - fl + LAPLACE_MINP - 1) as i32; - let ndi_max = (ndi_max - s) >> 1; - let di = (val - i).min(ndi_max - 1); - fl = (fl as i32 + (2 * di + 1 + s) * LAPLACE_MINP as i32) as u32; - fs = LAPLACE_MINP.min(32768 - fl); - } else { - fs += LAPLACE_MINP; - if s == 0 { - fl += fs; - } - } - } - e.encode(fl, fl + fs, 32768); - } - - #[test] - fn laplace_roundtrip() { - for &(fs, decay) in &[(72u32 << 7, 127u32 << 6), (24 << 7, 179 << 6), (177 << 7, 11 << 6)] { - let vals: Vec = (-12..=12).collect(); - let mut e = RangeEncoder::new(400); - for &v in &vals { - laplace_encode(&mut e, v, fs, decay); - } - let buf = e.done(); - let mut d = RangeDecoder::new(&buf); - for &v in &vals { - assert_eq!(laplace_decode(&mut d, fs, decay), v, "fs={fs} v={v}"); - } - } - } -} diff --git a/crates/opus/src/celt/mdct.rs b/crates/opus/src/celt/mdct.rs deleted file mode 100644 index 13dc4a366..000000000 --- a/crates/opus/src/celt/mdct.rs +++ /dev/null @@ -1,205 +0,0 @@ -//! Inverse MDCT with the low-overlap window and in-place TDAC (RFC 6716 §4.3.7). -//! -//! The IMDCT is computed through an N/4-point complex FFT (pre-rotation, FFT, post-rotation), -//! producing the N/2 "folded" time samples; the window overlap region is then unfolded against the -//! folded tail of the previous block, which the caller keeps in place in the output buffer. - -#[derive(Clone, Copy, Default, Debug)] -pub struct Cpx { - pub re: f32, - pub im: f32, -} - -impl Cpx { - #[inline] - fn mul(self, o: Cpx) -> Cpx { - Cpx { re: self.re * o.re - self.im * o.im, im: self.re * o.im + self.im * o.re } - } - #[inline] - fn add(self, o: Cpx) -> Cpx { - Cpx { re: self.re + o.re, im: self.im + o.im } - } -} - -/// Mixed-radix (2, 3, 4, 5) forward complex FFT, unscaled. -pub struct Fft { - n: usize, - factors: Vec, - twiddle: Vec, -} - -impl Fft { - pub fn new(n: usize) -> Fft { - let mut factors = Vec::new(); - let mut m = n; - for p in [4usize, 2, 3, 5] { - while m.is_multiple_of(p) { - factors.push(p); - m /= p; - } - } - assert_eq!(m, 1, "unsupported FFT size {n}"); - let twiddle = (0..n) - .map(|k| { - let ph = -2.0 * std::f64::consts::PI * k as f64 / n as f64; - Cpx { re: ph.cos() as f32, im: ph.sin() as f32 } - }) - .collect(); - Fft { n, factors, twiddle } - } - - pub fn process(&self, data: &mut [Cpx], scratch: &mut Vec) { - scratch.clear(); - scratch.extend_from_slice(&data[..self.n]); - self.rec(&mut data[..self.n], scratch, 1, self.n, 0, 1); - } - - fn rec(&self, out: &mut [Cpx], inp: &[Cpx], in_stride: usize, n: usize, fi: usize, tw_stride: usize) { - let p = self.factors[fi]; - let m = n / p; - if m == 1 { - for (i, o) in out.iter_mut().enumerate().take(p) { - *o = inp[i * in_stride]; - } - } else { - for q in 0..p { - self.rec(&mut out[q * m..(q + 1) * m], &inp[q * in_stride..], in_stride * p, m, fi + 1, tw_stride * p); - } - } - let big = self.n; - let mut t = [Cpx::default(); 5]; - for k in 0..m { - for q in 0..p { - let w = self.twiddle[(q * k * tw_stride) % big]; - t[q] = out[q * m + k].mul(w); - } - for q2 in 0..p { - let mut acc = Cpx::default(); - for (q, tq) in t.iter().enumerate().take(p) { - let w = self.twiddle[((q * q2) % p) * (big / p)]; - acc = acc.add(tq.mul(w)); - } - out[q2 * m + k] = acc; - } - } - } -} - -pub struct Imdct { - /// Full MDCT length (2x the number of coefficients). - n: usize, - trig: Vec, - fft: Fft, -} - -impl Imdct { - pub fn new(n: usize) -> Imdct { - let trig = (0..n / 2).map(|i| (2.0 * std::f64::consts::PI * (i as f64 + 0.125) / n as f64).cos() as f32).collect(); - Imdct { n, trig, fft: Fft::new(n / 4) } - } - - /// Inverse transform of `n/2` coefficients read from `input` at `stride`. - /// - /// Writes the folded block to `out[overlap/2 .. overlap/2 + n/2]` and unfolds the first - /// `overlap` samples against the previous block's folded tail already stored in - /// `out[0 .. overlap/2]`. - pub fn backward(&self, input: &[f32], stride: usize, out: &mut [f32], window: &[f32], overlap: usize, work: &mut ImdctScratch) { - let n2 = self.n >> 1; - let n4 = self.n >> 2; - let t = &self.trig; - let z = &mut work.z; - z.clear(); - for i in 0..n4 { - let x1 = input[2 * i * stride]; - let x2 = input[stride * (n2 - 1 - 2 * i)]; - let yr = x2 * t[i] + x1 * t[n4 + i]; - let yi = x1 * t[i] - x2 * t[n4 + i]; - z.push(Cpx { re: yi, im: yr }); - } - self.fft.process(z, &mut work.scratch); - let base = overlap >> 1; - let y = &mut out[base..base + n2]; - for i in 0..n4.div_ceil(2) { - let front = z[i]; - let back = z[n4 - 1 - i]; - let (re, im) = (front.im, front.re); - let (t0, t1) = (t[i], t[n4 + i]); - let yr0 = re * t0 + im * t1; - let yi0 = re * t1 - im * t0; - let (re, im) = (back.im, back.re); - let (t0, t1) = (t[n4 - i - 1], t[n2 - i - 1]); - let yr1 = re * t0 + im * t1; - let yi1 = re * t1 - im * t0; - y[2 * i] = yr0; - y[n2 - 1 - 2 * i] = yi0; - y[n2 - 2 - 2 * i] = yr1; - y[2 * i + 1] = yi1; - } - // Mirror on both sides for TDAC. - for i in 0..overlap / 2 { - let x1 = out[overlap - 1 - i]; - let x2 = out[i]; - let w1 = window[i]; - let w2 = window[overlap - 1 - i]; - out[i] = x2 * w2 - x1 * w1; - out[overlap - 1 - i] = x2 * w1 + x1 * w2; - } - } -} - -#[derive(Default)] -pub struct ImdctScratch { - z: Vec, - scratch: Vec, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn fft_matches_dft() { - for &n in &[60usize, 120, 240, 480] { - let fft = Fft::new(n); - let x: Vec = (0..n).map(|i| Cpx { re: ((i * 7) % 13) as f32 - 6.0, im: ((i * 3) % 5) as f32 - 2.0 }).collect(); - let mut y = x.clone(); - fft.process(&mut y, &mut Vec::new()); - for k in [0, 1, 7, n / 2, n - 1] { - let mut acc = (0f64, 0f64); - for (i, v) in x.iter().enumerate() { - let ph = -2.0 * std::f64::consts::PI * (i * k) as f64 / n as f64; - acc.0 += v.re as f64 * ph.cos() - v.im as f64 * ph.sin(); - acc.1 += v.re as f64 * ph.sin() + v.im as f64 * ph.cos(); - } - assert!((acc.0 - y[k].re as f64).abs() < 1e-2 && (acc.1 - y[k].im as f64).abs() < 1e-2, "n={n} k={k}"); - } - } - } - - /// The folded output must equal the direct IMDCT formula folded by TDAC symmetry. - #[test] - fn imdct_matches_direct_formula() { - let n = 240; - let n2 = n / 2; - let im = Imdct::new(n); - let x: Vec = (0..n2).map(|k| ((k * 37 % 11) as f32 - 5.0) / 5.0).collect(); - let overlap = 0; - let mut out = vec![0f32; n2]; - im.backward(&x, 1, &mut out, &[], overlap, &mut ImdctScratch::default()); - // Direct IMDCT y[t] = sum X[k] cos(2pi/N (t + 1/2 + N/4)(k + 1/2)), t in 0..N. - let y: Vec = (0..n) - .map(|t| { - (0..n2) - .map(|k| x[k] as f64 * (2.0 * std::f64::consts::PI / n as f64 * (t as f64 + 0.5 + n as f64 / 4.0) * (k as f64 + 0.5)).cos()) - .sum() - }) - .collect(); - // The folded half: out[i] relates to y in the middle half; check proportionality. - let mid: Vec = (0..n2).map(|i| y[n / 4 + i]).collect(); - let dot: f64 = mid.iter().zip(&out).map(|(a, b)| a * *b as f64).sum(); - let ea: f64 = mid.iter().map(|a| a * a).sum(); - let eb: f64 = out.iter().map(|b| (*b as f64).powi(2)).sum(); - let corr = dot.abs() / (ea.sqrt() * eb.sqrt()); - assert!(corr > 0.999, "corr {corr}"); - } -} diff --git a/crates/opus/src/celt/rate.rs b/crates/opus/src/celt/rate.rs deleted file mode 100644 index c857fc371..000000000 --- a/crates/opus/src/celt/rate.rs +++ /dev/null @@ -1,584 +0,0 @@ -//! Pulse cache and bit allocation (RFC 6716 §4.3.3, §4.3.4.1). - -use super::tables::{ALLOC, EBANDS, LOG2_FRAC_TABLE, MAX_LM, NB_EBANDS, OVERLAP}; -use crate::range::{BITRES, RangeDecoder}; - -const MAX_PSEUDO: i32 = 40; -const LOG_MAX_PSEUDO: i32 = 6; -pub const MAX_FINE_BITS: i32 = 8; -const FINE_OFFSET: i32 = 21; -pub const QTHETA_OFFSET: i32 = 4; -pub const QTHETA_OFFSET_TWOPHASE: i32 = 16; -const ALLOC_STEPS: i32 = 6; - -/// Number of pulses represented by pseudo-pulse index `i`. -pub fn get_pulses(i: i32) -> i32 { - if i < 8 { i } else { (8 + (i & 7)) << ((i >> 3) - 1) } -} - -/// Conservative (rounded up) log2 of `val` with `frac` fractional bits. -pub fn log2_frac(mut val: u32, mut frac: u32) -> i32 { - let mut l = crate::range::ilog(val); - if val & val.wrapping_sub(1) != 0 { - if l > 16 { - val = ((val - 1) >> (l - 16)) + 1; - } else { - val <<= 16 - l; - } - let mut l = (l - 1) << frac; - loop { - let b = (val >> 16) as i32; - l += b << frac; - val = (val + b as u32) >> b; - val = (val * val + 0x7FFF) >> 15; - if frac == 0 { - break; - } - frac -= 1; - } - l + (val > 0x8000) as i32 - } else { - l -= 1; - l << frac - } -} - -/// PVQ codebook size V(N, K) as u128 (saturating far above 2^32). -pub fn pvq_v(n: usize, k: usize) -> u128 { - // V(N,K) = V(N-1,K) + V(N,K-1) + V(N-1,K-1); computed row by row. - if k == 0 { - return 1; - } - if n == 0 { - return 0; - } - let mut row = vec![0u128; k + 1]; // V(0, k) - row[0] = 1; - for _ in 0..n { - let mut prev_diag = row[0]; // V(n-1, 0) - // V(n, 0) = 1 - for kk in 1..=k { - let up = row[kk]; // V(n-1, kk) - let v = up.saturating_add(row[kk - 1]).saturating_add(prev_diag); - prev_diag = up; - row[kk] = v.min(u128::MAX >> 8); - } - row[0] = 1; - } - row[k] -} - -fn fits_in32(n: usize, k: i32) -> bool { - pvq_v(n, k as usize) <= u32::MAX as u128 -} - -/// Precomputed per-mode data shared by all CELT decoders. -pub struct Mode { - /// log2 of band width in 1/8 bits. - pub log_n: [i32; NB_EBANDS], - /// Index into `cache_bits` for `(LM+1)*NB_EBANDS + band` (-1 when unused). - pub cache_index: Vec, - /// `cache[0]` = max pseudo-pulses; `cache[k]` = bits(k) - 1 in 1/8 bits. - pub cache_bits: Vec, - /// Per-band maximum allocation: `[(2*LM + C-1)*NB_EBANDS + band]`. - pub caps: Vec, - pub window: [f32; OVERLAP], - pub mdct: [super::mdct::Imdct; MAX_LM + 1], -} - -impl Mode { - pub fn new() -> Mode { - let mut log_n = [0i32; NB_EBANDS]; - for (i, l) in log_n.iter_mut().enumerate() { - *l = log2_frac((EBANDS[i + 1] - EBANDS[i]) as u32, BITRES); - } - let (cache_index, cache_bits) = compute_pulse_cache(MAX_LM as i32); - let mut m = Mode { - log_n, - cache_index, - cache_bits, - caps: Vec::new(), - window: [0.0; OVERLAP], - mdct: [super::mdct::Imdct::new(1920), super::mdct::Imdct::new(960), super::mdct::Imdct::new(480), super::mdct::Imdct::new(240)], - }; - m.caps = compute_caps(&m); - for i in 0..OVERLAP { - let x = std::f64::consts::FRAC_PI_2 * (i as f64 + 0.5) / OVERLAP as f64; - let s = x.sin(); - m.window[i] = (std::f64::consts::FRAC_PI_2 * s * s).sin() as f32; - } - m - } - - #[inline] - pub fn cache(&self, lm: i32, band: usize) -> &[u8] { - let idx = self.cache_index[((lm + 1) as usize) * NB_EBANDS + band]; - &self.cache_bits[idx.max(0) as usize..] - } - - pub fn bits2pulses(&self, band: usize, lm: i32, bits: i32) -> i32 { - let cache = self.cache(lm, band); - let mut lo = 0i32; - let mut hi = cache[0] as i32; - let bits = bits - 1; - for _ in 0..LOG_MAX_PSEUDO { - let mid = (lo + hi + 1) >> 1; - if cache[mid as usize] as i32 >= bits { - hi = mid; - } else { - lo = mid; - } - } - let lo_bits = if lo == 0 { -1 } else { cache[lo as usize] as i32 }; - if bits - lo_bits <= cache[hi as usize] as i32 - bits { lo } else { hi } - } - - pub fn pulses2bits(&self, band: usize, lm: i32, pulses: i32) -> i32 { - if pulses == 0 { 0 } else { self.cache(lm, band)[pulses as usize] as i32 + 1 } - } - - /// Per-band allocation caps for this frame (`init_caps`). - pub fn init_caps(&self, lm: usize, c: usize) -> [i32; NB_EBANDS] { - let mut cap = [0i32; NB_EBANDS]; - for (i, cp) in cap.iter_mut().enumerate() { - let n = (EBANDS[i + 1] - EBANDS[i]) << lm; - *cp = (self.caps[NB_EBANDS * (2 * lm + c - 1) + i] as i32 + 64) * c as i32 * n >> 2; - } - cap - } -} - -fn compute_pulse_cache(lm_max: i32) -> (Vec, Vec) { - let nb = NB_EBANDS; - let rows = (lm_max + 2) as usize; - let mut index = vec![-1i16; nb * rows]; - let mut entries: Vec<(usize, i32, usize)> = Vec::new(); // (N, K, offset) - let mut curr = 0usize; - for i in 0..rows { - for j in 0..nb { - let n = (((EBANDS[j + 1] - EBANDS[j]) << i) >> 1) as usize; - index[i * nb + j] = -1; - // Reuse the entry of an earlier band with the same size. - 'search: for k in 0..=i { - for nn in 0..nb { - if k == i && nn >= j { - break; - } - if n == (((EBANDS[nn + 1] - EBANDS[nn]) << k) >> 1) as usize { - index[i * nb + j] = index[k * nb + nn]; - break 'search; - } - } - } - if index[i * nb + j] == -1 && n != 0 { - let mut k = 0; - while k < MAX_PSEUDO && fits_in32(n, get_pulses(k + 1)) { - k += 1; - } - index[i * nb + j] = curr as i16; - entries.push((n, k, curr)); - curr += (k + 1) as usize; - } - } - } - let mut bits = vec![0u8; curr]; - for &(n, k, off) in &entries { - let maxk = get_pulses(k) as usize; - let tmp = required_bits(n, maxk, BITRES); - for j in 1..=k { - bits[off + j as usize] = (tmp[get_pulses(j) as usize] - 1) as u8; - } - bits[off] = k as u8; - } - (index, bits) -} - -/// Bits (1/2^frac units, rounded up) needed for each K in `0..=maxk` for dimension `n`. -fn required_bits(n: usize, maxk: usize, frac: u32) -> Vec { - let mut out = vec![0i32; maxk + 1]; - if n == 1 { - for o in out.iter_mut().skip(1) { - *o = 1 << frac; - } - } else { - for (k, o) in out.iter_mut().enumerate().skip(1) { - let v = pvq_v(n, k); - *o = log2_frac(v as u32, frac); - } - } - out -} - -fn compute_caps(m: &Mode) -> Vec { - let nb = NB_EBANDS; - let mut caps = vec![0u8; (MAX_LM + 1) * 2 * nb]; - for i in 0..=MAX_LM as i32 { - for c in 1..=2i32 { - for j in 0..nb { - let mut n0 = EBANDS[j + 1] - EBANDS[j]; - let max_bits = if n0 << i == 1 { - c * (1 + MAX_FINE_BITS) << BITRES - } else { - let mut lm0 = 0; - if n0 > 2 { - n0 >>= 1; - lm0 -= 1; - } else if n0 <= 1 { - lm0 = i.min(1); - n0 <<= lm0; - } - let pcache = m.cache(lm0, j); - let mut mb = pcache[pcache[0] as usize] as i32 + 1; - let mut n = n0; - for k in 0..(i - lm0) { - mb <<= 1; - let offset = ((m.log_n[j] + ((lm0 + k) << BITRES)) >> 1) - QTHETA_OFFSET; - let num = 459 * ((2 * n - 1) * offset + mb); - let den = ((2 * n - 1) << 9) - 459; - let qb = ((num + (den >> 1)) / den).min(57); - mb += qb; - n <<= 1; - } - if c == 2 { - mb <<= 1; - let offset = ((m.log_n[j] + (i << BITRES)) >> 1) - if n == 2 { QTHETA_OFFSET_TWOPHASE } else { QTHETA_OFFSET }; - let ndof = 2 * n - 1 - (n == 2) as i32; - let p = if n == 2 { 512 } else { 487 }; - let num = p * (mb + ndof * offset); - let den = (ndof << 9) - p; - let qb = ((num + (den >> 1)) / den).min(if n == 2 { 64 } else { 61 }); - mb += qb; - } - let ndof = c * n + if c == 2 && n > 2 { 1 } else { 0 }; - let mut offset = ((m.log_n[j] + (i << BITRES)) >> 1) - FINE_OFFSET; - if n == 2 { - offset += (1 << BITRES) >> 2; - } - let num = mb + ndof * offset; - let den = (ndof - 1) << BITRES; - let qb = ((num + (den >> 1)) / den).min(MAX_FINE_BITS); - mb += c * qb << BITRES; - mb - }; - let w = c * ((EBANDS[j + 1] - EBANDS[j]) << i); - let v = (4 * max_bits / w) - 64; - caps[((i * 2 + c - 1) as usize) * nb + j] = v.clamp(0, 255) as u8; - } - } - } - caps -} - -/// Result of the allocation process. -pub struct Allocation { - pub coded_bands: usize, - pub intensity: usize, - pub dual_stereo: bool, - pub balance: i32, - pub pulses: [i32; NB_EBANDS], - pub fine_quant: [i32; NB_EBANDS], - pub fine_priority: [i32; NB_EBANDS], -} - -/// `clt_compute_allocation` (decoder side). -#[allow(clippy::too_many_arguments)] -pub fn compute_allocation( - m: &Mode, - start: usize, - end: usize, - offsets: &[i32; NB_EBANDS], - cap: &[i32; NB_EBANDS], - alloc_trim: i32, - total: i32, - c: usize, - lm: usize, - dec: &mut RangeDecoder, -) -> Allocation { - let ci = c as i32; - let lmi = lm as i32; - let mut total = total.max(0); - let mut skip_start = start; - let skip_rsv = if total >= 1 << BITRES { 1 << BITRES } else { 0 }; - total -= skip_rsv; - let mut intensity_rsv = 0; - let mut dual_stereo_rsv = 0; - if c == 2 { - intensity_rsv = LOG2_FRAC_TABLE[end - start]; - if intensity_rsv > total { - intensity_rsv = 0; - } else { - total -= intensity_rsv; - dual_stereo_rsv = if total >= 1 << BITRES { 1 << BITRES } else { 0 }; - total -= dual_stereo_rsv; - } - } - let mut bits1 = [0i32; NB_EBANDS]; - let mut bits2 = [0i32; NB_EBANDS]; - let mut thresh = [0i32; NB_EBANDS]; - let mut trim_offset = [0i32; NB_EBANDS]; - for j in start..end { - let w = EBANDS[j + 1] - EBANDS[j]; - thresh[j] = (ci << BITRES).max((3 * w << lmi << BITRES) >> 4); - trim_offset[j] = ci * w * (alloc_trim - 5 - lmi) * (end as i32 - j as i32 - 1) * (1 << (lmi + BITRES as i32)) >> 6; - if w << lmi == 1 { - trim_offset[j] -= ci << BITRES; - } - } - let nb_alloc = ALLOC.len() as i32; - let mut lo = 1; - let mut hi = nb_alloc - 1; - loop { - let mut done = false; - let mut psum = 0; - let mid = (lo + hi) >> 1; - for j in (start..end).rev() { - let n = EBANDS[j + 1] - EBANDS[j]; - let mut bitsj = ci * n * (ALLOC[mid as usize][j] as i32) << lmi >> 2; - if bitsj > 0 { - bitsj = (bitsj + trim_offset[j]).max(0); - } - bitsj += offsets[j]; - if bitsj >= thresh[j] || done { - done = true; - psum += bitsj.min(cap[j]); - } else if bitsj >= ci << BITRES { - psum += ci << BITRES; - } - } - if psum > total { - hi = mid - 1; - } else { - lo = mid + 1; - } - if lo > hi { - break; - } - } - hi = lo; - lo -= 1; - for j in start..end { - let n = EBANDS[j + 1] - EBANDS[j]; - let mut bits1j = ci * n * (ALLOC[lo as usize][j] as i32) << lmi >> 2; - let mut bits2j = if hi >= nb_alloc { cap[j] } else { ci * n * (ALLOC[hi as usize][j] as i32) << lmi >> 2 }; - if bits1j > 0 { - bits1j = (bits1j + trim_offset[j]).max(0); - } - if bits2j > 0 { - bits2j = (bits2j + trim_offset[j]).max(0); - } - if lo > 0 { - bits1j += offsets[j]; - } - bits2j += offsets[j]; - if offsets[j] > 0 { - skip_start = j; - } - bits2j = (bits2j - bits1j).max(0); - bits1[j] = bits1j; - bits2[j] = bits2j; - } - interp_bits2pulses(m, start, end, skip_start, &bits1, &bits2, &thresh, cap, total, skip_rsv, intensity_rsv, dual_stereo_rsv, c, lm, dec) -} - -#[allow(clippy::too_many_arguments)] -fn interp_bits2pulses( - m: &Mode, - start: usize, - end: usize, - skip_start: usize, - bits1: &[i32; NB_EBANDS], - bits2: &[i32; NB_EBANDS], - thresh: &[i32; NB_EBANDS], - cap: &[i32; NB_EBANDS], - mut total: i32, - skip_rsv: i32, - mut intensity_rsv: i32, - mut dual_stereo_rsv: i32, - c: usize, - lm: usize, - dec: &mut RangeDecoder, -) -> Allocation { - let ci = c as i32; - let alloc_floor = ci << BITRES; - let stereo = (c > 1) as i32; - let log_m = (lm as i32) << BITRES; - let mut lo = 0; - let mut hi = 1 << ALLOC_STEPS; - for _ in 0..ALLOC_STEPS { - let mid = (lo + hi) >> 1; - let mut psum = 0; - let mut done = false; - for j in (start..end).rev() { - let tmp = bits1[j] + (mid * bits2[j] >> ALLOC_STEPS); - if tmp >= thresh[j] || done { - done = true; - psum += tmp.min(cap[j]); - } else if tmp >= alloc_floor { - psum += alloc_floor; - } - } - if psum > total { - hi = mid; - } else { - lo = mid; - } - } - let mut bits = [0i32; NB_EBANDS]; - let mut psum = 0; - let mut done = false; - for j in (start..end).rev() { - let mut tmp = bits1[j] + (lo * bits2[j] >> ALLOC_STEPS); - if tmp < thresh[j] && !done { - tmp = if tmp >= alloc_floor { alloc_floor } else { 0 }; - } else { - done = true; - } - tmp = tmp.min(cap[j]); - bits[j] = tmp; - psum += tmp; - } - let s = start; - let mut coded_bands = end; - loop { - let j = coded_bands - 1; - if j <= skip_start { - total += skip_rsv; - break; - } - let mut left = total - psum; - let span = EBANDS[coded_bands] - EBANDS[s]; - let percoeff = left / span; - left -= span * percoeff; - let rem = (left - (EBANDS[j] - EBANDS[s])).max(0); - let band_width = EBANDS[coded_bands] - EBANDS[j]; - let mut band_bits = bits[j] + percoeff * band_width + rem; - if band_bits >= thresh[j].max(alloc_floor + (1 << BITRES)) { - if dec.bit_logp(1) { - break; - } - psum += 1 << BITRES; - band_bits -= 1 << BITRES; - } - psum -= bits[j] + intensity_rsv; - if intensity_rsv > 0 { - intensity_rsv = LOG2_FRAC_TABLE[j - s]; - } - psum += intensity_rsv; - if band_bits >= alloc_floor { - psum += alloc_floor; - bits[j] = alloc_floor; - } else { - bits[j] = 0; - } - coded_bands -= 1; - } - let intensity = if intensity_rsv > 0 { s + dec.uint((coded_bands + 1 - s) as u32) as usize } else { 0 }; - if intensity <= s { - total += dual_stereo_rsv; - dual_stereo_rsv = 0; - } - let dual_stereo = if dual_stereo_rsv > 0 { dec.bit_logp(1) } else { false }; - let mut left = total - psum; - let span = EBANDS[coded_bands] - EBANDS[s]; - let percoeff = left / span; - left -= span * percoeff; - for j in s..coded_bands { - bits[j] += percoeff * (EBANDS[j + 1] - EBANDS[j]); - } - for j in s..coded_bands { - let tmp = left.min(EBANDS[j + 1] - EBANDS[j]); - bits[j] += tmp; - left -= tmp; - } - let mut ebits = [0i32; NB_EBANDS]; - let mut fine_priority = [0i32; NB_EBANDS]; - let mut balance = 0; - for j in s..coded_bands { - let n0 = EBANDS[j + 1] - EBANDS[j]; - let n = n0 << lm; - let bit = bits[j] + balance; - let mut excess; - if n > 1 { - excess = (bit - cap[j]).max(0); - bits[j] = bit - excess; - let den = ci * n + if c == 2 && n > 2 && !dual_stereo && j < intensity { 1 } else { 0 }; - let nclogn = den * (m.log_n[j] + log_m); - let mut offset = (nclogn >> 1) - den * FINE_OFFSET; - if n == 2 { - offset += (den << BITRES) >> 2; - } - if bits[j] + offset < den * 2 << BITRES { - offset += nclogn >> 2; - } else if bits[j] + offset < den * 3 << BITRES { - offset += nclogn >> 3; - } - let mut eb = (bits[j] + offset + (den << (BITRES - 1))).max(0); - eb = (eb / den) >> BITRES; - if ci * eb > (bits[j] >> BITRES) { - eb = bits[j] >> stereo >> BITRES; - } - eb = eb.min(MAX_FINE_BITS); - ebits[j] = eb; - fine_priority[j] = (eb * (den << BITRES) >= bits[j] + offset) as i32; - bits[j] -= ci * eb << BITRES; - } else { - excess = (bit - (ci << BITRES)).max(0); - bits[j] = bit - excess; - ebits[j] = 0; - fine_priority[j] = 1; - } - if excess > 0 { - let extra_fine = (excess >> (stereo + BITRES as i32)).min(MAX_FINE_BITS - ebits[j]); - ebits[j] += extra_fine; - let extra_bits = extra_fine * ci << BITRES; - fine_priority[j] = (extra_bits >= excess - balance) as i32; - excess -= extra_bits; - } - balance = excess; - } - for j in coded_bands..end { - ebits[j] = bits[j] >> stereo >> BITRES; - bits[j] = 0; - fine_priority[j] = (ebits[j] < 1) as i32; - } - Allocation { coded_bands, intensity, dual_stereo, balance, pulses: bits, fine_quant: ebits, fine_priority } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn log2_frac_values() { - assert_eq!(log2_frac(1, 3), 0); - assert_eq!(log2_frac(2, 3), 8); - assert_eq!(log2_frac(4, 3), 16); - assert_eq!(log2_frac(6, 3), 21); - assert_eq!(log2_frac(18, 3), 34); - assert_eq!(log2_frac(22, 3), 36); - } - - #[test] - fn pvq_counts() { - // V(N,1) = 2N, V(1,K) = 2 (K>0), V(2,K) = 4K. - assert_eq!(pvq_v(5, 1), 10); - assert_eq!(pvq_v(1, 7), 2); - assert_eq!(pvq_v(2, 9), 36); - assert_eq!(pvq_v(3, 2), 18); - assert_eq!(pvq_v(0, 0), 1); - assert_eq!(pvq_v(0, 3), 0); - } - - #[test] - fn caps_match_reference_shape() { - let m = Mode::new(); - // 1-bin bands at LM=0 (mono and stereo) are capped at 224. - assert!(m.caps[..8].iter().all(|&c| c == 224)); - assert!(m.caps[21..29].iter().all(|&c| c == 224)); - let first_rows: [u8; 42] = [ - 224, 224, 224, 224, 224, 224, 224, 224, 160, 160, 160, 160, 185, 185, 185, 178, 178, 168, 134, 61, 37, 224, 224, 224, 224, 224, 224, 224, - 224, 240, 240, 240, 240, 207, 207, 207, 198, 198, 183, 144, 66, 40, - ]; - assert_eq!(&m.caps[..42], &first_rows[..]); - } -} diff --git a/crates/opus/src/celt/tables.rs b/crates/opus/src/celt/tables.rs deleted file mode 100644 index f2381f714..000000000 --- a/crates/opus/src/celt/tables.rs +++ /dev/null @@ -1,130 +0,0 @@ -//! Constant tables of the 48 kHz CELT mode (RFC 6716 §4.3). - -/// Number of energy bands. -pub const NB_EBANDS: usize = 21; -/// Band edges in units of 2.5 ms MDCT bins (Table 55). -pub const EBANDS: [i32; NB_EBANDS + 1] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 34, 40, 48, 60, 78, 100]; -/// Overlap of the low-overlap window (samples at 48 kHz). -pub const OVERLAP: usize = 120; -/// Size of the shortest MDCT (2.5 ms). -pub const SHORT_MDCT: usize = 120; -/// Largest LM (20 ms frames). -pub const MAX_LM: usize = 3; -/// Pre-emphasis coefficient. -pub const PREEMPH: f32 = 0.850_006_1; - -/// Static allocation table (Table 57), indexed `[quality][band]`, in 1/32 bit per MDCT bin. -pub const ALLOC: [[u8; NB_EBANDS]; 11] = transpose([ - [0, 90, 110, 118, 126, 134, 144, 152, 162, 172, 200], - [0, 80, 100, 110, 119, 127, 137, 145, 155, 165, 200], - [0, 75, 90, 103, 112, 120, 130, 138, 148, 158, 200], - [0, 69, 84, 93, 104, 114, 124, 132, 142, 152, 200], - [0, 63, 78, 86, 95, 103, 113, 123, 133, 143, 200], - [0, 56, 71, 80, 89, 97, 107, 117, 127, 137, 200], - [0, 49, 65, 75, 83, 91, 101, 111, 121, 131, 200], - [0, 40, 58, 70, 78, 85, 95, 105, 115, 125, 200], - [0, 34, 51, 65, 72, 78, 88, 98, 108, 118, 198], - [0, 29, 45, 59, 66, 72, 82, 92, 102, 112, 193], - [0, 20, 39, 53, 60, 66, 76, 86, 96, 106, 188], - [0, 18, 32, 47, 54, 60, 70, 80, 90, 100, 183], - [0, 10, 26, 40, 47, 54, 64, 74, 84, 94, 178], - [0, 0, 20, 31, 39, 47, 57, 67, 77, 87, 173], - [0, 0, 12, 23, 32, 41, 51, 61, 71, 81, 168], - [0, 0, 0, 15, 25, 35, 45, 55, 65, 75, 163], - [0, 0, 0, 4, 17, 29, 39, 49, 59, 69, 158], - [0, 0, 0, 0, 12, 23, 33, 43, 53, 63, 153], - [0, 0, 0, 0, 1, 16, 26, 36, 46, 56, 148], - [0, 0, 0, 0, 0, 10, 15, 20, 30, 45, 129], - [0, 0, 0, 0, 0, 1, 1, 1, 1, 20, 104], -]); - -const fn transpose(t: [[u8; 11]; NB_EBANDS]) -> [[u8; NB_EBANDS]; 11] { - let mut out = [[0u8; NB_EBANDS]; 11]; - let mut b = 0; - while b < NB_EBANDS { - let mut q = 0; - while q < 11 { - out[q][b] = t[b][q]; - q += 1; - } - b += 1; - } - out -} - -/// Mean band energy (log2 domain) removed before coarse quantisation. -pub const E_MEANS: [f32; 25] = [ - 6.4375, 6.25, 5.75, 5.3125, 5.0625, 4.8125, 4.5, 4.375, 4.875, 4.6875, 4.5625, 4.4375, 4.875, 4.625, 4.3125, 4.5, 4.375, 4.625, 4.75, 4.4375, - 3.75, 3.75, 3.75, 3.75, 3.75, -]; - -/// Inter-frame prediction coefficient (alpha) per LM, Q15. -pub const PRED_COEF: [f32; 4] = [29440.0 / 32768.0, 26112.0 / 32768.0, 21248.0 / 32768.0, 16384.0 / 32768.0]; -/// Inter-band prediction coefficient (beta) per LM for inter frames, Q15. -pub const BETA_COEF: [f32; 4] = [30147.0 / 32768.0, 22282.0 / 32768.0, 12124.0 / 32768.0, 6554.0 / 32768.0]; -/// Beta for intra frames. -pub const BETA_INTRA: f32 = 4915.0 / 32768.0; - -/// Laplace model parameters for coarse energy: `[LM][intra][2*band + {0: prob, 1: decay}]`. -pub const E_PROB_MODEL: [[[u8; 42]; 2]; 4] = [ - [ - [ - 72, 127, 65, 129, 66, 128, 65, 128, 64, 128, 62, 128, 64, 128, 64, 128, 92, 78, 92, 79, 92, 78, 90, 79, 116, 41, 115, 40, 114, 40, 132, - 26, 132, 26, 145, 17, 161, 12, 176, 10, 177, 11, - ], - [ - 24, 179, 48, 138, 54, 135, 54, 132, 53, 134, 56, 133, 55, 132, 55, 132, 61, 114, 70, 96, 74, 88, 75, 88, 87, 74, 89, 66, 91, 67, 100, 59, - 108, 50, 120, 40, 122, 37, 97, 43, 78, 50, - ], - ], - [ - [ - 83, 78, 84, 81, 88, 75, 86, 74, 87, 71, 90, 73, 93, 74, 93, 74, 109, 40, 114, 36, 117, 34, 117, 34, 143, 17, 145, 18, 146, 19, 162, 12, - 165, 10, 178, 7, 189, 6, 190, 8, 177, 9, - ], - [ - 23, 178, 54, 115, 63, 102, 66, 98, 69, 99, 74, 89, 71, 91, 73, 91, 78, 89, 86, 80, 92, 66, 93, 64, 102, 59, 103, 60, 104, 60, 117, 52, - 123, 44, 138, 35, 133, 31, 97, 38, 77, 45, - ], - ], - [ - [ - 61, 90, 93, 60, 105, 42, 107, 41, 110, 45, 116, 38, 113, 38, 112, 38, 124, 26, 132, 27, 136, 19, 140, 20, 155, 14, 159, 16, 158, 18, 170, - 13, 177, 10, 187, 8, 192, 6, 175, 9, 159, 10, - ], - [ - 21, 178, 59, 110, 71, 86, 75, 85, 84, 83, 91, 66, 88, 73, 87, 72, 92, 75, 98, 72, 105, 58, 107, 54, 115, 52, 114, 55, 112, 56, 129, 51, - 132, 40, 150, 33, 140, 29, 98, 35, 77, 42, - ], - ], - [ - [ - 42, 121, 96, 66, 108, 43, 111, 40, 117, 44, 123, 32, 120, 36, 119, 33, 127, 33, 134, 34, 139, 21, 147, 23, 152, 20, 158, 25, 154, 26, - 166, 21, 173, 16, 184, 13, 184, 10, 150, 13, 139, 15, - ], - [ - 22, 178, 63, 114, 74, 82, 84, 83, 92, 82, 103, 62, 96, 72, 96, 67, 101, 73, 107, 72, 113, 55, 118, 52, 125, 52, 118, 52, 117, 55, 135, - 49, 137, 39, 157, 32, 145, 29, 97, 33, 77, 40, - ], - ], -]; - -/// Inverse CDF for coarse energy when only a few bits remain. -pub const SMALL_ENERGY_ICDF: [u8; 3] = [2, 1, 0]; -/// Conservative log2 in 1/8 bits of the number of coded bands (intensity reservation). -pub const LOG2_FRAC_TABLE: [i32; 24] = [0, 8, 13, 16, 19, 21, 23, 24, 26, 27, 28, 29, 30, 31, 32, 32, 33, 34, 34, 35, 36, 36, 37, 37]; -/// TF resolution changes: `[LM][4*transient + 2*tf_select + tf_flag]`. -pub const TF_SELECT_TABLE: [[i8; 8]; 4] = - [[0, -1, 0, -1, 0, -1, 0, -1], [0, -1, 0, -2, 1, 0, 1, -1], [0, -2, 0, -3, 2, 0, 1, -1], [0, -2, 0, -3, 3, 0, 1, -1]]; -/// Spreading decision `{7, 2, 21, 2}/32`. -pub const SPREAD_ICDF: [u8; 4] = [25, 23, 2, 0]; -/// Allocation trim (Table 58). -pub const TRIM_ICDF: [u8; 11] = [126, 124, 119, 109, 87, 41, 19, 9, 4, 2, 0]; -/// Post-filter tapset `{2, 1, 1}/4`. -pub const TAPSET_ICDF: [u8; 3] = [2, 1, 0]; -/// Post-filter tap gains per tapset. -pub const COMB_GAINS: [[f32; 3]; 3] = - [[0.306_640_62, 0.217_041_02, 0.129_638_67], [0.463_867_2, 0.268_066_4, 0.0], [0.799_804_7, 0.100_097_656, 0.0]]; - -/// Hadamard "sequency" ordering for 2, 4, 8 and 16 blocks, concatenated. -pub const ORDERY: [usize; 30] = [1, 0, 3, 0, 2, 1, 7, 0, 4, 3, 6, 1, 5, 2, 15, 0, 8, 7, 12, 3, 11, 4, 14, 1, 9, 6, 13, 2, 10, 5]; diff --git a/crates/opus/src/decoder.rs b/crates/opus/src/decoder.rs deleted file mode 100644 index cc3b01a0d..000000000 --- a/crates/opus/src/decoder.rs +++ /dev/null @@ -1,477 +0,0 @@ -//! Single-stream Opus decoder: mode switching, hybrid summation, redundancy and transitions -//! (RFC 6716 §4, §4.5). - -use crate::celt::CeltDecoder; -use crate::celt::tables::OVERLAP; -use crate::packet::{Bandwidth, Mode, Packet}; -use crate::range::RangeDecoder; -use crate::silk::{LostFlag, SilkDecoder}; -use crate::{Error, Result}; - -/// Output sample rates supported by the decoder. -pub const SAMPLE_RATES: [u32; 5] = [8000, 12000, 16000, 24000, 48000]; - -/// Decoder for one elementary Opus stream (mono or stereo), like `OpusDecoder`. -pub struct StreamDecoder { - rate: u32, - channels: usize, - celt: CeltDecoder, - silk: SilkDecoder, - stream_channels: usize, - bandwidth: Bandwidth, - mode: Option, - prev_mode: Option, - frame_size: usize, - prev_redundancy: bool, - last_packet_duration: usize, - range_final: u32, - /// Output gain in Q8 dB (OpusHead output gain). - gain_q8: i16, -} - -impl StreamDecoder { - /// Creates a decoder producing `channels` (1 or 2) at `rate` (8/12/16/24/48 kHz). - pub fn new(rate: u32, channels: usize) -> Result { - if !SAMPLE_RATES.contains(&rate) { - return Err(Error::InvalidArgument("sample rate must be 8000, 12000, 16000, 24000 or 48000")); - } - if !(1..=2).contains(&channels) { - return Err(Error::InvalidArgument("channels must be 1 or 2")); - } - Ok(StreamDecoder { - rate, - channels, - celt: CeltDecoder::new(rate, channels), - silk: SilkDecoder::new(rate, channels), - stream_channels: channels, - bandwidth: Bandwidth::Fullband, - mode: None, - prev_mode: None, - frame_size: rate as usize / 400, - prev_redundancy: false, - last_packet_duration: 0, - range_final: 0, - gain_q8: 0, - }) - } - - pub fn sample_rate(&self) -> u32 { - self.rate - } - - pub fn channels(&self) -> usize { - self.channels - } - - /// Final range coder state of the last decoded frame (for conformance checks). - pub fn final_range(&self) -> u32 { - self.range_final - } - - /// Duration (samples per channel) of the last decoded packet. - pub fn last_packet_duration(&self) -> usize { - self.last_packet_duration - } - - /// Sets the output gain in Q8 dB (as in the OpusHead header). - pub fn set_gain_q8(&mut self, gain: i16) { - self.gain_q8 = gain; - } - - /// Disables the 180-degree intensity-stereo phase inversion (RFC 8251 §10). - pub fn set_phase_inversion_disabled(&mut self, disabled: bool) { - self.celt.disable_inv = disabled; - } - - /// `OPUS_RESET_STATE`. - pub fn reset(&mut self) { - self.celt.reset(); - self.silk.reset(); - self.stream_channels = self.channels; - self.bandwidth = Bandwidth::Fullband; - self.mode = None; - self.prev_mode = None; - self.frame_size = self.rate as usize / 400; - self.prev_redundancy = false; - self.last_packet_duration = 0; - self.range_final = 0; - } - - /// Decodes a packet into interleaved samples appended to `out`. `None` conceals one lost - /// packet of the last packet's duration. Returns samples per channel. - pub fn decode_interleaved(&mut self, packet: Option<&[u8]>, out: &mut Vec) -> Result { - match packet { - Some(p) if !p.is_empty() => self.decode_packet(p, out), - _ => { - let n = if self.last_packet_duration > 0 { self.last_packet_duration } else { self.rate as usize / 50 }; - self.conceal(n, out) - } - } - } - - /// Conceals `n` samples (a multiple of 2.5 ms). - pub fn conceal(&mut self, n: usize, out: &mut Vec) -> Result { - let f2_5 = self.rate as usize / 400; - if n == 0 || !n.is_multiple_of(f2_5) { - return Err(Error::InvalidArgument("concealment length must be a multiple of 2.5 ms")); - } - let start = out.len(); - out.resize(start + n * self.channels, 0.0); - let mut done = 0; - while done < n { - let r = self.decode_frame(None, &mut out[start + done * self.channels..], n - done, false)?; - done += r; - } - self.last_packet_duration = n; - Ok(n) - } - - /// Decodes the in-band FEC (LBRR) of `packet` for the previous (lost) packet of `n` samples. - pub fn decode_fec(&mut self, packet: &[u8], n: usize, out: &mut Vec) -> Result { - let p = Packet::parse(packet)?; - let pfs = p.toc.frame_samples(self.rate); - if n < pfs || p.toc.mode == Mode::CeltOnly || self.mode == Some(Mode::CeltOnly) { - return self.conceal(n, out); - } - let start = out.len(); - if n > pfs { - self.conceal(n - pfs, out)?; - } - self.mode = Some(p.toc.mode); - self.bandwidth = p.toc.bandwidth; - self.frame_size = pfs; - self.stream_channels = p.toc.channels(); - let off = out.len(); - out.resize(off + pfs * self.channels, 0.0); - self.decode_frame(Some(p.frames[0]), &mut out[off..], pfs, true)?; - self.last_packet_duration = n; - debug_assert_eq!(out.len() - start, n * self.channels); - Ok(n) - } - - fn decode_packet(&mut self, data: &[u8], out: &mut Vec) -> Result { - let p = Packet::parse(data)?; - let fs = p.toc.frame_samples(self.rate); - self.mode = Some(p.toc.mode); - self.bandwidth = p.toc.bandwidth; - self.frame_size = fs; - self.stream_channels = p.toc.channels(); - let start = out.len(); - out.resize(start + fs * p.frames.len() * self.channels, 0.0); - let mut nb = 0; - for f in &p.frames { - let r = self.decode_frame(Some(f), &mut out[start + nb * self.channels..], fs, false)?; - nb += r; - } - out.truncate(start + nb * self.channels); - self.last_packet_duration = nb; - Ok(nb) - } - - /// `opus_decode_frame`: decodes one frame (or conceals when `data` is `None`/≤1 byte). - fn decode_frame(&mut self, data: Option<&[u8]>, pcm: &mut [f32], frame_size: usize, fec: bool) -> Result { - let ch = self.channels; - let fs = self.rate as usize; - let f20 = fs / 50; - let f10 = f20 >> 1; - let f5 = f10 >> 1; - let f2_5 = f5 >> 1; - let mut frame_size = frame_size.min(fs / 25 * 3); - let data = match data { - Some(d) if d.len() > 1 => Some(d), - _ => { - frame_size = frame_size.min(self.frame_size); - None - } - }; - let (mut audiosize, mode, bandwidth); - match data { - Some(_) => { - audiosize = self.frame_size; - mode = self.mode.unwrap_or(Mode::CeltOnly); - bandwidth = Some(self.bandwidth); - } - None => { - audiosize = frame_size; - bandwidth = None; - mode = match self.prev_mode { - None => { - pcm[..audiosize * ch].fill(0.0); - return Ok(audiosize); - } - Some(m) => m, - }; - if audiosize > f20 { - self.conceal_long(pcm, audiosize, f20)?; - return Ok(frame_size); - } - audiosize = shorten_concealment(audiosize, mode, fs); - } - } - let data_len = data.map_or(0, <[_]>::len); - let mut dec_storage; - let mut dec: Option<&mut RangeDecoder> = match data { - Some(d) => { - dec_storage = RangeDecoder::new(d); - Some(&mut dec_storage) - } - None => None, - }; - let mut transition = false; - if data.is_some() - && self.prev_mode.is_some() - && ((mode == Mode::CeltOnly && self.prev_mode != Some(Mode::CeltOnly) && !self.prev_redundancy) - || (mode != Mode::CeltOnly && self.prev_mode == Some(Mode::CeltOnly))) - { - transition = true; - } - let mut pcm_transition = Vec::new(); - if transition && mode == Mode::CeltOnly { - pcm_transition.resize(f5 * ch, 0.0); - let n = f5.min(audiosize); - self.decode_frame(None, &mut pcm_transition, n, false)?; - } - if audiosize > frame_size { - return Err(Error::InvalidArgument("frame larger than output buffer")); - } - frame_size = audiosize; - - // SILK. - let mut pcm_silk: Vec = Vec::new(); - if mode != Mode::CeltOnly { - pcm_silk = self.decode_silk_layer( - dec.as_deref_mut(), - SilkLayer { mode, bandwidth, lost: lost_flag(data.is_some(), fec), frame_size, audiosize, f10 }, - )?; - } - - let mut start_band = 0; - let mut redundancy = Redundancy::none(data_len); - if !fec - && mode != Mode::CeltOnly - && let Some(d) = dec.as_deref_mut() - { - redundancy = Redundancy::parse(d, mode, self.mode == Some(Mode::Hybrid), data_len); - } - let Redundancy { present: redundancy, celt_to_silk, bytes: redundancy_bytes, len } = redundancy; - if mode != Mode::CeltOnly { - start_band = 17; - } - if redundancy { - transition = false; - } - if transition && mode != Mode::CeltOnly { - pcm_transition.resize(f5 * ch, 0.0); - let n = f5.min(audiosize); - self.decode_frame(None, &mut pcm_transition, n, false)?; - } - if let Some(bw) = bandwidth { - self.celt.end = bw.celt_end_band(); - } - self.celt.stream_channels = self.stream_channels; - let mut redundant_audio = vec![0f32; if redundancy { f5 * ch } else { 0 }]; - let mut redundant_rng = 0u32; - let red_data = data.map(|d| &d[len..len + redundancy_bytes]); - if redundancy && celt_to_silk { - self.celt.start = 0; - self.celt.decode(red_data, f5, &mut redundant_audio, false, None)?; - redundant_rng = self.celt.rng; - } - self.celt.start = start_band; - - if mode != Mode::SilkOnly { - let celt_frame = f20.min(frame_size); - if self.prev_mode.is_some() && Some(mode) != self.prev_mode && !self.prev_redundancy { - self.celt.reset(); - } - let cdata = if fec { None } else { data.map(|d| &d[..len]) }; - let cdec = if fec { None } else { dec.as_deref_mut() }; - self.celt.decode(cdata, celt_frame, pcm, false, cdec)?; - } else { - pcm[..frame_size * ch].fill(0.0); - if self.prev_mode == Some(Mode::Hybrid) && !(redundancy && celt_to_silk && self.prev_redundancy) { - self.celt.start = 0; - let silence = [0xFFu8, 0xFF]; - self.celt.decode(Some(&silence), f2_5, pcm, true, None)?; - } - } - if mode != Mode::CeltOnly { - for (o, s) in pcm[..frame_size * ch].iter_mut().zip(&pcm_silk) { - *o += *s; - } - } - let window = &crate::celt::mode().window; - if redundancy && !celt_to_silk { - self.celt.reset(); - self.celt.start = 0; - self.celt.decode(red_data, f5, &mut redundant_audio, false, None)?; - redundant_rng = self.celt.rng; - let o = ch * (frame_size - f2_5); - let tail: Vec = pcm[o..o + f2_5 * ch].to_vec(); - smooth_fade(&tail, &redundant_audio[ch * f2_5..], &mut pcm[o..], f2_5, ch, window, self.rate); - } - if redundancy && celt_to_silk { - self.fade_in_from(pcm, &redundant_audio, f2_5); - } - if transition { - if audiosize >= f5 { - self.fade_in_from(pcm, &pcm_transition, f2_5); - } else { - let cur: Vec = pcm[..ch * f2_5].to_vec(); - smooth_fade(&pcm_transition, &cur, pcm, f2_5, ch, window, self.rate); - } - } - if self.gain_q8 != 0 { - let g = (6.488_141e-4f32 * self.gain_q8 as f32).exp2(); - for v in pcm[..frame_size * ch].iter_mut() { - *v *= g; - } - } - self.range_final = match dec.as_deref() { - Some(d) if data_len > 1 => d.rng() ^ redundant_rng, - _ => 0, - }; - self.prev_mode = Some(mode); - self.prev_redundancy = redundancy && !celt_to_silk; - Ok(audiosize) - } - - /// Conceals a lost span longer than 20 ms by decoding it in 20 ms pieces. - fn conceal_long(&mut self, pcm: &mut [f32], audiosize: usize, f20: usize) -> Result<()> { - let mut done = 0; - while done < audiosize { - done += self.decode_frame(None, &mut pcm[done * self.channels..], (audiosize - done).min(f20), false)?; - } - Ok(()) - } - - /// Replaces the first `f2_5` samples of `pcm` with `src` and cross-fades the following `f2_5` samples from `src` into `pcm`. - fn fade_in_from(&self, pcm: &mut [f32], src: &[f32], f2_5: usize) { - let ch = self.channels; - let window = &crate::celt::mode().window; - pcm[..f2_5 * ch].copy_from_slice(&src[..f2_5 * ch]); - let cur: Vec = pcm[ch * f2_5..ch * 2 * f2_5].to_vec(); - smooth_fade(&src[ch * f2_5..], &cur, &mut pcm[ch * f2_5..], f2_5, ch, window, self.rate); - } - - /// Runs the SILK layer over the frame, zero-filling spans it cannot decode when the packet was lost. - fn decode_silk_layer(&mut self, mut dec: Option<&mut RangeDecoder>, l: SilkLayer) -> Result> { - let ch = self.channels; - let mut pcm_silk = vec![0.0; l.frame_size.max(l.f10) * ch]; - if self.prev_mode == Some(Mode::CeltOnly) { - self.silk.reset(); - } - let payload_ms = (1000 * l.audiosize / self.rate as usize).max(10); - if l.lost != LostFlag::Lost { - self.silk.set_stream(self.stream_channels, silk_internal_rate(l.mode, l.bandwidth)); - } - let mut decoded = 0; - while decoded < l.frame_size { - let first = decoded == 0; - let out = &mut pcm_silk[decoded * ch..]; - let n = match self.silk.decode(dec.as_deref_mut(), l.lost, first, payload_ms, out) { - Ok(n) => n, - Err(_) if l.lost != LostFlag::Normal => { - let n = l.frame_size - decoded; - pcm_silk[decoded * ch..(decoded + n) * ch].fill(0.0); - n - } - Err(e) => return Err(e), - }; - if n == 0 { - break; - } - decoded += n; - } - Ok(pcm_silk) - } -} - -/// Inputs for one SILK-layer decode. -struct SilkLayer { - mode: Mode, - bandwidth: Option, - lost: LostFlag, - frame_size: usize, - audiosize: usize, - f10: usize, -} - -fn lost_flag(has_data: bool, fec: bool) -> LostFlag { - if !has_data { - LostFlag::Lost - } else if fec { - LostFlag::Lbrr - } else { - LostFlag::Normal - } -} - -fn silk_internal_rate(mode: Mode, bandwidth: Option) -> u32 { - match (mode, bandwidth) { - (Mode::SilkOnly, Some(Bandwidth::Narrowband)) => 8000, - (Mode::SilkOnly, Some(Bandwidth::Mediumband)) => 12000, - _ => 16000, - } -} - -/// Picks the shorter concealment length for spans of at most 20 ms (10 ms, or 5 ms outside SILK-only mode). -fn shorten_concealment(audiosize: usize, mode: Mode, rate: usize) -> usize { - let f20 = rate / 50; - let f10 = f20 >> 1; - let f5 = f10 >> 1; - if audiosize >= f20 { - audiosize - } else if audiosize > f10 { - f10 - } else if mode != Mode::SilkOnly && audiosize > f5 && audiosize < f10 { - f5 - } else { - audiosize - } -} - -/// In-band redundancy header of a packet: whether a redundant CELT frame follows, its direction and size. -struct Redundancy { - present: bool, - celt_to_silk: bool, - bytes: usize, - /// Payload length left for the main frame. - len: usize, -} - -impl Redundancy { - fn none(len: usize) -> Self { - Self { present: false, celt_to_silk: false, bytes: 0, len } - } - - fn parse(d: &mut RangeDecoder, mode: Mode, prev_hybrid: bool, len: usize) -> Self { - let mut r = Self::none(len); - if d.tell() + 17 + 20 * prev_hybrid as i32 > 8 * len as i32 { - return r; - } - r.present = if mode == Mode::Hybrid { d.bit_logp(12) } else { true }; - if !r.present { - return r; - } - r.celt_to_silk = d.bit_logp(1); - r.bytes = if mode == Mode::Hybrid { d.uint(256) as usize + 2 } else { len - ((d.tell() as usize + 7) >> 3) }; - if r.bytes > len || (len - r.bytes) * 8 < d.tell() as usize { - r = Self { celt_to_silk: r.celt_to_silk, ..Self::none(0) }; - } else { - r.len -= r.bytes; - } - d.shrink_storage(r.bytes); - r - } -} - -fn smooth_fade(in1: &[f32], in2: &[f32], out: &mut [f32], overlap: usize, ch: usize, window: &[f32; OVERLAP], rate: u32) { - let inc = (48000 / rate) as usize; - for c in 0..ch { - for i in 0..overlap { - let w = window[i * inc] * window[i * inc]; - out[i * ch + c] = w * in2[i * ch + c] + (1.0 - w) * in1[i * ch + c]; - } - } -} diff --git a/crates/opus/src/header.rs b/crates/opus/src/header.rs deleted file mode 100644 index 90423aecc..000000000 --- a/crates/opus/src/header.rs +++ /dev/null @@ -1,196 +0,0 @@ -//! Opus identification header (`OpusHead`, RFC 7845 §5.1) and the ISO-BMFF `dOps` box payload. - -use crate::{Error, Result}; - -/// Decoder configuration: channel layout, multistream mapping, pre-skip and output gain. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct OpusHead { - /// Output channel count (1..=255). - pub channels: usize, - /// Samples (at 48 kHz) to discard from the start of the decoded stream. - pub pre_skip: u16, - /// Sample rate of the original input (informational only). - pub input_sample_rate: u32, - /// Output gain in Q7.8 dB. - pub output_gain: i16, - /// Channel mapping family (0: mono/stereo, 1: Vorbis order surround, 255: unspecified order). - pub mapping_family: u8, - /// Number of elementary streams. - pub streams: usize, - /// Number of coupled (stereo) streams; these come first. - pub coupled_streams: usize, - /// Output channel → decoded channel index (255 = silence). - pub mapping: Vec, -} - -impl OpusHead { - /// A family-0 configuration for plain mono/stereo streams (no header present). - pub fn simple(channels: usize) -> Result { - if !(1..=2).contains(&channels) { - return Err(Error::InvalidArgument("family 0 supports 1 or 2 channels")); - } - Ok(OpusHead { - channels, - pre_skip: 0, - input_sample_rate: 48000, - output_gain: 0, - mapping_family: 0, - streams: 1, - coupled_streams: channels - 1, - mapping: (0..channels as u8).collect(), - }) - } - - /// Parses an `OpusHead` packet (Ogg first packet, Matroska `CodecPrivate`). - pub fn parse(data: &[u8]) -> Result { - if data.len() < 19 || &data[..8] != b"OpusHead" { - return Err(Error::InvalidHeader("missing OpusHead magic")); - } - let version = data[8]; - if version >> 4 != 0 { - return Err(Error::Unsupported("OpusHead major version")); - } - let channels = data[9] as usize; - let pre_skip = u16::from_le_bytes([data[10], data[11]]); - let input_sample_rate = u32::from_le_bytes([data[12], data[13], data[14], data[15]]); - let output_gain = i16::from_le_bytes([data[16], data[17]]); - let family = data[18]; - Self::build(channels, pre_skip, input_sample_rate, output_gain, family, &data[19..]) - } - - /// Parses the payload of an ISO-BMFF `dOps` box (Opus in MP4, big-endian fields). - pub fn from_dops(data: &[u8]) -> Result { - if data.len() < 11 { - return Err(Error::InvalidHeader("dOps too short")); - } - if data[0] != 0 { - return Err(Error::Unsupported("dOps version")); - } - let channels = data[1] as usize; - let pre_skip = u16::from_be_bytes([data[2], data[3]]); - let input_sample_rate = u32::from_be_bytes([data[4], data[5], data[6], data[7]]); - let output_gain = i16::from_be_bytes([data[8], data[9]]); - Self::build(channels, pre_skip, input_sample_rate, output_gain, data[10], &data[11..]) - } - - /// Serialises as an `OpusHead` packet. - pub fn to_bytes(&self) -> Vec { - let mut v = b"OpusHead".to_vec(); - v.push(1); - v.push(self.channels as u8); - v.extend(self.pre_skip.to_le_bytes()); - v.extend(self.input_sample_rate.to_le_bytes()); - v.extend(self.output_gain.to_le_bytes()); - v.push(self.mapping_family); - if self.mapping_family != 0 { - v.push(self.streams as u8); - v.push(self.coupled_streams as u8); - v.extend(&self.mapping); - } - v - } - - fn build(channels: usize, pre_skip: u16, input_sample_rate: u32, output_gain: i16, family: u8, rest: &[u8]) -> Result { - if channels == 0 { - return Err(Error::InvalidHeader("zero channels")); - } - let (streams, coupled, mapping) = if family == 0 { - if channels > 2 { - return Err(Error::InvalidHeader("family 0 allows at most 2 channels")); - } - (1, channels - 1, (0..channels as u8).collect::>()) - } else { - if family == 3 { - return Err(Error::Unsupported("channel mapping family 3 (projection)")); - } - if rest.len() < 2 + channels { - return Err(Error::InvalidHeader("truncated channel mapping table")); - } - let streams = rest[0] as usize; - let coupled = rest[1] as usize; - if streams == 0 || coupled > streams || streams + coupled > 255 { - return Err(Error::InvalidHeader("bad stream counts")); - } - if family == 1 && !(1..=8).contains(&channels) { - return Err(Error::InvalidHeader("family 1 allows 1 to 8 channels")); - } - let mapping = rest[2..2 + channels].to_vec(); - if mapping.iter().any(|&m| m != 255 && m as usize >= streams + coupled) { - return Err(Error::InvalidHeader("channel mapping out of range")); - } - (streams, coupled, mapping) - }; - Ok(OpusHead { channels, pre_skip, input_sample_rate, output_gain, mapping_family: family, streams, coupled_streams: coupled, mapping }) - } - - /// Checks the invariants the decoder indexes by, for headers built by hand rather than parsed. - pub fn validate(&self) -> Result<()> { - if self.channels == 0 { - return Err(Error::InvalidHeader("zero channels")); - } - if self.streams == 0 || self.coupled_streams > self.streams || self.streams + self.coupled_streams > 255 { - return Err(Error::InvalidHeader("bad stream counts")); - } - if self.mapping.len() != self.channels { - return Err(Error::InvalidHeader("channel mapping length differs from channel count")); - } - if self.mapping.iter().any(|&m| m != 255 && m as usize >= self.streams + self.coupled_streams) { - return Err(Error::InvalidHeader("channel mapping out of range")); - } - Ok(()) - } - - /// Linear output gain factor. - pub fn gain_linear(&self) -> f32 { - 10f32.powf(self.output_gain as f32 / (20.0 * 256.0)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parse_family0_and_roundtrip() { - let mut h = b"OpusHead".to_vec(); - h.extend([1, 2, 0x38, 0x01, 0x80, 0xBB, 0, 0, 0x00, 0x01, 0]); - let head = OpusHead::parse(&h).unwrap(); - assert_eq!(head.channels, 2); - assert_eq!(head.pre_skip, 312); - assert_eq!(head.input_sample_rate, 48000); - assert_eq!(head.output_gain, 256); - assert_eq!((head.streams, head.coupled_streams), (1, 1)); - assert_eq!(head.to_bytes(), h); - assert!((head.gain_linear() - 1.122).abs() < 1e-3); - } - - #[test] - fn parse_family1_51() { - let mut h = b"OpusHead".to_vec(); - h.extend([1, 6, 0x38, 0x01, 0x80, 0xBB, 0, 0, 0, 0, 1, 4, 2, 0, 4, 1, 2, 3, 5]); - let head = OpusHead::parse(&h).unwrap(); - assert_eq!((head.channels, head.streams, head.coupled_streams), (6, 4, 2)); - assert_eq!(head.mapping, vec![0, 4, 1, 2, 3, 5]); - assert_eq!(OpusHead::parse(&head.to_bytes()).unwrap(), head); - } - - #[test] - fn rejects_garbage() { - assert!(OpusHead::parse(b"OpusHea").is_err()); - assert!(OpusHead::parse(b"OpusTags...........").is_err()); - let mut h = b"OpusHead".to_vec(); - h.extend([1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0]); - assert!(OpusHead::parse(&h).is_err()); - let mut h = b"OpusHead".to_vec(); - h.extend([1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 5]); - assert!(OpusHead::parse(&h).is_err()); - } - - #[test] - fn dops() { - let d = [0u8, 2, 0x01, 0x38, 0, 0, 0xBB, 0x80, 0, 0, 0]; - let head = OpusHead::from_dops(&d).unwrap(); - assert_eq!(head.pre_skip, 312); - assert_eq!(head.channels, 2); - } -} diff --git a/crates/opus/src/lib.rs b/crates/opus/src/lib.rs deleted file mode 100644 index 6a909ea8a..000000000 --- a/crates/opus/src/lib.rs +++ /dev/null @@ -1,46 +0,0 @@ -//! Clean-room pure-Rust Opus decoder (RFC 6716, updated by RFC 8251). -//! -//! Layer L0: no dependencies beyond `std`; no `unsafe`; builds for `wasm32-unknown-unknown`. - -// Spec-style indexed loops (FilmCraft allows this lint workspace-wide). -#![allow(clippy::needless_range_loop)] -#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable))] -// The fixed-point parts mirror the normative integer arithmetic; keep C-like shift expressions. -#![allow(clippy::precedence, clippy::int_plus_one)] - -pub(crate) mod celt; -mod decoder; -mod header; -mod multistream; -pub mod packet; -pub(crate) mod range; -pub(crate) mod silk; - -pub use decoder::{SAMPLE_RATES, StreamDecoder}; -pub use header::OpusHead; -pub use multistream::Decoder; -pub use packet::{Bandwidth, Mode, Packet, Toc}; - -/// Errors from header parsing and decoding. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Error { - InvalidArgument(&'static str), - InvalidPacket(&'static str), - InvalidHeader(&'static str), - Unsupported(&'static str), -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Error::InvalidArgument(s) => write!(f, "invalid argument: {s}"), - Error::InvalidPacket(s) => write!(f, "invalid packet: {s}"), - Error::InvalidHeader(s) => write!(f, "invalid header: {s}"), - Error::Unsupported(s) => write!(f, "unsupported: {s}"), - } - } -} - -impl std::error::Error for Error {} - -pub type Result = std::result::Result; diff --git a/crates/opus/src/multistream.rs b/crates/opus/src/multistream.rs deleted file mode 100644 index e3042ca09..000000000 --- a/crates/opus/src/multistream.rs +++ /dev/null @@ -1,198 +0,0 @@ -//! Public decoder: OpusHead-configured (multistream) decoding to planar f32. - -use crate::decoder::StreamDecoder; -use crate::header::OpusHead; -use crate::packet::Packet; -use crate::{Error, Result}; - -/// Opus decoder for an `OpusHead` configuration (mono, stereo, or multistream surround). -/// -/// Produces planar f32 at the chosen output rate. Pre-skip is *not* removed unless -/// [`Decoder::set_trim_pre_skip`] is enabled (containers usually handle it via edit lists / -/// granule positions). The header's output gain is applied. -pub struct Decoder { - head: OpusHead, - rate: u32, - streams: Vec, - trim_pre_skip: bool, - skip_remaining: usize, - scratch: Vec>, -} - -impl Decoder { - /// Creates a 48 kHz decoder from an `OpusHead` packet (Ogg / Matroska `CodecPrivate`). - pub fn new(opus_head: &[u8]) -> Result { - Self::from_head(OpusHead::parse(opus_head)?, 48000) - } - - /// Creates a decoder for plain mono/stereo packets without a header. - pub fn simple(sample_rate: u32, channels: usize) -> Result { - Self::from_head(OpusHead::simple(channels)?, sample_rate) - } - - /// Creates a decoder for a parsed header at `sample_rate` (8/12/16/24/48 kHz). - pub fn from_head(head: OpusHead, sample_rate: u32) -> Result { - head.validate()?; - let mut streams = Vec::with_capacity(head.streams); - for s in 0..head.streams { - let ch = if s < head.coupled_streams { 2 } else { 1 }; - let mut d = StreamDecoder::new(sample_rate, ch)?; - d.set_gain_q8(head.output_gain); - streams.push(d); - } - Ok(Decoder { rate: sample_rate, streams, trim_pre_skip: false, skip_remaining: 0, scratch: vec![Vec::new(); head.streams], head }) - } - - pub fn head(&self) -> &OpusHead { - &self.head - } - - pub fn sample_rate(&self) -> u32 { - self.rate - } - - pub fn channels(&self) -> usize { - self.head.channels - } - - /// Pre-skip in samples at the output rate. - pub fn pre_skip(&self) -> usize { - self.head.pre_skip as usize * self.rate as usize / 48000 - } - - /// When enabled, the first [`Self::pre_skip`] output samples are dropped. - pub fn set_trim_pre_skip(&mut self, enable: bool) { - self.trim_pre_skip = enable; - self.skip_remaining = if enable { self.pre_skip() } else { 0 }; - } - - /// Disables intensity-stereo phase inversion (useful when downmixing; RFC 8251 section 10). - pub fn set_phase_inversion_disabled(&mut self, disabled: bool) { - for s in &mut self.streams { - s.set_phase_inversion_disabled(disabled); - } - } - - /// Final range coder state of the first stream (conformance testing). - pub fn final_range(&self) -> u32 { - self.streams.first().map_or(0, |s| s.final_range()) - } - - pub fn reset(&mut self) { - for s in &mut self.streams { - s.reset(); - } - self.skip_remaining = if self.trim_pre_skip { self.pre_skip() } else { 0 }; - } - - /// Decodes one packet (`None` = lost packet, concealed) into planar channels. - pub fn decode(&mut self, packet: Option<&[u8]>) -> Result>> { - let n = self.streams.len(); - for b in &mut self.scratch { - b.clear(); - } - let mut samples = None; - match packet { - Some(data) if !data.is_empty() => { - let mut off = 0; - for s in 0..n { - let rest = &data[off..]; - if rest.is_empty() { - return Err(Error::InvalidPacket("truncated multistream packet")); - } - let used = if s + 1 < n { Packet::parse_self_delimited(rest)?.1 } else { rest.len() }; - let pkt = &rest[..used]; - let r = if s + 1 < n { - // Re-frame the self-delimited packet as a regular one for the stream decoder. - let (p, _) = Packet::parse_self_delimited(pkt)?; - let regular = reframe(&p); - self.streams[s].decode_interleaved(Some(®ular), &mut self.scratch[s])? - } else { - self.streams[s].decode_interleaved(Some(pkt), &mut self.scratch[s])? - }; - if *samples.get_or_insert(r) != r { - return Err(Error::InvalidPacket("multistream frame durations differ")); - } - off += used; - } - } - _ => { - let dur = match self.streams[0].last_packet_duration() { - 0 => self.rate as usize / 50, - d => d, - }; - for s in 0..n { - self.streams[s].conceal(dur, &mut self.scratch[s])?; - } - samples = Some(dur); - } - } - let samples = samples.unwrap_or(0); - let mut out = vec![vec![0f32; samples]; self.head.channels]; - for (c, o) in out.iter_mut().enumerate() { - let m = self.head.mapping[c] as usize; - if m == 255 { - continue; - } - let (s, ch, nch) = if m < 2 * self.head.coupled_streams { (m / 2, m % 2, 2) } else { (m - self.head.coupled_streams, 0, 1) }; - let src = &self.scratch[s]; - for (i, v) in o.iter_mut().enumerate() { - *v = src[i * nch + ch]; - } - } - if self.skip_remaining > 0 { - let k = self.skip_remaining.min(samples); - for o in &mut out { - o.drain(..k); - } - self.skip_remaining -= k; - } - Ok(out) - } -} - -/// Rebuilds a regular (code 3 VBR) packet from a parsed self-delimited one. -fn reframe(p: &Packet) -> Vec { - let mut v = vec![(p.toc.config << 3) | if p.toc.stereo { 4 } else { 0 } | 3]; - v.push(0x80 | p.frames.len() as u8); - for f in &p.frames[..p.frames.len() - 1] { - let s = f.len(); - if s < 252 { - v.push(s as u8); - } else { - let b0 = 252 + (s & 3); - v.push(b0 as u8); - v.push(((s - b0) >> 2) as u8); - } - } - for f in &p.frames { - v.extend_from_slice(f); - } - v -} - -#[cfg(test)] -mod tests { - use super::*; - - fn head(channels: usize, streams: usize, coupled: usize, mapping: Vec) -> OpusHead { - OpusHead { channels, pre_skip: 0, input_sample_rate: 48000, output_gain: 0, mapping_family: 1, streams, coupled_streams: coupled, mapping } - } - - #[test] - fn from_head_rejects_inconsistent_headers() { - assert!(Decoder::from_head(head(1, 0, 0, vec![0]), 48000).is_err(), "zero streams"); - assert!(Decoder::from_head(head(2, 1, 0, vec![0]), 48000).is_err(), "short mapping"); - assert!(Decoder::from_head(head(1, 1, 0, vec![1]), 48000).is_err(), "mapping index out of range"); - assert!(Decoder::from_head(head(1, 1, 2, vec![0]), 48000).is_err(), "more coupled than streams"); - assert!(Decoder::from_head(head(0, 1, 0, vec![]), 48000).is_err(), "zero channels"); - } - - #[test] - fn from_head_accepts_valid_headers() { - assert!(Decoder::from_head(head(1, 1, 0, vec![0]), 48000).is_ok()); - assert!(Decoder::from_head(head(2, 1, 1, vec![0, 1]), 48000).is_ok()); - assert!(Decoder::from_head(head(3, 2, 1, vec![0, 1, 2]), 48000).is_ok()); - assert!(Decoder::from_head(head(2, 1, 0, vec![0, 255]), 48000).is_ok()); - } -} diff --git a/crates/opus/src/packet.rs b/crates/opus/src/packet.rs deleted file mode 100644 index a4a3f2e26..000000000 --- a/crates/opus/src/packet.rs +++ /dev/null @@ -1,340 +0,0 @@ -//! TOC byte and frame packing (RFC 6716 §3). - -use crate::Error; - -/// Coding mode of an Opus frame. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Mode { - SilkOnly, - Hybrid, - CeltOnly, -} - -/// Audio bandwidth. -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub enum Bandwidth { - /// 4 kHz. - Narrowband, - /// 6 kHz. - Mediumband, - /// 8 kHz. - Wideband, - /// 12 kHz. - SuperWideband, - /// 20 kHz. - Fullband, -} - -impl Bandwidth { - /// Number of CELT bands coded for this bandwidth. - pub fn celt_end_band(self) -> usize { - match self { - Bandwidth::Narrowband => 13, - Bandwidth::Mediumband | Bandwidth::Wideband => 17, - Bandwidth::SuperWideband => 19, - Bandwidth::Fullband => 21, - } - } -} - -/// Decoded table-of-contents byte. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct Toc { - pub config: u8, - pub mode: Mode, - pub bandwidth: Bandwidth, - pub stereo: bool, - /// Frame count code (0..=3). - pub code: u8, -} - -impl Toc { - pub fn parse(b: u8) -> Toc { - let config = b >> 3; - let (mode, bandwidth) = match config { - 0..=3 => (Mode::SilkOnly, Bandwidth::Narrowband), - 4..=7 => (Mode::SilkOnly, Bandwidth::Mediumband), - 8..=11 => (Mode::SilkOnly, Bandwidth::Wideband), - 12..=13 => (Mode::Hybrid, Bandwidth::SuperWideband), - 14..=15 => (Mode::Hybrid, Bandwidth::Fullband), - 16..=19 => (Mode::CeltOnly, Bandwidth::Narrowband), - 20..=23 => (Mode::CeltOnly, Bandwidth::Wideband), - 24..=27 => (Mode::CeltOnly, Bandwidth::SuperWideband), - _ => (Mode::CeltOnly, Bandwidth::Fullband), - }; - Toc { config, mode, bandwidth, stereo: b & 4 != 0, code: b & 3 } - } - - /// Samples per frame at 48 kHz. - pub fn frame_samples_48k(&self) -> usize { - self.frame_samples(48000) - } - - /// Samples per frame at `rate`. - pub fn frame_samples(&self, rate: u32) -> usize { - let rate = rate as usize; - let c = self.config as usize; - match self.mode { - Mode::CeltOnly => (rate << (c & 3)) / 400, - Mode::Hybrid => { - if c & 1 != 0 { - rate / 50 - } else { - rate / 100 - } - } - Mode::SilkOnly => match c & 3 { - 3 => rate * 60 / 1000, - s => (rate << s) / 100, - }, - } - } - - pub fn channels(&self) -> usize { - if self.stereo { 2 } else { 1 } - } -} - -/// A parsed packet: TOC plus the byte ranges of each frame. -#[derive(Clone, Debug)] -pub struct Packet<'a> { - pub toc: Toc, - pub frames: Vec<&'a [u8]>, - /// Bytes of padding (code 3 only). - pub padding: usize, -} - -fn parse_size(data: &[u8]) -> Option<(usize, usize)> { - match data.first() { - None => None, - Some(&b) if b < 252 => Some((b as usize, 1)), - Some(&b) => data.get(1).map(|&b1| (4 * b1 as usize + b as usize, 2)), - } -} - -impl<'a> Packet<'a> { - /// Parses a packet (RFC 6716 section 3.2), enforcing requirements R1 to R7. - pub fn parse(data: &'a [u8]) -> Result, Error> { - Self::parse_impl(data, false).map(|(p, _)| p) - } - - /// Parses a self-delimited packet (RFC 6716 Appendix B, used for all but the last stream of - /// a multistream packet); returns the packet and the number of bytes it occupies. - pub fn parse_self_delimited(data: &'a [u8]) -> Result<(Packet<'a>, usize), Error> { - Self::parse_impl(data, true) - } - - fn parse_impl(data: &'a [u8], self_delimited: bool) -> Result<(Packet<'a>, usize), Error> { - const BAD: Error = Error::InvalidPacket("malformed packet framing"); - if data.is_empty() { - return Err(Error::InvalidPacket("empty packet")); - } - let toc = Toc::parse(data[0]); - let fs = toc.frame_samples_48k(); - let mut pos = 1usize; - let mut len = data.len() as isize - 1; - let mut sizes: Vec = Vec::new(); - let mut padding = 0usize; - let mut cbr = false; - let count; - let mut last_size = len; - let read_size = |pos: &mut usize, len: &mut isize| -> Result { - let avail = &data[*pos..(*pos + (*len).max(0) as usize).min(data.len())]; - let (s, n) = parse_size(avail).ok_or(BAD)?; - *pos += n; - *len -= n as isize; - Ok(s as isize) - }; - match toc.code { - 0 => count = 1, - 1 => { - count = 2; - cbr = true; - if !self_delimited { - if len & 1 != 0 { - return Err(BAD); - } - last_size = len / 2; - sizes.push(last_size); - } - } - 2 => { - count = 2; - let s0 = read_size(&mut pos, &mut len)?; - if s0 > len { - return Err(BAD); - } - sizes.push(s0); - last_size = len - s0; - } - _ => { - if len < 1 { - return Err(BAD); - } - let ch = data[pos]; - pos += 1; - len -= 1; - count = (ch & 0x3F) as usize; - if count == 0 || fs * count > 5760 { - return Err(BAD); - } - if ch & 0x40 != 0 { - loop { - if len <= 0 { - return Err(BAD); - } - let b = data[pos]; - pos += 1; - len -= 1; - let tmp = if b == 255 { 254 } else { b as isize }; - len -= tmp; - padding += tmp as usize; - if b != 255 { - break; - } - } - } - if len < 0 { - return Err(BAD); - } - cbr = ch & 0x80 == 0; - if !cbr { - last_size = len; - for _ in 0..count - 1 { - let before = len; - let s = read_size(&mut pos, &mut len)?; - if s > len { - return Err(BAD); - } - sizes.push(s); - last_size -= (before - len) + s; - } - if last_size < 0 { - return Err(BAD); - } - } else if !self_delimited { - last_size = len / count as isize; - if last_size * count as isize != len { - return Err(BAD); - } - for _ in 0..count - 1 { - sizes.push(last_size); - } - } - } - } - if self_delimited { - let before = len; - let s = read_size(&mut pos, &mut len)?; - if s > len { - return Err(BAD); - } - if cbr { - if s * count as isize > len { - return Err(BAD); - } - sizes = vec![s; count - 1]; - } else if (before - len) + s > last_size { - return Err(BAD); - } - last_size = s; - } - if last_size > 1275 || sizes.iter().any(|&s| s > 1275) { - return Err(BAD); - } - sizes.push(last_size); - let mut frames = Vec::with_capacity(count); - for &s in &sizes { - let s = s as usize; - if pos + s > data.len() { - return Err(BAD); - } - frames.push(&data[pos..pos + s]); - pos += s; - } - let total = if self_delimited { pos + padding } else { data.len() }; - if total > data.len() { - return Err(BAD); - } - Ok((Packet { toc, frames, padding }, total)) - } -} - -/// Number of samples (at 48 kHz) in a packet, or an error for malformed framing. -pub fn packet_samples_48k(data: &[u8]) -> Result { - let p = Packet::parse(data)?; - Ok(p.toc.frame_samples_48k() * p.frames.len()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn toc_configs() { - for cfg in 0u8..32 { - let t = Toc::parse(cfg << 3); - let ms10 = t.frame_samples(48000) * 10 / 48; // tenths of ms - let expect = match cfg { - 0..=11 => [100, 200, 400, 600][cfg as usize & 3], - 12..=15 => [100, 200][cfg as usize & 1], - _ => [25, 50, 100, 200][cfg as usize & 3], - }; - assert_eq!(ms10, expect, "config {cfg}"); - } - assert!(Toc::parse(0x04).stereo); - } - - #[test] - fn frame_codes() { - // code 0 - let p = Packet::parse(&[0x00, 1, 2, 3]).unwrap(); - assert_eq!(p.frames, vec![&[1u8, 2, 3][..]]); - // code 1 (odd payload is invalid) - assert!(Packet::parse(&[0x01, 1, 2, 3]).is_err()); - let p = Packet::parse(&[0x01, 1, 2, 3, 4]).unwrap(); - assert_eq!(p.frames.len(), 2); - // code 2 - let p = Packet::parse(&[0x02, 1, 9, 8, 7]).unwrap(); - assert_eq!(p.frames, vec![&[9u8][..], &[8u8, 7][..]]); - assert!(Packet::parse(&[0x02, 5, 1]).is_err()); - // code 3 CBR with padding: 2 frames of 2 bytes, 3 bytes padding - let p = Packet::parse(&[0x03, 0x42, 3, 1, 2, 3, 4, 0, 0, 0]).unwrap(); - assert_eq!(p.frames, vec![&[1u8, 2][..], &[3u8, 4][..]]); - assert_eq!(p.padding, 3); - // code 3 VBR - let p = Packet::parse(&[0x03, 0x83, 1, 2, 10, 20, 21, 30]).unwrap(); - assert_eq!(p.frames, vec![&[10u8][..], &[20u8, 21][..], &[30u8][..]]); - // too many frames (> 120 ms) - assert!(Packet::parse(&[0x03 | (3 << 3), 0x03, 0]).is_err()); - // zero frames - assert!(Packet::parse(&[0x03, 0x00]).is_err()); - assert!(Packet::parse(&[]).is_err()); - } - - #[test] - fn two_byte_sizes() { - let mut pkt = vec![0x02u8, 252, 1]; - pkt.extend(std::iter::repeat_n(7u8, 256)); - pkt.extend([1, 2]); - let p = Packet::parse(&pkt).unwrap(); - assert_eq!(p.frames[0].len(), 256); - assert_eq!(p.frames[1], &[1, 2]); - } - - #[test] - fn self_delimited() { - // code 0 with explicit size, followed by trailing data of the next stream. - let (p, used) = Packet::parse_self_delimited(&[0x00, 2, 5, 6, 0xAA, 0xBB]).unwrap(); - assert_eq!(p.frames, vec![&[5u8, 6][..]]); - assert_eq!(used, 4); - // code 1: one size for both frames. - let (p, used) = Packet::parse_self_delimited(&[0x01, 1, 5, 6, 9]).unwrap(); - assert_eq!(p.frames, vec![&[5u8][..], &[6u8][..]]); - assert_eq!(used, 4); - // code 3 VBR, 2 frames: size of frame 0, then size of last frame. - let (p, used) = Packet::parse_self_delimited(&[0x03, 0x82, 1, 2, 7, 8, 9, 0xFF]).unwrap(); - assert_eq!(p.frames, vec![&[7u8][..], &[8u8, 9][..]]); - assert_eq!(used, 7); - } -} diff --git a/crates/opus/src/range.rs b/crates/opus/src/range.rs deleted file mode 100644 index 33ba14f2b..000000000 --- a/crates/opus/src/range.rs +++ /dev/null @@ -1,497 +0,0 @@ -//! Range decoder (RFC 6716 §4.1). -//! -//! The decoder reads range-coded symbols from the front of the buffer and raw bits from the back. -//! Reading past either end yields zeros (never panics); callers detect corruption through -//! [`RangeDecoder::tell`] exceeding the budget or through out-of-range values. - -const SYM_BITS: u32 = 8; -const CODE_BITS: u32 = 32; -const SYM_MAX: u32 = (1 << SYM_BITS) - 1; -#[cfg(test)] -const CODE_SHIFT: u32 = CODE_BITS - SYM_BITS - 1; -const CODE_TOP: u32 = 1 << (CODE_BITS - 1); -const CODE_BOT: u32 = CODE_TOP >> SYM_BITS; -const CODE_EXTRA: u32 = (CODE_BITS - 2) % SYM_BITS + 1; -/// Bit resolution of [`RangeDecoder::tell_frac`] (1/8 bit). -pub const BITRES: u32 = 3; - -/// Number of bits needed to represent `x` (0 for 0). -#[inline] -pub fn ilog(x: u32) -> i32 { - (32 - x.leading_zeros()) as i32 -} - -#[derive(Clone, Debug)] -pub struct RangeDecoder<'a> { - buf: &'a [u8], - /// Bytes available to the decoder (may be reduced, e.g. for redundancy). - storage: usize, - offs: usize, - end_offs: usize, - end_window: u32, - nend_bits: i32, - nbits_total: i32, - rng: u32, - val: u32, - rem: u32, - /// Set when an out-of-range value was decoded. - pub error: bool, -} - -impl<'a> RangeDecoder<'a> { - pub fn new(buf: &'a [u8]) -> Self { - let mut d = RangeDecoder { - buf, - storage: buf.len(), - offs: 0, - end_offs: 0, - end_window: 0, - nend_bits: 0, - nbits_total: (CODE_BITS + 1 - ((CODE_BITS - CODE_EXTRA) / SYM_BITS) * SYM_BITS) as i32, - rng: 1 << CODE_EXTRA, - val: 0, - rem: 0, - error: false, - }; - d.rem = d.read_byte(); - d.val = d.rng - 1 - (d.rem >> (SYM_BITS - CODE_EXTRA)); - d.normalize(); - d - } - - /// Size of the buffer in bytes as seen by the decoder. - pub fn storage(&self) -> usize { - self.storage - } - - /// Shrinks the buffer seen by the decoder (raw bits are then read from the new end). - pub fn shrink_storage(&mut self, by: usize) { - self.storage = self.storage.saturating_sub(by); - } - - pub fn rng(&self) -> u32 { - self.rng - } - - #[inline] - fn read_byte(&mut self) -> u32 { - if self.offs < self.storage { - let b = self.buf[self.offs]; - self.offs += 1; - b as u32 - } else { - 0 - } - } - - #[inline] - fn read_byte_from_end(&mut self) -> u32 { - if self.end_offs < self.storage { - self.end_offs += 1; - self.buf[self.storage - self.end_offs] as u32 - } else { - 0 - } - } - - #[inline] - fn normalize(&mut self) { - while self.rng <= CODE_BOT { - self.nbits_total += SYM_BITS as i32; - self.rng <<= SYM_BITS; - let sym = self.rem; - self.rem = self.read_byte(); - let sym = ((sym << SYM_BITS) | self.rem) >> (SYM_BITS - CODE_EXTRA); - self.val = ((self.val << SYM_BITS).wrapping_add(SYM_MAX & !sym)) & (CODE_TOP - 1); - } - } - - /// Returns the cumulative frequency of the next symbol for total `ft` (call [`Self::update`] next). - #[inline] - pub fn decode(&mut self, ft: u32) -> u32 { - let ext = self.rng / ft; - let s = self.val / ext; - // stash ext in rem-free form: recomputed by update() - ft - (s + 1).min(ft) - } - - /// Like [`Self::decode`] with `ft = 1 << bits`. - #[inline] - pub fn decode_bin(&mut self, bits: u32) -> u32 { - let ext = self.rng >> bits; - let s = self.val / ext; - (1 << bits) - (s + 1).min(1 << bits) - } - - /// Consumes the symbol with cumulative range `[fl, fh)` of total `ft`. - #[inline] - pub fn update(&mut self, fl: u32, fh: u32, ft: u32) { - let ext = self.rng / ft; - let s = ext * (ft - fh); - self.val = self.val.wrapping_sub(s); - self.rng = if fl > 0 { ext * (fh - fl) } else { self.rng - s }; - self.normalize(); - } - - /// Like [`Self::update`] for a symbol decoded with [`Self::decode_bin`]. - #[inline] - pub fn update_bin(&mut self, fl: u32, fh: u32, bits: u32) { - let ext = self.rng >> bits; - let ft = 1u32 << bits; - let s = ext * (ft - fh); - self.val = self.val.wrapping_sub(s); - self.rng = if fl > 0 { ext * (fh - fl) } else { self.rng - s }; - self.normalize(); - } - - /// Decodes a binary symbol whose probability of being 1 is `1/2^logp`. - #[inline] - pub fn bit_logp(&mut self, logp: u32) -> bool { - let r = self.rng; - let d = self.val; - let s = r >> logp; - let ret = d < s; - if !ret { - self.val = d - s; - } - self.rng = if ret { s } else { r - s }; - self.normalize(); - ret - } - - /// Decodes a symbol with an "inverse CDF" table (`icdf[i] = ft - cdf[i+1]`, `ft = 1 << ftb`). - #[inline] - pub fn icdf(&mut self, icdf: &[u8], ftb: u32) -> usize { - let mut s = self.rng; - let d = self.val; - let r = s >> ftb; - let mut ret = 0usize; - let mut t; - loop { - t = s; - let v = icdf.get(ret).copied().unwrap_or(0) as u32; - s = r * v; - if d >= s || ret + 1 >= icdf.len() { - break; - } - ret += 1; - } - // If the table is exhausted without d >= s, force the last symbol (only on corrupt tables). - if d < s { - s = 0; - } - self.val = d - s; - self.rng = t - s; - self.normalize(); - ret - } - - /// Decodes a uniformly distributed integer in `[0, ft)` (`ft >= 2`). - pub fn uint(&mut self, ft: u32) -> u32 { - debug_assert!(ft > 1); - let ft1 = ft - 1; - let ftb = ilog(ft1); - if ftb > 8 { - let ftb = (ftb - 8) as u32; - let top = (ft1 >> ftb) + 1; - let s = self.decode(top); - self.update(s, s + 1, top); - let t = (s << ftb) | self.bits(ftb); - if t <= ft1 { - return t; - } - self.error = true; - ft1 - } else { - let s = self.decode(ft); - self.update(s, s + 1, ft); - s - } - } - - /// Reads `bits` raw bits (0..=25) from the end of the buffer. - pub fn bits(&mut self, bits: u32) -> u32 { - if bits == 0 { - return 0; - } - let mut window = self.end_window; - let mut available = self.nend_bits; - if (available as u32) < bits { - loop { - window |= self.read_byte_from_end() << available; - available += SYM_BITS as i32; - if available > (CODE_BITS - SYM_BITS) as i32 { - break; - } - } - } - let ret = window & ((1u32 << bits) - 1); - window >>= bits; - available -= bits as i32; - self.end_window = window; - self.nend_bits = available; - self.nbits_total += bits as i32; - ret - } - - /// Advances the bit counter so that [`Self::tell`] reports `total` (CELT silence frames). - pub fn skip_to_end(&mut self, total: i32) { - let t = self.tell(); - self.nbits_total += total - t; - } - - /// Number of bits consumed so far (rounded up). - #[inline] - pub fn tell(&self) -> i32 { - self.nbits_total - ilog(self.rng) - } - - /// Number of 1/8 bits consumed so far (rounded up). - pub fn tell_frac(&self) -> u32 { - let nbits = (self.nbits_total as u32) << BITRES; - let mut l = ilog(self.rng) as u32; - let mut r = self.rng >> (l - 16); - for _ in 0..BITRES { - r = (r * r) >> 15; - let b = r >> 16; - l = (l << 1) | b; - r >>= b; - } - nbits.wrapping_sub(l) - } -} - -#[cfg(test)] -pub(crate) mod enc { - //! Minimal range encoder (RFC 6716 §5.1), used only to build test streams. - use super::*; - - pub struct RangeEncoder { - out: Vec, - end: Vec, - end_window: u32, - nend_bits: i32, - rng: u32, - low: u32, - rem: i32, - ext: u32, - size: usize, - } - - impl RangeEncoder { - pub fn new(size: usize) -> Self { - RangeEncoder { out: Vec::new(), end: Vec::new(), end_window: 0, nend_bits: 0, rng: CODE_TOP, low: 0, rem: -1, ext: 0, size } - } - fn carry_out(&mut self, c: u32) { - if c != SYM_MAX { - let carry = c >> SYM_BITS; - if self.rem >= 0 { - self.out.push((self.rem as u32 + carry) as u8); - } - if self.ext > 0 { - let sym = (SYM_MAX + carry) & SYM_MAX; - for _ in 0..self.ext { - self.out.push(sym as u8); - } - self.ext = 0; - } - self.rem = (c & SYM_MAX) as i32; - } else { - self.ext += 1; - } - } - fn normalize(&mut self) { - while self.rng <= CODE_BOT { - self.carry_out(self.low >> CODE_SHIFT); - self.low = (self.low << SYM_BITS) & (CODE_TOP - 1); - self.rng <<= SYM_BITS; - } - } - pub fn encode(&mut self, fl: u32, fh: u32, ft: u32) { - let r = self.rng / ft; - if fl > 0 { - self.low += self.rng - r * (ft - fl); - self.rng = r * (fh - fl); - } else { - self.rng -= r * (ft - fh); - } - self.normalize(); - } - pub fn bit_logp(&mut self, val: bool, logp: u32) { - let r = self.rng; - let s = r >> logp; - let r2 = r - s; - if val { - self.low += r2; - self.rng = s; - } else { - self.rng = r2; - } - self.normalize(); - } - pub fn icdf(&mut self, s: usize, icdf: &[u8], ftb: u32) { - let r = self.rng >> ftb; - if s > 0 { - self.low += self.rng - r * icdf[s - 1] as u32; - self.rng = r * (icdf[s - 1] as u32 - icdf[s] as u32); - } else { - self.rng -= r * icdf[s] as u32; - } - self.normalize(); - } - pub fn uint(&mut self, fl: u32, ft: u32) { - let ft1 = ft - 1; - let ftb = ilog(ft1); - if ftb > 8 { - let ftb = (ftb - 8) as u32; - let top = (ft1 >> ftb) + 1; - let v = fl >> ftb; - self.encode(v, v + 1, top); - self.bits(fl & ((1 << ftb) - 1), ftb); - } else { - self.encode(fl, fl + 1, ft); - } - } - pub fn bits(&mut self, val: u32, bits: u32) { - let mut window = self.end_window; - let mut used = self.nend_bits; - if used as u32 + bits > CODE_BITS { - while used >= SYM_BITS as i32 { - self.end.push((window & SYM_MAX) as u8); - window >>= SYM_BITS; - used -= SYM_BITS as i32; - } - } - window |= val << used; - used += bits as i32; - self.end_window = window; - self.nend_bits = used; - } - /// Finalises and returns a buffer of exactly `size` bytes. - pub fn done(mut self) -> Vec { - let mut l = CODE_BITS as i32 - ilog(self.rng); - let mut msk = (CODE_TOP - 1) >> l; - let mut end = (self.low + msk) & !msk; - if (end | msk) >= self.low + self.rng { - l += 1; - msk >>= 1; - end = (self.low + msk) & !msk; - } - while l > 0 { - self.carry_out(end >> CODE_SHIFT); - end = (end << SYM_BITS) & (CODE_TOP - 1); - l -= SYM_BITS as i32; - } - if self.rem >= 0 || self.ext > 0 { - self.carry_out(0); - } - let mut window = self.end_window; - let mut used = self.nend_bits; - while used > 0 { - self.end.push((window & SYM_MAX) as u8); - window >>= SYM_BITS; - used -= SYM_BITS as i32; - } - assert!(self.out.len() + self.end.len() <= self.size, "range encoder overflow"); - let mut buf = self.out; - buf.resize(self.size - self.end.len(), 0); - for &b in self.end.iter().rev() { - buf.push(b); - } - buf - } - } -} - -#[cfg(test)] -mod tests { - use super::enc::RangeEncoder; - use super::*; - - struct Lcg(u32); - impl Lcg { - fn next(&mut self) -> u32 { - self.0 = self.0.wrapping_mul(1664525).wrapping_add(1013904223); - self.0 >> 8 - } - } - - #[test] - fn roundtrip_mixed_symbols() { - let mut rng = Lcg(1); - for trial in 0..200 { - let n = 1 + (rng.next() % 300) as usize; - let mut ops = Vec::new(); - for _ in 0..n { - match rng.next() % 5 { - 0 => { - let ft = 2 + rng.next() % 1000; - let v = rng.next() % ft; - ops.push((0u8, v, ft)); - } - 1 => { - let logp = 1 + rng.next() % 15; - ops.push((1, rng.next().is_multiple_of(7) as u32, logp)); - } - 2 => { - let bits = 1 + rng.next() % 24; - ops.push((2, rng.next() & ((1 << bits) - 1), bits)); - } - 3 => { - let ft = 2 + rng.next() % 100_000_000; - ops.push((3, rng.next() % ft, ft)); - } - _ => ops.push((4, rng.next() % 4, 0)), - } - } - let icdf: [u8; 4] = [200, 120, 30, 0]; - let mut e = RangeEncoder::new(8 * n + 16); - for &(k, v, p) in &ops { - match k { - 0 => e.encode(v, v + 1, p), - 1 => e.bit_logp(v != 0, p), - 2 => e.bits(v, p), - 3 => e.uint(v, p), - _ => e.icdf(v as usize, &icdf, 8), - } - } - let buf = e.done(); - let mut d = RangeDecoder::new(&buf); - for (i, &(k, v, p)) in ops.iter().enumerate() { - let got = match k { - 0 => { - let s = d.decode(p); - d.update(s, s + 1, p); - s - } - 1 => d.bit_logp(p) as u32, - 2 => d.bits(p), - 3 => d.uint(p), - _ => d.icdf(&icdf, 8) as u32, - }; - assert_eq!(got, v, "trial {trial} op {i} kind {k}"); - } - assert!(!d.error); - } - } - - #[test] - fn tell_starts_at_one_bit() { - let buf = [0u8; 8]; - let d = RangeDecoder::new(&buf); - assert_eq!(d.tell(), 1); - assert_eq!(d.tell_frac(), 8); - } - - #[test] - fn empty_and_garbage_never_panic() { - let mut d = RangeDecoder::new(&[]); - for i in 0..100 { - d.bits(1 + i % 20); - d.uint(3 + i * 77); - d.icdf(&[250, 100, 0], 8); - d.bit_logp(1 + i % 15); - } - let _ = d.tell_frac(); - } -} diff --git a/crates/opus/src/silk.rs b/crates/opus/src/silk.rs deleted file mode 100644 index 6f739c3a0..000000000 --- a/crates/opus/src/silk.rs +++ /dev/null @@ -1,912 +0,0 @@ -//! SILK layer decoder (RFC 6716 §4.2). -//! -//! Parameter decoding and reconstruction follow the normative fixed-point arithmetic so that the -//! output matches the reference decoder sample for sample at the internal rate. Resampling to the -//! output rate uses our own linear-phase interpolator with the delays of RFC 6716 Table 54. - -pub(crate) mod lpc; -mod resampler; -#[allow(dead_code)] -pub(crate) mod tables; - -use lpc::{MAX_LPC_ORDER, bwexpander, div32_varq, inverse32_varq, nlsf_decode, nlsf2a, rshift_round, sat16, smlawb, smulwb, smulww}; -use resampler::Resampler; -use tables::{ - CB_NB_10, CB_NB_20, CB_WB_10, CB_WB_20, DELTA_GAIN, EXC_LSB, EXC_SIGN, GAIN_MSB, LBRR_FLAGS, LTP_FILTER, LTP_PERIODICITY, LTP_SCALE, LTP_TAPS_0, - LTP_TAPS_1, LTP_TAPS_2, MID_ONLY, NLSF_EXT, NLSF_INTERP, NLSF_SEL_NBMB, NLSF_SEL_WB, NLSF_STAGE1, NLSF_STAGE2, PITCH_CONTOUR, PITCH_DELTA, - PITCH_HIGH, PITCH_LOW, PULSE_COUNT, RATE_LEVEL, SHELL_2, SHELL_4, SHELL_8, SHELL_16, STEREO_PRED_JOINT, STEREO_WEIGHTS_Q13, TYPE_ACTIVE, - TYPE_INACTIVE, UNIFORM3, UNIFORM4, UNIFORM5, UNIFORM8, -}; - -use crate::range::RangeDecoder; -use crate::{Error, Result}; - -const MAX_FRAME: usize = 320; -const MAX_SUBFR: usize = 80; -const LTP_ORDER: usize = 5; -const OUT_BUF: usize = MAX_FRAME + 2 * MAX_SUBFR; - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum LostFlag { - Normal, - Lost, - Lbrr, -} - -#[derive(Clone, Copy, PartialEq, Eq)] -enum Cond { - Independently, - IndependentlyNoLtpScaling, - Conditionally, -} - -#[derive(Clone, Default)] -struct Indices { - gains: [i32; 4], - ltp_index: [usize; 4], - nlsf: [i32; 17], - lag_index: i32, - contour_index: usize, - signal_type: usize, - quant_offset_type: usize, - nlsf_interp_q2: i32, - per_index: usize, - ltp_scale_index: usize, - seed: i32, -} - -#[derive(Clone)] -struct Channel { - fs_khz: usize, - nb_subfr: usize, - frame_length: usize, - subfr_length: usize, - ltp_mem_length: usize, - lpc_order: usize, - prev_gain_q16: i32, - s_lpc_q14: [i32; MAX_LPC_ORDER], - out_buf: [i16; OUT_BUF], - lag_prev: i32, - last_gain_index: i32, - prev_signal_type: usize, - first_frame_after_reset: bool, - prev_nlsf_q15: [i32; MAX_LPC_ORDER], - ec_prev_signal_type: usize, - ec_prev_lag_index: i32, - vad_flags: [bool; 3], - lbrr_flag: bool, - lbrr_flags: [bool; 3], - n_frames_decoded: usize, - n_frames_per_packet: usize, - loss_cnt: u32, - indices: Indices, - resampler: Resampler, - // PLC memory. - plc_lpc_q12: [i32; MAX_LPC_ORDER], - plc_gain_q16: i32, - plc_pitch: i32, - plc_rand: u32, -} - -impl Channel { - fn new() -> Channel { - Channel { - fs_khz: 0, - nb_subfr: 4, - frame_length: 0, - subfr_length: 0, - ltp_mem_length: 0, - lpc_order: 0, - prev_gain_q16: 65536, - s_lpc_q14: [0; MAX_LPC_ORDER], - out_buf: [0; OUT_BUF], - lag_prev: 100, - last_gain_index: 10, - prev_signal_type: 0, - first_frame_after_reset: true, - prev_nlsf_q15: [0; MAX_LPC_ORDER], - ec_prev_signal_type: 0, - ec_prev_lag_index: 0, - vad_flags: [false; 3], - lbrr_flag: false, - lbrr_flags: [false; 3], - n_frames_decoded: 0, - n_frames_per_packet: 1, - loss_cnt: 0, - indices: Indices::default(), - resampler: Resampler::new(16000, 16000), - plc_lpc_q12: [0; MAX_LPC_ORDER], - plc_gain_q16: 0, - plc_pitch: 0, - plc_rand: 22222, - } - } - - fn set_fs(&mut self, fs_khz: usize, api_rate: u32) { - self.subfr_length = 5 * fs_khz; - let frame_length = self.nb_subfr * self.subfr_length; - if self.fs_khz != fs_khz || self.resampler.rates() != (fs_khz as u32 * 1000, api_rate) { - self.resampler = Resampler::new(fs_khz as u32 * 1000, api_rate); - } - if self.fs_khz != fs_khz || frame_length != self.frame_length { - if self.fs_khz != fs_khz { - self.ltp_mem_length = 20 * fs_khz; - self.lpc_order = if fs_khz == 16 { 16 } else { 10 }; - self.first_frame_after_reset = true; - self.lag_prev = 100; - self.last_gain_index = 10; - self.prev_signal_type = 0; - self.out_buf = [0; OUT_BUF]; - self.s_lpc_q14 = [0; MAX_LPC_ORDER]; - } - self.fs_khz = fs_khz; - self.frame_length = frame_length; - } - } - - fn decode_indices(&mut self, dec: &mut RangeDecoder, frame_index: usize, decode_lbrr: bool, cond: Cond) { - let ix = if decode_lbrr || self.vad_flags[frame_index] { dec.icdf(&TYPE_ACTIVE, 8) + 2 } else { dec.icdf(&TYPE_INACTIVE, 8) }; - let ind = &mut self.indices; - ind.signal_type = ix >> 1; - ind.quant_offset_type = ix & 1; - if cond == Cond::Conditionally { - ind.gains[0] = dec.icdf(&DELTA_GAIN, 8) as i32; - } else { - ind.gains[0] = (dec.icdf(GAIN_MSB[ind.signal_type], 8) as i32) << 3; - ind.gains[0] += dec.icdf(&UNIFORM8, 8) as i32; - } - for i in 1..self.nb_subfr { - ind.gains[i] = dec.icdf(&DELTA_GAIN, 8) as i32; - } - let wb = self.lpc_order == 16; - let s1 = (ind.signal_type >> 1) + if wb { 2 } else { 0 }; - let i1 = dec.icdf(NLSF_STAGE1[s1], 8); - ind.nlsf[0] = i1 as i32; - for i in 0..self.lpc_order { - let cb = if wb { 8 + NLSF_SEL_WB[i1][i] as usize } else { NLSF_SEL_NBMB[i1][i] as usize }; - let mut v = dec.icdf(NLSF_STAGE2[cb], 8) as i32; - if v == 0 { - v -= dec.icdf(&NLSF_EXT, 8) as i32; - } else if v == 8 { - v += dec.icdf(&NLSF_EXT, 8) as i32; - } - ind.nlsf[i + 1] = v - 4; - } - ind.nlsf_interp_q2 = if self.nb_subfr == 4 { dec.icdf(&NLSF_INTERP, 8) as i32 } else { 4 }; - if ind.signal_type == 2 { - let mut absolute = true; - if cond == Cond::Conditionally && self.ec_prev_signal_type == 2 { - let d = dec.icdf(&PITCH_DELTA, 8) as i32; - if d > 0 { - ind.lag_index = self.ec_prev_lag_index + d - 9; - absolute = false; - } - } - if absolute { - ind.lag_index = dec.icdf(&PITCH_HIGH, 8) as i32 * (self.fs_khz as i32 >> 1); - let low = match self.fs_khz { - 8 => PITCH_LOW[0], - 12 => PITCH_LOW[1], - _ => PITCH_LOW[2], - }; - ind.lag_index += dec.icdf(low, 8) as i32; - } - self.ec_prev_lag_index = ind.lag_index; - let contour = match (self.fs_khz, self.nb_subfr) { - (8, 2) => PITCH_CONTOUR[0], - (8, _) => PITCH_CONTOUR[1], - (_, 2) => PITCH_CONTOUR[2], - _ => PITCH_CONTOUR[3], - }; - ind.contour_index = dec.icdf(contour, 8); - ind.per_index = dec.icdf(<P_PERIODICITY, 8); - for k in 0..self.nb_subfr { - ind.ltp_index[k] = dec.icdf(LTP_FILTER[ind.per_index], 8); - } - ind.ltp_scale_index = if cond == Cond::Independently { dec.icdf(<P_SCALE, 8) } else { 0 }; - } - self.ec_prev_signal_type = ind.signal_type; - ind.seed = dec.icdf(&UNIFORM4, 8) as i32; - } - - fn decode_frame(&mut self, dec: Option<&mut RangeDecoder>, out: &mut [i16], lost: LostFlag, cond: Cond) { - let l = self.frame_length; - let decode = match (lost, &dec) { - (LostFlag::Normal, Some(_)) => true, - (LostFlag::Lbrr, Some(_)) => self.lbrr_flags[self.n_frames_decoded], - _ => false, - }; - if decode && let Some(dec) = dec { - self.decode_indices(dec, self.n_frames_decoded, lost == LostFlag::Lbrr, cond); - let mut pulses = [0i32; MAX_FRAME + 16]; - decode_pulses(dec, &mut pulses, self.indices.signal_type, self.indices.quant_offset_type, l); - let ctrl = self.decode_parameters(cond); - self.decode_core(&ctrl, &pulses, out); - self.plc_lpc_q12 = ctrl.pred_coef_q12[1]; - self.plc_gain_q16 = ctrl.gains_q16[self.nb_subfr - 1]; - self.plc_pitch = if self.indices.signal_type == 2 { ctrl.pitch_l[self.nb_subfr - 1] } else { 0 }; - self.loss_cnt = 0; - self.prev_signal_type = self.indices.signal_type; - self.first_frame_after_reset = false; - self.lag_prev = ctrl.pitch_l[self.nb_subfr - 1]; - } else { - self.conceal(out); - } - // Update the output buffer. - let mv = self.ltp_mem_length - l; - self.out_buf.copy_within(l..l + mv, 0); - self.out_buf[mv..mv + l].copy_from_slice(&out[..l]); - } - - fn decode_parameters(&mut self, cond: Cond) -> Ctrl { - let mut ctrl = Ctrl::default(); - let nb = self.nb_subfr; - // Gains. - let mut prev = self.last_gain_index; - for k in 0..nb { - let ind = self.indices.gains[k]; - if k == 0 && cond != Cond::Conditionally { - prev = ind.max(prev - 16); - } else { - let tmp = ind - 4; - let thr = 2 * 36 - 64 + prev; - if tmp > thr { - prev += (tmp << 1) - thr; - } else { - prev += tmp; - } - } - prev = prev.clamp(0, 63); - ctrl.gains_q16[k] = log2lin((smulwb(0x1D1C71, prev) + 2090).min(3967)); - } - self.last_gain_index = prev; - // LPC. - let order = self.lpc_order; - let nlsf = nlsf_decode(&self.indices.nlsf, order == 16); - ctrl.pred_coef_q12[1] = nlsf2a(&nlsf[..order]); - if self.first_frame_after_reset { - self.indices.nlsf_interp_q2 = 4; - } - if self.indices.nlsf_interp_q2 < 4 { - let mut n0 = [0i32; MAX_LPC_ORDER]; - for i in 0..order { - n0[i] = self.prev_nlsf_q15[i] + ((self.indices.nlsf_interp_q2 * (nlsf[i] - self.prev_nlsf_q15[i])) >> 2); - } - ctrl.pred_coef_q12[0] = nlsf2a(&n0[..order]); - } else { - ctrl.pred_coef_q12[0] = ctrl.pred_coef_q12[1]; - } - self.prev_nlsf_q15 = nlsf; - if self.loss_cnt > 0 { - bwexpander(&mut ctrl.pred_coef_q12[0][..order], 63570); - bwexpander(&mut ctrl.pred_coef_q12[1][..order], 63570); - } - if self.indices.signal_type == 2 { - let fs = self.fs_khz as i32; - let min_lag = 2 * fs; - let max_lag = 18 * fs; - let lag = min_lag + self.indices.lag_index; - for k in 0..nb { - let off = match (self.fs_khz, nb) { - (8, 2) => CB_NB_10[self.indices.contour_index][k], - (8, _) => CB_NB_20[self.indices.contour_index][k], - (_, 2) => CB_WB_10[self.indices.contour_index][k], - _ => CB_WB_20[self.indices.contour_index][k], - }; - ctrl.pitch_l[k] = (lag + off).clamp(min_lag, max_lag); - } - for k in 0..nb { - let ix = self.indices.ltp_index[k]; - let taps = match self.indices.per_index { - 0 => LTP_TAPS_0[ix.min(7)], - 1 => LTP_TAPS_1[ix.min(15)], - _ => LTP_TAPS_2[ix.min(31)], - }; - for i in 0..LTP_ORDER { - ctrl.ltp_coef_q14[k * LTP_ORDER + i] = taps[i] << 7; - } - } - ctrl.ltp_scale_q14 = [15565, 12288, 8192][self.indices.ltp_scale_index]; - } - ctrl - } - - fn decode_core(&mut self, ctrl: &Ctrl, pulses: &[i32], xq: &mut [i16]) { - let ind = &self.indices; - let l = self.frame_length; - let sub = self.subfr_length; - let order = self.lpc_order; - let ltp_mem = self.ltp_mem_length; - let mut s_ltp = [0i16; MAX_FRAME]; - let mut s_ltp_q15 = [0i32; 2 * MAX_FRAME]; - let mut res_q14 = [0i32; MAX_SUBFR]; - let mut s_lpc = [0i32; MAX_SUBFR + MAX_LPC_ORDER]; - let offset_q10 = [[100, 240], [32, 100]][ind.signal_type >> 1][ind.quant_offset_type]; - let interp = ind.nlsf_interp_q2 < 4; - let mut exc = [0i32; MAX_FRAME]; - let mut seed = ind.seed; - for i in 0..l { - seed = seed.wrapping_mul(196_314_165).wrapping_add(907_633_515); - let mut e = pulses[i] << 14; - if e > 0 { - e -= 80 << 4; - } else if e < 0 { - e += 80 << 4; - } - e += offset_q10 << 4; - if seed < 0 { - e = -e; - } - exc[i] = e; - seed = seed.wrapping_add(pulses[i]); - } - s_lpc[..MAX_LPC_ORDER].copy_from_slice(&self.s_lpc_q14); - let mut ltp_buf_idx = ltp_mem; - for k in 0..self.nb_subfr { - let a_q12 = &ctrl.pred_coef_q12[k >> 1]; - let mut b_q14 = [0i32; LTP_ORDER]; - b_q14.copy_from_slice(&ctrl.ltp_coef_q14[k * LTP_ORDER..(k + 1) * LTP_ORDER]); - let mut signal_type = ind.signal_type; - let gain_q10 = ctrl.gains_q16[k] >> 6; - let mut inv_gain_q31 = inverse32_varq(ctrl.gains_q16[k], 47); - let gain_adj_q16 = if ctrl.gains_q16[k] != self.prev_gain_q16 { - let g = div32_varq(self.prev_gain_q16, ctrl.gains_q16[k], 16); - for v in s_lpc[..MAX_LPC_ORDER].iter_mut() { - *v = smulww(g, *v); - } - g - } else { - 1 << 16 - }; - self.prev_gain_q16 = ctrl.gains_q16[k]; - let mut lag = ctrl.pitch_l[k]; - if self.loss_cnt > 0 && self.prev_signal_type == 2 && signal_type != 2 && k < 2 { - b_q14 = [0; LTP_ORDER]; - b_q14[LTP_ORDER / 2] = 1 << 12; - signal_type = 2; - lag = self.lag_prev; - } - if signal_type == 2 { - let lag_u = lag as usize; - if k == 0 || (k == 2 && interp) { - let start_idx = ltp_mem as isize - lag as isize - order as isize - (LTP_ORDER / 2) as isize; - let start_idx = start_idx.max(0) as usize; - if k == 2 { - self.out_buf[ltp_mem..ltp_mem + 2 * sub].copy_from_slice(&xq[..2 * sub]); - } - let src_off = start_idx + k * sub; - lpc_analysis_filter(&mut s_ltp[start_idx..ltp_mem], &self.out_buf[src_off..src_off + ltp_mem - start_idx], a_q12, order); - if k == 0 { - inv_gain_q31 = smulwb(inv_gain_q31, ctrl.ltp_scale_q14) << 2; - } - for i in 0..(lag_u + LTP_ORDER / 2).min(ltp_mem) { - s_ltp_q15[ltp_buf_idx - i - 1] = smulwb(inv_gain_q31, s_ltp[ltp_mem - i - 1] as i32); - } - } else if gain_adj_q16 != 1 << 16 { - for i in 0..(lag_u + LTP_ORDER / 2).min(ltp_buf_idx) { - let p = ltp_buf_idx - i - 1; - s_ltp_q15[p] = smulww(gain_adj_q16, s_ltp_q15[p]); - } - } - let base = ltp_buf_idx as isize - lag as isize + (LTP_ORDER / 2) as isize; - for i in 0..sub { - let p = base + i as isize; - let g = |o: isize| -> i32 { if p - o >= 0 { s_ltp_q15[(p - o) as usize] } else { 0 } }; - let mut pred = 2i32; - pred = smlawb(pred, g(0), b_q14[0]); - pred = smlawb(pred, g(1), b_q14[1]); - pred = smlawb(pred, g(2), b_q14[2]); - pred = smlawb(pred, g(3), b_q14[3]); - pred = smlawb(pred, g(4), b_q14[4]); - res_q14[i] = exc[k * sub + i].wrapping_add(pred << 1); - s_ltp_q15[ltp_buf_idx] = res_q14[i] << 1; - ltp_buf_idx += 1; - } - } else { - res_q14[..sub].copy_from_slice(&exc[k * sub..(k + 1) * sub]); - } - for i in 0..sub { - let mut pred = (order >> 1) as i32; - for j in 0..order { - pred = smlawb(pred, s_lpc[MAX_LPC_ORDER + i - 1 - j], a_q12[j]); - } - let v = res_q14[i].saturating_add(pred.saturating_mul(16)); - s_lpc[MAX_LPC_ORDER + i] = v; - xq[k * sub + i] = sat16(rshift_round(smulww(v, gain_q10), 8)) as i16; - } - s_lpc.copy_within(sub..sub + MAX_LPC_ORDER, 0); - } - self.s_lpc_q14.copy_from_slice(&s_lpc[..MAX_LPC_ORDER]); - } - - /// Basic concealment: pitch-periodic repetition for voiced history, otherwise LPC-shaped - /// noise at the residual level of the history; both fade out over successive losses. - fn conceal(&mut self, out: &mut [i16]) { - let l = self.frame_length; - if l == 0 { - return; - } - self.loss_cnt += 1; - let order = self.lpc_order; - let mem = self.ltp_mem_length; - let fade = 0.85f32.powi(self.loss_cnt as i32); - let hist: Vec = self.out_buf[..mem].iter().map(|&v| v as f32).collect(); - let pitch = self.plc_pitch as usize; - let mut y = hist.clone(); - if pitch > 0 && pitch < mem { - for _ in 0..l { - let v = y[y.len() - pitch] * fade; - y.push(v); - } - } else { - let mut a12 = self.plc_lpc_q12; - bwexpander(&mut a12[..order], 64880); - self.plc_lpc_q12 = a12; - let a: Vec = a12[..order].iter().map(|&v| v as f32 / 4096.0).collect(); - let mut e = 0f32; - for i in order..mem { - let mut r = hist[i]; - for j in 0..order { - r -= a[j] * hist[i - 1 - j]; - } - e += r * r; - } - let rms = (e / (mem - order).max(1) as f32).sqrt() * fade; - for _ in 0..l { - self.plc_rand = self.plc_rand.wrapping_mul(196_314_165).wrapping_add(907_633_515); - let mut v = ((self.plc_rand as i32) >> 16) as f32 / 32768.0 * rms * 1.7; - for j in 0..order { - v += a[j] * y[y.len() - 1 - j]; - } - y.push(v.clamp(-32768.0, 32767.0)); - } - } - for (o, v) in out.iter_mut().zip(&y[mem..mem + l]) { - *o = v.clamp(-32768.0, 32767.0) as i16; - } - // Keep the LPC synthesis state consistent for the next decoded frame. - let g = self.prev_gain_q16.max(1) as i64; - for j in 0..MAX_LPC_ORDER { - let v = out[l - MAX_LPC_ORDER + j] as i64; - self.s_lpc_q14[j] = ((v << 30) / g).clamp(i32::MIN as i64, i32::MAX as i64) as i32; - } - self.lag_prev = self.plc_pitch.max(1); - self.first_frame_after_reset = false; - } -} - -#[derive(Default)] -struct Ctrl { - gains_q16: [i32; 4], - pred_coef_q12: [[i32; MAX_LPC_ORDER]; 2], - pitch_l: [i32; 4], - ltp_coef_q14: [i32; 4 * LTP_ORDER], - ltp_scale_q14: i32, -} - -fn log2lin(in_log_q7: i32) -> i32 { - if in_log_q7 < 0 { - return 0; - } - if in_log_q7 >= 3967 { - return i32::MAX; - } - let out = 1i32 << (in_log_q7 >> 7); - let frac = in_log_q7 & 0x7F; - if in_log_q7 < 2048 { - out + ((out * smlawb(frac, frac * (128 - frac), -174)) >> 7) - } else { - out + (out >> 7) * smlawb(frac, frac * (128 - frac), -174) - } -} - -fn lpc_analysis_filter(out: &mut [i16], input: &[i16], b: &[i32], d: usize) { - let len = out.len(); - for ix in d..len { - let mut acc: i32 = 0; - for j in 0..d { - acc = acc.wrapping_add((input[ix - 1 - j] as i32).wrapping_mul(b[j])); - } - let v = ((input[ix] as i32) << 12).wrapping_sub(acc); - out[ix] = sat16(rshift_round(v, 12)) as i16; - } - for v in out.iter_mut().take(d.min(len)) { - *v = 0; - } -} - -fn decode_pulses(dec: &mut RangeDecoder, pulses: &mut [i32], signal_type: usize, quant_offset_type: usize, frame_length: usize) { - let rate_level = dec.icdf(RATE_LEVEL[signal_type >> 1], 8); - let mut iter = frame_length >> 4; - if iter * 16 < frame_length { - iter += 1; - } - let mut sum_pulses = [0i32; 20]; - let mut n_lshifts = [0i32; 20]; - for i in 0..iter { - let mut s = dec.icdf(PULSE_COUNT[rate_level], 8) as i32; - while s == 17 { - n_lshifts[i] += 1; - let tab = if n_lshifts[i] == 10 { PULSE_COUNT[10] } else { PULSE_COUNT[9] }; - s = dec.icdf(tab, 8) as i32; - } - sum_pulses[i] = s; - } - for i in 0..iter { - let blk = &mut pulses[i * 16..(i + 1) * 16]; - if sum_pulses[i] > 0 { - shell_decode(dec, blk, sum_pulses[i]); - } else { - blk.fill(0); - } - } - for i in 0..iter { - let nls = n_lshifts[i]; - if nls > 0 { - for p in pulses[i * 16..(i + 1) * 16].iter_mut() { - let mut a = *p; - for _ in 0..nls { - a = (a << 1) + dec.icdf(&EXC_LSB, 8) as i32; - } - *p = a; - } - sum_pulses[i] |= nls << 5; - } - } - // Signs. - let base = 7 * (quant_offset_type + (signal_type << 1)); - let len = (frame_length + 8) >> 4; - for i in 0..len { - let p = sum_pulses[i]; - if p > 0 { - let tab = EXC_SIGN[base + ((p & 0x1F) as usize).min(6)]; - for q in pulses[i * 16..(i + 1) * 16].iter_mut() { - if *q > 0 && dec.icdf(tab, 8) == 0 { - *q = -*q; - } - } - } - } -} - -fn shell_decode(dec: &mut RangeDecoder, out: &mut [i32], total: i32) { - fn split(dec: &mut RangeDecoder, out: &mut [i32], n: usize, count: i32) { - if n == 1 { - out[0] = count; - return; - } - if count == 0 { - out[..n].fill(0); - return; - } - let tab = match n { - 16 => SHELL_16[(count - 1) as usize], - 8 => SHELL_8[(count - 1) as usize], - 4 => SHELL_4[(count - 1) as usize], - _ => SHELL_2[(count - 1) as usize], - }; - let left = dec.icdf(tab, 8) as i32; - let h = n / 2; - let (a, b) = out.split_at_mut(h); - split(dec, a, h, left); - split(dec, b, h, count - left); - } - split(dec, out, 16, total.min(16)); -} - -#[derive(Default, Clone)] -struct Stereo { - pred_prev_q13: [i32; 2], - s_mid: [i16; 2], - s_side: [i16; 2], -} - -fn stereo_decode_pred(dec: &mut RangeDecoder) -> [i32; 2] { - let n = dec.icdf(&STEREO_PRED_JOINT, 8); - let mut ix = [[0usize; 3]; 2]; - ix[0][2] = n / 5; - ix[1][2] = n - 5 * ix[0][2]; - for v in ix.iter_mut() { - v[0] = dec.icdf(&UNIFORM3, 8); - v[1] = dec.icdf(&UNIFORM5, 8); - } - let mut pred = [0i32; 2]; - for (p, v) in pred.iter_mut().zip(ix.iter_mut()) { - v[0] += 3 * v[2]; - let low = STEREO_WEIGHTS_Q13[v[0]]; - let step = smulwb(STEREO_WEIGHTS_Q13[v[0] + 1] - low, 6554); - *p = low + step * (2 * v[1] as i32 + 1); - } - pred[0] -= pred[1]; - pred -} - -fn stereo_ms_to_lr(st: &mut Stereo, x1: &mut [i16], x2: &mut [i16], pred_q13: [i32; 2], fs_khz: usize, len: usize) { - x1[..2].copy_from_slice(&st.s_mid); - x2[..2].copy_from_slice(&st.s_side); - st.s_mid.copy_from_slice(&x1[len..len + 2]); - st.s_side.copy_from_slice(&x2[len..len + 2]); - let mut p0 = st.pred_prev_q13[0]; - let mut p1 = st.pred_prev_q13[1]; - let denom_q16 = (1 << 16) / (8 * fs_khz as i32); - let d0 = rshift_round((pred_q13[0] - st.pred_prev_q13[0]) as i16 as i32 * denom_q16 as i16 as i32, 16); - let d1 = rshift_round((pred_q13[1] - st.pred_prev_q13[1]) as i16 as i32 * denom_q16 as i16 as i32, 16); - let interp = 8 * fs_khz; - for n in 0..len { - if n < interp { - p0 += d0; - p1 += d1; - } else if n == interp { - p0 = pred_q13[0]; - p1 = pred_q13[1]; - } - let sum = ((x1[n] as i32 + x1[n + 2] as i32) + ((x1[n + 1] as i32) << 1)) << 9; - let mut s = smlawb((x2[n + 1] as i32) << 8, sum, p0); - s = smlawb(s, (x1[n + 1] as i32) << 11, p1); - x2[n + 1] = sat16(rshift_round(s, 8)) as i16; - } - st.pred_prev_q13 = pred_q13; - for n in 0..len { - let a = x1[n + 1] as i32; - let b = x2[n + 1] as i32; - x1[n + 1] = sat16(a + b) as i16; - x2[n + 1] = sat16(a - b) as i16; - } -} - -/// SILK decoder for up to two internal channels (`silk_decoder`). -pub struct SilkDecoder { - api_rate: u32, - api_channels: usize, - ch: [Channel; 2], - stereo: Stereo, - channels_internal: usize, - channels_internal_prev: usize, - internal_rate: u32, - prev_decode_only_middle: bool, - ms_pred: [i32; 2], - decode_only_middle: bool, -} - -impl SilkDecoder { - pub fn new(api_rate: u32, api_channels: usize) -> SilkDecoder { - SilkDecoder { - api_rate, - api_channels, - ch: [Channel::new(), Channel::new()], - stereo: Stereo::default(), - channels_internal: api_channels, - channels_internal_prev: 0, - internal_rate: 16000, - prev_decode_only_middle: false, - ms_pred: [0; 2], - decode_only_middle: false, - } - } - - pub fn reset(&mut self) { - self.ch = [Channel::new(), Channel::new()]; - self.stereo = Stereo::default(); - self.prev_decode_only_middle = false; - self.channels_internal_prev = 0; - } - - pub fn set_stream(&mut self, channels: usize, internal_rate: u32) { - self.channels_internal = channels; - self.internal_rate = internal_rate; - } - - /// Decodes one SILK frame (10 or 20 ms) into interleaved `out` at the API rate. - pub fn decode( - &mut self, - mut dec: Option<&mut RangeDecoder>, - lost: LostFlag, - new_packet: bool, - payload_ms: usize, - out: &mut [f32], - ) -> Result { - let nci = self.channels_internal; - if new_packet { - for c in self.ch.iter_mut() { - c.n_frames_decoded = 0; - } - } - if nci > self.channels_internal_prev && self.channels_internal_prev != 0 { - self.ch[1] = Channel::new(); - } - let stereo_to_mono = nci == 1 && self.channels_internal_prev == 2 && self.internal_rate == 1000 * self.ch[0].fs_khz as u32; - if self.ch[0].n_frames_decoded == 0 { - self.configure_channels(nci, payload_ms)?; - } - if self.api_channels == 2 && nci == 2 && self.channels_internal_prev == 1 { - self.stereo.pred_prev_q13 = [0; 2]; - self.stereo.s_side = [0; 2]; - self.ch[1].resampler = self.ch[0].resampler.clone(); - } - self.channels_internal_prev = nci; - - if lost != LostFlag::Lost && self.ch[0].n_frames_decoded == 0 { - let d = dec.as_deref_mut().ok_or(Error::InvalidPacket("missing SILK data"))?; - self.read_packet_flags(d, nci); - if lost == LostFlag::Normal { - self.skip_lbrr_data(d, nci); - } - } - if nci == 2 { - self.decode_stereo_prediction(dec.as_deref_mut(), lost); - } - if nci == 2 && !self.decode_only_middle && self.prev_decode_only_middle { - self.reset_side_channel(); - } - let l = self.ch[0].frame_length; - let mut buf = [[0i16; MAX_FRAME + 2]; 2]; - self.decode_channel_frames(dec, lost, nci, &mut buf); - if self.api_channels == 2 && nci == 2 { - let (a, b) = buf.split_at_mut(1); - stereo_ms_to_lr(&mut self.stereo, &mut a[0], &mut b[0], self.ms_pred, self.ch[0].fs_khz, l); - } else { - buf[0][..2].copy_from_slice(&self.stereo.s_mid); - self.stereo.s_mid.copy_from_slice(&buf[0][l..l + 2]); - } - let n_out = self.resample_to_output(&buf, nci, stereo_to_mono, out); - if lost == LostFlag::Lost { - for c in self.ch.iter_mut().take(nci) { - c.last_gain_index = 10; - } - } else { - self.prev_decode_only_middle = self.decode_only_middle; - } - Ok(n_out) - } - - /// Applies the packet's frame layout and internal sampling rate to the first `nci` channels. - fn configure_channels(&mut self, nci: usize, payload_ms: usize) -> Result<()> { - let (nfpp, nb) = match payload_ms { - 10 => (1, 2), - 20 => (1, 4), - 40 => (2, 4), - 60 => (3, 4), - _ => return Err(Error::InvalidPacket("bad SILK frame size")), - }; - let fs_khz = ((self.internal_rate >> 10) + 1) as usize; - for c in self.ch.iter_mut().take(nci) { - c.n_frames_per_packet = nfpp; - c.nb_subfr = nb; - c.set_fs(fs_khz, self.api_rate); - } - Ok(()) - } - - /// Reads the per-frame VAD flags and LBRR flags at the start of a packet. - fn read_packet_flags(&mut self, d: &mut RangeDecoder, nci: usize) { - for c in self.ch.iter_mut().take(nci) { - for i in 0..c.n_frames_per_packet { - c.vad_flags[i] = d.bit_logp(1); - } - c.lbrr_flag = d.bit_logp(1); - } - for c in self.ch.iter_mut().take(nci) { - c.lbrr_flags = [false; 3]; - if !c.lbrr_flag { - continue; - } - if c.n_frames_per_packet == 1 { - c.lbrr_flags[0] = true; - } else { - let sym = d.icdf(LBRR_FLAGS[c.n_frames_per_packet - 2], 8) + 1; - for i in 0..c.n_frames_per_packet { - c.lbrr_flags[i] = (sym >> i) & 1 != 0; - } - } - } - } - - /// Consumes (without using) the in-band LBRR frames of a normally decoded packet. - fn skip_lbrr_data(&mut self, d: &mut RangeDecoder, nci: usize) { - for i in 0..self.ch[0].n_frames_per_packet { - for n in 0..nci { - if !self.ch[n].lbrr_flags[i] { - continue; - } - if nci == 2 && n == 0 { - stereo_decode_pred(d); - if !self.ch[1].lbrr_flags[i] { - d.icdf(&MID_ONLY, 8); - } - } - let cond = if i > 0 && self.ch[n].lbrr_flags[i - 1] { Cond::Conditionally } else { Cond::Independently }; - let c = &mut self.ch[n]; - c.decode_indices(d, i, true, cond); - let mut pulses = [0i32; MAX_FRAME + 16]; - decode_pulses(d, &mut pulses, c.indices.signal_type, c.indices.quant_offset_type, c.frame_length); - } - } - } - - /// Reads the mid/side prediction weights, or reuses the previous ones when this frame carries none. - fn decode_stereo_prediction(&mut self, dec: Option<&mut RangeDecoder>, lost: LostFlag) { - let fi = self.ch[0].n_frames_decoded; - let coded = lost == LostFlag::Normal || (lost == LostFlag::Lbrr && self.ch[0].lbrr_flags[fi]); - match dec { - Some(d) if coded => { - self.ms_pred = stereo_decode_pred(d); - let side_inactive = (lost == LostFlag::Normal && !self.ch[1].vad_flags[fi]) || (lost == LostFlag::Lbrr && !self.ch[1].lbrr_flags[fi]); - self.decode_only_middle = side_inactive && d.icdf(&MID_ONLY, 8) == 1; - } - _ => self.ms_pred = self.stereo.pred_prev_q13, - } - } - - /// Resets the side channel's decoder state when it resumes after a mid-only stretch. - fn reset_side_channel(&mut self) { - let c = &mut self.ch[1]; - c.out_buf = [0; OUT_BUF]; - c.s_lpc_q14 = [0; MAX_LPC_ORDER]; - c.lag_prev = 100; - c.last_gain_index = 10; - c.prev_signal_type = 0; - c.first_frame_after_reset = true; - } - - /// Frame-coding condition for channel `n` of the frame currently being decoded. - fn frame_condition(&self, n: usize, lost: LostFlag) -> Cond { - let frame_index = self.ch[0].n_frames_decoded as isize - n as isize; - if frame_index <= 0 { - Cond::Independently - } else if lost == LostFlag::Lbrr { - if self.ch[n].lbrr_flags[frame_index as usize - 1] { Cond::Conditionally } else { Cond::Independently } - } else if n > 0 && self.prev_decode_only_middle { - Cond::IndependentlyNoLtpScaling - } else { - Cond::Conditionally - } - } - - /// Decodes the current frame of every active channel into `buf` (silence for an inactive side channel). - fn decode_channel_frames(&mut self, mut dec: Option<&mut RangeDecoder>, lost: LostFlag, nci: usize, buf: &mut [[i16; MAX_FRAME + 2]; 2]) { - let has_side = if lost == LostFlag::Normal { - !self.decode_only_middle - } else { - !self.prev_decode_only_middle || (nci == 2 && lost == LostFlag::Lbrr && self.ch[1].lbrr_flags[self.ch[1].n_frames_decoded]) - }; - let l = self.ch[0].frame_length; - for n in 0..nci { - if n == 0 || has_side { - let cond = self.frame_condition(n, lost); - self.ch[n].decode_frame(dec.as_deref_mut(), &mut buf[n][2..], lost, cond); - } else { - buf[n][2..2 + l].fill(0); - } - self.ch[n].n_frames_decoded += 1; - } - } - - /// Resamples the decoded channels to the API rate into interleaved `out`; returns samples per channel. - fn resample_to_output(&mut self, buf: &[[i16; MAX_FRAME + 2]; 2], nci: usize, stereo_to_mono: bool, out: &mut [f32]) -> usize { - let l = self.ch[0].frame_length; - let n_out = l * self.api_rate as usize / (self.ch[0].fs_khz * 1000); - let ach = self.api_channels; - let mut tmp = Vec::with_capacity(n_out); - for n in 0..ach.min(nci) { - tmp.clear(); - self.ch[n].resampler.process(&buf[n][1..1 + l], &mut tmp); - for (i, &v) in tmp.iter().enumerate().take(n_out) { - out[i * ach + n] = v / 32768.0; - } - } - if ach == 2 && nci == 1 { - if stereo_to_mono { - tmp.clear(); - self.ch[1].resampler.process(&buf[0][1..1 + l], &mut tmp); - for (i, &v) in tmp.iter().enumerate().take(n_out) { - out[i * 2 + 1] = v / 32768.0; - } - } else { - for i in 0..n_out { - out[i * 2 + 1] = out[i * 2]; - } - } - } - n_out - } -} diff --git a/crates/opus/src/silk/lpc.rs b/crates/opus/src/silk/lpc.rs deleted file mode 100644 index 32a6d0234..000000000 --- a/crates/opus/src/silk/lpc.rs +++ /dev/null @@ -1,377 +0,0 @@ -//! Fixed-point NLSF decoding, NLSF→LPC conversion and filter stabilisation -//! (RFC 6716 §4.2.7.5, with the RFC 8251 §6/§7 overflow fixes). - -use super::tables::{ - COS_Q12, LSF_ORDER_NBMB, LSF_ORDER_WB, NLSF_CB_NBMB, NLSF_CB_WB, NLSF_MIN_SPACING_NBMB, NLSF_MIN_SPACING_WB, NLSF_PRED_NBMB, NLSF_PRED_WB, - NLSF_PSEL_NBMB, NLSF_PSEL_WB, -}; - -pub const MAX_LPC_ORDER: usize = 16; - -#[inline] -pub fn smulwb(a: i32, b: i32) -> i32 { - ((a as i64 * (b as i16) as i64) >> 16) as i32 -} -#[inline] -pub fn smlawb(a: i32, b: i32, c: i32) -> i32 { - a.wrapping_add(smulwb(b, c)) -} -#[inline] -pub fn smulww(a: i32, b: i32) -> i32 { - ((a as i64 * b as i64) >> 16) as i32 -} -#[inline] -pub fn smmul(a: i32, b: i32) -> i32 { - ((a as i64 * b as i64) >> 32) as i32 -} -#[inline] -pub fn rshift_round(a: i32, s: u32) -> i32 { - if s == 1 { (a >> 1) + (a & 1) } else { ((a >> (s - 1)) + 1) >> 1 } -} -#[inline] -pub fn rshift_round64(a: i64, s: u32) -> i64 { - if s == 1 { (a >> 1) + (a & 1) } else { ((a >> (s - 1)) + 1) >> 1 } -} -#[inline] -pub fn sat16(a: i32) -> i32 { - a.clamp(-32768, 32767) -} - -/// `silk_INVERSE32_varQ`: approximation of `(1 << q_res) / b32`. -pub fn inverse32_varq(b32: i32, q_res: i32) -> i32 { - let b_headrm = b32.unsigned_abs().leading_zeros() as i32 - 1; - let b32_nrm = b32.wrapping_shl(b_headrm as u32); - let b32_inv = (i32::MAX >> 2) / (b32_nrm >> 16); - let mut result = b32_inv << 16; - let err_q32 = ((1i32 << 29).wrapping_sub(smulwb(b32_nrm, b32_inv))).wrapping_shl(3); - result = result.wrapping_add(((err_q32 as i64 * b32_inv as i64) >> 16) as i32); - let lshift = 61 - b_headrm - q_res; - if lshift <= 0 { - let s = (-lshift) as u32; - let lim_hi = i32::MAX >> s; - let lim_lo = i32::MIN >> s; - result.clamp(lim_lo, lim_hi) << s - } else if lshift < 32 { - result >> lshift - } else { - 0 - } -} - -/// `silk_DIV32_varQ`: approximation of `(a32 << q_res) / b32`. -pub fn div32_varq(a32: i32, b32: i32, q_res: i32) -> i32 { - let a_headrm = a32.unsigned_abs().leading_zeros() as i32 - 1; - let a32_nrm = a32.wrapping_shl(a_headrm as u32); - let b_headrm = b32.unsigned_abs().leading_zeros() as i32 - 1; - let b32_nrm = b32.wrapping_shl(b_headrm as u32); - let b32_inv = (i32::MAX >> 2) / (b32_nrm >> 16); - let mut result = smulwb(a32_nrm, b32_inv); - let a32_nrm = a32_nrm.wrapping_sub(((smmul(b32_nrm, result) as i64) << 3) as i32); - result = smlawb(result, a32_nrm, b32_inv); - let lshift = 29 + a_headrm - b_headrm - q_res; - if lshift < 0 { - let s = (-lshift) as u32; - let lim_hi = i32::MAX >> s; - let lim_lo = i32::MIN >> s; - result.clamp(lim_lo, lim_hi) << s - } else if lshift < 32 { - result >> lshift - } else { - 0 - } -} - -fn sqrt_approx(x: i32) -> i32 { - if x <= 0 { - return 0; - } - let lz = x.leading_zeros() as i32; - let frac = (x.rotate_right((24 - lz).rem_euclid(32) as u32)) & 0x7f; - let mut y = if lz & 1 != 0 { 32768 } else { 46214 }; - y >>= lz >> 1; - smlawb(y, y, 213 * frac) -} - -/// Decodes the stage-1/stage-2 indices into stabilised NLSFs (Q15). -pub fn nlsf_decode(indices: &[i32], wb: bool) -> [i32; MAX_LPC_ORDER] { - let order = if wb { 16 } else { 10 }; - let i1 = indices[0] as usize; - let cb1: &[i32] = if wb { &NLSF_CB_WB[i1] } else { &NLSF_CB_NBMB[i1] }; - let qstep = if wb { 9830 } else { 11796 }; - // Residual dequantisation with backwards prediction. - let mut res_q10 = [0i32; MAX_LPC_ORDER]; - let mut out_q10 = 0i32; - for i in (0..order).rev() { - let pred = if i + 1 < order { - let sel = if wb { NLSF_PSEL_WB[i1][i] } else { NLSF_PSEL_NBMB[i1][i] } as usize; - let w = if wb { NLSF_PRED_WB[sel][i] } else { NLSF_PRED_NBMB[sel][i] }; - (out_q10 * w) >> 8 - } else { - 0 - }; - let mut v = indices[i + 1] << 10; - if v > 0 { - v -= 102; - } else if v < 0 { - v += 102; - } - out_q10 = smlawb(pred, v, qstep); - res_q10[i] = out_q10; - } - // Laroia weights. - let mut nlsf = [0i32; MAX_LPC_ORDER]; - for k in 0..order { - let prev = if k == 0 { 0 } else { cb1[k - 1] }; - let next = if k + 1 == order { 256 } else { cb1[k + 1] }; - let w = (1024 / (cb1[k] - prev).max(1) + 1024 / (next - cb1[k]).max(1)).min(32767); - let w_q9 = sqrt_approx(w << 16); - let v = (cb1[k] << 7) + (res_q10[k] << 14) / w_q9.max(1); - nlsf[k] = v.clamp(0, 32767); - } - let dmin: &[i32] = if wb { &NLSF_MIN_SPACING_WB } else { &NLSF_MIN_SPACING_NBMB }; - nlsf_stabilize(&mut nlsf[..order], dmin); - nlsf -} - -pub fn nlsf_stabilize(nlsf: &mut [i32], dmin: &[i32]) { - let l = nlsf.len(); - for _ in 0..20 { - let mut min_diff = nlsf[0] - dmin[0]; - let mut idx = 0; - for i in 1..l { - let d = nlsf[i] - (nlsf[i - 1] + dmin[i]); - if d < min_diff { - min_diff = d; - idx = i; - } - } - let d = 32768 - (nlsf[l - 1] + dmin[l]); - if d < min_diff { - min_diff = d; - idx = l; - } - if min_diff >= 0 { - return; - } - if idx == 0 { - nlsf[0] = dmin[0]; - } else if idx == l { - nlsf[l - 1] = 32768 - dmin[l]; - } else { - let mut min_center = dmin[..idx].iter().sum::(); - min_center += dmin[idx] >> 1; - let mut max_center = 32768 - dmin[idx + 1..=l].iter().sum::(); - max_center -= dmin[idx] >> 1; - let c = rshift_round(nlsf[idx - 1] + nlsf[idx], 1); - let c = if min_center > max_center { c.clamp(max_center, min_center) } else { c.clamp(min_center, max_center) }; - nlsf[idx - 1] = c - (dmin[idx] >> 1); - nlsf[idx] = nlsf[idx - 1] + dmin[idx]; - } - } - // Fallback. - nlsf.sort_unstable(); - nlsf[0] = nlsf[0].max(dmin[0]); - for i in 1..l { - nlsf[i] = nlsf[i].max(sat16(nlsf[i - 1] + dmin[i])); - } - nlsf[l - 1] = nlsf[l - 1].min(32768 - dmin[l]); - for i in (0..l - 1).rev() { - nlsf[i] = nlsf[i].min(nlsf[i + 1] - dmin[i + 1]); - } -} - -fn bwexpander_32(ar: &mut [i32], mut chirp_q16: i32) { - let d = ar.len(); - let chirp_minus_one = chirp_q16 - 65536; - for v in ar.iter_mut().take(d - 1) { - *v = smulww(chirp_q16, *v); - chirp_q16 += rshift_round(chirp_q16.wrapping_mul(chirp_minus_one), 16); - } - ar[d - 1] = smulww(chirp_q16, ar[d - 1]); -} - -/// `silk_bwexpander` on Q12 coefficients (16-bit). -pub fn bwexpander(ar: &mut [i32], mut chirp_q16: i32) { - let d = ar.len(); - let chirp_minus_one = chirp_q16 - 65536; - for v in ar.iter_mut().take(d - 1) { - *v = rshift_round(chirp_q16.wrapping_mul(*v), 16); - chirp_q16 += rshift_round(chirp_q16.wrapping_mul(chirp_minus_one), 16); - } - ar[d - 1] = rshift_round(chirp_q16.wrapping_mul(ar[d - 1]), 16); -} - -fn lpc_inverse_pred_gain(a_q12: &[i32]) -> i32 { - const QA: u32 = 24; - const A_LIMIT: i32 = 16_773_022; - let order = a_q12.len(); - let mut a = [0i32; MAX_LPC_ORDER]; - let mut dc = 0; - for k in 0..order { - dc += a_q12[k]; - a[k] = a_q12[k] << (QA - 12); - } - if dc >= 4096 { - return 0; - } - let mut inv_gain: i32 = 1 << 30; - let mul32_frac_q31 = |x: i32, y: i32| rshift_round64(x as i64 * y as i64, 31) as i32; - for k in (1..order).rev() { - if a[k] > A_LIMIT || a[k] < -A_LIMIT { - return 0; - } - let rc_q31 = -(a[k] << (31 - QA)); - let rc_mult1 = (1i32 << 30) - smmul(rc_q31, rc_q31); - inv_gain = smmul(inv_gain, rc_mult1) << 2; - if inv_gain < 107_374 { - return 0; - } - let mult2q = 32 - rc_mult1.unsigned_abs().leading_zeros() as i32; - let rc_mult2 = inverse32_varq(rc_mult1, mult2q + 30); - for n in 0..(k + 1) >> 1 { - let t1 = a[n]; - let t2 = a[k - n - 1]; - let v1 = rshift_round64(t1.saturating_sub(mul32_frac_q31(t2, rc_q31)) as i64 * rc_mult2 as i64, mult2q as u32); - if v1 > i32::MAX as i64 || v1 < i32::MIN as i64 { - return 0; - } - let v2 = rshift_round64(t2.saturating_sub(mul32_frac_q31(t1, rc_q31)) as i64 * rc_mult2 as i64, mult2q as u32); - if v2 > i32::MAX as i64 || v2 < i32::MIN as i64 { - return 0; - } - a[n] = v1 as i32; - a[k - n - 1] = v2 as i32; - } - } - if a[0] > A_LIMIT || a[0] < -A_LIMIT { - return 0; - } - let rc_q31 = -(a[0] << (31 - QA)); - let rc_mult1 = (1i32 << 30) - smmul(rc_q31, rc_q31); - inv_gain = smmul(inv_gain, rc_mult1) << 2; - if inv_gain < 107_374 { - return 0; - } - inv_gain -} - -/// Converts NLSFs (Q15) to stable Q12 LPC coefficients (`silk_NLSF2A`). -pub fn nlsf2a(nlsf: &[i32]) -> [i32; MAX_LPC_ORDER] { - let d = nlsf.len(); - let ordering: &[usize] = if d == 16 { &LSF_ORDER_WB } else { &LSF_ORDER_NBMB }; - let mut c_q17 = [0i32; MAX_LPC_ORDER]; - for k in 0..d { - let i = (nlsf[k] >> 8) as usize; - let f = nlsf[k] & 255; - let c0 = COS_Q12[i]; - let c1 = COS_Q12[(i + 1).min(128)]; - c_q17[ordering[k]] = (c0 * 256 + (c1 - c0) * f + 4) >> 3; - } - let d2 = d / 2; - let find_poly = |off: usize| -> [i64; 10] { - let mut out = [0i64; 10]; - out[0] = 1 << 16; - out[1] = -(c_q17[off] as i64); - for k in 1..d2 { - let ftmp = c_q17[2 * k + off] as i64; - out[k + 1] = 2 * out[k - 1] - ((ftmp * out[k] + 32768) >> 16); - for n in (2..=k).rev() { - out[n] += out[n - 2] - ((ftmp * out[n - 1] + 32768) >> 16); - } - out[1] -= ftmp; - } - out - }; - let p = find_poly(0); - let q = find_poly(1); - let mut a32 = [0i32; MAX_LPC_ORDER]; - for k in 0..d2 { - let ptmp = p[k + 1] + p[k]; - let qtmp = q[k + 1] - q[k]; - a32[k] = (-qtmp - ptmp) as i32; - a32[d - k - 1] = (qtmp - ptmp) as i32; - } - let a32 = &mut a32[..d]; - // Limit the dynamic range (silk_LPC_fit with QIN = 17, QOUT = 12). - let mut a_q12 = [0i32; MAX_LPC_ORDER]; - let mut i = 0; - while i < 10 { - let mut maxabs = 0i32; - let mut idx = 0; - for (k, &v) in a32.iter().enumerate() { - let av = v.saturating_abs(); - if av > maxabs { - maxabs = av; - idx = k; - } - } - let maxabs = rshift_round(maxabs, 5); - if maxabs > 32767 { - let maxabs = maxabs.min(163_838); - let chirp = 65470 - ((maxabs - 32767) << 14) / ((maxabs * (idx as i32 + 1)) >> 2); - bwexpander_32(a32, chirp); - } else { - break; - } - i += 1; - } - if i == 10 { - for k in 0..d { - a_q12[k] = sat16(rshift_round(a32[k], 5)); - a32[k] = a_q12[k] << 5; - } - } else { - for k in 0..d { - a_q12[k] = rshift_round(a32[k], 5); - } - } - for i in 0..16 { - if lpc_inverse_pred_gain(&a_q12[..d]) == 0 { - bwexpander_32(a32, 65536 - (2 << i)); - for k in 0..d { - a_q12[k] = rshift_round(a32[k], 5); - } - } else { - break; - } - } - a_q12 -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn flat_nlsf_gives_small_lpc() { - // Uniformly spaced NLSFs correspond to an (almost) flat spectrum. - let nlsf: Vec = (1..=16).map(|k| k * 32768 / 17).collect(); - let a = nlsf2a(&nlsf); - assert!(a.iter().all(|v| v.abs() < 600), "{a:?}"); - let nlsf: Vec = (1..=10).map(|k| k * 32768 / 11).collect(); - let a = nlsf2a(&nlsf); - assert!(a[..10].iter().all(|v| v.abs() < 600), "{a:?}"); - } - - #[test] - fn stabilize_enforces_spacing() { - let mut v = [100, 90, 80, 30000, 30001, 30002, 31000, 32000, 32700, 32760]; - nlsf_stabilize(&mut v, &NLSF_MIN_SPACING_NBMB); - assert!(v[0] >= NLSF_MIN_SPACING_NBMB[0]); - for i in 1..10 { - assert!(v[i] - v[i - 1] >= NLSF_MIN_SPACING_NBMB[i], "{v:?}"); - } - assert!(32768 - v[9] >= NLSF_MIN_SPACING_NBMB[10]); - } - - #[test] - fn inverse_approximations() { - for &b in &[65536, 100_000, 1 << 20, 81920, 1_686_110_208] { - let inv = inverse32_varq(b, 47) as f64; - let exact = 2f64.powi(47) / b as f64; - assert!((inv - exact).abs() / exact < 1e-3, "{b}"); - } - let q = div32_varq(65536, 131072, 16) as f64; - assert!((q - 32768.0).abs() < 2.0); - } -} diff --git a/crates/opus/src/silk/resampler.rs b/crates/opus/src/silk/resampler.rs deleted file mode 100644 index b8ae6f5b6..000000000 --- a/crates/opus/src/silk/resampler.rs +++ /dev/null @@ -1,205 +0,0 @@ -//! SILK output resampler (RFC 6716 §4.2.9: non-normative filter, normative delay budget). -//! -//! A windowed-sinc polyphase interpolator whose group delay matches the delay allocation of -//! RFC 6716 Table 54 (and the integer delays used when no rate conversion is needed), so that the -//! SILK output lines up with the CELT layer in Hybrid mode. - -#[derive(Clone)] -pub struct Resampler { - fin: u32, - fout: u32, - /// Pure-delay mode (equal rates): delay line. - copy_delay: usize, - /// Polyphase taps per output phase. - phases: Vec<(i64, Vec)>, - period: u64, - /// Total delay in units of 1/fout input samples. - delay_q: i64, - half: i64, - future: i64, - hist: Vec, - /// Absolute input index of `hist[0]`. - hist_start: i64, - n_in: i64, - n_out: u64, -} - -fn gcd(a: u32, b: u32) -> u32 { - if b == 0 { a } else { gcd(b, a % b) } -} - -/// Least-squares fractional-delay interpolator: taps at offsets `-past+1 ..= future` (in input -/// samples) approximating a delay of `frac` samples over `[0, wp]`, with zero response weighted -/// over `[ws, pi]` when `ws < pi` (decimation). -fn design_fractional(past: i64, future: i64, frac: f64, wp: f64, ws: f64) -> Vec { - let offs: Vec = ((-past + 1)..=future).map(|m| m as f64).collect(); - let n = offs.len(); - // \int_a^b cos(w x) dw - let ic = |x: f64, a: f64, b: f64| if x.abs() < 1e-12 { b - a } else { ((b * x).sin() - (a * x).sin()) / x }; - let stop_w = 30.0; - const LOW_W: f64 = 0.05; - let mut q = vec![vec![0f64; n + 1]; n]; - for i in 0..n { - for k in 0..n { - let d = offs[i] - offs[k]; - let mut v = ic(d, 0.0, 0.6 * wp) + LOW_W * ic(d, 0.6 * wp, wp); - if ws < std::f64::consts::PI { - v += stop_w * ic(d, ws, std::f64::consts::PI); - } - q[i][k] = v + if i == k { 1e-9 } else { 0.0 }; - } - q[i][n] = ic(offs[i] - frac, 0.0, 0.6 * wp) + LOW_W * ic(offs[i] - frac, 0.6 * wp, wp); - } - // Gaussian elimination with partial pivoting. - for c in 0..n { - let p = (c..n).max_by(|&a, &b| q[a][c].abs().total_cmp(&q[b][c].abs())).unwrap_or(c); - q.swap(c, p); - let piv = q[c][c]; - for r in 0..n { - if r != c { - let f = q[r][c] / piv; - if f != 0.0 { - for k in c..=n { - q[r][k] -= f * q[c][k]; - } - } - } - } - } - (0..n).map(|i| q[i][n] / q[i][i]).collect() -} - -impl Resampler { - pub fn new(fin: u32, fout: u32) -> Resampler { - let mut r = Resampler { - fin, - fout, - copy_delay: 0, - phases: Vec::new(), - period: 1, - delay_q: 0, - half: 0, - future: 0, - hist: Vec::new(), - hist_start: 0, - n_in: 0, - n_out: 0, - }; - if fin == fout { - r.copy_delay = match fin { - 8000 => 4, - 12000 => 9, - _ => 12, - }; - r.hist = vec![0.0; r.copy_delay]; - return r; - } - // Delay in milliseconds per internal bandwidth (RFC 6716 Table 54). - let delay_ms = match fin { - 8000 => 0.538, - 12000 => 0.692, - _ => 0.706, - }; - let d_in = delay_ms * fin as f64 / 1000.0; // input samples - // Future taps are limited by the delay budget; past taps are free. - let future = (d_in.floor() as i64).max(1); - let past = 24i64; - r.half = past; - r.future = future; - r.delay_q = (d_in * fout as f64).round() as i64; - let g = gcd(fin, fout); - r.period = (fout / g) as u64; - let pi = std::f64::consts::PI; - // Passband edge and (for decimation) stopband edge in radians per input sample. - let (wp, ws) = if fout > fin { (0.94 * pi, pi) } else { (0.88 * pi * fout as f64 / fin as f64, pi * fout as f64 / fin as f64) }; - for j in 0..r.period as i64 { - let num = j * fin as i64 - r.delay_q; - let base = num.div_euclid(fout as i64); - let frac = num.rem_euclid(fout as i64) as f64 / fout as f64; - let taps = design_fractional(past, future, frac, wp, ws); - r.phases.push((base, taps.iter().map(|&t| t as f32).collect())); - } - r.hist = vec![0.0; (past + future + 4) as usize]; - r.hist_start = -(past + future + 4); - r - } - - pub fn rates(&self) -> (u32, u32) { - (self.fin, self.fout) - } - - /// Resamples `input` (16-bit scale) and appends the output samples. - pub fn process(&mut self, input: &[i16], out: &mut Vec) { - if self.fin == self.fout { - for &v in input { - self.hist.push(v as f32); - } - let n = self.hist.len() - self.copy_delay; - out.extend_from_slice(&self.hist[..n]); - self.hist.drain(..n); - return; - } - self.hist.extend(input.iter().map(|&v| v as f32)); - self.n_in += input.len() as i64; - let total_out = (self.n_in as u64 * self.fout as u64) / self.fin as u64; - while self.n_out < total_out { - let j = self.n_out; - let cycle = (j / self.period) as i64; - let (base0, ref taps) = self.phases[(j % self.period) as usize]; - let base = base0 + cycle * self.fin as i64 * self.period as i64 / self.fout as i64; - let first = base - self.half + 1; - let mut acc = 0f32; - for (m, &t) in taps.iter().enumerate() { - let idx = first + m as i64 - self.hist_start; - if idx >= 0 && (idx as usize) < self.hist.len() { - acc += self.hist[idx as usize] * t; - } - } - out.push(acc); - self.n_out += 1; - } - // Trim history, keeping enough for the next taps. - let keep_from = self.n_in - (self.half + self.future + 4); - if keep_from > self.hist_start { - let drop = (keep_from - self.hist_start) as usize; - self.hist.drain(..drop.min(self.hist.len())); - self.hist_start = keep_from; - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn upsample_sine_and_delay() { - for &(fin, fout) in &[(16000u32, 48000u32), (8000, 48000), (12000, 48000), (16000, 24000), (16000, 8000), (12000, 16000)] { - let mut r = Resampler::new(fin, fout); - let f = 440.0; - let n = fin as usize / 10; - let x: Vec = (0..n).map(|i| ((2.0 * std::f64::consts::PI * f * i as f64 / fin as f64).sin() * 10000.0) as i16).collect(); - let mut y = Vec::new(); - for c in x.chunks(fin as usize / 50) { - r.process(c, &mut y); - } - assert_eq!(y.len(), n * fout as usize / fin as usize); - // Compare against the ideal delayed sine. - let delay_ms = match fin { - 8000 => 0.538, - 12000 => 0.692, - _ => 0.706, - }; - let mut err = 0f64; - let mut sig = 0f64; - for (j, &v) in y.iter().enumerate().skip(fout as usize / 100) { - let t = j as f64 / fout as f64 - delay_ms / 1000.0; - let ideal = (2.0 * std::f64::consts::PI * f * t).sin() * 10000.0; - err += (v as f64 - ideal).powi(2); - sig += ideal * ideal; - } - let snr = 10.0 * (sig / err).log10(); - assert!(snr > 35.0, "{fin}->{fout}: {snr} dB"); - } - } -} diff --git a/crates/opus/src/silk/tables.rs b/crates/opus/src/silk/tables.rs deleted file mode 100644 index a4e4e2782..000000000 --- a/crates/opus/src/silk/tables.rs +++ /dev/null @@ -1,540 +0,0 @@ -//! SILK probability tables and codebooks, transcribed from RFC 6716 §4.2 (Tables 4–53). -//! -//! PDFs are written as in the RFC (`{...}/256`) and converted at compile time to the inverse-CDF -//! form consumed by the range decoder. - -/// Converts a PDF (summing to 256) into an inverse CDF (`256 - cdf[i+1]`). -pub const fn icdf(pdf: &[u8; N]) -> [u8; N] { - let mut out = [0u8; N]; - let mut acc: u32 = 0; - let mut i = 0; - while i < N { - acc += pdf[i] as u32; - out[i] = (256 - acc) as u8; - i += 1; - } - out -} - -pub const STEREO_PRED_JOINT: [u8; 25] = icdf(&[7, 2, 1, 1, 1, 10, 24, 8, 1, 1, 3, 23, 92, 23, 3, 1, 1, 8, 24, 10, 1, 1, 1, 2, 7]); -pub const UNIFORM3: [u8; 3] = icdf(&[85, 86, 85]); -pub const UNIFORM5: [u8; 5] = icdf(&[51, 51, 52, 51, 51]); -pub const STEREO_WEIGHTS_Q13: [i32; 16] = [-13732, -10050, -8266, -7526, -6500, -5000, -2950, -820, 820, 2950, 5000, 6500, 7526, 8266, 10050, 13732]; -pub const MID_ONLY: [u8; 2] = icdf(&[192, 64]); -pub const TYPE_INACTIVE: [u8; 2] = icdf(&[26, 230]); -pub const TYPE_ACTIVE: [u8; 4] = icdf(&[24, 74, 148, 10]); -pub const GAIN_MSB: [&[u8]; 3] = - [&icdf::<8>(&[32, 112, 68, 29, 12, 1, 1, 1]), &icdf::<8>(&[2, 17, 45, 60, 62, 47, 19, 4]), &icdf::<8>(&[1, 3, 26, 71, 94, 50, 9, 2])]; -pub const UNIFORM8: [u8; 8] = icdf(&[32, 32, 32, 32, 32, 32, 32, 32]); -pub const DELTA_GAIN: [u8; 41] = - icdf(&[6, 5, 11, 31, 132, 21, 8, 4, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]); -pub const NLSF_STAGE1: [&[u8]; 4] = [ - &icdf::<32>(&[44, 34, 30, 19, 21, 12, 11, 3, 3, 2, 16, 2, 2, 1, 5, 2, 1, 3, 3, 1, 1, 2, 2, 2, 3, 1, 9, 9, 2, 7, 2, 1]), - &icdf::<32>(&[1, 10, 1, 8, 3, 8, 8, 14, 13, 14, 1, 14, 12, 13, 11, 11, 12, 11, 10, 10, 11, 8, 9, 8, 7, 8, 1, 1, 6, 1, 6, 5]), - &icdf::<32>(&[31, 21, 3, 17, 1, 8, 17, 4, 1, 18, 16, 4, 2, 3, 1, 10, 1, 3, 16, 11, 16, 2, 2, 3, 2, 11, 1, 4, 9, 8, 7, 3]), - &icdf::<32>(&[1, 4, 16, 5, 18, 11, 5, 14, 15, 1, 3, 12, 13, 14, 14, 6, 14, 12, 2, 6, 1, 12, 12, 11, 10, 3, 10, 5, 1, 1, 1, 3]), -]; -pub const NLSF_STAGE2: [&[u8]; 16] = [ - &icdf::<9>(&[1, 1, 1, 15, 224, 11, 1, 1, 1]), - &icdf::<9>(&[1, 1, 2, 34, 183, 32, 1, 1, 1]), - &icdf::<9>(&[1, 1, 4, 42, 149, 55, 2, 1, 1]), - &icdf::<9>(&[1, 1, 8, 52, 123, 61, 8, 1, 1]), - &icdf::<9>(&[1, 3, 16, 53, 101, 74, 6, 1, 1]), - &icdf::<9>(&[1, 3, 17, 55, 90, 73, 15, 1, 1]), - &icdf::<9>(&[1, 7, 24, 53, 74, 67, 26, 3, 1]), - &icdf::<9>(&[1, 1, 18, 63, 78, 58, 30, 6, 1]), - &icdf::<9>(&[1, 1, 1, 9, 232, 9, 1, 1, 1]), - &icdf::<9>(&[1, 1, 2, 28, 186, 35, 1, 1, 1]), - &icdf::<9>(&[1, 1, 3, 42, 152, 53, 2, 1, 1]), - &icdf::<9>(&[1, 1, 10, 49, 126, 65, 2, 1, 1]), - &icdf::<9>(&[1, 4, 19, 48, 100, 77, 5, 1, 1]), - &icdf::<9>(&[1, 1, 14, 54, 100, 72, 12, 1, 1]), - &icdf::<9>(&[1, 1, 15, 61, 87, 61, 25, 4, 1]), - &icdf::<9>(&[1, 7, 21, 50, 77, 81, 17, 1, 1]), -]; -pub const NLSF_SEL_NBMB: [[u8; 10]; 32] = [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1, 3, 1, 2, 2, 1, 2, 1, 1, 1], - [2, 1, 1, 1, 1, 1, 1, 1, 1, 1], - [1, 2, 2, 2, 2, 1, 2, 1, 1, 1], - [2, 3, 3, 3, 3, 2, 2, 2, 2, 2], - [0, 5, 3, 3, 2, 2, 2, 2, 1, 1], - [0, 2, 2, 2, 2, 2, 2, 2, 2, 1], - [2, 3, 6, 4, 4, 4, 5, 4, 5, 5], - [2, 4, 5, 5, 4, 5, 4, 6, 4, 4], - [2, 4, 4, 7, 4, 5, 4, 5, 5, 4], - [4, 3, 3, 3, 2, 3, 2, 2, 2, 2], - [1, 5, 5, 6, 4, 5, 4, 5, 5, 5], - [2, 7, 4, 6, 5, 5, 5, 5, 5, 5], - [2, 7, 5, 5, 5, 5, 5, 6, 5, 4], - [3, 3, 5, 4, 4, 5, 4, 5, 4, 4], - [2, 3, 3, 5, 5, 4, 4, 4, 4, 4], - [2, 4, 4, 6, 4, 5, 4, 5, 5, 5], - [2, 5, 4, 6, 5, 5, 5, 4, 5, 4], - [2, 7, 4, 5, 4, 5, 4, 5, 5, 5], - [2, 5, 4, 6, 7, 6, 5, 6, 5, 4], - [3, 6, 7, 4, 6, 5, 5, 6, 4, 5], - [2, 7, 6, 4, 4, 4, 5, 4, 5, 5], - [4, 5, 5, 4, 6, 6, 5, 6, 5, 4], - [2, 5, 5, 6, 5, 6, 4, 6, 4, 4], - [4, 5, 5, 5, 3, 7, 4, 5, 5, 4], - [2, 3, 4, 5, 5, 6, 4, 5, 5, 4], - [2, 3, 2, 3, 3, 4, 2, 3, 3, 3], - [1, 1, 2, 2, 2, 2, 2, 3, 2, 2], - [4, 5, 5, 6, 6, 6, 5, 6, 4, 5], - [3, 5, 5, 4, 4, 4, 4, 3, 3, 2], - [2, 5, 3, 7, 5, 5, 4, 4, 5, 4], - [4, 4, 5, 4, 5, 6, 5, 6, 5, 4], -]; -pub const NLSF_SEL_WB: [[u8; 16]; 32] = [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [2, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 1, 1, 1, 0, 3], - [2, 5, 5, 3, 7, 4, 4, 5, 2, 5, 4, 5, 5, 4, 3, 3], - [0, 2, 1, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1], - [0, 6, 5, 4, 6, 4, 7, 5, 4, 4, 4, 5, 5, 4, 4, 3], - [0, 3, 5, 5, 4, 3, 3, 5, 3, 3, 3, 3, 3, 3, 2, 4], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 2, 6, 3, 7, 2, 5, 3, 4, 5, 5, 4, 3, 3, 2, 3], - [0, 6, 2, 6, 6, 4, 5, 4, 6, 5, 4, 4, 5, 3, 3, 3], - [2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], - [2, 2, 3, 4, 5, 3, 3, 3, 3, 3, 3, 3, 2, 2, 1, 3], - [2, 2, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 2, 1, 3], - [3, 4, 4, 4, 6, 4, 4, 5, 3, 5, 4, 4, 5, 4, 3, 4], - [0, 6, 4, 5, 4, 7, 5, 2, 6, 5, 7, 4, 4, 3, 5, 3], - [0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0], - [1, 6, 5, 7, 5, 4, 5, 3, 4, 5, 4, 4, 4, 3, 3, 4], - [1, 3, 3, 4, 4, 3, 3, 5, 2, 3, 3, 5, 5, 5, 3, 4], - [2, 3, 3, 2, 2, 2, 3, 2, 1, 2, 1, 2, 1, 1, 1, 4], - [0, 2, 3, 5, 3, 3, 2, 2, 2, 1, 1, 0, 0, 0, 0, 0], - [3, 4, 3, 5, 3, 3, 2, 2, 1, 1, 1, 1, 1, 2, 2, 4], - [2, 6, 3, 7, 7, 4, 5, 4, 5, 3, 5, 3, 3, 2, 3, 3], - [2, 3, 5, 6, 6, 3, 5, 3, 4, 4, 3, 3, 3, 3, 2, 4], - [1, 3, 3, 4, 4, 4, 4, 3, 5, 5, 5, 3, 1, 1, 1, 1], - [2, 5, 3, 6, 6, 4, 7, 4, 4, 5, 3, 4, 4, 3, 3, 3], - [0, 6, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 6, 6, 3, 5, 2, 5, 5, 3, 4, 4, 7, 7, 4, 4, 4], - [3, 3, 7, 3, 5, 4, 3, 3, 3, 2, 2, 3, 3, 3, 2, 3], - [0, 0, 1, 0, 0, 0, 2, 1, 2, 1, 1, 2, 2, 2, 1, 1], - [0, 3, 2, 5, 3, 3, 2, 3, 2, 1, 0, 0, 1, 0, 0, 1], - [3, 5, 5, 4, 7, 5, 3, 3, 2, 3, 2, 2, 1, 0, 1, 0], - [2, 3, 5, 3, 4, 3, 3, 3, 2, 1, 2, 6, 4, 0, 0, 0], -]; -pub const NLSF_EXT: [u8; 7] = icdf(&[156, 60, 24, 9, 4, 2, 1]); -pub const NLSF_PRED_NBMB: [[i32; 9]; 2] = [[179, 138, 140, 148, 151, 149, 153, 151, 163], [116, 67, 82, 59, 92, 72, 100, 89, 92]]; -pub const NLSF_PRED_WB: [[i32; 15]; 2] = [ - [175, 148, 160, 176, 178, 173, 174, 164, 177, 174, 196, 182, 198, 192, 182], - [68, 62, 66, 60, 72, 117, 85, 90, 118, 136, 151, 142, 160, 142, 155], -]; -pub const NLSF_PSEL_NBMB: [[u8; 9]; 32] = [ - [0, 1, 0, 0, 0, 0, 0, 0, 0], - [1, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0], - [1, 1, 1, 0, 0, 0, 0, 1, 0], - [0, 1, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 0, 0, 0, 0, 0, 0, 0], - [1, 0, 1, 1, 0, 0, 0, 1, 0], - [0, 1, 1, 0, 0, 1, 1, 0, 0], - [0, 0, 1, 1, 0, 1, 0, 1, 1], - [0, 0, 1, 1, 0, 0, 1, 1, 1], - [0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 0, 1, 1, 1, 1, 1, 0], - [0, 1, 0, 1, 1, 1, 1, 1, 0], - [0, 1, 1, 1, 1, 1, 1, 1, 0], - [1, 0, 1, 1, 0, 1, 1, 1, 1], - [0, 1, 1, 1, 1, 1, 0, 1, 0], - [0, 0, 1, 1, 0, 1, 0, 1, 0], - [0, 0, 1, 1, 1, 0, 1, 1, 1], - [0, 1, 1, 0, 0, 1, 1, 1, 0], - [0, 0, 0, 1, 1, 1, 0, 1, 0], - [0, 1, 1, 0, 0, 1, 0, 1, 0], - [0, 1, 1, 0, 0, 0, 1, 1, 0], - [0, 0, 0, 0, 0, 1, 1, 1, 1], - [0, 0, 1, 1, 0, 0, 0, 1, 1], - [0, 0, 0, 1, 0, 1, 1, 1, 1], - [0, 1, 1, 1, 1, 1, 1, 1, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 1, 0, 1, 1, 0, 1, 0], - [1, 0, 0, 1, 0, 0, 0, 0, 0], - [0, 0, 0, 1, 1, 0, 1, 0, 1], - [1, 0, 1, 1, 0, 1, 1, 1, 1], -]; -pub const NLSF_PSEL_WB: [[u8; 15]; 32] = [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], - [0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0], - [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0], - [0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1], - [0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], - [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0], - [0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0], - [0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0], - [0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1], - [0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0], - [0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0], - [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0], - [0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0], - [0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0], - [0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], - [0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1], - [0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], - [0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0], -]; -pub const NLSF_CB_NBMB: [[i32; 10]; 32] = [ - [12, 35, 60, 83, 108, 132, 157, 180, 206, 228], - [15, 32, 55, 77, 101, 125, 151, 175, 201, 225], - [19, 42, 66, 89, 114, 137, 162, 184, 209, 230], - [12, 25, 50, 72, 97, 120, 147, 172, 200, 223], - [26, 44, 69, 90, 114, 135, 159, 180, 205, 225], - [13, 22, 53, 80, 106, 130, 156, 180, 205, 228], - [15, 25, 44, 64, 90, 115, 142, 168, 196, 222], - [19, 24, 62, 82, 100, 120, 145, 168, 190, 214], - [22, 31, 50, 79, 103, 120, 151, 170, 203, 227], - [21, 29, 45, 65, 106, 124, 150, 171, 196, 224], - [30, 49, 75, 97, 121, 142, 165, 186, 209, 229], - [19, 25, 52, 70, 93, 116, 143, 166, 192, 219], - [26, 34, 62, 75, 97, 118, 145, 167, 194, 217], - [25, 33, 56, 70, 91, 113, 143, 165, 196, 223], - [21, 34, 51, 72, 97, 117, 145, 171, 196, 222], - [20, 29, 50, 67, 90, 117, 144, 168, 197, 221], - [22, 31, 48, 66, 95, 117, 146, 168, 196, 222], - [24, 33, 51, 77, 116, 134, 158, 180, 200, 224], - [21, 28, 70, 87, 106, 124, 149, 170, 194, 217], - [26, 33, 53, 64, 83, 117, 152, 173, 204, 225], - [27, 34, 65, 95, 108, 129, 155, 174, 210, 225], - [20, 26, 72, 99, 113, 131, 154, 176, 200, 219], - [34, 43, 61, 78, 93, 114, 155, 177, 205, 229], - [23, 29, 54, 97, 124, 138, 163, 179, 209, 229], - [30, 38, 56, 89, 118, 129, 158, 178, 200, 231], - [21, 29, 49, 63, 85, 111, 142, 163, 193, 222], - [27, 48, 77, 103, 133, 158, 179, 196, 215, 232], - [29, 47, 74, 99, 124, 151, 176, 198, 220, 237], - [33, 42, 61, 76, 93, 121, 155, 174, 207, 225], - [29, 53, 87, 112, 136, 154, 170, 188, 208, 227], - [24, 30, 52, 84, 131, 150, 166, 186, 203, 229], - [37, 48, 64, 84, 104, 118, 156, 177, 201, 230], -]; -pub const NLSF_CB_WB: [[i32; 16]; 32] = [ - [7, 23, 38, 54, 69, 85, 100, 116, 131, 147, 162, 178, 193, 208, 223, 239], - [13, 25, 41, 55, 69, 83, 98, 112, 127, 142, 157, 171, 187, 203, 220, 236], - [15, 21, 34, 51, 61, 78, 92, 106, 126, 136, 152, 167, 185, 205, 225, 240], - [10, 21, 36, 50, 63, 79, 95, 110, 126, 141, 157, 173, 189, 205, 221, 237], - [17, 20, 37, 51, 59, 78, 89, 107, 123, 134, 150, 164, 184, 205, 224, 240], - [10, 15, 32, 51, 67, 81, 96, 112, 129, 142, 158, 173, 189, 204, 220, 236], - [8, 21, 37, 51, 65, 79, 98, 113, 126, 138, 155, 168, 179, 192, 209, 218], - [12, 15, 34, 55, 63, 78, 87, 108, 118, 131, 148, 167, 185, 203, 219, 236], - [16, 19, 32, 36, 56, 79, 91, 108, 118, 136, 154, 171, 186, 204, 220, 237], - [11, 28, 43, 58, 74, 89, 105, 120, 135, 150, 165, 180, 196, 211, 226, 241], - [6, 16, 33, 46, 60, 75, 92, 107, 123, 137, 156, 169, 185, 199, 214, 225], - [11, 19, 30, 44, 57, 74, 89, 105, 121, 135, 152, 169, 186, 202, 218, 234], - [12, 19, 29, 46, 57, 71, 88, 100, 120, 132, 148, 165, 182, 199, 216, 233], - [17, 23, 35, 46, 56, 77, 92, 106, 123, 134, 152, 167, 185, 204, 222, 237], - [14, 17, 45, 53, 63, 75, 89, 107, 115, 132, 151, 171, 188, 206, 221, 240], - [9, 16, 29, 40, 56, 71, 88, 103, 119, 137, 154, 171, 189, 205, 222, 237], - [16, 19, 36, 48, 57, 76, 87, 105, 118, 132, 150, 167, 185, 202, 218, 236], - [12, 17, 29, 54, 71, 81, 94, 104, 126, 136, 149, 164, 182, 201, 221, 237], - [15, 28, 47, 62, 79, 97, 115, 129, 142, 155, 168, 180, 194, 208, 223, 238], - [8, 14, 30, 45, 62, 78, 94, 111, 127, 143, 159, 175, 192, 207, 223, 239], - [17, 30, 49, 62, 79, 92, 107, 119, 132, 145, 160, 174, 190, 204, 220, 235], - [14, 19, 36, 45, 61, 76, 91, 108, 121, 138, 154, 172, 189, 205, 222, 238], - [12, 18, 31, 45, 60, 76, 91, 107, 123, 138, 154, 171, 187, 204, 221, 236], - [13, 17, 31, 43, 53, 70, 83, 103, 114, 131, 149, 167, 185, 203, 220, 237], - [17, 22, 35, 42, 58, 78, 93, 110, 125, 139, 155, 170, 188, 206, 224, 240], - [8, 15, 34, 50, 67, 83, 99, 115, 131, 146, 162, 178, 193, 209, 224, 239], - [13, 16, 41, 66, 73, 86, 95, 111, 128, 137, 150, 163, 183, 206, 225, 241], - [17, 25, 37, 52, 63, 75, 92, 102, 119, 132, 144, 160, 175, 191, 212, 231], - [19, 31, 49, 65, 83, 100, 117, 133, 147, 161, 174, 187, 200, 213, 227, 242], - [18, 31, 52, 68, 88, 103, 117, 126, 138, 149, 163, 177, 192, 207, 223, 239], - [16, 29, 47, 61, 76, 90, 106, 119, 133, 147, 161, 176, 193, 209, 224, 240], - [15, 21, 35, 50, 61, 73, 86, 97, 110, 119, 129, 141, 175, 198, 218, 237], -]; -pub const NLSF_MIN_SPACING_NBMB: [i32; 11] = [250, 3, 6, 3, 3, 3, 4, 3, 3, 3, 461]; -pub const NLSF_MIN_SPACING_WB: [i32; 17] = [100, 3, 40, 3, 3, 3, 5, 14, 14, 10, 11, 3, 8, 9, 7, 3, 347]; -pub const NLSF_INTERP: [u8; 5] = icdf(&[13, 22, 29, 11, 181]); -pub const LSF_ORDER_NBMB: [usize; 10] = [0, 9, 6, 3, 4, 5, 8, 1, 2, 7]; -pub const LSF_ORDER_WB: [usize; 16] = [0, 15, 8, 7, 4, 11, 12, 3, 2, 13, 10, 5, 6, 9, 14, 1]; -pub const COS_Q12: [i32; 129] = [ - 4096, 4095, 4091, 4085, 4076, 4065, 4052, 4036, 4017, 3997, 3973, 3948, 3920, 3889, 3857, 3822, 3784, 3745, 3703, 3659, 3613, 3564, 3513, 3461, - 3406, 3349, 3290, 3229, 3166, 3102, 3035, 2967, 2896, 2824, 2751, 2676, 2599, 2520, 2440, 2359, 2276, 2191, 2106, 2019, 1931, 1842, 1751, 1660, - 1568, 1474, 1380, 1285, 1189, 1093, 995, 897, 799, 700, 601, 501, 401, 301, 201, 101, 0, -101, -201, -301, -401, -501, -601, -700, -799, -897, - -995, -1093, -1189, -1285, -1380, -1474, -1568, -1660, -1751, -1842, -1931, -2019, -2106, -2191, -2276, -2359, -2440, -2520, -2599, -2676, -2751, - -2824, -2896, -2967, -3035, -3102, -3166, -3229, -3290, -3349, -3406, -3461, -3513, -3564, -3613, -3659, -3703, -3745, -3784, -3822, -3857, - -3889, -3920, -3948, -3973, -3997, -4017, -4036, -4052, -4065, -4076, -4085, -4091, -4095, -4096, -]; -pub const PITCH_HIGH: [u8; 32] = icdf(&[3, 3, 6, 11, 21, 30, 32, 19, 11, 10, 12, 13, 13, 12, 11, 9, 8, 7, 6, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1]); -pub const PITCH_LOW: [&[u8]; 3] = - [&icdf::<4>(&[64, 64, 64, 64]), &icdf::<6>(&[43, 42, 43, 43, 42, 43]), &icdf::<8>(&[32, 32, 32, 32, 32, 32, 32, 32])]; -pub const PITCH_DELTA: [u8; 21] = icdf(&[46, 2, 2, 3, 4, 6, 10, 15, 26, 38, 30, 22, 15, 10, 7, 6, 4, 4, 2, 2, 2]); -pub const PITCH_CONTOUR: [&[u8]; 4] = [ - &icdf::<3>(&[143, 50, 63]), - &icdf::<11>(&[68, 12, 21, 17, 19, 22, 30, 24, 17, 16, 10]), - &icdf::<12>(&[91, 46, 39, 19, 14, 12, 8, 7, 6, 5, 5, 4]), - &icdf::<34>(&[33, 22, 18, 16, 15, 14, 14, 13, 13, 10, 9, 9, 8, 6, 6, 6, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1]), -]; -pub const CB_NB_10: [[i32; 2]; 3] = [[0, 0], [1, 0], [0, 1]]; -pub const CB_NB_20: [[i32; 4]; 11] = [ - [0, 0, 0, 0], - [2, 1, 0, -1], - [-1, 0, 1, 2], - [-1, 0, 0, 1], - [-1, 0, 0, 0], - [0, 0, 0, 1], - [0, 0, 1, 1], - [1, 1, 0, 0], - [1, 0, 0, 0], - [0, 0, 0, -1], - [1, 0, 0, -1], -]; -pub const CB_WB_10: [[i32; 2]; 12] = [[0, 0], [0, 1], [1, 0], [-1, 1], [1, -1], [-1, 2], [2, -1], [-2, 2], [2, -2], [-2, 3], [3, -2], [-3, 3]]; -pub const CB_WB_20: [[i32; 4]; 34] = [ - [0, 0, 0, 0], - [0, 0, 1, 1], - [1, 1, 0, 0], - [-1, 0, 0, 0], - [0, 0, 0, 1], - [1, 0, 0, 0], - [-1, 0, 0, 1], - [0, 0, 0, -1], - [-1, 0, 1, 2], - [1, 0, 0, -1], - [-2, -1, 1, 2], - [2, 1, 0, -1], - [-2, 0, 0, 2], - [-2, 0, 1, 3], - [2, 1, -1, -2], - [-3, -1, 1, 3], - [2, 0, 0, -2], - [3, 1, 0, -2], - [-3, -1, 2, 4], - [-4, -1, 1, 4], - [3, 1, -1, -3], - [-4, -1, 2, 5], - [4, 2, -1, -3], - [4, 1, -1, -4], - [-5, -1, 2, 6], - [5, 2, -1, -4], - [-6, -2, 2, 6], - [-5, -2, 2, 5], - [6, 2, -1, -5], - [-7, -2, 3, 8], - [6, 2, -2, -6], - [5, 2, -2, -5], - [8, 3, -2, -7], - [-9, -3, 3, 9], -]; -pub const LTP_PERIODICITY: [u8; 3] = icdf(&[77, 80, 99]); -pub const LTP_FILTER: [&[u8]; 3] = [ - &icdf::<8>(&[185, 15, 13, 13, 9, 9, 6, 6]), - &icdf::<16>(&[57, 34, 21, 20, 15, 13, 12, 13, 10, 10, 9, 10, 9, 8, 7, 8]), - &icdf::<32>(&[15, 16, 14, 12, 12, 12, 11, 11, 11, 10, 9, 9, 9, 9, 8, 8, 8, 8, 7, 7, 6, 6, 5, 4, 5, 4, 4, 4, 3, 4, 3, 2]), -]; -pub const LTP_TAPS_0: [[i32; 5]; 8] = [ - [4, 6, 24, 7, 5], - [0, 0, 2, 0, 0], - [12, 28, 41, 13, -4], - [-9, 15, 42, 25, 14], - [1, -2, 62, 41, -9], - [-10, 37, 65, -4, 3], - [-6, 4, 66, 7, -8], - [16, 14, 38, -3, 33], -]; -pub const LTP_TAPS_1: [[i32; 5]; 16] = [ - [13, 22, 39, 23, 12], - [-1, 36, 64, 27, -6], - [-7, 10, 55, 43, 17], - [1, 1, 8, 1, 1], - [6, -11, 74, 53, -9], - [-12, 55, 76, -12, 8], - [-3, 3, 93, 27, -4], - [26, 39, 59, 3, -8], - [2, 0, 77, 11, 9], - [-8, 22, 44, -6, 7], - [40, 9, 26, 3, 9], - [-7, 20, 101, -7, 4], - [3, -8, 42, 26, 0], - [-15, 33, 68, 2, 23], - [-2, 55, 46, -2, 15], - [3, -1, 21, 16, 41], -]; -pub const LTP_TAPS_2: [[i32; 5]; 32] = [ - [-6, 27, 61, 39, 5], - [-11, 42, 88, 4, 1], - [-2, 60, 65, 6, -4], - [-1, -5, 73, 56, 1], - [-9, 19, 94, 29, -9], - [0, 12, 99, 6, 4], - [8, -19, 102, 46, -13], - [3, 2, 13, 3, 2], - [9, -21, 84, 72, -18], - [-11, 46, 104, -22, 8], - [18, 38, 48, 23, 0], - [-16, 70, 83, -21, 11], - [5, -11, 117, 22, -8], - [-6, 23, 117, -12, 3], - [3, -8, 95, 28, 4], - [-10, 15, 77, 60, -15], - [-1, 4, 124, 2, -4], - [3, 38, 84, 24, -25], - [2, 13, 42, 13, 31], - [21, -4, 56, 46, -1], - [-1, 35, 79, -13, 19], - [-7, 65, 88, -9, -14], - [20, 4, 81, 49, -29], - [20, 0, 75, 3, -17], - [5, -9, 44, 92, -8], - [1, -3, 22, 69, 31], - [-6, 95, 41, -12, 5], - [39, 67, 16, -4, 1], - [0, -6, 120, 55, -36], - [-13, 44, 122, 4, -24], - [81, 5, 11, 3, 7], - [2, 0, 9, 10, 88], -]; -pub const LTP_SCALE: [u8; 3] = icdf(&[128, 64, 64]); -pub const UNIFORM4: [u8; 4] = icdf(&[64, 64, 64, 64]); -pub const RATE_LEVEL: [&[u8]; 2] = [&icdf::<9>(&[15, 51, 12, 46, 45, 13, 33, 27, 14]), &icdf::<9>(&[33, 30, 36, 17, 34, 49, 18, 21, 18])]; -pub const PULSE_COUNT: [&[u8]; 11] = [ - &icdf::<18>(&[131, 74, 25, 8, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]), - &icdf::<18>(&[58, 93, 60, 23, 7, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]), - &icdf::<18>(&[43, 51, 46, 33, 24, 16, 11, 8, 6, 3, 3, 3, 2, 1, 1, 2, 1, 2]), - &icdf::<18>(&[17, 52, 71, 57, 31, 12, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]), - &icdf::<18>(&[6, 21, 41, 53, 49, 35, 21, 11, 6, 3, 2, 2, 1, 1, 1, 1, 1, 1]), - &icdf::<18>(&[7, 14, 22, 28, 29, 28, 25, 20, 17, 13, 11, 9, 7, 5, 4, 4, 3, 10]), - &icdf::<18>(&[2, 5, 14, 29, 42, 46, 41, 31, 19, 11, 6, 3, 2, 1, 1, 1, 1, 1]), - &icdf::<18>(&[1, 2, 4, 10, 19, 29, 35, 37, 34, 28, 20, 14, 8, 5, 4, 2, 2, 2]), - &icdf::<18>(&[1, 2, 2, 5, 9, 14, 20, 24, 27, 28, 26, 23, 20, 15, 11, 8, 6, 15]), - &icdf::<18>(&[1, 1, 1, 6, 27, 58, 56, 39, 25, 14, 10, 6, 3, 3, 2, 1, 1, 2]), - &icdf::<18>(&[2, 1, 6, 27, 58, 56, 39, 25, 14, 10, 6, 3, 3, 2, 1, 1, 2, 0]), -]; -pub const SHELL_16: [&[u8]; 16] = [ - &icdf::<2>(&[126, 130]), - &icdf::<3>(&[56, 142, 58]), - &icdf::<4>(&[25, 101, 104, 26]), - &icdf::<5>(&[12, 60, 108, 64, 12]), - &icdf::<6>(&[7, 35, 84, 87, 37, 6]), - &icdf::<7>(&[4, 20, 59, 86, 63, 21, 3]), - &icdf::<8>(&[3, 12, 38, 72, 75, 42, 12, 2]), - &icdf::<9>(&[2, 8, 25, 54, 73, 59, 27, 7, 1]), - &icdf::<10>(&[2, 5, 17, 39, 63, 65, 42, 18, 4, 1]), - &icdf::<11>(&[1, 4, 12, 28, 49, 63, 54, 30, 11, 3, 1]), - &icdf::<12>(&[1, 4, 8, 20, 37, 55, 57, 41, 22, 8, 2, 1]), - &icdf::<13>(&[1, 3, 7, 15, 28, 44, 53, 48, 33, 16, 6, 1, 1]), - &icdf::<14>(&[1, 2, 6, 12, 21, 35, 47, 48, 40, 25, 12, 5, 1, 1]), - &icdf::<15>(&[1, 1, 4, 10, 17, 27, 37, 47, 43, 33, 21, 9, 4, 1, 1]), - &icdf::<16>(&[1, 1, 1, 8, 14, 22, 33, 40, 43, 38, 28, 16, 8, 1, 1, 1]), - &icdf::<17>(&[1, 1, 1, 1, 13, 18, 27, 36, 41, 41, 34, 24, 14, 1, 1, 1, 1]), -]; -pub const SHELL_8: [&[u8]; 16] = [ - &icdf::<2>(&[127, 129]), - &icdf::<3>(&[53, 149, 54]), - &icdf::<4>(&[22, 105, 106, 23]), - &icdf::<5>(&[11, 61, 111, 63, 10]), - &icdf::<6>(&[6, 35, 86, 88, 36, 5]), - &icdf::<7>(&[4, 20, 59, 87, 62, 21, 3]), - &icdf::<8>(&[3, 13, 40, 71, 73, 41, 13, 2]), - &icdf::<9>(&[3, 9, 27, 53, 70, 56, 28, 9, 1]), - &icdf::<10>(&[3, 8, 19, 37, 57, 61, 44, 20, 6, 1]), - &icdf::<11>(&[3, 7, 15, 28, 44, 54, 49, 33, 17, 5, 1]), - &icdf::<12>(&[1, 7, 13, 22, 34, 46, 48, 38, 28, 14, 4, 1]), - &icdf::<13>(&[1, 1, 11, 22, 27, 35, 42, 47, 33, 25, 10, 1, 1]), - &icdf::<14>(&[1, 1, 6, 14, 26, 37, 43, 43, 37, 26, 14, 6, 1, 1]), - &icdf::<15>(&[1, 1, 4, 10, 20, 31, 40, 42, 40, 31, 20, 10, 4, 1, 1]), - &icdf::<16>(&[1, 1, 3, 8, 16, 26, 35, 38, 38, 35, 26, 16, 8, 3, 1, 1]), - &icdf::<17>(&[1, 1, 2, 6, 12, 21, 30, 36, 38, 36, 30, 21, 12, 6, 2, 1, 1]), -]; -pub const SHELL_4: [&[u8]; 16] = [ - &icdf::<2>(&[127, 129]), - &icdf::<3>(&[49, 157, 50]), - &icdf::<4>(&[20, 107, 109, 20]), - &icdf::<5>(&[11, 60, 113, 62, 10]), - &icdf::<6>(&[7, 36, 84, 87, 36, 6]), - &icdf::<7>(&[6, 24, 57, 82, 60, 23, 4]), - &icdf::<8>(&[5, 18, 39, 64, 68, 42, 16, 4]), - &icdf::<9>(&[6, 14, 29, 47, 61, 52, 30, 14, 3]), - &icdf::<10>(&[1, 15, 23, 35, 51, 50, 40, 30, 10, 1]), - &icdf::<11>(&[1, 1, 21, 32, 42, 52, 46, 41, 18, 1, 1]), - &icdf::<12>(&[1, 6, 16, 27, 36, 42, 42, 36, 27, 16, 6, 1]), - &icdf::<13>(&[1, 5, 12, 21, 31, 38, 40, 38, 31, 21, 12, 5, 1]), - &icdf::<14>(&[1, 3, 9, 17, 26, 34, 38, 38, 34, 26, 17, 9, 3, 1]), - &icdf::<15>(&[1, 3, 7, 14, 22, 29, 34, 36, 34, 29, 22, 14, 7, 3, 1]), - &icdf::<16>(&[1, 2, 5, 11, 18, 25, 31, 35, 35, 31, 25, 18, 11, 5, 2, 1]), - &icdf::<17>(&[1, 1, 4, 9, 15, 21, 28, 32, 34, 32, 28, 21, 15, 9, 4, 1, 1]), -]; -pub const SHELL_2: [&[u8]; 16] = [ - &icdf::<2>(&[128, 128]), - &icdf::<3>(&[42, 172, 42]), - &icdf::<4>(&[21, 107, 107, 21]), - &icdf::<5>(&[12, 60, 112, 61, 11]), - &icdf::<6>(&[8, 34, 86, 86, 35, 7]), - &icdf::<7>(&[8, 23, 55, 90, 55, 20, 5]), - &icdf::<8>(&[5, 15, 38, 72, 72, 36, 15, 3]), - &icdf::<9>(&[6, 12, 27, 52, 77, 47, 20, 10, 5]), - &icdf::<10>(&[6, 19, 28, 35, 40, 40, 35, 28, 19, 6]), - &icdf::<11>(&[4, 14, 22, 31, 37, 40, 37, 31, 22, 14, 4]), - &icdf::<12>(&[3, 10, 18, 26, 33, 38, 38, 33, 26, 18, 10, 3]), - &icdf::<13>(&[2, 8, 13, 21, 29, 36, 38, 36, 29, 21, 13, 8, 2]), - &icdf::<14>(&[1, 5, 10, 17, 25, 32, 38, 38, 32, 25, 17, 10, 5, 1]), - &icdf::<15>(&[1, 4, 7, 13, 21, 29, 35, 36, 35, 29, 21, 13, 7, 4, 1]), - &icdf::<16>(&[1, 2, 5, 10, 17, 25, 32, 36, 36, 32, 25, 17, 10, 5, 2, 1]), - &icdf::<17>(&[1, 2, 4, 7, 13, 21, 28, 34, 36, 34, 28, 21, 13, 7, 4, 2, 1]), -]; -pub const EXC_LSB: [u8; 2] = icdf(&[136, 120]); -pub const EXC_SIGN: [&[u8]; 42] = [ - &icdf::<2>(&[2, 254]), - &icdf::<2>(&[207, 49]), - &icdf::<2>(&[189, 67]), - &icdf::<2>(&[179, 77]), - &icdf::<2>(&[174, 82]), - &icdf::<2>(&[163, 93]), - &icdf::<2>(&[157, 99]), - &icdf::<2>(&[58, 198]), - &icdf::<2>(&[245, 11]), - &icdf::<2>(&[238, 18]), - &icdf::<2>(&[232, 24]), - &icdf::<2>(&[225, 31]), - &icdf::<2>(&[220, 36]), - &icdf::<2>(&[211, 45]), - &icdf::<2>(&[1, 255]), - &icdf::<2>(&[210, 46]), - &icdf::<2>(&[190, 66]), - &icdf::<2>(&[178, 78]), - &icdf::<2>(&[169, 87]), - &icdf::<2>(&[162, 94]), - &icdf::<2>(&[152, 104]), - &icdf::<2>(&[48, 208]), - &icdf::<2>(&[242, 14]), - &icdf::<2>(&[235, 21]), - &icdf::<2>(&[224, 32]), - &icdf::<2>(&[214, 42]), - &icdf::<2>(&[205, 51]), - &icdf::<2>(&[190, 66]), - &icdf::<2>(&[1, 255]), - &icdf::<2>(&[162, 94]), - &icdf::<2>(&[152, 104]), - &icdf::<2>(&[147, 109]), - &icdf::<2>(&[144, 112]), - &icdf::<2>(&[141, 115]), - &icdf::<2>(&[138, 118]), - &icdf::<2>(&[8, 248]), - &icdf::<2>(&[203, 53]), - &icdf::<2>(&[187, 69]), - &icdf::<2>(&[176, 80]), - &icdf::<2>(&[168, 88]), - &icdf::<2>(&[161, 95]), - &icdf::<2>(&[154, 102]), -]; -pub const LBRR_FLAGS: [&[u8]; 2] = [&icdf::<3>(&[53, 53, 150]), &icdf::<7>(&[41, 20, 29, 41, 15, 28, 82])]; diff --git a/docs/architecture.md b/docs/architecture.md index 2a600e57f..2d27e707c 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,6 +1,6 @@ # DeckCraft architecture -> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-18 · **Change:** minor (native chart payloads stored in bounded ZIP members; previous: minor, community PR wave 2026-10-14: 320 engine commands, 41 dialogs, the `metafile` crate, 1,641 tests; duplicated L2/L5/L6 rows and headers folded; previous: minor, transition frames) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365) +> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-11-11 · **Change:** trivial (containers and the Opus decoder come from craft-libs; previous: minor, native chart payloads stored in bounded ZIP members; previous: minor, community PR wave 2026-10-14: 320 engine commands, 41 dialogs, the `metafile` crate, 1,641 tests; duplicated L2/L5/L6 rows and headers folded; previous: minor, transition frames) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365) How DeckCraft is built today. The binding rules (layering, everything is a command, thin UI, never break wasm, never crash) are in [AGENTS.md](../AGENTS.md) §3–§5; this document describes what @@ -15,9 +15,9 @@ nothing below L6 uses egui, eframe, winit or rfd. | Layer | Crates | What they do | |---|---|---| -| L0 | `geom`, `color`, `metafile`; standalone `bitstream`, `matroska`, `ogg`, `opus` | EMU/pt units, transforms, ~180 preset shape generators re-derived from ECMA-376, adjust handles, hit testing; RGB/HSL and DrawingML colour transforms; MKV/WebM and Ogg demuxers, the Opus decoder; EMF/WMF read into a neutral vector model (`metafile`) | -| L1 | `model`, `fonts`; `h264`, `hevc`, `vp9`, `av1`, `isobmff` | Presentation, masters, layouts, slides, shapes, text, themes, tables, charts, transitions, animations, placeholder inheritance (`resolve`); font database (craft-fonts + system) with metric-compatible substitutes for Office fonts (Carlito for Calibri, Caladea for Cambria, Liberation for Arial/Times); video decoders and the MP4/MOV demuxer | -| L2 | `text`, `anim`, `format`, `media` | Shaping (HarfRust), UAX #9 bidi, UAX #14 line breaking, autofit, caret hit testing; per-shape animation state over time, the show state machine, transition frame layouts, Morph matching; `.deckcraft` (zip + JSON + media) and outline text; media probing, decoding, mixer and clock | +| L0 | `geom`, `color`, `metafile`; standalone `bitstream` | EMU/pt units, transforms, ~180 preset shape generators re-derived from ECMA-376, adjust handles, hit testing; RGB/HSL and DrawingML colour transforms; bit-level I/O and the video decoders' memory budget (`bitstream`); EMF/WMF read into a neutral vector model (`metafile`) | +| L1 | `model`, `fonts`; `h264`, `hevc`, `vp9`, `av1` | Presentation, masters, layouts, slides, shapes, text, themes, tables, charts, transitions, animations, placeholder inheritance (`resolve`); font database (craft-fonts + system) with metric-compatible substitutes for Office fonts (Carlito for Calibri, Caladea for Cambria, Liberation for Arial/Times); video decoders | +| L2 | `text`, `anim`, `format`, `media` | Shaping (HarfRust), UAX #9 bidi, UAX #14 line breaking, autofit, caret hit testing; per-shape animation state over time, the show state machine, transition frame layouts, Morph matching; `.deckcraft` (zip + JSON + media) and outline text; media probing, decoding, mixer and clock (MP4/MOV, MKV/WebM and Ogg demuxers from craft-media and the Opus decoder from craft-audio, both in craft-libs) | | L3 | `render`, `pptx` | `vello_cpu` raster of slides, masters and layouts (fills, outlines, effects, pictures, tables, charts, Morph frames); PPTX import/export from ECMA-376 | | L4 | `pdf` | App-owned export layout and rendering through the shared `craft-pdf` writer | | L5 | `engine` | Sessions, documents, selection, undo history on shared slide snapshots, the command registry (320 commands in `cmd/*`), pointer tools, keys, AutoRecover, the panic guard (`guard.rs`) | @@ -81,6 +81,7 @@ macOS, Windows, Linux, FreeBSD and web artifacts. | Date | Change | Summary | |---|---|---| +| 2026-11-11 | trivial | The `opus`, `ogg`, `isobmff` and `matroska` crates are gone: `media` uses craft-audio and craft-media from craft-libs | | 2026-10-18 | minor | Native chart payloads use referenced ZIP members and share media byte/item limits; old inline sidecars remain readable. Synthetic tests cover multi-megabyte layout, all shape owners, shared budgets, missing/corrupt members and invalid references. Scores and milestone scope unchanged | | 2026-10-14 | minor | Community PR wave folded: one L2/L5/L6 row each; command registry 320 (`cmd!` registrations in `cmd/*`), 41 dialogs (titled in `dialogs.rs`), 202k lines, 24 crates (`metafile` added at L0), 1,641 tests. The per-PR rows below give the counts each PR saw on its own branch | | 2026-10-14 | minor | Size and Position dialog (`shape.sizePosition`): position, size, rotation, scale and lock aspect in one undo step. 227 commands, 28 dialogs | diff --git a/docs/file-format-parity.md b/docs/file-format-parity.md index 70b74f7e1..9016acab7 100644 --- a/docs/file-format-parity.md +++ b/docs/file-format-parity.md @@ -56,7 +56,7 @@ intermediate build that required inline `bytes` cannot read the new referenced r | SVG | yes (with PNG fallback) | **no** | The PNG fallback shows; SVG files can't be inserted | | EMF / WMF | yes | partial | Drawn as vectors (paths, pens, pattern and hatch brushes, bitmaps, text in the document's fonts with the file's character advances). Rectangle clip regions (`INTERSECTCLIPRECT`, `EXCLUDECLIPRECT`, `EXTSELECTCLIPRGN`) limit later drawing. EMF+ only pictures play every EMF+ drawing record (brushes incl. gradients and textures, pens, paths, curves, images, strings, transforms, clipping; see `crates/metafile/src/emfplus.rs` for the few exceptions). Legacy OLE objects (VML preview) and ActiveX controls show their pictures | | HEIC | yes (macOS) | no | | -| Audio: MP3, AAC/M4A, ALAC, WAV, AIFF, CAF, FLAC, Ogg Vorbis, Opus | yes (Ogg/Opus partly) | yes | Pure-Rust decoders in `crates/media`, `crates/opus` | +| Audio: MP3, AAC/M4A, ALAC, WAV, AIFF, CAF, FLAC, Ogg Vorbis, Opus | yes (Ogg/Opus partly) | yes | Pure-Rust decoders in `crates/media`; Opus from craft-audio (craft-libs) | | WMA | yes (Windows) | embedded, not playable | | | Video: H.264, HEVC in MP4/MOV | yes | yes | Pure-Rust, software decode | | Video: VP9, AV1 in WebM/MKV | partly (OS codecs) | yes | Ahead of PowerPoint for Mac | diff --git a/xtask/src/layers.rs b/xtask/src/layers.rs index 47a9e67a6..d8edf371f 100644 --- a/xtask/src/layers.rs +++ b/xtask/src/layers.rs @@ -34,17 +34,14 @@ impl Class { /// prefix. pub const TABLE: &[(&str, Class)] = &[ ("geom", Class::Layer(0)), - // Codecs and containers (ported from FilmCraft): L0 standalone leaves, L1 codecs over - // `bitstream`, and `media` (probe, decode, playback mixer) at L2. + // Video codecs (ported from FilmCraft): `bitstream` is an L0 standalone leaf, the codecs sit + // over it at L1, and `media` (probe, decode, playback mixer) at L2. Containers and the Opus + // decoder come from craft-libs (craft-media, craft-audio). ("bitstream", Class::Standalone), - ("matroska", Class::Standalone), - ("ogg", Class::Standalone), - ("opus", Class::Standalone), ("h264", Class::Layer(1)), ("hevc", Class::Layer(1)), ("vp9", Class::Layer(1)), ("av1", Class::Layer(1)), - ("isobmff", Class::Layer(1)), ("media", Class::Layer(2)), ("color", Class::Layer(0)), // WMF/EMF reader (ported from WordCraft).