Skip to main content

rusty_opus/
lib.rs

1//! A complete, pure-Rust implementation of the [Opus audio codec](https://opus-codec.org/)
2//! (RFC 6716 / RFC 8251): encoder and decoder, SILK, CELT and Hybrid modes, with no C code
3//! and no dependencies.
4//!
5//! # Quick start
6//!
7//! ```
8//! use rusty_opus::{Application, Error, OpusDecoder, OpusEncoder};
9//!
10//! # fn main() -> Result<(), Error> {
11//! const RATE: i32 = 48_000;
12//! const FRAME: usize = 960; // 20 ms at 48 kHz
13//!
14//! let mut encoder = OpusEncoder::new(RATE, 2, Application::Audio)?;
15//! encoder.bitrate_bps = 96_000;
16//! let pcm = vec![0.0f32; FRAME * 2]; // interleaved stereo, nominal range [-1, 1]
17//! let mut packet = [0u8; 1500];
18//! let len = encoder.encode(&pcm, FRAME, &mut packet)?;
19//!
20//! let mut decoder = OpusDecoder::new(RATE, 2)?;
21//! let mut out = vec![0.0f32; FRAME * 2];
22//! assert_eq!(decoder.decode(&packet[..len], FRAME, &mut out)?, FRAME);
23//!
24//! // An empty packet signals a lost frame: the decoder conceals it.
25//! assert_eq!(decoder.decode(&[], FRAME, &mut out)?, FRAME);
26//!
27//! // Malformed input is an error, never a panic.
28//! assert!(decoder.decode(&[0xFF, 0x03], FRAME, &mut out).is_err());
29//! # Ok(())
30//! # }
31//! ```
32//!
33//! # Public API
34//!
35//! - [`OpusEncoder`] and [`OpusDecoder`] — mono/stereo coding, packet-loss concealment
36//!   ([`OpusDecoder::decode`] with an empty packet) and in-band FEC
37//!   ([`OpusDecoder::decode_fec`]).
38//! - [`multistream`] — surround encoding and decoding (mapping families 0 and 1).
39//! - [`repacketizer`] — merge, split, pad and unpad packets without re-encoding.
40//! - [`parallel`] — frame-parallel and batch encoding across threads.
41//! - [`Error`] — the error type of every fallible operation; variants mirror `libopus`
42//!   error codes.
43//!
44//! Other modules are internal codec stages: they are public so integration tests can
45//! reach them, but hidden from this documentation and not covered by semantic versioning.
46//!
47//! # Performance and platforms
48//!
49//! SIMD kernels (AVX2/FMA, AVX and SSE2 on x86; NEON on aarch64) are selected at runtime,
50//! each with a scalar fallback; `RUSTY_OPUS_ISA=scalar|sse2|avx|avx2` caps the instruction
51//! set. After warm-up, [`OpusEncoder::encode`] and [`OpusDecoder::decode`] perform no heap
52//! allocation per frame. The crate builds
53//! for every Rust target, including `wasm32`.
54
55// The documented (non-hidden) public API is fully documented; keep it that way.
56#![warn(missing_docs)]
57#![allow(unsafe_op_in_unsafe_fn)]
58#![allow(clippy::too_many_arguments)]
59#![allow(clippy::needless_range_loop)]
60
61// Internal codec stages. They stay `pub` so the integration tests can exercise
62// them directly, but they are hidden from the documentation and are NOT part of
63// the semver-covered API: depend only on the items re-exported at the crate root
64// and the `parallel`, `repacketizer` and `multistream` modules.
65#[doc(hidden)]
66pub mod analysis;
67mod error;
68pub use error::Error;
69#[doc(hidden)]
70pub mod analysis_data;
71#[doc(hidden)]
72pub mod bands;
73#[doc(hidden)]
74pub mod celt;
75#[doc(hidden)]
76pub mod celt_lpc;
77#[doc(hidden)]
78pub mod hp_cutoff;
79#[doc(hidden)]
80pub mod isa;
81#[doc(hidden)]
82pub mod kiss_fft;
83#[doc(hidden)]
84pub mod mdct;
85#[doc(hidden)]
86pub mod modes;
87pub mod multistream;
88pub mod parallel;
89#[doc(hidden)]
90pub mod pitch;
91#[doc(hidden)]
92pub mod prof;
93#[doc(hidden)]
94pub mod pvq;
95#[doc(hidden)]
96pub mod quant_bands;
97#[doc(hidden)]
98pub mod range_coder;
99#[doc(hidden)]
100pub mod rate;
101pub mod repacketizer;
102#[doc(hidden)]
103pub mod silk;
104
105#[doc(hidden)]
106pub use silk::{SilkResampler, SilkResamplerDown1_3, SilkResamplerDown1_6};
107
108// Low-level stage types: hidden (not semver-covered), see the module note above.
109#[doc(hidden)]
110pub use celt::{CeltDecoder, CeltEncoder};
111use hp_cutoff::hp_cutoff;
112use range_coder::RangeCoder;
113use silk::control_codec::silk_control_encoder;
114use silk::enc_api::silk_encode;
115use silk::init_encoder::silk_init_encoder;
116use silk::lin2log::silk_lin2log;
117use silk::log2lin::silk_log2lin;
118use silk::macros::*;
119use silk::structs::SilkEncoderState;
120
121/// The intended use of an encoder, which steers its mode and tuning decisions
122/// (libopus `OPUS_APPLICATION_*`; the discriminants are the libopus values).
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub enum Application {
125    /// Interactive speech: favours intelligibility and SILK/Hybrid coding.
126    Voip = 2048,
127    /// General audio and music: favours fidelity to the input.
128    Audio = 2049,
129    /// Lowest possible latency: CELT only, no speech-optimised modes.
130    RestrictedLowDelay = 2051,
131}
132
133/// OPUS_SET_SIGNAL hint: bias mode selection toward speech or music. `None` =
134/// OPUS_AUTO (let the analysis decide).
135#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub enum SignalType {
137    /// The input is predominantly speech.
138    Voice,
139    /// The input is predominantly music.
140    Music,
141}
142
143/// Audio bandwidth of a coded stream (libopus `OPUS_BANDWIDTH_*`; the
144/// discriminants are the libopus values).
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146pub enum Bandwidth {
147    /// Let the encoder choose from the bitrate and content.
148    Auto = -1000,
149    /// 4 kHz audio bandwidth (8 kHz sampling).
150    Narrowband = 1101,
151    /// 6 kHz audio bandwidth (12 kHz sampling).
152    Mediumband = 1102,
153    /// 8 kHz audio bandwidth (16 kHz sampling).
154    Wideband = 1103,
155    /// 12 kHz audio bandwidth (24 kHz sampling).
156    Superwideband = 1104,
157    /// 20 kHz audio bandwidth (48 kHz sampling).
158    Fullband = 1105,
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq)]
162enum OpusMode {
163    SilkOnly,
164    Hybrid,
165    CeltOnly,
166}
167
168/// An Opus encoder for one mono or stereo stream.
169///
170/// Create it with [`OpusEncoder::new`], adjust the public fields (bitrate,
171/// complexity, CBR, FEC, DTX, ...) as needed, then call [`OpusEncoder::encode`]
172/// once per frame. After the first few frames, encoding performs no heap
173/// allocation.
174pub struct OpusEncoder {
175    celt_enc: CeltEncoder,
176    silk_enc: Box<SilkEncoderState>,
177    application: Application,
178    sampling_rate: i32,
179    channels: usize,
180    bandwidth: Bandwidth,
181    /// Target bitrate in bits per second (default 64 000).
182    pub bitrate_bps: i32,
183    /// Computational complexity, 0 (fastest) to 10 (best quality).
184    pub complexity: i32,
185    /// Constant bitrate when `true`; variable bitrate (the default) when `false`.
186    pub use_cbr: bool,
187
188    /// Embed in-band forward error correction (SILK LBRR) so the next packet can
189    /// recover a lost one; most useful with `packet_loss_perc > 0`.
190    pub use_inband_fec: bool,
191
192    /// Discontinuous transmission: after enough consecutive inactive frames,
193    /// emit a 1-byte (TOC-only) packet so the decoder runs comfort-noise/PLC.
194    pub use_dtx: bool,
195    /// Consecutive inactive milliseconds, in Q1 (opus_encoder.c nb_no_activity).
196    nb_no_activity_ms_q1: i32,
197    /// Final range-coder state of the last packet (0 for DTX/PLC packets, which
198    /// carry no coded range — opus_encoder.c st->rangeFinal).
199    range_final: u32,
200
201    /// Expected packet-loss percentage (0-100); raises robustness at some
202    /// cost in quality.
203    pub packet_loss_perc: i32,
204    silk_initialized: bool,
205    mode: OpusMode,
206    prev_enc_mode: Option<OpusMode>,
207
208    variable_hp_smth2_q15: i32,
209    /// Rate-dependent automatic bandwidth (libopus auto_bandwidth), stored as the
210    /// Bandwidth discriminant (1101 NB .. 1105 FB). Hysteresis state.
211    auto_bandwidth: i32,
212    first_frame: bool,
213    /// Overrides automatic bandwidth selection when set (OPUS_SET_BANDWIDTH).
214    pub force_bandwidth: Option<Bandwidth>,
215    /// OPUS_SET_SIGNAL: force the voice/music bias (None = auto from analysis).
216    pub signal_type: Option<SignalType>,
217    /// OPUS_SET_MAX_BANDWIDTH: cap the automatically-selected bandwidth.
218    pub max_bandwidth: Bandwidth,
219    /// Tonality/music/bandwidth analysis (libopus src/analysis.c); runs when
220    /// complexity >= 7 and the API rate is >= 16 kHz.
221    tonality: analysis::TonalityAnalysisState,
222    analysis_kfft: Option<kiss_fft::KissFftState>,
223    /// Input bit depth assumed by the analysis noise floors. The float API
224    /// default is 24; set 16 for s16-sourced content (opus_demo parity).
225    pub lsb_depth: i32,
226    /// 0..100 voice probability from the analysis (-1 = unknown), C voice_ratio.
227    voice_ratio: i32,
228    detected_bandwidth: i32,
229    hp_mem: Vec<i32>,
230
231    buf_filtered: Vec<i16>,
232    buf_silk_input: Vec<i16>,
233    buf_stereo_mid: Vec<i16>,
234    buf_stereo_side: Vec<i16>,
235    buf_celt_input: Vec<f32>,
236    down2_state_first: [i32; 2],
237    down2_state_second: [i32; 2],
238    down2_3_state: [i32; 6],
239    down_1_3_state: silk::resampler::SilkResamplerDown1_3,
240    down2_3_state_r: [i32; 6],
241    down_1_3_state_r: silk::resampler::SilkResamplerDown1_3,
242    down_fir_l: Option<silk::resampler::SilkDownFirResampler>,
243    down_fir_r: Option<silk::resampler::SilkDownFirResampler>,
244    /// Last 10 ms of API-rate mono input, for the SILK prefill after a
245    /// CELT-only -> SILK/hybrid transition (opus_encoder.c:1449 prefill=1).
246    silk_prefill_tail: Vec<i16>,
247    silk_prefill_pending: bool,
248    buf_left: Vec<i16>,
249    buf_right: Vec<i16>,
250    /// Last 2.5 ms of the previous frame's input (planar), for the CELT
251    /// prefill after a mode-transition reset (opus_encoder.c:2060).
252    celt_prefill_tail: Vec<f32>,
253
254    rc: RangeCoder,
255
256    // ---- Great Gate P1 instrumentation (docs/great-gate.md) ----
257    /// Observe-only harvest tap: when `RUSTY_OPUS_GATE_HARVEST=<path>` is set at
258    /// construction, every encoded frame appends one CSV row with the signals
259    /// the mode/bandwidth decision consumed plus the outcome (mode, bw, bytes).
260    /// The bitstream is byte-identical on or off — the tap only reads. Env is
261    /// read ONCE here, never per frame. Serial encoders only (the parallel path
262    /// would interleave rows).
263    gate_tap: Option<std::io::BufWriter<std::fs::File>>,
264    /// Clip label stamped into harvest rows (`RUSTY_OPUS_GATE_CLIP`).
265    gate_clip: String,
266    /// Frame counter for harvest rows.
267    gate_frame: u64,
268    /// Truth-table lever: `RUSTY_OPUS_FORCE_MODE=silk|celt|hybrid` pins the
269    /// coding mode after the auto decision (bandwidth reconciled to a valid TOC
270    /// config). Unset = None = byte-identical to shipped behavior.
271    force_mode: Option<OpusMode>,
272    /// Mode-dwell hysteresis: a proposed mode change must persist this many
273    /// consecutive frames before it is committed. **1 = OFF and
274    /// byte-identical**; set via `RUSTY_OPUS_MODE_DWELL`.
275    ///
276    /// Measured ineffective for the startup-mode defect it was built for and
277    /// left default-off — see the refutation at its use site in `encode`.
278    pub mode_dwell: u32,
279    /// Consecutive frames the current proposal has differed from the coded mode.
280    mode_dwell_run: u32,
281    /// Analysis warm-up guard: ignore the tonality classifier's verdict for
282    /// this many analysis frames and fall back to the application default.
283    /// **Default 10** (`RUSTY_OPUS_ANALYSIS_WARMUP`; 0 = OFF and restores the
284    /// pre-2026-08-07 byte-identical behaviour).
285    ///
286    /// libopus feeds its analysis a lookahead buffer, so the classifier is
287    /// already converged when the first frame is coded. We call `run_analysis`
288    /// with `analysis_frame_size == frame_size` — zero lookahead — so on our
289    /// encoder the classifier spends its first ~20 frames climbing from
290    /// "voice" to its steady-state verdict. On music-ish content that made the
291    /// first 480 ms code as hybrid before flipping to CELT for good.
292    analysis_warmup: u32,
293    /// Analysis frames seen (saturating), compared against `analysis_warmup`.
294    analysis_frames: u32,
295    /// Multi-frame packet in progress (opus_encode_native's >20 ms CELT/hybrid
296    /// and >60 ms paths): mode + bandwidth decided ONCE for the whole packet,
297    /// then each sub-frame is coded with them locked so every sub-frame
298    /// carries the same TOC config (a code-3 packet requires it).
299    mf_lock: Option<(OpusMode, Bandwidth)>,
300    /// Multi-frame packet assembly: reused across calls so 40-120 ms frames
301    /// encode without allocating once warmed up.
302    mf_rp: repacketizer::Repacketizer,
303    mf_buf: Vec<u8>,
304    /// Set by encode_multiframe for the last sub-frame of a to_celt packet.
305    mf_to_celt: bool,
306}
307
308// libopus opus_encoder.c bandwidth thresholds: (threshold, hysteresis) pairs for
309// NB<->MB, MB<->WB, WB<->SWB, SWB<->FB, interpolated voice<->music by voice_est^2.
310const MONO_VOICE_BANDWIDTH_THRESHOLDS: [i32; 8] = [9000, 700, 9000, 700, 13500, 1000, 14000, 2000];
311const MONO_MUSIC_BANDWIDTH_THRESHOLDS: [i32; 8] = [9000, 700, 9000, 700, 11000, 1000, 12000, 2000];
312const STEREO_VOICE_BANDWIDTH_THRESHOLDS: [i32; 8] =
313    [9000, 700, 9000, 700, 13500, 1000, 14000, 2000];
314const STEREO_MUSIC_BANDWIDTH_THRESHOLDS: [i32; 8] =
315    [9000, 700, 9000, 700, 11000, 1000, 12000, 2000];
316
317/// Coerce a bandwidth to one the given mode can actually signal in the TOC:
318/// CELT has no mediumband config, SILK-only tops out at wideband, and hybrid
319/// exists only at SWB/FB. Used wherever a mode is overridden after the
320/// bandwidth has already been chosen (dwell hysteresis, forced mode).
321fn reconcile_bandwidth(mode: OpusMode, bw: Bandwidth) -> Bandwidth {
322    match mode {
323        // libopus: "CELT mode doesn't support mediumband, use wideband instead".
324        OpusMode::CeltOnly if bw == Bandwidth::Mediumband => Bandwidth::Wideband,
325        OpusMode::SilkOnly if matches!(bw, Bandwidth::Superwideband | Bandwidth::Fullband) => {
326            Bandwidth::Wideband
327        }
328        OpusMode::Hybrid if !matches!(bw, Bandwidth::Superwideband | Bandwidth::Fullband) => {
329            Bandwidth::Superwideband
330        }
331        _ => bw,
332    }
333}
334
335/// Development toggles read from the environment: mode forcing, A/B switches
336/// and the tuning-harvest log used by the `tools/gate_*` research harnesses.
337///
338/// Always `None` unless the crate is built with the `research` feature, so the
339/// behaviour of a production build can never be changed by its environment.
340/// (The one documented variable, `RUSTY_OPUS_ISA`, only caps the SIMD level and
341/// is read in `crate::isa`.)
342#[inline]
343pub(crate) fn research_env(name: &str) -> Option<std::ffi::OsString> {
344    #[cfg(feature = "research")]
345    {
346        std::env::var_os(name)
347    }
348    #[cfg(not(feature = "research"))]
349    {
350        let _ = name;
351        None
352    }
353}
354
355fn research_str(name: &str) -> Option<String> {
356    research_env(name).and_then(|v| v.into_string().ok())
357}
358
359fn research_parse<T: std::str::FromStr>(name: &str) -> Option<T> {
360    research_str(name).and_then(|s| s.parse().ok())
361}
362
363/// The tuning-harvest CSV (`RUSTY_OPUS_GATE_HARVEST=<path>`), research builds only.
364#[cfg(feature = "research")]
365fn open_gate_tap() -> Option<std::io::BufWriter<std::fs::File>> {
366    let p = research_str("RUSTY_OPUS_GATE_HARVEST")?;
367    use std::io::Write as _;
368    let mut f = std::fs::OpenOptions::new()
369        .create(true)
370        .append(true)
371        .open(&p)
372        .ok()?;
373    if f.metadata().map_or(0, |m| m.len()) == 0 {
374        let _ = writeln!(
375            f,
376            "clip,frame,mode,bw,ch,bitrate,complexity,equiv,voice_est,\
377                 is_silence,active,valid,tonality,tonality_slope,noisiness,\
378                 activity_prob,music_prob,music_prob_min,music_prob_max,\
379                 det_bw,max_pitch_ratio,bytes"
380        );
381    }
382    Some(std::io::BufWriter::new(f))
383}
384
385#[cfg(not(feature = "research"))]
386fn open_gate_tap() -> Option<std::io::BufWriter<std::fs::File>> {
387    None
388}
389
390/// opus_encoder.c compute_redundancy_bytes.
391fn compute_redundancy_bytes(
392    max_data_bytes: usize,
393    bitrate_bps: i32,
394    frame_rate: i32,
395    channels: usize,
396) -> usize {
397    let base_bits = 40 * channels as i32 + 20;
398    // Equivalent rate for 5 ms frames; 1.5x for VBR (short, avoids artefacts).
399    let redundancy_rate = bitrate_bps + base_bits * (200 - frame_rate);
400    let redundancy_rate = 3 * redundancy_rate / 2;
401    let mut redundancy_bytes = redundancy_rate / 1600;
402    // The max rate we can use given CBR or VBR with cap.
403    let available_bits = max_data_bytes as i32 * 8 - 2 * base_bits;
404    let cap = (available_bits * 240 / (240 + 48000 / frame_rate) + base_bits) / 8;
405    redundancy_bytes = redundancy_bytes.min(cap);
406    if redundancy_bytes > 4 + 8 * channels as i32 {
407        redundancy_bytes.min(257) as usize
408    } else {
409        0
410    }
411}
412
413fn compute_equiv_rate(
414    bitrate: i32,
415    channels: usize,
416    frame_rate: i32,
417    vbr: bool,
418    complexity: i32,
419    loss: i32,
420) -> i32 {
421    let mut equiv = bitrate;
422    if frame_rate > 50 {
423        equiv -= (40 * channels as i32 + 20) * (frame_rate - 50);
424    }
425    if !vbr {
426        equiv -= equiv / 12;
427    }
428    equiv = equiv * (90 + complexity) / 100;
429    if loss > 0 {
430        equiv -= equiv * loss / (12 * loss + 20);
431    }
432    equiv
433}
434
435fn compute_mode_threshold(
436    application: Application,
437    channels: usize,
438    prev_was_celt: bool,
439    has_prev_mode: bool,
440    voice_est: i32,
441) -> i32 {
442    let mode_voice = if channels == 1 { 64000 } else { 44000 };
443    let mode_music = 10000;
444
445    let diff = mode_voice - mode_music;
446    let offset = (voice_est * voice_est * diff) >> 14;
447    let mut threshold = mode_music + offset;
448
449    if application == Application::Voip {
450        threshold += 8000;
451    }
452
453    if has_prev_mode {
454        if prev_was_celt {
455            threshold -= 4000;
456        } else {
457            threshold += 4000;
458        }
459    }
460
461    if application == Application::RestrictedLowDelay {
462        threshold = 0;
463    }
464
465    threshold
466}
467
468fn compute_silk_rate_for_hybrid(
469    rate_bps: i32,
470    bandwidth: Bandwidth,
471    frame20ms: bool,
472    vbr: bool,
473) -> i32 {
474    const RATE_TABLE: &[(i32, i32, i32)] = &[
475        (0, 0, 0),
476        (12000, 10000, 10000),
477        (16000, 13500, 13500),
478        (20000, 16000, 16000),
479        (24000, 18000, 18000),
480        (32000, 22000, 22000),
481        (64000, 38000, 38000),
482    ];
483    let n = RATE_TABLE.len();
484    let mut i = 1;
485    while i < n && RATE_TABLE[i].0 <= rate_bps {
486        i += 1;
487    }
488    let mut silk_rate = if i == n {
489        let (x_last, r10_last, r20_last) = RATE_TABLE[n - 1];
490        let base = if frame20ms { r20_last } else { r10_last };
491        base + (rate_bps - x_last) / 2
492    } else {
493        let (x0, lo10, lo20) = RATE_TABLE[i - 1];
494        let (x1, hi10, hi20) = RATE_TABLE[i];
495        let (lo, hi) = if frame20ms {
496            (lo20, hi20)
497        } else {
498            (lo10, hi10)
499        };
500        (lo * (x1 - rate_bps) + hi * (rate_bps - x0)) / (x1 - x0)
501    };
502    // C tail adjustments (opus_encoder.c:789): tiny SILK boost for CBR, and
503    // +300 for SWB hybrid (the CELT part starts at band 17 either way but
504    // covers less spectrum, so SILK earns a bigger share).
505    if !vbr {
506        silk_rate += 100;
507    }
508    if bandwidth == Bandwidth::Superwideband {
509        silk_rate += 300;
510    }
511    silk_rate
512}
513
514#[cfg(test)]
515mod reconcile_bandwidth_tests {
516    use super::{Bandwidth, OpusMode, reconcile_bandwidth};
517
518    #[test]
519    fn celt_maps_mediumband_up_to_wideband() {
520        // The CELT TOC has no mediumband config; libopus uses WIDEBAND
521        // (opus_encoder.c:1680). Mapping it DOWN to NB was a divergence.
522        assert_eq!(
523            reconcile_bandwidth(OpusMode::CeltOnly, Bandwidth::Mediumband),
524            Bandwidth::Wideband
525        );
526    }
527
528    #[test]
529    fn celt_leaves_every_other_bandwidth_alone() {
530        for bw in [
531            Bandwidth::Narrowband,
532            Bandwidth::Wideband,
533            Bandwidth::Superwideband,
534            Bandwidth::Fullband,
535        ] {
536            assert_eq!(reconcile_bandwidth(OpusMode::CeltOnly, bw), bw);
537        }
538    }
539
540    #[test]
541    fn silk_only_caps_at_wideband() {
542        assert_eq!(
543            reconcile_bandwidth(OpusMode::SilkOnly, Bandwidth::Superwideband),
544            Bandwidth::Wideband
545        );
546        assert_eq!(
547            reconcile_bandwidth(OpusMode::SilkOnly, Bandwidth::Fullband),
548            Bandwidth::Wideband
549        );
550        // At or below wideband it is already codeable.
551        for bw in [
552            Bandwidth::Narrowband,
553            Bandwidth::Mediumband,
554            Bandwidth::Wideband,
555        ] {
556            assert_eq!(reconcile_bandwidth(OpusMode::SilkOnly, bw), bw);
557        }
558    }
559
560    #[test]
561    fn hybrid_floors_at_superwideband() {
562        for bw in [
563            Bandwidth::Narrowband,
564            Bandwidth::Mediumband,
565            Bandwidth::Wideband,
566        ] {
567            assert_eq!(
568                reconcile_bandwidth(OpusMode::Hybrid, bw),
569                Bandwidth::Superwideband
570            );
571        }
572        // Hybrid exists only at SWB/FB, so those pass through.
573        assert_eq!(
574            reconcile_bandwidth(OpusMode::Hybrid, Bandwidth::Superwideband),
575            Bandwidth::Superwideband
576        );
577        assert_eq!(
578            reconcile_bandwidth(OpusMode::Hybrid, Bandwidth::Fullband),
579            Bandwidth::Fullband
580        );
581    }
582}
583
584#[cfg(test)]
585mod silk_rate_tests {
586    use super::compute_silk_rate_for_hybrid;
587    use crate::Bandwidth;
588
589    #[test]
590    fn test_reference_table_exact_entries() {
591        assert_eq!(
592            compute_silk_rate_for_hybrid(12000, Bandwidth::Fullband, true, true),
593            10000
594        );
595        assert_eq!(
596            compute_silk_rate_for_hybrid(16000, Bandwidth::Fullband, true, true),
597            13500
598        );
599        assert_eq!(
600            compute_silk_rate_for_hybrid(20000, Bandwidth::Fullband, true, true),
601            16000
602        );
603        assert_eq!(
604            compute_silk_rate_for_hybrid(24000, Bandwidth::Fullband, true, true),
605            18000
606        );
607        assert_eq!(
608            compute_silk_rate_for_hybrid(32000, Bandwidth::Fullband, true, true),
609            22000
610        );
611        assert_eq!(
612            compute_silk_rate_for_hybrid(64000, Bandwidth::Fullband, true, true),
613            38000
614        );
615    }
616
617    #[test]
618    fn test_32kbps_gives_22kbps_silk() {
619        assert_eq!(
620            compute_silk_rate_for_hybrid(32000, Bandwidth::Fullband, true, true),
621            22000
622        );
623    }
624
625    #[test]
626    fn test_interpolation_between_table_entries() {
627        let r = compute_silk_rate_for_hybrid(18000, Bandwidth::Fullband, true, true);
628        assert_eq!(r, 14750);
629    }
630
631    #[test]
632    fn test_above_table_max_gives_half_extra() {
633        let r = compute_silk_rate_for_hybrid(72000, Bandwidth::Fullband, true, true);
634        assert_eq!(r, 38000 + (72000 - 64000) / 2);
635    }
636}
637
638impl OpusEncoder {
639    ///
640    /// # Errors
641    ///
642    /// [`Error::BadArg`] if `sampling_rate` is not 8000, 12000, 16000, 24000 or
643    /// 48000 Hz or `channels` is not 1 or 2; [`Error::Internal`] if the SILK
644    /// encoder fails to initialise.
645    pub fn new(
646        sampling_rate: i32,
647        channels: usize,
648        application: Application,
649    ) -> Result<Self, Error> {
650        if ![8000, 12000, 16000, 24000, 48000].contains(&sampling_rate) {
651            return Err(Error::BadArg("Invalid sampling rate"));
652        }
653        if ![1, 2].contains(&channels) {
654            return Err(Error::BadArg("Invalid number of channels"));
655        }
656
657        let mode = modes::default_mode();
658        let mut celt_enc = CeltEncoder::new(mode, channels);
659        // CELT always codes a 48 kHz frame (libopus resampling_factor).
660        celt_enc.upsample = (48000 / sampling_rate) as usize;
661
662        let mut silk_enc = Box::new(SilkEncoderState::default());
663        if silk_init_encoder(&mut silk_enc, 0) != 0 {
664            return Err(Error::Internal("SILK encoder initialization failed"));
665        }
666
667        let (opus_mode, bw) = match application {
668            Application::Voip => {
669                let bw = match sampling_rate {
670                    8000 => Bandwidth::Narrowband,
671                    12000 => Bandwidth::Mediumband,
672                    16000 => Bandwidth::Wideband,
673                    24000 => Bandwidth::Superwideband,
674                    48000 => Bandwidth::Fullband,
675                    _ => Bandwidth::Narrowband,
676                };
677
678                let mode = if sampling_rate > 16000 {
679                    OpusMode::Hybrid
680                } else {
681                    OpusMode::SilkOnly
682                };
683                (mode, bw)
684            }
685            Application::RestrictedLowDelay => {
686                let bw = match sampling_rate {
687                    8000 => Bandwidth::Narrowband,
688                    12000 => Bandwidth::Mediumband,
689                    16000 => Bandwidth::Wideband,
690                    24000 => Bandwidth::Superwideband,
691                    _ => Bandwidth::Fullband,
692                };
693                (OpusMode::CeltOnly, bw)
694            }
695            Application::Audio => {
696                if sampling_rate <= 16000 {
697                    let bw = match sampling_rate {
698                        8000 => Bandwidth::Narrowband,
699                        12000 => Bandwidth::Mediumband,
700                        _ => Bandwidth::Wideband,
701                    };
702                    (OpusMode::SilkOnly, bw)
703                } else {
704                    let bw = match sampling_rate {
705                        24000 => Bandwidth::Superwideband,
706                        _ => Bandwidth::Fullband,
707                    };
708                    (OpusMode::Hybrid, bw)
709                }
710            }
711        };
712
713        use silk::lin2log::silk_lin2log;
714        let variable_hp_smth2_q15 = silk_lin2log(60) << 8;
715
716        Ok(Self {
717            celt_enc,
718            silk_enc,
719            application,
720            sampling_rate,
721            channels,
722            bandwidth: bw,
723            bitrate_bps: 64000,
724            complexity: 9,
725            use_cbr: false,
726            use_inband_fec: false,
727            use_dtx: false,
728            nb_no_activity_ms_q1: 0,
729            range_final: 0,
730            packet_loss_perc: 0,
731            silk_initialized: false,
732            prev_enc_mode: None,
733            mode: opus_mode,
734            variable_hp_smth2_q15,
735            auto_bandwidth: 0,
736            first_frame: true,
737            force_bandwidth: None,
738            signal_type: None,
739            max_bandwidth: Bandwidth::Fullband,
740            tonality: analysis::TonalityAnalysisState::new(sampling_rate),
741            analysis_kfft: kiss_fft::KissFftState::new(480),
742            // Float-API default, faithful to opus_encoder.c. `RUSTY_OPUS_LSB_DEPTH`
743            // overrides it for the D1 bandwidth-detector investigation: the
744            // analysis noise floor is (5.7e-4 / 2^(lsb_depth-8))^2, so feeding
745            // s16-sourced material at depth 24 puts the floor 2^16 too low.
746            lsb_depth: research_parse("RUSTY_OPUS_LSB_DEPTH").unwrap_or(24),
747            voice_ratio: -1,
748            detected_bandwidth: 0,
749            hp_mem: vec![0; channels * 2],
750
751            buf_filtered: Vec::new(),
752            buf_silk_input: Vec::new(),
753            buf_stereo_mid: Vec::new(),
754            buf_stereo_side: Vec::new(),
755            buf_celt_input: Vec::new(),
756            down2_state_first: [0; 2],
757            down2_state_second: [0; 2],
758            down2_3_state: [0; 6],
759            down_1_3_state: silk::resampler::SilkResamplerDown1_3::default(),
760            down2_3_state_r: [0; 6],
761            down_1_3_state_r: silk::resampler::SilkResamplerDown1_3::default(),
762            down_fir_l: None,
763            down_fir_r: None,
764            silk_prefill_tail: Vec::new(),
765            silk_prefill_pending: false,
766            buf_left: Vec::new(),
767            buf_right: Vec::new(),
768            celt_prefill_tail: Vec::new(),
769            rc: RangeCoder::new_encoder(1),
770            gate_tap: open_gate_tap(),
771            gate_clip: research_str("RUSTY_OPUS_GATE_CLIP").unwrap_or_default(),
772            gate_frame: 0,
773            force_mode: match research_str("RUSTY_OPUS_FORCE_MODE").as_deref() {
774                Some("silk") => Some(OpusMode::SilkOnly),
775                Some("celt") => Some(OpusMode::CeltOnly),
776                Some("hybrid") => Some(OpusMode::Hybrid),
777                _ => None,
778            },
779            mode_dwell: research_parse("RUSTY_OPUS_MODE_DWELL").unwrap_or(1),
780            mode_dwell_run: 0,
781            // DEFAULT-ON at 10 since 2026-08-07: 14 wins / 0 losses / 1 neutral
782            // (-0.005) over a 65-rung, 13-class PEAQ ladder, with all VoIP
783            // classes bit-for-bit unchanged. `RUSTY_OPUS_ANALYSIS_WARMUP=0`
784            // restores the previous byte-identical behaviour.
785            analysis_warmup: research_parse("RUSTY_OPUS_ANALYSIS_WARMUP").unwrap_or(10),
786            analysis_frames: 0,
787            mf_lock: None,
788            mf_rp: repacketizer::Repacketizer::new(),
789            mf_buf: Vec::new(),
790            mf_to_celt: false,
791        })
792    }
793
794    ///
795    /// # Errors
796    ///
797    /// [`Error::BadArg`] unless the encoder runs at 24 or 48 kHz, the only rates
798    /// where hybrid (SILK + CELT) coding exists.
799    pub fn enable_hybrid_mode(&mut self) -> Result<(), Error> {
800        if self.sampling_rate != 24000 && self.sampling_rate != 48000 {
801            return Err(Error::BadArg(
802                "Hybrid mode requires 24kHz or 48kHz sampling rate",
803            ));
804        }
805        let bw = if self.sampling_rate == 48000 {
806            Bandwidth::Fullband
807        } else {
808            Bandwidth::Superwideband
809        };
810        self.mode = OpusMode::Hybrid;
811        self.bandwidth = bw;
812        self.silk_initialized = false;
813        Ok(())
814    }
815
816    /// Final range-coder state of the last encoded packet (libopus
817    /// OPUS_GET_FINAL_RANGE). Stored in opus_demo `.bit` framing so the reference
818    /// decoder can verify encoder/decoder range-coder agreement per packet.
819    pub fn final_range(&self) -> u32 {
820        self.range_final
821    }
822
823    /// opus_encoder.c:1296 voice_est ladder: forced by `signal_type` when set,
824    /// else analysis-driven when voice_ratio is known, else application defaults.
825    fn compute_voice_est(&self) -> i32 {
826        match self.signal_type {
827            Some(SignalType::Voice) => return 127,
828            Some(SignalType::Music) => return 0,
829            None => {}
830        }
831        if self.voice_ratio >= 0 {
832            let mut v = (self.voice_ratio * 327) >> 8;
833            // For AUDIO, never be more than 90% confident of having speech.
834            if self.application == Application::Audio {
835                v = v.min(115);
836            }
837            v
838        } else {
839            match self.application {
840                Application::Voip => 115,
841                Application::Audio => 48,
842                Application::RestrictedLowDelay => 0,
843            }
844        }
845    }
846
847    /// opus_encode_native's multi-frame path: `frame_size` exceeds what one
848    /// Opus frame of `mode` can hold (>20 ms CELT/hybrid, >60 ms anything), so
849    /// code it as several sub-frames with the packet's mode + bandwidth locked,
850    /// and join them into one code-1/2/3 packet. Sub-frame size as libopus:
851    /// SILK 80 ms = 2x40, 120 ms = 2x60, 100 ms = 5x20; CELT/hybrid 20 ms.
852    fn encode_multiframe(
853        &mut self,
854        input: &[f32],
855        frame_size: usize,
856        output: &mut [u8],
857        mode: OpusMode,
858        to_celt: bool,
859    ) -> Result<usize, Error> {
860        let fs = self.sampling_rate as usize;
861        let enc_frame_size = if mode == OpusMode::SilkOnly {
862            if frame_size == 2 * fs / 25 {
863                fs / 25
864            } else if frame_size == 3 * fs / 25 {
865                3 * fs / 50
866            } else {
867                fs / 50
868            }
869        } else {
870            fs / 50
871        };
872        let nb_frames = frame_size / enc_frame_size;
873        // Worst-case repacketizer header: code 2 = 3 bytes, code-3 VBR =
874        // 2 + 2 per length field (opus_encoder.c max_header_bytes).
875        let max_header = if nb_frames == 2 {
876            3
877        } else {
878            2 + (nb_frames - 1) * 2
879        };
880        let per_frame = (output.len().saturating_sub(max_header) / nb_frames).clamp(2, 1276);
881        let ch = self.channels;
882
883        self.mf_lock = Some((mode, self.bandwidth));
884        // Taken out of `self` for the loop (the sub-frame encodes borrow it);
885        // `mem::take` of a Vec does not allocate, and both go back below.
886        let mut rp = std::mem::take(&mut self.mf_rp);
887        let mut buf = std::mem::take(&mut self.mf_buf);
888        rp.reset();
889        buf.resize(per_frame, 0);
890        let mut result = Ok(());
891        for i in 0..nb_frames {
892            let sub = &input[i * enc_frame_size * ch..(i + 1) * enc_frame_size * ch];
893            // libopus: only the last frame of the packet asks for the switch.
894            self.mf_to_celt = to_celt && i == nb_frames - 1;
895            let r = self.encode(sub, enc_frame_size, &mut buf);
896            self.mf_to_celt = false;
897            match r {
898                Ok(n) => {
899                    if let Err(e) = rp.cat(&buf[..n]) {
900                        result = Err(e);
901                        break;
902                    }
903                }
904                Err(e) => {
905                    result = Err(e);
906                    break;
907                }
908            }
909        }
910        self.mf_lock = None;
911        let written = result.and_then(|()| {
912            let len = rp.out_into(output)?;
913            // CBR: pad the joined packet to the packet's byte budget, as libopus
914            // does when repacketizing a non-VBR multi-frame packet.
915            if self.use_cbr {
916                let target =
917                    ((self.bitrate_bps as i64 * frame_size as i64) / (8 * fs as i64)) as usize;
918                let target = target.min(output.len());
919                if target > len {
920                    return rp.out_padded_into(target, output);
921                }
922            }
923            Ok(len)
924        });
925        self.mf_rp = rp;
926        self.mf_buf = buf;
927        written
928    }
929
930    ///
931    /// # Errors
932    ///
933    /// [`Error::BadArg`] if `frame_size` is not a valid Opus frame duration
934    /// (2.5–120 ms) at this sampling rate or for the selected mode;
935    /// [`Error::BufferTooSmall`] if `output` cannot hold the packet;
936    /// [`Error::Internal`] if a codec stage fails.
937    pub fn encode(
938        &mut self,
939        input: &[f32],
940        frame_size: usize,
941        output: &mut [u8],
942    ) -> Result<usize, Error> {
943        let _prof_total = crate::prof::scope(crate::prof::Stage::Total);
944        if output.len() < 2 {
945            return Err(Error::BufferTooSmall("Output buffer too small"));
946        }
947
948        // Every Opus frame size (opus_encoder.c frame_size_select): 2.5/5/10/20
949        // ms, then 40/60/80/100/120 ms as multiples of 20 ms. 60/100/120 ms do
950        // not divide the rate evenly at 48 kHz, so they are matched explicitly
951        // rather than by `Fs % frame_size` (which rejected all of them).
952        let fs = self.sampling_rate as usize;
953        let valid = [
954            fs / 400,
955            fs / 200,
956            fs / 100,
957            fs / 50,
958            fs / 25,
959            3 * fs / 50,
960            4 * fs / 50,
961            5 * fs / 50,
962            6 * fs / 50,
963        ]
964        .contains(&frame_size);
965        if !valid || frame_size == 0 {
966            return Err(Error::BadArg("Invalid frame size for sampling rate"));
967        }
968        // libopus: frame_rate = Fs/frame_size (integer; 16 for 60 ms).
969        let frame_rate = self.sampling_rate / frame_size as i32;
970
971        // ---- Tonality analysis (opus_encoder.c:1123) ----
972        // A multi-frame packet's sub-frames skip it: the whole packet was
973        // analysed by the outer call that made the mode decision.
974        let mut analysis_info = analysis::AnalysisInfo::default();
975        if self.mf_lock.is_none() && self.complexity >= 7 && self.sampling_rate >= 16000 {
976            if let Some(kfft) = &self.analysis_kfft {
977                analysis_info = analysis::run_analysis(
978                    &mut self.tonality,
979                    kfft,
980                    input,
981                    frame_size,
982                    frame_size,
983                    self.channels,
984                    self.sampling_rate,
985                    self.lsb_depth,
986                );
987            }
988        } else if self.tonality.initialized() {
989            self.tonality.reset();
990        }
991
992        // voice_ratio / detected_bandwidth from the analysis (opus_encoder.c:1154).
993        let silence_thresh = 1.0f32 / (1i64 << self.lsb_depth) as f32;
994        let is_silence = input[..(frame_size * self.channels).min(input.len())]
995            .iter()
996            .fold(0.0f32, |m, &v| m.max(v.abs()))
997            <= silence_thresh;
998        if !is_silence {
999            self.voice_ratio = -1;
1000        }
1001        // Voice-activity flag for DTX (opus_encoder.c:1160). Silence is always
1002        // inactive; with analysis, use the VAD probability; without it, assume
1003        // active (conservative — never DTX away real audio). We skip the
1004        // peak-energy SNR fallback, which only ever ADDS activity.
1005        let activity = if is_silence {
1006            false
1007        } else if analysis_info.valid {
1008            analysis_info.activity_probability >= 0.1
1009        } else {
1010            true
1011        };
1012        // Analysis warm-up guard (see the `analysis_warmup` field doc): until
1013        // the classifier has seen enough frames to converge, leave
1014        // `voice_ratio` at -1 so `compute_voice_est` uses the APPLICATION
1015        // default instead of a half-climbed verdict. That is the right answer
1016        // for both applications — Audio falls back to 48 (music-leaning, which
1017        // is what these clips settle on anyway) and Voip falls back to 115
1018        // (speech-leaning, which is what voip content wants from frame 0).
1019        if analysis_info.valid {
1020            self.analysis_frames = self.analysis_frames.saturating_add(1);
1021        }
1022        let analysis_converged = self.analysis_frames >= self.analysis_warmup;
1023
1024        self.detected_bandwidth = 0;
1025        if analysis_info.valid && analysis_converged {
1026            // Auto path (signal_type override applies later in compute_voice_est):
1027            // pick the hysteresis-correct probability.
1028            let prob = if self.prev_enc_mode.is_none() {
1029                analysis_info.music_prob
1030            } else if self.prev_enc_mode == Some(OpusMode::CeltOnly) {
1031                analysis_info.music_prob_max
1032            } else {
1033                analysis_info.music_prob_min
1034            };
1035            self.voice_ratio = (0.5 + 100.0 * (1.0 - prob)).floor() as i32;
1036            let ab = analysis_info.bandwidth;
1037            self.detected_bandwidth = if ab <= 12 {
1038                Bandwidth::Narrowband as i32
1039            } else if ab <= 14 {
1040                Bandwidth::Mediumband as i32
1041            } else if ab <= 16 {
1042                Bandwidth::Wideband as i32
1043            } else if ab <= 18 {
1044                Bandwidth::Superwideband as i32
1045            } else {
1046                Bandwidth::Fullband as i32
1047            };
1048        }
1049
1050        // Mode selection: match C's opus_encode_native() behavior.
1051        // C reference auto-selects between SILK_ONLY and CELT_ONLY; Hybrid is
1052        // produced afterwards by bandwidth overrides (SILK-only + FB/SWB → Hybrid).
1053        let mut mode = if self.application == Application::RestrictedLowDelay {
1054            OpusMode::CeltOnly
1055        } else {
1056            let equiv = compute_equiv_rate(
1057                self.bitrate_bps,
1058                self.channels,
1059                frame_rate,
1060                !self.use_cbr,
1061                self.complexity,
1062                self.packet_loss_perc,
1063            );
1064            let prev_was_celt = self.prev_enc_mode == Some(OpusMode::CeltOnly);
1065            let has_prev_mode = self.prev_enc_mode.is_some();
1066            let voice_est = self.compute_voice_est();
1067            let threshold = compute_mode_threshold(
1068                self.application,
1069                self.channels,
1070                prev_was_celt,
1071                has_prev_mode,
1072                voice_est,
1073            );
1074            if equiv >= threshold && self.sampling_rate >= 24000 {
1075                OpusMode::CeltOnly
1076            } else {
1077                OpusMode::SilkOnly
1078            }
1079        };
1080        // SILK and hybrid have no frame shorter than 10 ms: a 2.5/5 ms request is
1081        // CELT-only (opus_encoder.c:1533). Without this, the voip/audio selector
1082        // could pick SILK for a 2.5/5 ms frame, and gen_toc then wrote a TOC
1083        // claiming 10/20 ms over 2.5/5 ms of audio -- every decoder output 4x
1084        // the samples and libopus failed the range check on frame 1.
1085        if mode != OpusMode::CeltOnly && frame_rate > 100 {
1086            mode = OpusMode::CeltOnly;
1087        }
1088
1089        // ---- Automatic rate-dependent bandwidth selection (opus_encoder.c:1456) ----
1090        // Walk down from FB; stop at the first bandwidth whose hysteresis-adjusted
1091        // threshold the equivalent rate meets. Thresholds interpolate voice<->music
1092        // by voice_est^2. Without the tonality analysis we cannot do
1093        // detected-bandwidth reduction, so this reproduces libopus's
1094        // complexity-0 choices (measured: WB @16k, SWB @20k, FB @24k+ voip mono).
1095        {
1096            let equiv = compute_equiv_rate(
1097                self.bitrate_bps,
1098                self.channels,
1099                frame_rate,
1100                !self.use_cbr,
1101                self.complexity,
1102                self.packet_loss_perc,
1103            );
1104            let voice_est: i32 = self.compute_voice_est();
1105            let (vt, mt) = if self.channels == 2 {
1106                (
1107                    &STEREO_VOICE_BANDWIDTH_THRESHOLDS,
1108                    &STEREO_MUSIC_BANDWIDTH_THRESHOLDS,
1109                )
1110            } else {
1111                (
1112                    &MONO_VOICE_BANDWIDTH_THRESHOLDS,
1113                    &MONO_MUSIC_BANDWIDTH_THRESHOLDS,
1114                )
1115            };
1116            let mut th = [0i32; 8];
1117            for i in 0..8 {
1118                // libopus' formula verbatim (voice_est squared, scaled by >> 14).
1119                #[allow(clippy::suspicious_operation_groupings)]
1120                {
1121                    th[i] = mt[i] + ((voice_est * voice_est * (vt[i] - mt[i])) >> 14);
1122                }
1123            }
1124            const NB: i32 = Bandwidth::Narrowband as i32; // 1101
1125            const MB: i32 = Bandwidth::Mediumband as i32; // 1102
1126            const FB: i32 = Bandwidth::Fullband as i32; // 1105
1127            let mut bw = FB;
1128            while bw > NB {
1129                let idx = (2 * (bw - MB)) as usize;
1130                let mut threshold = th[idx];
1131                let hysteresis = th[idx + 1];
1132                if !self.first_frame {
1133                    if self.auto_bandwidth >= bw {
1134                        threshold -= hysteresis;
1135                    } else {
1136                        threshold += hysteresis;
1137                    }
1138                }
1139                if equiv >= threshold {
1140                    break;
1141                }
1142                bw -= 1;
1143            }
1144            // Mediumband is no longer used by libopus's selector.
1145            if bw == MB {
1146                bw = Bandwidth::Wideband as i32;
1147            }
1148            self.auto_bandwidth = bw;
1149            // OPUS_SET_MAX_BANDWIDTH, then OPUS_SET_BANDWIDTH (opus_encoder.c:1629-1633).
1150            // The user's forced bandwidth is applied HERE, BEFORE the safety and
1151            // Nyquist caps below, exactly as libopus orders it. It used to replace
1152            // the result after every cap, which let a forced SWB/FB code hybrid
1153            // from 16 kHz input, a forced FB label 8 kHz packets, and a forced MB
1154            // reach CELT (which has no MB config) -- all streams libopus's
1155            // decoder rejects with a range-coder mismatch.
1156            bw = bw.min(self.max_bandwidth as i32);
1157            if let Some(f) = self.force_bandwidth {
1158                bw = f as i32;
1159            }
1160            // Hybrid at unsafe CBR rates starves SILK: cap at WB below 15 kb/s.
1161            if mode != OpusMode::CeltOnly && self.use_cbr && self.bitrate_bps < 15000 {
1162                bw = bw.min(Bandwidth::Wideband as i32);
1163            }
1164            // (A WB floor for SILK from >16 kHz input used to sit here: the
1165            // 48/24 -> 8/12 kHz encode resamplers did not exist. They do now
1166            // -- SilkDownFirResampler covers every libopus ratio -- so NB/MB
1167            // SILK is codeable from every API rate, as in libopus.)
1168            // Never code above the input's Nyquist (opus_encoder.c:1516).
1169            if self.sampling_rate <= 24000 {
1170                bw = bw.min(Bandwidth::Superwideband as i32);
1171            }
1172            if self.sampling_rate <= 16000 {
1173                bw = bw.min(Bandwidth::Wideband as i32);
1174            }
1175            if self.sampling_rate <= 12000 {
1176                bw = bw.min(Bandwidth::Mediumband as i32);
1177            }
1178            if self.sampling_rate <= 8000 {
1179                bw = bw.min(Bandwidth::Narrowband as i32);
1180            }
1181            // (An MB -> WB remap above 12 kHz used to sit here for the same
1182            // missing-resampler reason; a user/max MB is honoured as in libopus.
1183            // The AUTO walk still never yields MB -- that remap is above.)
1184            // Use the detected bandwidth to reduce the coded bandwidth
1185            // (opus_encoder.c:1526), conservatively floored by rate. (For
1186            // CELT-only this is currently undone below — no end-band support.)
1187            // For CELT-only, hold the detected-bandwidth narrowing until the
1188            // leak_boost dynalloc lands: decisions already match libopus
1189            // frame-for-frame (64k st music: 27:704/31:680/23:90 both), but our
1190            // dynalloc lacks C's leakage compensation at the spectral cut, so
1191            // the same narrowing costs 0.25 ODG more than C pays (PEAQ-gated
1192            // out). Hybrid/SILK caps (incl. hybrid SWB) stay live.
1193            // CELT-only keeps FULL bandwidth by choice: C's detected-bandwidth
1194            // narrowing costs PEAQ universally (libopus's own -2.11 at 64k st
1195            // IS its narrowed score; our FB encode scores -1.65 on the same
1196            // clip). leak_boost did NOT change this verdict (tested 2026-07-09
1197            // with the full dynalloc live: narrowing still -2.37). Hybrid/SILK
1198            // caps stay (they pick coding MODE, not spectral truncation).
1199            if self.detected_bandwidth != 0
1200                && self.force_bandwidth.is_none()
1201                && mode != OpusMode::CeltOnly
1202            {
1203                let ch = self.channels as i32;
1204                let equiv2 = equiv; // same 20-ms equivalent rate as the walk
1205                let min_det = if equiv2 <= 18000 * ch && mode == OpusMode::CeltOnly {
1206                    NB
1207                } else if equiv2 <= 24000 * ch && mode == OpusMode::CeltOnly {
1208                    MB
1209                } else if equiv2 <= 30000 * ch {
1210                    Bandwidth::Wideband as i32
1211                } else if equiv2 <= 44000 * ch {
1212                    Bandwidth::Superwideband as i32
1213                } else {
1214                    FB
1215                };
1216                bw = bw.min(self.detected_bandwidth.max(min_det));
1217            }
1218            // (max/forced bandwidth were applied above, before the caps.)
1219            // CELT has no mediumband config: libopus uses WIDEBAND instead
1220            // (opus_encoder.c:1680, "CELT mode doesn't support mediumband").
1221            if mode == OpusMode::CeltOnly && bw == MB {
1222                bw = Bandwidth::Wideband as i32;
1223            }
1224            self.bandwidth = match bw {
1225                x if x == NB => Bandwidth::Narrowband,
1226                x if x == MB => Bandwidth::Mediumband,
1227                x if x == Bandwidth::Wideband as i32 => Bandwidth::Wideband,
1228                x if x == Bandwidth::Superwideband as i32 => Bandwidth::Superwideband,
1229                x if x == FB => Bandwidth::Fullband,
1230                _ => Bandwidth::Wideband,
1231            };
1232            self.first_frame = false;
1233        }
1234
1235        let curr_bw = self.bandwidth;
1236        if mode == OpusMode::SilkOnly
1237            && (curr_bw == Bandwidth::Superwideband || curr_bw == Bandwidth::Fullband)
1238        {
1239            mode = OpusMode::Hybrid;
1240        }
1241        if mode == OpusMode::Hybrid
1242            && (curr_bw == Bandwidth::Narrowband
1243                || curr_bw == Bandwidth::Mediumband
1244                || curr_bw == Bandwidth::Wideband)
1245        {
1246            mode = OpusMode::SilkOnly;
1247        }
1248
1249        // Stereo hybrid is now CONFORMANT (the CELT intensity-clamp fix), but
1250        // our FIXED-point stereo SILK executes it worse than plain CELT-FB above
1251        // ~28 kb/s: PEAQ on stereo speech (ODG) measured hybrid −2.196/−2.193 vs
1252        // CELT-FB −2.136/−2.057 at 32k/48k (CELT-FB wins), while at 24k hybrid
1253        // −2.198 beats CELT-FB −2.240. libopus's FLOAT stereo SILK hybrid beats
1254        // both everywhere — the gap is fixed-vs-float, not a bug. So route
1255        // stereo hybrid to CELT-FB except at the low rates where it wins. (Force
1256        // via OPUS_SET_BANDWIDTH if the true hybrid path is wanted.) The clean
1257        // fix is float stereo SILK — a large port, tracked in the roadmap.
1258        if self.channels == 2 && mode == OpusMode::Hybrid && self.bitrate_bps > 28000 {
1259            mode = OpusMode::CeltOnly;
1260            self.bandwidth = Bandwidth::Fullband;
1261        }
1262
1263        // ---- Mode-dwell hysteresis (Great Gate P2) — MEASURED INEFFECTIVE ----
1264        // Require a proposed mode change to persist for `mode_dwell` frames
1265        // before committing. `mode_dwell <= 1` is OFF and byte-identical.
1266        //
1267        // REFUTED for the defect it was built for (2026-08-07), kept behind the
1268        // env toggle so re-testing is cheap if the mode pattern ever changes.
1269        // The non-CELT frames it was meant to suppress are NOT isolated flips:
1270        // they are a single contiguous run at the START of the stream (frames
1271        // 0-23 on every clip measured), while the analysis classifier warms up.
1272        // Dwell delays transitions in BOTH directions, so on one long run it
1273        // only postpones the exit — measured non-CELT frames went UP with
1274        // dwell, 24 -> 25/26/28/33 for dwell 2/3/5/10, i.e. exactly +(N-1).
1275        // The fix that works is `analysis_warmup` below.
1276        if self.mode_dwell > 1 {
1277            match self.prev_enc_mode {
1278                Some(prev) if mode != prev => {
1279                    self.mode_dwell_run += 1;
1280                    if self.mode_dwell_run < self.mode_dwell {
1281                        // Not yet persistent: hold the previous mode. Bandwidth
1282                        // was chosen for the proposed mode, so reconcile it or
1283                        // the TOC config would be invalid.
1284                        mode = prev;
1285                        self.bandwidth = reconcile_bandwidth(mode, self.bandwidth);
1286                    } else {
1287                        // Persisted long enough — commit and re-arm.
1288                        self.mode_dwell_run = 0;
1289                    }
1290                }
1291                _ => self.mode_dwell_run = 0,
1292            }
1293        }
1294
1295        // Great Gate truth-table lever: pin the mode after the auto decision,
1296        // reconciling bandwidth to a valid TOC config for the forced mode.
1297        // Unset = byte-identical to the auto path above.
1298        if let Some(fm) = self.force_mode {
1299            mode = fm;
1300            self.bandwidth = reconcile_bandwidth(fm, self.bandwidth);
1301        }
1302
1303        // A sub-frame of a multi-frame packet codes with the packet's decision.
1304        if let Some((m, bw)) = self.mf_lock {
1305            mode = m;
1306            self.bandwidth = bw;
1307        }
1308
1309        // ---- Transition redundancy (opus_encoder.c:1541) ----
1310        // CELT->SILK/hybrid: this frame carries a 5 ms CELT frame continuing the
1311        // old CELT state (the decoder fades it into the new mode). SILK/hybrid
1312        // ->CELT: stay ONE more frame in the old mode and end it with a 5 ms CELT
1313        // frame from a fresh CELT state that the following CELT frames continue
1314        // ("to_celt"); below 10 ms there is no room, so switch directly.
1315        let mut redundancy = false;
1316        let mut celt_to_silk = false;
1317        let mut to_celt = false;
1318        if let Some(prev) = self.prev_enc_mode {
1319            if mode != OpusMode::CeltOnly && prev == OpusMode::CeltOnly {
1320                redundancy = true;
1321                celt_to_silk = true;
1322            } else if self.mf_lock.is_none()
1323                && mode == OpusMode::CeltOnly
1324                && prev != OpusMode::CeltOnly
1325                && frame_size >= fs / 100
1326            {
1327                mode = prev;
1328                to_celt = true;
1329                redundancy = true;
1330                // The bandwidth was picked for CELT; SILK/hybrid follow it.
1331                let bw = self.bandwidth;
1332                if mode == OpusMode::SilkOnly
1333                    && matches!(bw, Bandwidth::Superwideband | Bandwidth::Fullband)
1334                {
1335                    mode = OpusMode::Hybrid;
1336                } else if mode == OpusMode::Hybrid
1337                    && !matches!(bw, Bandwidth::Superwideband | Bandwidth::Fullband)
1338                {
1339                    mode = OpusMode::SilkOnly;
1340                }
1341            }
1342        }
1343        if self.mf_lock.is_some() && self.mf_to_celt {
1344            // Last sub-frame of a multi-frame to_celt packet.
1345            redundancy = true;
1346            celt_to_silk = false;
1347            to_celt = true;
1348        }
1349
1350        // opus_encode_native: ">60 ms frames, and >20 ms when in Hybrid or
1351        // CELT-only modes" are coded as several frames in one packet.
1352        if self.mf_lock.is_none()
1353            && ((frame_size > fs / 50 && mode != OpusMode::SilkOnly) || frame_size > 3 * fs / 50)
1354        {
1355            return self.encode_multiframe(input, frame_size, output, mode, to_celt);
1356        }
1357
1358        if mode == OpusMode::CeltOnly {
1359            match frame_rate {
1360                400 | 200 | 100 | 50 => {}
1361                _ => return Err(Error::BadArg("Unsupported frame size for CELT-only mode")),
1362            }
1363        }
1364
1365        if mode == OpusMode::Hybrid {
1366            match frame_rate {
1367                100 | 50 => {}
1368                _ => return Err(Error::BadArg("Unsupported frame size for Hybrid mode")),
1369            }
1370        }
1371
1372        if mode == OpusMode::SilkOnly {
1373            // 10/20/40/60 ms (60 ms: frame_rate = Fs/frame_size = 16). Below
1374            // 10 ms the selector already switched to CELT.
1375            match frame_rate {
1376                100 | 50 | 25 | 16 => {}
1377                _ => return Err(Error::BadArg("Unsupported frame size for SILK-only mode")),
1378            }
1379        }
1380
1381        let n400 = (self.sampling_rate / 400) as usize;
1382
1383        // The CELT->SILK redundant frame continues the OLD CELT state, which
1384        // the reset below discards for hybrid: keep a copy for it.
1385        let mut red_celt = if redundancy && celt_to_silk {
1386            Some(self.celt_enc.clone())
1387        } else {
1388            None
1389        };
1390
1391        // ---- Mode-transition resets (opus_encoder.c:1449 + 2054) ----
1392        // The decoder resets its CELT state on ANY mode change (when there is
1393        // no redundancy) and its SILK state when leaving CELT-only; the
1394        // encoder must mirror both or the streams desync from that frame on.
1395        // CELT_SET_PREDICTION(2) every CELT/hybrid frame, (0) right after a reset.
1396        self.celt_enc.prediction_off = false;
1397        if let Some(prev) = self.prev_enc_mode {
1398            if prev != mode {
1399                if mode != OpusMode::SilkOnly {
1400                    let ch = self.channels;
1401                    self.celt_enc = CeltEncoder::new(modes::default_mode(), ch);
1402                    self.celt_enc.upsample = (48000 / self.sampling_rate) as usize;
1403                    // Prefill 2.5 ms so the fresh state has real preemph/overlap
1404                    // history instead of a hard edge (opus_encoder.c:2060).
1405                    let n400 = (self.sampling_rate / 400) as usize;
1406                    if self.celt_prefill_tail.len() == n400 * ch {
1407                        let mut dummy = RangeCoder::new_encoder(2);
1408                        let tail = std::mem::take(&mut self.celt_prefill_tail);
1409                        self.celt_enc
1410                            .encode_with_budget(&tail, n400, &mut dummy, 0, 21, 16);
1411                        self.celt_prefill_tail = tail;
1412                    }
1413                    self.celt_enc.prediction_off = true;
1414                }
1415                if mode != OpusMode::CeltOnly && prev == OpusMode::CeltOnly {
1416                    self.silk_initialized = false;
1417                    self.silk_prefill_pending = true;
1418                }
1419            }
1420        }
1421
1422        // SILK prefill tail: last 10 ms of API-rate mono input.
1423        if self.channels == 1 {
1424            let n10 = (self.sampling_rate / 100) as usize;
1425            if frame_size >= n10 {
1426                self.silk_prefill_tail.resize(n10, 0);
1427                for i in 0..n10 {
1428                    self.silk_prefill_tail[i] =
1429                        (input[frame_size - n10 + i] * 32768.0).clamp(-32768.0, 32767.0) as i16;
1430                }
1431            }
1432        }
1433
1434        // Save THIS frame's last 2.5 ms (planar) for a possible prefill at the
1435        // next mode transition. (The transition block above consumed the
1436        // PREVIOUS frame's tail.)
1437        {
1438            let ch = self.channels;
1439            self.celt_prefill_tail.resize(n400 * ch, 0.0);
1440            let base = frame_size - n400;
1441            for c in 0..ch {
1442                for i in 0..n400 {
1443                    self.celt_prefill_tail[c * n400 + i] = input[(base + i) * ch + c];
1444                }
1445            }
1446        }
1447
1448        let toc = gen_toc(mode, frame_rate, self.bandwidth, self.channels);
1449        output[0] = toc;
1450
1451        // opus_encode_native: with fewer than 3 bytes there is no room for a
1452        // coded frame, so emit a TOC-only packet that the decoder conceals.
1453        // Reached directly with a 2-byte buffer, and by the sub-frames of a
1454        // multi-frame packet whose buffer is too small to share out.
1455        if output.len() < 3 {
1456            self.prev_enc_mode = Some(mode);
1457            self.range_final = 0;
1458            return Ok(1);
1459        }
1460
1461        // ---- DTX decision (opus_encoder.c:2137 decide_dtx_mode) ----
1462        // After enough consecutive inactive frames, emit a TOC-only 1-byte
1463        // packet: the decoder sees an empty payload and runs comfort-noise /
1464        // PLC. We decide before the (skipped) SILK/CELT encode — SILK's own DTX
1465        // likewise stops coding, so the encoder state simply doesn't advance;
1466        // the codecs resync on the next active frame.
1467        if self.use_dtx && (analysis_info.valid || is_silence) {
1468            let frame_ms_q1 = 2 * 1000 * frame_size as i32 / self.sampling_rate;
1469            let dtx = if !activity {
1470                self.nb_no_activity_ms_q1 += frame_ms_q1;
1471                const LO: i32 = silk::define::NB_SPEECH_FRAMES_BEFORE_DTX * 20 * 2; // 400
1472                const HI: i32 = (silk::define::NB_SPEECH_FRAMES_BEFORE_DTX
1473                    + silk::define::MAX_CONSECUTIVE_DTX)
1474                    * 20
1475                    * 2; // 1200
1476                if self.nb_no_activity_ms_q1 > LO {
1477                    if self.nb_no_activity_ms_q1 <= HI {
1478                        true
1479                    } else {
1480                        self.nb_no_activity_ms_q1 = LO;
1481                        false
1482                    }
1483                } else {
1484                    false
1485                }
1486            } else {
1487                self.nb_no_activity_ms_q1 = 0;
1488                false
1489            };
1490            if dtx {
1491                self.prev_enc_mode = Some(mode);
1492                self.range_final = 0;
1493                return Ok(1);
1494            }
1495        } else {
1496            self.nb_no_activity_ms_q1 = 0;
1497        }
1498
1499        let target_bits =
1500            (self.bitrate_bps as i64 * frame_size as i64 / self.sampling_rate as i64) as i32;
1501        let cbr_bytes = ((target_bits + 4) / 8) as usize;
1502        // opus_encode_native: no single Opus frame exceeds 1275 bytes (+1 TOC),
1503        // whatever the caller's buffer; CBR at a high rate must not plan more.
1504        let max_data_bytes = output.len().min(1276);
1505
1506        // CBR: the packet is exactly the target size. VBR: start the coder on a
1507        // generous buffer — SILK-only packets end at whatever SILK produced, and
1508        // the CELT layer picks its own frame size (compute_vbr) and shrinks the
1509        // coder to it (libopus opus_encoder.c / celt_encoder.c VBR flow).
1510        let n_bytes = if self.use_cbr {
1511            cbr_bytes.min(max_data_bytes).max(1)
1512        } else {
1513            max_data_bytes
1514                .min(1276)
1515                .max(cbr_bytes.min(max_data_bytes))
1516                .max(3)
1517        };
1518
1519        // Redundant-frame budget (opus_encoder.c compute_redundancy_bytes); too
1520        // few bytes to be worth it -> rely on the decoder's transition PLC.
1521        let mut redundancy_bytes = 0usize;
1522        if mode == OpusMode::CeltOnly {
1523            redundancy = false;
1524        }
1525        if redundancy {
1526            redundancy_bytes =
1527                compute_redundancy_bytes(n_bytes, self.bitrate_bps, frame_rate, self.channels);
1528            if redundancy_bytes == 0 {
1529                redundancy = false;
1530            }
1531        }
1532
1533        let init_rc_size = n_bytes - 1;
1534        self.rc.reset_for_encode(init_rc_size as u32);
1535
1536        if mode == OpusMode::SilkOnly || mode == OpusMode::Hybrid {
1537            // SILK's internal rate follows the coded BANDWIDTH (NB 8 / MB 12 /
1538            // WB 16 kHz; hybrid is WB SILK), capped by the API rate -- libopus
1539            // maxInternalSampleRate. It used to follow the API rate alone, so a
1540            // NB TOC from 12/16 kHz input carried SILK coded at 12/16 kHz and the
1541            // decoder (which takes the rate from the TOC) desynced on frame 1.
1542            let silk_fs_khz = if mode == OpusMode::Hybrid {
1543                16
1544            } else {
1545                // self.bandwidth, not curr_bw: it is what gen_toc wrote, after
1546                // every later reconciliation (forced mode, dwell).
1547                let bw_khz = match self.bandwidth {
1548                    Bandwidth::Narrowband => 8,
1549                    Bandwidth::Mediumband => 12,
1550                    _ => 16,
1551                };
1552                bw_khz.min(self.sampling_rate / 1000)
1553            };
1554            let silk_fs_hz = silk_fs_khz * 1000;
1555
1556            let frame_ms = (frame_size as i32 * 1000) / self.sampling_rate;
1557            if !self.silk_initialized || self.silk_enc.s_cmn.fs_khz != silk_fs_khz {
1558                let silk_init_bitrate = if self.use_cbr {
1559                    (((n_bytes - 1) * 8) as i64 * self.sampling_rate as i64 / frame_size as i64)
1560                        as i32
1561                } else {
1562                    self.bitrate_bps
1563                };
1564                silk_control_encoder(
1565                    &mut self.silk_enc,
1566                    silk_fs_khz,
1567                    frame_ms,
1568                    silk_init_bitrate,
1569                    self.complexity,
1570                );
1571                self.silk_enc.s_cmn.use_cbr = i32::from(self.use_cbr);
1572
1573                self.silk_enc.s_cmn.n_channels = self.channels as i32;
1574                self.silk_initialized = true;
1575                self.down2_state_first = [0; 2];
1576                self.down2_state_second = [0; 2];
1577                self.down2_3_state = [0; 6];
1578                self.down_1_3_state = silk::resampler::SilkResamplerDown1_3::default();
1579                self.down2_3_state_r = [0; 6];
1580                self.down_1_3_state_r = silk::resampler::SilkResamplerDown1_3::default();
1581                // API -> SILK-internal (None when the rates are equal: copy path).
1582                self.down_fir_l =
1583                    silk::resampler::SilkDownFirResampler::new(self.sampling_rate, silk_fs_hz);
1584                self.down_fir_r =
1585                    silk::resampler::SilkDownFirResampler::new(self.sampling_rate, silk_fs_hz);
1586            } else if self.silk_enc.s_cmn.packet_size_ms != frame_ms {
1587                // silk_control_encoder runs every packet in libopus and re-derives
1588                // nFramesPerPacket/nb_subfr on a PacketSize_ms change. Skipping it
1589                // left SILK at 20 ms after a hybrid multiframe run (20 ms subframes)
1590                // while the TOC said 60 ms: one coded frame per 60 ms packet.
1591                silk::control_codec::silk_setup_fs(&mut self.silk_enc, silk_fs_khz, frame_ms);
1592            }
1593
1594            // SILK prefill after CELT-only (opus_encoder.c prefill=1): run 10 ms
1595            // of the previous audio through the fresh resampler + SILK warmup
1596            // path so the first coded SILK frame has real LTP/shape history.
1597            if self.silk_prefill_pending {
1598                self.silk_prefill_pending = false;
1599                let n10 = (self.sampling_rate / 100) as usize;
1600                if self.channels == 1 && self.silk_prefill_tail.len() == n10 {
1601                    let need = silk_fs_khz as usize * 10;
1602                    let mut resampled = vec![0i16; need];
1603                    if self.sampling_rate != silk_fs_hz {
1604                        if let Some(r) = &mut self.down_fir_l {
1605                            r.process(&mut resampled, &self.silk_prefill_tail);
1606                        }
1607                    } else {
1608                        resampled.copy_from_slice(&self.silk_prefill_tail[..need]);
1609                    }
1610                    silk::enc_api::silk_encode_prefill(&mut self.silk_enc, &resampled, 0);
1611                }
1612            }
1613
1614            self.silk_enc.s_cmn.use_in_band_fec = i32::from(self.use_inband_fec);
1615            self.silk_enc.s_cmn.packet_loss_perc = self.packet_loss_perc.clamp(0, 100);
1616
1617            let lbrr_in_previous_packet = self.silk_enc.s_cmn.lbrr_enabled != 0;
1618            self.silk_enc.s_cmn.lbrr_enabled = i32::from(self.use_inband_fec);
1619
1620            // libopus silk_setup_LBRR: the first LBRR packet copies frames coded
1621            // at the full rate, so it takes the coarsest step (7); after that the
1622            // step shrinks as loss rises, max(7 - 0.4 loss%, 2). FEC-off path
1623            // unaffected.
1624            self.silk_enc.s_cmn.lbrr_gain_increases = if lbrr_in_previous_packet {
1625                (7 - ((self.packet_loss_perc.clamp(0, 100) * 26214) >> 16)).max(2)
1626            } else {
1627                7
1628            };
1629
1630            let hp_freq_smth1 = if mode == OpusMode::CeltOnly {
1631                silk_lin2log(60) << 8
1632            } else {
1633                self.silk_enc.s_cmn.variable_hp_smth1_q15
1634            };
1635
1636            const VARIABLE_HP_SMTH_COEF2_Q16: i32 = 984;
1637            self.variable_hp_smth2_q15 = silk_smlawb(
1638                self.variable_hp_smth2_q15,
1639                hp_freq_smth1 - self.variable_hp_smth2_q15,
1640                VARIABLE_HP_SMTH_COEF2_Q16,
1641            );
1642
1643            let cutoff_hz = silk_log2lin(silk_rshift(self.variable_hp_smth2_q15, 8));
1644
1645            let prof_rs = crate::prof::scope(crate::prof::Stage::Resample);
1646            let required_size = frame_size * self.channels;
1647            self.buf_filtered.resize(required_size, 0);
1648            if self.application == Application::Voip {
1649                hp_cutoff(
1650                    input,
1651                    cutoff_hz,
1652                    &mut self.buf_filtered,
1653                    &mut self.hp_mem,
1654                    frame_size,
1655                    self.channels,
1656                    self.sampling_rate,
1657                );
1658            } else {
1659                for (i, &x) in input.iter().enumerate() {
1660                    self.buf_filtered[i] = (x * 32768.0).clamp(-32768.0, 32767.0) as i16;
1661                }
1662            }
1663
1664            let input_i16 = &self.buf_filtered;
1665
1666            let silk_input: &[i16] = if self.channels == 2 {
1667                // Stereo SILK/hybrid: deinterleave, resample EACH channel to the
1668                // SILK-internal rate (separate filter states), then split
1669                // mid/side — C's order (per-channel resampling inside
1670                // silk_Encode, then silk_stereo_LR_to_MS). The old code only
1671                // handled stereo at <=16 kHz and fed resampled INTERLEAVED
1672                // audio to a stereo-configured SILK above that (never
1673                // exercised until the analysis started picking stereo hybrid).
1674                let frame_length = input_i16.len() / 2;
1675                self.buf_left.resize(frame_length, 0);
1676                self.buf_right.resize(frame_length, 0);
1677                for i in 0..frame_length {
1678                    self.buf_left[i] = input_i16[2 * i];
1679                    self.buf_right[i] = input_i16[2 * i + 1];
1680                }
1681                let need_resample = self.sampling_rate != silk_fs_hz;
1682                let ds_len =
1683                    (frame_length as i64 * silk_fs_hz as i64 / self.sampling_rate as i64) as usize;
1684                if need_resample {
1685                    self.buf_stereo_mid.resize(ds_len, 0);
1686                    self.buf_stereo_side.resize(ds_len, 0);
1687                    if let (Some(rl), Some(rr)) = (&mut self.down_fir_l, &mut self.down_fir_r) {
1688                        rl.process(&mut self.buf_stereo_mid, &self.buf_left);
1689                        rr.process(&mut self.buf_stereo_side, &self.buf_right);
1690                    }
1691                    self.buf_left.resize(ds_len, 0);
1692                    self.buf_right.resize(ds_len, 0);
1693                    self.buf_left
1694                        .copy_from_slice(&self.buf_stereo_mid[..ds_len]);
1695                    self.buf_right
1696                        .copy_from_slice(&self.buf_stereo_side[..ds_len]);
1697                }
1698                self.buf_stereo_mid.resize(ds_len, 0);
1699                self.buf_stereo_side.resize(ds_len, 0);
1700                for i in 0..ds_len {
1701                    let l = self.buf_left[i] as i32;
1702                    let r = self.buf_right[i] as i32;
1703                    self.buf_stereo_mid[i] = ((l + r) / 2) as i16;
1704                    self.buf_stereo_side[i] = (l - r) as i16;
1705                }
1706                self.silk_enc.stereo.side.resize(ds_len, 0);
1707                self.silk_enc
1708                    .stereo
1709                    .side
1710                    .copy_from_slice(&self.buf_stereo_side[..ds_len]);
1711                &self.buf_stereo_mid
1712            } else if self.sampling_rate != silk_fs_hz {
1713                // Mono SILK/hybrid: API -> SILK-internal through libopus's
1714                // silk_resampler down-FIR for this ratio (48k->16k is the same
1715                // direct FIR as before; the old down2 + down2_3 chain ALIASED --
1716                // a 1 kHz sine came out with a 7 kHz mirror).
1717                let silk_frame_size =
1718                    (frame_size as i64 * silk_fs_hz as i64 / self.sampling_rate as i64) as usize;
1719                self.buf_silk_input.resize(silk_frame_size, 0);
1720                if let Some(r) = &mut self.down_fir_l {
1721                    r.process(&mut self.buf_silk_input, input_i16);
1722                }
1723                &self.buf_silk_input
1724            } else {
1725                input_i16
1726            };
1727
1728            drop(prof_rs);
1729
1730            let mut pn_bytes = 0;
1731
1732            // The frames-per-second math below divides by silk_input.len(), which is
1733            // at the SILK-INTERNAL rate — so the rate here must be internal too.
1734            // Using the API rate at 48 kHz told SILK to target 3x the real budget
1735            // with a hard max_bits cap -> the gain loop crushed every frame to fit
1736            // -> near-silent output (only worked at 16 kHz API where they coincide).
1737            let silk_rate_for_calc = silk_fs_hz;
1738            let silk_frame_len = silk_input.len();
1739
1740            let silk_bitrate = if mode == OpusMode::Hybrid {
1741                let frame_duration_ms = frame_size as i32 * 1000 / self.sampling_rate;
1742                let frame20ms = frame_duration_ms >= 20;
1743                compute_silk_rate_for_hybrid(self.bitrate_bps, curr_bw, frame20ms, !self.use_cbr)
1744            } else if self.use_cbr {
1745                (8i64 * (n_bytes - 1 - redundancy_bytes) as i64 * silk_rate_for_calc as i64
1746                    / silk_frame_len as i64) as i32
1747            } else {
1748                // VBR: n_bytes is only the buffer cap; target the configured rate.
1749                self.bitrate_bps
1750            };
1751            // Max bits for SILK, counting ToC, redundancy bytes, and 1 bit for
1752            // the redundancy position + 20 for flag/size (hybrid only).
1753            let red_bits = if redundancy && redundancy_bytes >= 2 {
1754                (redundancy_bytes * 8 + 1) as i32 + if mode == OpusMode::Hybrid { 20 } else { 0 }
1755            } else {
1756                0
1757            };
1758            let silk_max_bits = if mode == OpusMode::Hybrid {
1759                let total_max_bits = ((n_bytes - 1) * 8) as i32 - red_bits;
1760                if self.use_cbr {
1761                    let silk_bits = (silk_bitrate as i64 * silk_frame_len as i64
1762                        / silk_rate_for_calc as i64) as i32;
1763                    let other_bits = 0i32.max(total_max_bits - silk_bits);
1764                    0i32.max(total_max_bits - other_bits * 3 / 4)
1765                } else {
1766                    let frame_duration_ms = frame_size as i32 * 1000 / self.sampling_rate;
1767                    let frame20ms = frame_duration_ms >= 20;
1768                    let max_bit_rate = compute_silk_rate_for_hybrid(
1769                        total_max_bits * self.sampling_rate / frame_size as i32,
1770                        curr_bw,
1771                        frame20ms,
1772                        !self.use_cbr,
1773                    );
1774                    max_bit_rate * frame_size as i32 / self.sampling_rate
1775                }
1776            } else {
1777                ((n_bytes - 1) * 8) as i32 - red_bits
1778            };
1779            let silk_use_cbr = if mode == OpusMode::Hybrid && self.use_cbr {
1780                0
1781            } else {
1782                i32::from(self.use_cbr)
1783            };
1784            let ret = silk_encode(
1785                &mut self.silk_enc,
1786                silk_input,
1787                silk_input.len(),
1788                &mut self.rc,
1789                &mut pn_bytes,
1790                silk_bitrate,
1791                silk_max_bits,
1792                silk_use_cbr,
1793                1,
1794            );
1795            if ret != 0 {
1796                return Err(Error::Internal("SILK encoding failed"));
1797            }
1798        }
1799
1800        // Redundancy signalling (opus_encoder.c): only when >= 17 (+20 hybrid)
1801        // bits remain -- the decoder gates its read identically. Hybrid codes the
1802        // flag, position and size; SILK-only implies redundancy from the length
1803        // and codes only the position (celt_to_silk) bit.
1804        let hybrid = mode == OpusMode::Hybrid;
1805        if mode != OpusMode::CeltOnly
1806            && self.rc.tell() + 17 + if hybrid { 20 } else { 0 } <= ((n_bytes - 1) * 8) as i32
1807        {
1808            if hybrid {
1809                self.rc.encode_bit_logp(redundancy, 12);
1810            }
1811            if redundancy {
1812                self.rc.encode_bit_logp(celt_to_silk, 1);
1813                // Hybrid reserves the 8 size bits and a few CELT bits.
1814                let max_redundancy = if hybrid {
1815                    (n_bytes - 1) as i32 - ((self.rc.tell() + 8 + 3 + 7) >> 3)
1816                } else {
1817                    (n_bytes - 1) as i32 - ((self.rc.tell() + 7) >> 3)
1818                };
1819                // Not `clamp`: max_redundancy may be < 2, where clamp panics;
1820                // this order (cap, then floor at 2, then 257) matches libopus.
1821                #[allow(clippy::manual_clamp)]
1822                let capped = (redundancy_bytes as i32)
1823                    .min(max_redundancy)
1824                    .max(2)
1825                    .min(257) as usize;
1826                redundancy_bytes = capped;
1827                if hybrid {
1828                    self.rc.enc_uint((redundancy_bytes - 2) as u32, 256);
1829                }
1830            }
1831        } else {
1832            redundancy = false;
1833        }
1834        if !redundancy {
1835            redundancy_bytes = 0;
1836        }
1837
1838        if hybrid {
1839            let nb_compr_bytes = (n_bytes - 1 - redundancy_bytes) as u32;
1840            self.rc.shrink(nb_compr_bytes);
1841        }
1842
1843        let celt_end_band = match self.bandwidth {
1844            Bandwidth::Narrowband => 13,
1845            Bandwidth::Mediumband | Bandwidth::Wideband => 17,
1846            Bandwidth::Superwideband => 19,
1847            _ => 21,
1848        };
1849        let celt_analysis = celt::AnalysisInfo {
1850            valid: analysis_info.valid,
1851            tonality: analysis_info.tonality,
1852            tonality_slope: analysis_info.tonality_slope,
1853            noisiness: analysis_info.noisiness,
1854            activity: analysis_info.activity,
1855            music_prob: analysis_info.music_prob,
1856            music_prob_min: analysis_info.music_prob_min,
1857            music_prob_max: analysis_info.music_prob_max,
1858            bandwidth: analysis_info.bandwidth,
1859            activity_probability: analysis_info.activity_probability,
1860            max_pitch_ratio: analysis_info.max_pitch_ratio,
1861            leak_boost: analysis_info.leak_boost,
1862        };
1863        // Planar copy of `len` input samples from `start` (CELT takes planar).
1864        let api_ch = self.channels;
1865        let planar = |start: usize, len: usize| -> Vec<f32> {
1866            let ch = api_ch;
1867            let mut v = vec![0.0f32; len * ch];
1868            for c in 0..ch {
1869                for i in 0..len {
1870                    v[c * len + i] = input[(start + i) * ch + c];
1871                }
1872            }
1873            v
1874        };
1875
1876        // 5 ms redundant frame for CELT->SILK: the OLD CELT state, start band 0,
1877        // CBR at the redundancy size; written after the main payload.
1878        let mut red_data: Vec<u8> = Vec::new();
1879        let mut redundant_rng = 0u32;
1880        if redundancy && celt_to_silk {
1881            if let Some(mut enc) = red_celt.take() {
1882                let n2 = (self.sampling_rate / 200) as usize;
1883                enc.analysis = celt_analysis;
1884                enc.vbr_rate = 0;
1885                let mut rrc = RangeCoder::new_encoder(redundancy_bytes as u32);
1886                enc.encode_with_budget(
1887                    &planar(0, n2),
1888                    n2,
1889                    &mut rrc,
1890                    0,
1891                    celt_end_band,
1892                    (redundancy_bytes * 8) as i32,
1893                );
1894                rrc.done();
1895                redundant_rng = rrc.rng;
1896                red_data = rrc.buf[..redundancy_bytes].to_vec();
1897            }
1898        }
1899
1900        let silk_ret_bytes = if mode == OpusMode::SilkOnly {
1901            ((self.rc.tell() + 7) >> 3) as usize
1902        } else {
1903            0
1904        };
1905
1906        if mode == OpusMode::CeltOnly || mode == OpusMode::Hybrid {
1907            self.celt_enc.analysis = celt_analysis;
1908            self.celt_enc.complexity = self.complexity;
1909            self.celt_enc.lsb_depth = self.lsb_depth;
1910            // Census 2026-08-07 fix: loss_rate was never assigned, so CELT's
1911            // prefilter loss ladder (celt.rs) and coarse-energy intra bias were
1912            // dead even with OPUS_SET_PACKET_LOSS_PERC set. Default 0 = no
1913            // change on the default path (libopus opus_encoder.c parity).
1914            self.celt_enc.loss_rate = self.packet_loss_perc;
1915            let start_band = if mode == OpusMode::Hybrid { 17 } else { 0 };
1916            // CELT end band from the coded bandwidth (mirrors the decoder's
1917            // celt_endband_for_bandwidth): NB->13, MB/WB->17, SWB->19, FB->21.
1918            let end_band = celt_end_band;
1919            // nb_compr_bytes: the redundant frame's bytes are not CELT's.
1920            let total_packet_bits = ((n_bytes - 1 - redundancy_bytes) * 8) as i32;
1921            // VBR: hand CELT the target in eighth-bits per frame; it picks the
1922            // frame's size (compute_vbr) and shrinks the range coder to it. The
1923            // hybrid target covers the whole packet (CELT adds back the SILK
1924            // bits via `target += tell`).
1925            self.celt_enc.vbr_rate = if self.use_cbr {
1926                0
1927            } else {
1928                let den = self.sampling_rate >> 3; // Fs >> BITRES
1929                ((self.bitrate_bps as i64 * frame_size as i64 + (den >> 1) as i64) / den as i64)
1930                    as i32
1931            };
1932
1933            let celt_input: &[f32] = if self.channels == 1 {
1934                input
1935            } else {
1936                let n = frame_size * self.channels;
1937                self.buf_celt_input.resize(n, 0.0);
1938                for i in 0..frame_size {
1939                    for ch in 0..self.channels {
1940                        self.buf_celt_input[ch * frame_size + i] = input[i * self.channels + ch];
1941                    }
1942                }
1943                &self.buf_celt_input
1944            };
1945
1946            if self.rc.tell() <= total_packet_bits {
1947                self.celt_enc.encode_with_budget(
1948                    celt_input,
1949                    frame_size,
1950                    &mut self.rc,
1951                    start_band,
1952                    end_band,
1953                    total_packet_bits,
1954                );
1955            }
1956        }
1957
1958        self.rc.done();
1959
1960        // 5 ms redundant frame for SILK->CELT: a FRESH CELT state (reset, start
1961        // band 0, prediction off, CBR), prefilled with the 2.5 ms before it, codes
1962        // the frame's last 5 ms. That state becomes the encoder's, so the next
1963        // (CELT) frame continues from it -- as the decoder's does.
1964        if redundancy && !celt_to_silk {
1965            let n2 = (self.sampling_rate / 200) as usize;
1966            let n4 = (self.sampling_rate / 400) as usize;
1967            let mut enc = CeltEncoder::new(modes::default_mode(), self.channels);
1968            enc.upsample = (48000 / self.sampling_rate) as usize;
1969            enc.complexity = self.complexity;
1970            enc.lsb_depth = self.lsb_depth;
1971            enc.loss_rate = self.packet_loss_perc;
1972            enc.analysis = celt_analysis;
1973            enc.prediction_off = true;
1974            enc.vbr_rate = 0;
1975            let mut dummy = RangeCoder::new_encoder(2);
1976            enc.encode_with_budget(
1977                &planar(frame_size - n2 - n4, n4),
1978                n4,
1979                &mut dummy,
1980                0,
1981                celt_end_band,
1982                16,
1983            );
1984            let mut rrc = RangeCoder::new_encoder(redundancy_bytes as u32);
1985            enc.encode_with_budget(
1986                &planar(frame_size - n2, n2),
1987                n2,
1988                &mut rrc,
1989                0,
1990                celt_end_band,
1991                (redundancy_bytes * 8) as i32,
1992            );
1993            rrc.done();
1994            redundant_rng = rrc.rng;
1995            red_data = rrc.buf[..redundancy_bytes].to_vec();
1996            enc.prediction_off = false;
1997            self.celt_enc = enc;
1998        }
1999        self.range_final = self.rc.rng ^ redundant_rng;
2000        // libopus prev_mode: CELT after a to_celt frame (the redundant frame
2001        // primed CELT; the next CELT frame must not reset it). Set on EVERY
2002        // path -- the VBR SILK-only return used to skip it, so after one CELT
2003        // frame every SILK frame re-ran the CELT->SILK reset + prefill.
2004        let next_prev_mode = if to_celt { OpusMode::CeltOnly } else { mode };
2005        self.prev_enc_mode = Some(next_prev_mode);
2006
2007        if mode == OpusMode::SilkOnly {
2008            let mut ret = silk_ret_bytes.min(self.rc.storage as usize);
2009            // Trailing zeros may be stripped (the decoder pads them) -- but not
2010            // with redundancy, whose position is inferred from the length.
2011            while !redundancy && ret > 2 && self.rc.buf[ret - 1] == 0 {
2012                ret -= 1;
2013            }
2014            // Payload = SILK bytes ++ redundant CELT frame, copied straight into
2015            // `output` (was: chained-iterator collect into a Vec, then copied).
2016            let (main, red) = (&self.rc.buf[..ret], &red_data[..]);
2017            let silk_len = ret + red.len();
2018            let put = |dst: &mut [u8], n: usize| {
2019                let a = n.min(main.len());
2020                dst[..a].copy_from_slice(&main[..a]);
2021                dst[a..n].copy_from_slice(&red[..n - a]);
2022            };
2023
2024            let target_total = if self.use_cbr {
2025                n_bytes.min(output.len())
2026            } else {
2027                (silk_len + 1).min(output.len())
2028            };
2029
2030            if !self.use_cbr || silk_len + 1 >= target_total {
2031                // VBR or payload fills the target: simple code 0 packet
2032                output[0] = toc;
2033                let copy_len = silk_len.min(target_total - 1);
2034                put(&mut output[1..], copy_len);
2035                return Ok((copy_len + 1).min(output.len()));
2036            }
2037
2038            output[0] = toc | 0x03;
2039
2040            if silk_len + 2 >= target_total {
2041                output[1] = 0x01;
2042                let copy_len = (target_total - 2).min(silk_len);
2043                put(&mut output[2..], copy_len);
2044                return Ok(target_total.min(output.len()));
2045            }
2046
2047            let pad_amount = target_total - silk_len - 2;
2048            output[1] = 0x41;
2049
2050            let nb_255s = (pad_amount - 1) / 255;
2051            let mut ptr = 2;
2052            for _ in 0..nb_255s {
2053                output[ptr] = 255;
2054                ptr += 1;
2055            }
2056            output[ptr] = (pad_amount - 255 * nb_255s - 1) as u8;
2057            ptr += 1;
2058
2059            put(&mut output[ptr..], silk_len);
2060            ptr += silk_len;
2061
2062            let fill_end = target_total.min(output.len());
2063            for byte in &mut output[ptr..fill_end] {
2064                *byte = 0;
2065            }
2066
2067            return Ok(target_total.min(output.len()));
2068        }
2069
2070        // CBR: fixed payload. VBR (CELT/hybrid): the CELT layer shrank the coder
2071        // to this frame's chosen size — emit exactly that many payload bytes.
2072        // The redundant frame (if any) follows the main payload.
2073        let nb_compr_bytes = n_bytes - 1 - redundancy_bytes;
2074        let main_len = if self.use_cbr {
2075            nb_compr_bytes
2076        } else {
2077            (self.rc.storage as usize).min(nb_compr_bytes)
2078        };
2079        output[1..1 + main_len].copy_from_slice(&self.rc.buf[..main_len]);
2080        output[1 + main_len..1 + main_len + red_data.len()].copy_from_slice(&red_data);
2081        let payload_len = main_len + red_data.len();
2082        // Great Gate harvest tap (observe-only; see the field doc). Signals are
2083        // recomputed read-only here — the decision code above is untouched.
2084        if self.gate_tap.is_some() {
2085            let equiv = compute_equiv_rate(
2086                self.bitrate_bps,
2087                self.channels,
2088                frame_rate,
2089                !self.use_cbr,
2090                self.complexity,
2091                self.packet_loss_perc,
2092            );
2093            let voice_est = self.compute_voice_est();
2094            let (clip, frame) = (self.gate_clip.clone(), self.gate_frame);
2095            if let Some(tap) = self.gate_tap.as_mut() {
2096                use std::io::Write as _;
2097                let mode_s = match mode {
2098                    OpusMode::SilkOnly => "silk",
2099                    OpusMode::CeltOnly => "celt",
2100                    OpusMode::Hybrid => "hybrid",
2101                };
2102                let _ = writeln!(
2103                    tap,
2104                    "{},{},{},{},{},{},{},{},{},{},{},{},{:.4},{:.4},{:.4},{:.4},{:.4},{:.4},{:.4},{},{:.4},{}",
2105                    clip,
2106                    frame,
2107                    mode_s,
2108                    self.bandwidth as i32,
2109                    self.channels,
2110                    self.bitrate_bps,
2111                    self.complexity,
2112                    equiv,
2113                    voice_est,
2114                    is_silence as u8,
2115                    activity as u8,
2116                    analysis_info.valid as u8,
2117                    analysis_info.tonality,
2118                    analysis_info.tonality_slope,
2119                    analysis_info.noisiness,
2120                    analysis_info.activity_probability,
2121                    analysis_info.music_prob,
2122                    analysis_info.music_prob_min,
2123                    analysis_info.music_prob_max,
2124                    self.detected_bandwidth,
2125                    analysis_info.max_pitch_ratio,
2126                    1 + payload_len,
2127                );
2128            }
2129        }
2130        self.gate_frame += 1;
2131
2132        Ok(1 + payload_len)
2133    }
2134}
2135
2136/// An Opus decoder for one mono or stereo stream.
2137///
2138/// Decodes packets from any Opus encoder at any of the five API rates and
2139/// either channel count (a stereo stream can be decoded to mono and vice
2140/// versa). Pass an empty packet to conceal a lost one. After the first few
2141/// frames, decoding performs no heap allocation.
2142pub struct OpusDecoder {
2143    /// Range-decoder payload buffer, reused across frames (was a fresh copy
2144    /// of every payload).
2145    rc_scratch: Vec<u8>,
2146    /// Payload buffer for the redundant CELT frame's range decoder (at most
2147    /// 257 bytes), separate because `rc_scratch` is in use when it is decoded.
2148    red_rc_scratch: Vec<u8>,
2149    celt_dec: CeltDecoder,
2150    silk_dec: silk::dec_api::SilkDecoder,
2151    sampling_rate: i32,
2152    channels: usize,
2153
2154    prev_mode: Option<OpusMode>,
2155    frame_size: usize,
2156    /// libopus st->frame_size: the last packet's PER-FRAME size (caps PLC chunks).
2157    last_frame_size: usize,
2158
2159    bandwidth: Bandwidth,
2160
2161    stream_channels: usize,
2162
2163    silk_resampler: silk::resampler::SilkResampler,
2164    // Second resampler for the SILK stereo right channel (L uses silk_resampler).
2165    silk_resampler_r: silk::resampler::SilkResampler,
2166
2167    prev_internal_rate: i32,
2168
2169    w_pcm_i16: Vec<i16>,
2170    w_silk_out: Vec<f32>,
2171    w_pcm_resampled: Vec<i16>,
2172    w_celt_planar: Vec<f32>,
2173    w_celt_out: Vec<f32>,
2174
2175    // SILK per-frame history: libopus prepends the previous frame's last two
2176    // decoded samples (`sStereo.sMid`) and feeds the resampler from offset 1, a
2177    // 1-internal-sample delay line. Replicated here so our SILK output aligns
2178    // with the reference across every bandwidth (was leading by 1 internal
2179    // sample = 3/4/6 output samples at WB/MB/NB).
2180    silk_s_mid: [i16; 2],
2181
2182    /// Final range-decoder state of the last decoded packet (libopus
2183    /// `OPUS_GET_FINAL_RANGE`): equal to the encoder's for a correctly
2184    /// transmitted packet, so it detects corruption and desynchronisation.
2185    pub last_range: u32,
2186
2187    // Auxiliary decoder for packets whose channel count differs from ours
2188    // (a stream may switch between mono and stereo). It decodes at the packet's
2189    // native channel count; we then up/downmix to our output count. Persistent
2190    // so the "other" channel mode keeps its own inter-frame state.
2191    aux: Option<Box<Self>>,
2192    // Set when a packet was just decoded by the aux (a mono packet in a stereo
2193    // stream); triggers seeding the primary CELT decoder's overlap/energy state
2194    // from the aux at the next primary (stereo) CELT/Hybrid packet, so the MDCT
2195    // overlap-add is continuous across the mono->stereo switch.
2196    prev_used_aux: bool,
2197    // libopus st->prev_redundancy: the previous frame carried a SILK->CELT
2198    // redundant frame (redundancy && !celt_to_silk). Suppresses the CELT reset on
2199    // the following mode change (the redundant frame already primed CELT state).
2200    prev_redundancy: bool,
2201    /// Redundancy flag of the current packet's FIRST frame (libopus cancels a
2202    /// CELT->SILK/hybrid transition fade when the frame carries redundancy).
2203    first_frame_redundancy: bool,
2204    /// CELT->SILK/hybrid switch: samples of CELT concealment still owed for the
2205    /// transition fade. Deferred into the SILK/hybrid arm because libopus only
2206    /// conceals once it knows the frame has NO redundancy (`if (redundancy)
2207    /// transition = 0`) -- concealing first advanced the CELT state the
2208    /// redundant frame continues from.
2209    transition_pending: usize,
2210    transition_pcm: Option<Vec<f32>>,
2211}
2212
2213impl OpusDecoder {
2214    ///
2215    /// # Errors
2216    ///
2217    /// [`Error::BadArg`] if `sampling_rate` is not 8000, 12000, 16000, 24000 or
2218    /// 48000 Hz or `channels` is not 1 or 2.
2219    pub fn new(sampling_rate: i32, channels: usize) -> Result<Self, Error> {
2220        if ![8000, 12000, 16000, 24000, 48000].contains(&sampling_rate) {
2221            return Err(Error::BadArg("Invalid sampling rate"));
2222        }
2223        if ![1, 2].contains(&channels) {
2224            return Err(Error::BadArg("Invalid number of channels"));
2225        }
2226
2227        let mode = modes::default_mode();
2228        let mut celt_dec = CeltDecoder::new(mode, channels);
2229        // CELT always decodes the 48 kHz frame (libopus resampling_factor).
2230        celt_dec.downsample = (48000 / sampling_rate) as usize;
2231
2232        let mut silk_dec = silk::dec_api::SilkDecoder::new();
2233        silk_dec.init(sampling_rate.min(16000), channels as i32);
2234        silk_dec.channel_state[0].fs_api_hz = sampling_rate;
2235
2236        Ok(Self {
2237            rc_scratch: Vec::with_capacity(1275),
2238            red_rc_scratch: Vec::with_capacity(257),
2239            celt_dec,
2240            silk_dec,
2241            sampling_rate,
2242            channels,
2243            prev_mode: None,
2244            frame_size: 0,
2245            last_frame_size: 0,
2246            bandwidth: Bandwidth::Auto,
2247            stream_channels: channels,
2248            silk_resampler: silk::resampler::SilkResampler::default(),
2249            silk_resampler_r: silk::resampler::SilkResampler::default(),
2250            prev_internal_rate: 0,
2251
2252            // SILK internal scratch: max frame is 60 ms at the 16 kHz WB internal
2253            // rate (960 samples/ch), i.e. 1920 stereo. Sized like the sibling
2254            // buffers below for headroom — the old fixed 640 overflowed on any
2255            // 60 ms SILK frame (panic decoding valid streams).
2256            w_pcm_i16: vec![0i16; 5760 * channels],
2257
2258            w_silk_out: vec![0.0f32; 5760 * channels],
2259            w_pcm_resampled: vec![0i16; 5760 * channels],
2260            w_celt_planar: vec![0.0f32; 5760 * channels],
2261            w_celt_out: vec![0.0f32; 5760 * channels],
2262            silk_s_mid: [0; 2],
2263            last_range: 0,
2264            aux: None,
2265            prev_used_aux: false,
2266            prev_redundancy: false,
2267            first_frame_redundancy: false,
2268            transition_pending: 0,
2269            transition_pcm: None,
2270        })
2271    }
2272
2273    /// Packet-loss concealment for a lost frame (empty/None packet). Runs the
2274    /// SILK PLC (LTP+LPC extrapolation) for the last-known SILK/hybrid mode and
2275    /// resamples to the output rate. CELT-only loss has no CELT PLC yet, so it
2276    /// yields silence (a documented Tier-1 follow-up); the SILK path covers the
2277    /// dominant VoIP case. Mono conceal is duplicated to both channels on a
2278    /// stereo output.
2279    fn decode_plc(&mut self, frame_size: usize, output: &mut [f32]) -> Result<usize, Error> {
2280        if output.len() < frame_size * self.channels {
2281            return Err(Error::BufferTooSmall("Output buffer too small"));
2282        }
2283        // opus_decode_native(data==NULL) + opus_decode_frame: conceal in chunks
2284        // of at most the last packet's frame size and 20 ms, snapping shorter
2285        // requests to 10 ms (or 5 ms outside SILK). CELT never conceals more
2286        // than 20 ms per call -- which is what lets its history buffer be
2287        // libopus's 2048 samples, and the PLC pitch search see the same window.
2288        let fs = self.sampling_rate as usize;
2289        let (f20, f10, f5) = (fs / 50, fs / 100, fs / 200);
2290        let mode = if self.prev_redundancy {
2291            OpusMode::CeltOnly
2292        } else {
2293            self.prev_mode.unwrap_or(OpusMode::SilkOnly)
2294        };
2295        let cap = if self.last_frame_size > 0 {
2296            self.last_frame_size
2297        } else {
2298            frame_size
2299        };
2300        let ch = self.channels;
2301        let mut done = 0;
2302        while done < frame_size {
2303            let mut n = (frame_size - done).min(cap);
2304            if n > f20 {
2305                n = f20;
2306            } else if n < f20 {
2307                if n > f10 {
2308                    n = f10;
2309                } else if mode != OpusMode::SilkOnly && n > f5 && n < f10 {
2310                    n = f5;
2311                }
2312            }
2313            self.decode_plc_frame(n, &mut output[done * ch..])?;
2314            done += n;
2315        }
2316        Ok(frame_size)
2317    }
2318
2319    /// One opus_decode_frame(data==NULL): conceal `frame_size` (<= 20 ms) in the
2320    /// last mode -- CELT if the last frame ended in SILK->CELT redundancy. SILK
2321    /// conceals at least 10 ms (keeping the head); a hybrid frame adds the CELT
2322    /// high band (start band 17 -> noise PLC) on top of the SILK concealment.
2323    fn decode_plc_frame(&mut self, frame_size: usize, output: &mut [f32]) -> Result<usize, Error> {
2324        let ch = self.channels;
2325        let out_samples = frame_size * ch;
2326        for v in output.iter_mut().take(out_samples) {
2327            *v = 0.0;
2328        }
2329        let Some(prev) = self.prev_mode else {
2330            // No packet yet: all we can do is return zeros.
2331            return Ok(frame_size);
2332        };
2333        let mode = if self.prev_redundancy {
2334            OpusMode::CeltOnly
2335        } else {
2336            prev
2337        };
2338        if mode != OpusMode::CeltOnly {
2339            let f10 = (self.sampling_rate / 100) as usize;
2340            if frame_size < f10 {
2341                let mut tmp = vec![0.0f32; f10 * ch];
2342                self.decode_plc_silk(f10, &mut tmp, mode)?;
2343                output[..out_samples].copy_from_slice(&tmp[..out_samples]);
2344            } else {
2345                self.decode_plc_silk(frame_size, output, mode)?;
2346            }
2347        }
2348        if mode != OpusMode::SilkOnly {
2349            let celt_n = frame_size.min((self.sampling_rate / 50) as usize);
2350            if mode == OpusMode::Hybrid {
2351                // celt_accum: the high band adds onto the SILK concealment.
2352                let mut tmp = vec![0.0f32; celt_n * ch];
2353                self.celt_dec.plc_start = 17;
2354                self.celt_dec.conceal_lost(celt_n, &mut tmp);
2355                self.celt_dec.plc_start = 0;
2356                for (o, t) in output[..celt_n * ch].iter_mut().zip(&tmp) {
2357                    *o += *t;
2358                }
2359            } else {
2360                self.celt_dec.conceal_lost(celt_n, output);
2361            }
2362        }
2363        self.prev_mode = Some(mode);
2364        self.prev_redundancy = false;
2365        Ok(frame_size)
2366    }
2367
2368    /// The SILK half of decode_plc_frame (`frame_size` >= 10 ms).
2369    fn decode_plc_silk(
2370        &mut self,
2371        frame_size: usize,
2372        output: &mut [f32],
2373        mode: OpusMode,
2374    ) -> Result<(), Error> {
2375        let frame_ms = (frame_size as i32 * 1000 / self.sampling_rate).max(1);
2376        let internal_rate = if mode == OpusMode::Hybrid {
2377            16000
2378        } else {
2379            match self.bandwidth {
2380                Bandwidth::Narrowband => 8000,
2381                Bandwidth::Mediumband => 12000,
2382                _ => 16000,
2383            }
2384        };
2385        if internal_rate != self.prev_internal_rate {
2386            self.silk_resampler.init(internal_rate, self.sampling_rate);
2387            self.prev_internal_rate = internal_rate;
2388        }
2389        let n_silk = match frame_ms {
2390            40 => 2,
2391            60 => 3,
2392            _ => 1,
2393        };
2394        let internal_frame = (frame_ms * internal_rate / 1000) as usize;
2395        let internal_sub = internal_frame / n_silk.max(1);
2396        let ratio = self.sampling_rate as f64 / internal_rate as f64;
2397        // Conceal with the previous frame's internal channel count: libopus runs
2398        // PLC on both SILK channels of a stereo stream and unmixes M/S -> L/R
2399        // with the previous predictor (dec_API.c). Concealing mid only and
2400        // duplicating it put every stereo mode transition off by ~3k LSB.
2401        let silk_lr = self.channels == 2 && self.silk_dec.n_channels_internal == 2;
2402        self.silk_dec.produce_lr = silk_lr;
2403
2404        let mut off = 0usize; // output samples/ch written so far
2405        for sf in 0..n_silk {
2406            let mut rc = RangeCoder::new_decoder(&[]);
2407            let n16 = internal_sub;
2408            if n16 + 2 > self.w_pcm_i16.len() {
2409                return Err(Error::BufferTooSmall("opus PLC: frame exceeds buffer"));
2410            }
2411            self.w_pcm_i16[0] = self.silk_s_mid[0];
2412            self.w_pcm_i16[1] = self.silk_s_mid[1];
2413            let ret = self.silk_dec.decode(
2414                &mut rc,
2415                &mut self.w_pcm_i16[2..n16 + 2],
2416                silk::decode_frame::FLAG_PACKET_LOST,
2417                sf == 0,
2418                frame_ms,
2419                internal_rate,
2420            );
2421            if ret < 0 {
2422                return Err(Error::Internal("SILK PLC failed"));
2423            }
2424            let dec = ret as usize;
2425            if dec >= 2 {
2426                self.silk_s_mid[0] = self.w_pcm_i16[dec];
2427                self.silk_s_mid[1] = self.w_pcm_i16[dec + 1];
2428            }
2429            let base = off * self.channels;
2430            // Always through the resampler, even at equal rates: its Copy mode
2431            // carries libopus's delay_matrix_dec delay (see decode()).
2432            let out_len = (dec as f64 * ratio) as usize;
2433            if silk_lr {
2434                // L and R each through their own resampler, as the normal path.
2435                // Disjoint fields: resample straight from l_out/r_out (were
2436                // `.to_vec()` copies per lost frame).
2437                self.silk_resampler.process(
2438                    &mut self.w_pcm_resampled[..out_len],
2439                    &self.silk_dec.l_out[..dec],
2440                    dec as i32,
2441                );
2442                for i in 0..out_len {
2443                    let idx = base + i * 2;
2444                    if idx < output.len() {
2445                        output[idx] = self.w_pcm_resampled[i] as f32 / 32768.0;
2446                    }
2447                }
2448                self.silk_resampler_r.process(
2449                    &mut self.w_pcm_resampled[..out_len],
2450                    &self.silk_dec.r_out[..dec],
2451                    dec as i32,
2452                );
2453                for i in 0..out_len {
2454                    let idx = base + i * 2 + 1;
2455                    if idx < output.len() {
2456                        output[idx] = self.w_pcm_resampled[i] as f32 / 32768.0;
2457                    }
2458                }
2459            } else {
2460                let src = &self.w_pcm_i16[1..1 + dec];
2461                self.silk_resampler
2462                    .process(&mut self.w_pcm_resampled[..out_len], src, dec as i32);
2463                for i in 0..out_len {
2464                    let v = self.w_pcm_resampled[i] as f32 / 32768.0;
2465                    for ch in 0..self.channels {
2466                        let idx = base + i * self.channels + ch;
2467                        if idx < output.len() {
2468                            output[idx] = v;
2469                        }
2470                    }
2471                }
2472                // Mono into a stereo output: keep the right-channel resampler
2473                // continuous, as the normal path does (dec_API.c:351-355).
2474                if self.channels == 2 {
2475                    self.silk_resampler_r.process(
2476                        &mut self.w_pcm_resampled[..out_len],
2477                        src,
2478                        dec as i32,
2479                    );
2480                    for i in 0..out_len {
2481                        let idx = base + i * 2 + 1;
2482                        if idx < output.len() {
2483                            output[idx] = self.w_pcm_resampled[i] as f32 / 32768.0;
2484                        }
2485                    }
2486                }
2487            }
2488            off += out_len;
2489        }
2490        Ok(())
2491    }
2492
2493    /// Forward-error-correction decode: reconstruct a LOST frame from the LBRR
2494    /// (low-bitrate redundancy) embedded in the NEXT received `packet`. Drives
2495    /// the SILK decoder in FLAG_DECODE_LBRR mode, which self-selects: it decodes
2496    /// the redundant frame when the packet carries LBRR for it, and falls back
2497    /// to PLC extrapolation when it doesn't. CELT-only or multi-frame packets
2498    /// fall back to plain PLC (no SILK LBRR to recover). After this call the
2499    /// caller decodes `packet` normally for the following frame.
2500    ///
2501    /// # Errors
2502    ///
2503    /// [`Error::InvalidPacket`] if `input` is malformed or truncated;
2504    /// [`Error::BufferTooSmall`] if `frame_size` exceeds the decoder's capacity or
2505    /// `output`; [`Error::Internal`] if a codec stage fails.
2506    pub fn decode_fec(
2507        &mut self,
2508        packet: &[u8],
2509        frame_size: usize,
2510        output: &mut [f32],
2511    ) -> Result<usize, Error> {
2512        if packet.is_empty() {
2513            return self.decode_plc(frame_size, output);
2514        }
2515        let toc = packet[0];
2516        let mode = mode_from_toc(toc);
2517        // FEC only lives in SILK/hybrid low band; code-0 (single frame) only.
2518        if mode == OpusMode::CeltOnly || (toc & 0x03) != 0 {
2519            return self.decode_plc(frame_size, output);
2520        }
2521        let bandwidth = bandwidth_from_toc(toc);
2522        let payload = &packet[1..];
2523
2524        let out_samples = frame_size * self.channels;
2525        if output.len() < out_samples {
2526            return Err(Error::BufferTooSmall("Output buffer too small"));
2527        }
2528        for v in output.iter_mut().take(out_samples) {
2529            *v = 0.0;
2530        }
2531        let frame_ms = (frame_size as i32 * 1000 / self.sampling_rate).max(1);
2532        let internal_rate = if mode == OpusMode::Hybrid {
2533            16000
2534        } else {
2535            match bandwidth {
2536                Bandwidth::Narrowband => 8000,
2537                Bandwidth::Mediumband => 12000,
2538                _ => 16000,
2539            }
2540        };
2541        if internal_rate != self.prev_internal_rate {
2542            self.silk_resampler.init(internal_rate, self.sampling_rate);
2543            self.prev_internal_rate = internal_rate;
2544        }
2545        let internal_frame = (frame_ms * internal_rate / 1000) as usize;
2546        let ratio = self.sampling_rate as f64 / internal_rate as f64;
2547        self.silk_dec.produce_lr = false;
2548        self.silk_dec.n_channels_internal = 1;
2549
2550        let mut rc = RangeCoder::new_decoder(payload);
2551        let n16 = internal_frame;
2552        if n16 + 2 > self.w_pcm_i16.len() {
2553            return Err(Error::BufferTooSmall("opus FEC: frame exceeds buffer"));
2554        }
2555        self.w_pcm_i16[0] = self.silk_s_mid[0];
2556        self.w_pcm_i16[1] = self.silk_s_mid[1];
2557        let ret = self.silk_dec.decode(
2558            &mut rc,
2559            &mut self.w_pcm_i16[2..n16 + 2],
2560            silk::decode_frame::FLAG_DECODE_LBRR,
2561            true,
2562            frame_ms,
2563            internal_rate,
2564        );
2565        if ret < 0 {
2566            return Err(Error::Internal("SILK FEC failed"));
2567        }
2568        let dec = ret as usize;
2569        if dec >= 2 {
2570            self.silk_s_mid[0] = self.w_pcm_i16[dec];
2571            self.silk_s_mid[1] = self.w_pcm_i16[dec + 1];
2572        }
2573        // Always through the resampler (equal rates included): Copy mode carries
2574        // libopus's delay_matrix_dec delay (see decode()).
2575        {
2576            let out_len = (dec as f64 * ratio) as usize;
2577            let src: Vec<i16> = self.w_pcm_i16[1..1 + dec].to_vec();
2578            self.silk_resampler
2579                .process(&mut self.w_pcm_resampled[..out_len], &src, dec as i32);
2580            for i in 0..out_len {
2581                let v = self.w_pcm_resampled[i] as f32 / 32768.0;
2582                for ch in 0..self.channels {
2583                    let idx = i * self.channels + ch;
2584                    if idx < output.len() {
2585                        output[idx] = v;
2586                    }
2587                }
2588            }
2589        }
2590        self.prev_mode = Some(mode);
2591        Ok(frame_size)
2592    }
2593
2594    ///
2595    /// # Errors
2596    ///
2597    /// [`Error::InvalidPacket`] if `input` is malformed, truncated, or longer than
2598    /// 120 ms; [`Error::BufferTooSmall`] if `output` cannot hold the decoded frame;
2599    /// [`Error::Internal`] if a codec stage fails. A malformed packet never panics.
2600    pub fn decode(
2601        &mut self,
2602        input: &[u8],
2603        frame_size: usize,
2604        output: &mut [f32],
2605    ) -> Result<usize, Error> {
2606        // Lost packet (data==NULL / empty) -> packet-loss concealment.
2607        if input.is_empty() {
2608            return self.decode_plc(frame_size, output);
2609        }
2610
2611        let toc = input[0];
2612        let mode = mode_from_toc(toc);
2613        let packet_channels = channels_from_toc(toc);
2614        let bandwidth = bandwidth_from_toc(toc);
2615        let frame_duration_ms = frame_duration_ms_from_toc(toc);
2616
2617        // A mono SILK packet inside a stereo stream is decoded through the PRIMARY
2618        // decoder (unified path), not a separate aux — the aux's SILK/resampler
2619        // state is blind to the interleaved stereo packets, so its state is stale
2620        // at every mono<->stereo switch. libopus keeps ONE decoder whose channel-0
2621        // resampler and stereo state run continuously across the switches.
2622        // A mono packet of ANY mode in a stereo stream decodes through the PRIMARY
2623        // (unified path) so inter-frame state stays one continuous chain across
2624        // mono<->stereo switches — SILK resampler/stereo state; CELT (and the
2625        // redundant/silence transition frames) via stream_channels=1 (C=1/CC=2) —
2626        // matching libopus's single decoder.
2627        let mono_in_stereo = packet_channels == 1 && self.channels == 2;
2628
2629        if packet_channels != self.channels && !mono_in_stereo {
2630            // The packet's channel count differs from ours (a stream can switch
2631            // between mono and stereo). Decode it at its native channel count in
2632            // a persistent auxiliary decoder, then render to our output count:
2633            // mono->stereo duplicates, stereo->mono averages the two channels.
2634            if self
2635                .aux
2636                .as_ref()
2637                .is_none_or(|a| a.channels != packet_channels)
2638            {
2639                let mut aux = Box::new(Self::new(self.sampling_rate, packet_channels)?);
2640                // The C decoder is ONE mono decoder (disable_inv), not a stereo
2641                // one averaged afterwards: ignore inversion when we downmix.
2642                aux.celt_dec.disable_inv = self.channels == 1;
2643                self.aux = Some(aux);
2644            }
2645            // Reverse of the mono->stereo seed: on a stereo->mono switch, seed the
2646            // aux (mono) CELT decoder from the primary (stereo channel 0) so its
2647            // MDCT-overlap/energy state is continuous with the preceding stereo
2648            // packets (the primary was the continuous decoder during them).
2649            if !self.prev_used_aux
2650                && packet_channels == 1
2651                && self.channels == 2
2652                && (mode == OpusMode::CeltOnly || mode == OpusMode::Hybrid)
2653            {
2654                let (aux_opt, primary) = (&mut self.aux, &self.celt_dec);
2655                if let Some(aux) = aux_opt.as_mut() {
2656                    aux.celt_dec.seed_from(primary);
2657                }
2658            }
2659            let Some(aux) = self.aux.as_mut() else {
2660                return Err(Error::Internal("auxiliary decoder missing"));
2661            };
2662            let mut buf = vec![0.0f32; frame_size * packet_channels];
2663            let n = aux.decode(input, frame_size, &mut buf)?;
2664            self.last_range = aux.last_range;
2665            if packet_channels == 1 && self.channels == 2 {
2666                for i in 0..n {
2667                    let v = buf[i];
2668                    output[2 * i] = v;
2669                    output[2 * i + 1] = v;
2670                }
2671            } else if packet_channels == 2 && self.channels == 1 {
2672                for i in 0..n {
2673                    output[i] = 0.5 * (buf[2 * i] + buf[2 * i + 1]);
2674                }
2675            } else {
2676                let m = (n * self.channels).min(output.len()).min(buf.len());
2677                output[..m].copy_from_slice(&buf[..m]);
2678            }
2679            self.prev_mode = Some(mode);
2680            self.prev_used_aux = true;
2681            return Ok(n);
2682        }
2683
2684        // First primary (native-channel) packet after a run of aux (mono-in-stereo)
2685        // packets: seed the primary CELT decoder's inter-frame state from the aux
2686        // so the mono->stereo MDCT overlap-add is continuous (matches libopus's
2687        // single continuous decoder). SILK carries its own state through the
2688        // primary already; this is for the CELT/Hybrid high band.
2689        if self.prev_used_aux {
2690            self.prev_used_aux = false;
2691            if (mode == OpusMode::CeltOnly || mode == OpusMode::Hybrid) && self.channels == 2 {
2692                if let Some(aux) = self.aux.as_ref() {
2693                    self.celt_dec.seed_from(&aux.celt_dec);
2694                }
2695            }
2696        }
2697
2698        let code = toc & 0x03;
2699        let frame_count: usize;
2700        // At most 48 frames per packet (RFC 6716 3.2.5): a fixed table, not a
2701        // Vec per packet.
2702        let mut payload_tab: [&[u8]; 48] = [&[]; 48];
2703
2704        match code {
2705            0 => {
2706                frame_count = 1;
2707                payload_tab[0] = &input[1..];
2708            }
2709            1 => {
2710                frame_count = 2;
2711                // Two frames of equal size (RFC 6716 3.2.3), possibly both
2712                // empty (concealed); an odd payload length is malformed.
2713                if (input.len() - 1) % 2 != 0 {
2714                    return Err(Error::InvalidPacket("Code 1: odd payload length"));
2715                }
2716                let half = (input.len() - 1) / 2;
2717                payload_tab[0] = &input[1..1 + half];
2718                payload_tab[1] = &input[1 + half..];
2719            }
2720            2 => {
2721                frame_count = 2;
2722                let data = &input[1..];
2723                if data.is_empty() {
2724                    return Err(Error::InvalidPacket("Code 2 packet has no data"));
2725                }
2726                let (first_len, header_size) = read_opus_frame_len(data, 0)?;
2727                if header_size + first_len > data.len() {
2728                    return Err(Error::InvalidPacket(
2729                        "Code 2: first frame size exceeds packet",
2730                    ));
2731                }
2732                payload_tab[0] = &data[header_size..header_size + first_len];
2733                payload_tab[1] = &data[header_size + first_len..];
2734            }
2735            _ => {
2736                // code == 3.
2737                // RFC 6716 §3.2.5. Frame-count byte: bit 7 = VBR flag, bit 6 =
2738                // padding flag, bits 5..0 = frame count M. VBR and padding are
2739                // independent; the earlier code conflated them (and used a
2740                // non-standard length coding), which mis-parsed CBR and padded
2741                // packets — exactly what the RFC test vectors exercise.
2742                if input.len() < 2 {
2743                    return Err(Error::InvalidPacket("Code 3 packet too short"));
2744                }
2745                let count_byte = input[1];
2746                let m = (count_byte & 0x3F) as usize;
2747                if !(1..=48).contains(&m) {
2748                    return Err(Error::InvalidPacket("Code 3: invalid frame count"));
2749                }
2750                // libopus opus.c opus_packet_parse_impl (code 3):
2751                //   if (count <= 0 || framesize*(opus_int32)count > 5760)
2752                //      return OPUS_INVALID_PACKET;
2753                // (framesize at 48 kHz; 5760 = 120 ms, the RFC 6716 packet cap.)
2754                // A hostile frame count past this cap would otherwise shrink our
2755                // per-frame size below the redundancy-fade windows further down.
2756                if m as i32 * repacketizer::samples_per_frame(toc, 48000) > 5760 {
2757                    return Err(Error::InvalidPacket(
2758                        "Code 3: packet duration exceeds 120 ms",
2759                    ));
2760                }
2761                frame_count = m;
2762                let vbr = (count_byte & 0x80) != 0;
2763                let padding = (count_byte & 0x40) != 0;
2764
2765                // Padding length indicator bytes follow the count byte; the
2766                // padding data itself sits at the end of the packet.
2767                let mut ptr = 2usize;
2768                let mut pad_len = 0usize;
2769                if padding {
2770                    loop {
2771                        let p = *input
2772                            .get(ptr)
2773                            .ok_or(Error::InvalidPacket("Code 3: padding overflow"))?
2774                            as usize;
2775                        ptr += 1;
2776                        if p == 255 {
2777                            pad_len += 254;
2778                        } else {
2779                            pad_len += p;
2780                            break;
2781                        }
2782                    }
2783                }
2784                let end = input
2785                    .len()
2786                    .checked_sub(pad_len)
2787                    .ok_or(Error::InvalidPacket("Code 3: padding exceeds packet"))?;
2788                if ptr > end {
2789                    return Err(Error::InvalidPacket("Code 3: padding exceeds packet"));
2790                }
2791                // Frame-data region, with the length headers (VBR) at its front
2792                // and the trailing padding already excluded.
2793                let region = &input[ptr..end];
2794
2795                if vbr {
2796                    // M-1 explicit frame lengths, contiguous, then the frame
2797                    // data; the last frame is the remainder.
2798                    let mut lens = [0usize; 48];
2799                    let mut hp = 0usize;
2800                    for l_out in lens.iter_mut().take(m - 1) {
2801                        let (l, nb) = read_opus_frame_len(region, hp)?;
2802                        hp += nb;
2803                        *l_out = l;
2804                    }
2805                    let mut fp = hp;
2806                    for (i, &l) in lens[..m - 1].iter().enumerate() {
2807                        if fp + l > region.len() {
2808                            return Err(Error::InvalidPacket(
2809                                "Code 3 VBR: frame length exceeds packet",
2810                            ));
2811                        }
2812                        payload_tab[i] = &region[fp..fp + l];
2813                        fp += l;
2814                    }
2815                    if fp > region.len() {
2816                        return Err(Error::InvalidPacket("Code 3 VBR: no data for last frame"));
2817                    }
2818                    payload_tab[m - 1] = &region[fp..];
2819                } else {
2820                    // CBR: the region splits into M equal frames (possibly all
2821                    // empty, e.g. DTX).
2822                    if region.len() % m != 0 {
2823                        return Err(Error::InvalidPacket(
2824                            "Code 3 CBR: frame data not divisible by frame count",
2825                        ));
2826                    }
2827                    let frame_len = region.len() / m;
2828                    for (i, p) in payload_tab[..m].iter_mut().enumerate() {
2829                        *p = &region[i * frame_len..(i + 1) * frame_len];
2830                    }
2831                }
2832            }
2833        }
2834        let frame_payloads = &payload_tab[..frame_count];
2835        // No Opus frame exceeds 1275 bytes (RFC 6716 3.2.1, R2).
2836        if frame_payloads.iter().any(|p| p.len() > 1275) {
2837            return Err(Error::InvalidPacket("frame exceeds 1275 bytes"));
2838        }
2839
2840        // libopus opus_decoder.c opus_decode_native:
2841        //   if (count*packet_frame_size > frame_size)
2842        //      return OPUS_BUFFER_TOO_SMALL;
2843        // The packet's own TOC duration must fit the caller's frame_size. We split
2844        // the caller's buffer as sub_frame_size = frame_size / frame_count, so a
2845        // malformed multi-frame packet (large frame count vs. a small caller
2846        // buffer) would otherwise make sub_frame_size smaller than the 2.5/5 ms
2847        // redundancy-fade region — the fuzzer-found out-of-bounds/underflow panics
2848        // in redundancy_fade_start/redundancy_fade_end. C rejects such packets
2849        // here; so do we.
2850        let packet_frame_samples =
2851            repacketizer::samples_per_frame(toc, self.sampling_rate) as usize;
2852        // ...and the decoded samples must fit the caller's `output` slice, which
2853        // is independent of `frame_size` (an undersized slice used to panic on
2854        // an out-of-range slice index; found by the decode property tests).
2855        if frame_count * packet_frame_samples > frame_size
2856            || output.len() < frame_count * packet_frame_samples * self.channels
2857        {
2858            return Err(Error::BufferTooSmall("Output buffer too small"));
2859        }
2860        // The caller's frame_size is a CAPACITY (libopus): decode exactly the
2861        // packet's own duration and return it.
2862        let frame_size = frame_count * packet_frame_samples;
2863
2864        // ---- Mode-transition frame (opus_decoder.c opus_decode_frame) ----
2865        // Entering CELT from SILK/hybrid without a redundant frame, or leaving
2866        // CELT for SILK/hybrid: libopus conceals min(5 ms, frame) in the
2867        // PREVIOUS mode before decoding, then uses it for the first 2.5 ms and
2868        // cross-fades into the decoded audio over the next 2.5 ms. Without it
2869        // our first post-switch frame differed from libopus by up to ~4.7k LSB.
2870        // Generated BEFORE bandwidth/stream_channels move to the new packet, so
2871        // the concealment runs on the previous mode's state (as data==NULL
2872        // does). Only the packet's first frame can be a switch: all frames in
2873        // one packet share a mode.
2874        let f5 = (self.sampling_rate / 200) as usize;
2875        let audiosize = frame_size / frame_count;
2876        let mut pcm_transition: Option<Vec<f32>> = None;
2877        if let Some(pm) = self.prev_mode {
2878            let transition =
2879                (mode == OpusMode::CeltOnly && pm != OpusMode::CeltOnly && !self.prev_redundancy)
2880                    || (mode != OpusMode::CeltOnly && pm == OpusMode::CeltOnly);
2881            if transition && pm == OpusMode::CeltOnly {
2882                self.transition_pending = f5.min(audiosize);
2883            } else if transition {
2884                // Conceal min(5 ms, frame) in the previous mode (SILK pads its
2885                // concealment to 10 ms internally; hybrid adds the CELT high band).
2886                let n = f5.min(audiosize);
2887                let mut buf = vec![0.0f32; n * self.channels];
2888                self.decode_plc(n, &mut buf)?;
2889                pcm_transition = Some(buf);
2890            }
2891        }
2892        self.first_frame_redundancy = false;
2893
2894        // opus_decode_frame: `if (st->prev_mode==MODE_CELT_ONLY)
2895        // silk_ResetDecoder(silk_dec)` before any SILK/hybrid frame. That clears
2896        // fs_kHz too, so the SILK resampler restarts from zero state, and it
2897        // zeroes sStereo (incl. the 2-sample mid history). Carrying the old
2898        // SILK/resampler/stereo history across a CELT run left every SILK frame
2899        // after the switch 20-300 LSB off libopus.
2900        if mode != OpusMode::CeltOnly && self.prev_mode == Some(OpusMode::CeltOnly) {
2901            self.silk_dec.reset();
2902            self.silk_s_mid = [0; 2];
2903            self.prev_internal_rate = 0; // re-init both SILK resamplers
2904        }
2905
2906        self.frame_size = frame_size;
2907        self.last_frame_size = frame_size / frame_count;
2908        self.bandwidth = bandwidth;
2909        self.stream_channels = packet_channels;
2910
2911        let sub_frame_size = frame_size / frame_count;
2912        let sub_output_len = sub_frame_size * self.channels;
2913
2914        let result = match mode {
2915            OpusMode::SilkOnly => {
2916                let internal_sample_rate = match bandwidth {
2917                    Bandwidth::Narrowband => 8000,
2918                    Bandwidth::Mediumband => 12000,
2919                    Bandwidth::Wideband => 16000,
2920                    _ => 16000,
2921                };
2922                let internal_frame_size =
2923                    (frame_duration_ms * internal_sample_rate / 1000) as usize;
2924
2925                // Initialised at EQUAL rates too: libopus always runs
2926                // silk_resampler, whose Copy mode delays SILK by
2927                // delay_matrix_dec[in][out] (8k:4, 12k:9, 16k:12 samples) so it
2928                // stays aligned with CELT. Bypassing it at 8/12/16 kHz output
2929                // shifted every SILK sample against libopus's decoder.
2930                if internal_sample_rate != self.prev_internal_rate {
2931                    self.silk_resampler
2932                        .init(internal_sample_rate, self.sampling_rate);
2933                    self.silk_resampler_r
2934                        .init(internal_sample_rate, self.sampling_rate);
2935                    self.prev_internal_rate = internal_sample_rate;
2936                }
2937
2938                // Pure-SILK stereo (both stream and output are 2ch): reconstruct
2939                // true L/R via SILK MS->LR instead of duplicating the mono mid.
2940                let silk_lr = self.channels == 2 && packet_channels == 2;
2941                self.silk_dec.produce_lr = silk_lr;
2942
2943                // Per-packet internal channel switch (libopus dec_API.c:119-166).
2944                let prev_internal_ch = self.silk_dec.n_channels_internal;
2945                if packet_channels as i32 > prev_internal_ch {
2946                    // mono -> stereo: reset the side channel decoder.
2947                    silk::init_decoder::silk_init_decoder(&mut self.silk_dec.channel_state[1]);
2948                }
2949                if self.channels == 2 && packet_channels == 2 && prev_internal_ch == 1 {
2950                    // Switching to stereo: clear stereo prediction/side history and
2951                    // seed the right-channel resampler from the (continuous) left.
2952                    self.silk_dec.s_stereo_pred_prev_q13 = [0; 2];
2953                    self.silk_dec.s_stereo_side = [0; 2];
2954                    self.silk_resampler_r = self.silk_resampler.clone();
2955                }
2956                self.silk_dec.n_channels_internal = packet_channels as i32;
2957
2958                // A 40/60 ms Opus frame carries 2/3 internal 20 ms SILK frames;
2959                // 10/20 ms carry one. libopus calls silk_Decode once per internal
2960                // frame (continuing the same range coder within the payload). We
2961                // must too — decoding only the first internal frame leaves the
2962                // rest of a 40/60 ms packet silent (the "collapse" bug).
2963                let n_silk = match frame_duration_ms {
2964                    40 => 2,
2965                    60 => 3,
2966                    _ => 1,
2967                };
2968                let internal_sub_frame_size = internal_frame_size / n_silk;
2969                let ratio = self.sampling_rate as f64 / internal_sample_rate as f64;
2970                // Per-FRAME previous mode (libopus updates prev_mode per frame; for
2971                // payloads after the first, the previous frame is this same packet).
2972                let mut prev_mode_frame = self.prev_mode;
2973
2974                for (fi, payload) in frame_payloads.iter().enumerate() {
2975                    let mut rc =
2976                        RangeCoder::new_decoder_in(std::mem::take(&mut self.rc_scratch), payload);
2977                    let pcm_i16_len = internal_sub_frame_size * self.channels;
2978                    // A malformed packet can imply a frame larger than our scratch
2979                    // buffer; reject it gracefully instead of slicing out of bounds
2980                    // (a decode-path DoS on attacker-controlled input).
2981                    if pcm_i16_len + 2 > self.w_pcm_i16.len() {
2982                        return Err(Error::InvalidPacket("opus: SILK frame size exceeds buffer"));
2983                    }
2984                    let out_start = fi * sub_output_len;
2985                    let mut silk_off = 0usize; // output samples/ch within this Opus frame
2986
2987                    for sf in 0..n_silk {
2988                        let s_mid = self.silk_s_mid;
2989                        let ret = {
2990                            let (silk_dec, pcm_i16) = (&mut self.silk_dec, &mut self.w_pcm_i16);
2991                            // Prepend the previous frame's last two samples (sMid) at
2992                            // [0..2] and decode at offset 2, matching libopus's
2993                            // samplesOut1_tmp[n][2] layout.
2994                            pcm_i16[0] = s_mid[0];
2995                            pcm_i16[1] = s_mid[1];
2996                            silk_dec.decode(
2997                                &mut rc,
2998                                &mut pcm_i16[2..pcm_i16_len + 2],
2999                                silk::decode_frame::FLAG_DECODE_NORMAL,
3000                                sf == 0,
3001                                frame_duration_ms,
3002                                internal_sample_rate,
3003                            )
3004                        };
3005
3006                        if ret < 0 {
3007                            return Err(Error::Internal("SILK decoding failed"));
3008                        }
3009
3010                        let decoded_samples = ret as usize;
3011                        // Carry the last two decoded samples as next frame's sMid.
3012                        if decoded_samples >= 2 {
3013                            self.silk_s_mid[0] = self.w_pcm_i16[decoded_samples];
3014                            self.silk_s_mid[1] = self.w_pcm_i16[decoded_samples + 1];
3015                        }
3016                        let base = out_start + silk_off * self.channels;
3017
3018                        // Stereo SILK: L in silk_dec.l_out, R in silk_dec.r_out,
3019                        // both already in the 1-sample-delay-line layout. Resample
3020                        // each channel through its own resampler.
3021                        let out_len = if silk_lr {
3022                            let out_len = (decoded_samples as f64 * ratio) as usize;
3023                            // Left
3024                            self.silk_resampler.process(
3025                                &mut self.w_pcm_resampled[..out_len],
3026                                &self.silk_dec.l_out[..decoded_samples],
3027                                decoded_samples as i32,
3028                            );
3029                            for i in 0..out_len {
3030                                let idx = base + i * 2;
3031                                if idx < output.len() {
3032                                    output[idx] = self.w_pcm_resampled[i] as f32 / 32768.0;
3033                                }
3034                            }
3035                            // Right (reuse the scratch)
3036                            self.silk_resampler_r.process(
3037                                &mut self.w_pcm_resampled[..out_len],
3038                                &self.silk_dec.r_out[..decoded_samples],
3039                                decoded_samples as i32,
3040                            );
3041                            for i in 0..out_len {
3042                                let idx = base + i * 2 + 1;
3043                                if idx < output.len() {
3044                                    output[idx] = self.w_pcm_resampled[i] as f32 / 32768.0;
3045                                }
3046                            }
3047                            out_len
3048                        } else {
3049                            let out_len = (decoded_samples as f64 * ratio) as usize;
3050                            debug_assert!(out_len <= self.w_pcm_resampled.len());
3051                            {
3052                                let (silk_res, pcm_i16, pcm_out) = (
3053                                    &mut self.silk_resampler,
3054                                    &self.w_pcm_i16,
3055                                    &mut self.w_pcm_resampled,
3056                                );
3057                                silk_res.process(
3058                                    &mut pcm_out[..out_len],
3059                                    &pcm_i16[1..1 + decoded_samples],
3060                                    decoded_samples as i32,
3061                                );
3062                            }
3063                            for i in 0..out_len {
3064                                let v = self.w_pcm_resampled[i] as f32 / 32768.0;
3065                                for ch in 0..self.channels {
3066                                    let idx = base + i * self.channels + ch;
3067                                    if idx < output.len() {
3068                                        output[idx] = v;
3069                                    }
3070                                }
3071                            }
3072                            // Stereo output, mono packet: also run the mono signal
3073                            // through the RIGHT-channel resampler so its state stays
3074                            // continuous for the next stereo packet (libopus
3075                            // dec_API.c:351-355). Its output overwrites channel 1,
3076                            // which is numerically ~identical to the left here.
3077                            if self.channels == 2 {
3078                                self.silk_resampler_r.process(
3079                                    &mut self.w_pcm_resampled[..out_len],
3080                                    &self.w_pcm_i16[1..1 + decoded_samples],
3081                                    decoded_samples as i32,
3082                                );
3083                                for i in 0..out_len {
3084                                    let idx = base + i * 2 + 1;
3085                                    if idx < output.len() {
3086                                        output[idx] = self.w_pcm_resampled[i] as f32 / 32768.0;
3087                                    }
3088                                }
3089                            }
3090                            out_len
3091                        };
3092                        silk_off += out_len;
3093                    }
3094
3095                    // --- Opus redundancy layer (opus_decoder.c:420-580) ---
3096                    // A SILK-only frame carries IMPLICIT CELT redundancy: if >= 17
3097                    // bits remain after SILK, the trailing bytes ARE a 5 ms CELT
3098                    // frame (no flag) used to smooth mode/bandwidth transitions.
3099                    let mut redundant_rng = 0u32;
3100                    let mut redundancy = false;
3101                    let mut celt_to_silk = false;
3102                    let plen = payload.len();
3103                    let f5 = (self.sampling_rate / 200) as usize;
3104                    let f2_5 = f5 / 2;
3105                    let red_end_band = celt_endband_for_bandwidth(bandwidth);
3106                    let mut red_buf = [0.0f32; 480]; // F5 * <=2ch, planar
3107                    let mut red_bytes = 0usize;
3108                    if rc.tell() + 17 <= (plen as i32) * 8 {
3109                        redundancy = true;
3110                        celt_to_silk = rc.decode_bit_logp(1);
3111                        red_bytes = plen - (((rc.tell() + 7) >> 3) as usize);
3112                        if red_bytes < 2 || red_bytes >= plen {
3113                            redundancy = false;
3114                            red_bytes = 0;
3115                        }
3116                    }
3117                    self.run_transition_plc(fi, redundancy);
3118                    // CELT->SILK: the redundant frame continues the prior CELT
3119                    // state (a fade-out of the previous CELT mode). Decode BEFORE
3120                    // the hybrid->SILK silence frame to keep libopus state order.
3121                    if redundancy && celt_to_silk {
3122                        redundant_rng = self.decode_redundant_celt(
3123                            &payload[plen - red_bytes..],
3124                            false,
3125                            packet_channels,
3126                            red_end_band,
3127                            &mut red_buf[..f5 * self.channels],
3128                        );
3129                    }
3130                    // Hybrid->SILK transition: let the CELT MDCT fade out by
3131                    // decoding a 2-byte silence frame; its 2.5 ms overlap tail is
3132                    // ADDED to the output (libopus decodes it into pcm before the
3133                    // SILK sum).
3134                    if prev_mode_frame == Some(OpusMode::Hybrid)
3135                        && !(redundancy && celt_to_silk && self.prev_redundancy)
3136                    {
3137                        let silence = [0xFFu8, 0xFF];
3138                        let mut sil_buf = [0.0f32; 240]; // F2_5 * <=2ch, planar
3139                        self.celt_dec.set_stream_channels(packet_channels);
3140                        let mut src = RangeCoder::new_decoder(&silence);
3141                        self.celt_dec.decode_from_range_coder_with_band_range(
3142                            &mut src,
3143                            16,
3144                            f2_5,
3145                            &mut sil_buf[..f2_5 * self.channels],
3146                            0,
3147                            red_end_band,
3148                        );
3149                        let region = &mut output[out_start..out_start + sub_output_len];
3150                        for i in 0..f2_5 {
3151                            for c in 0..self.channels {
3152                                region[i * self.channels + c] += sil_buf[c * f2_5 + i];
3153                            }
3154                        }
3155                    }
3156                    // SILK->CELT: reset, then decode — this PRIMES the CELT state
3157                    // for the upcoming CELT-mode frames (which is why the next mode
3158                    // change skips its reset when prev_redundancy is set).
3159                    if redundancy && !celt_to_silk {
3160                        redundant_rng = self.decode_redundant_celt(
3161                            &payload[plen - red_bytes..],
3162                            true,
3163                            packet_channels,
3164                            red_end_band,
3165                            &mut red_buf[..f5 * self.channels],
3166                        );
3167                    }
3168                    if redundancy {
3169                        let window = modes::default_mode().window;
3170                        let region = &mut output[out_start..out_start + sub_output_len];
3171                        if celt_to_silk {
3172                            redundancy_fade_start(
3173                                region,
3174                                &red_buf,
3175                                f5,
3176                                f2_5,
3177                                self.channels,
3178                                window,
3179                            );
3180                        } else {
3181                            redundancy_fade_end(
3182                                region,
3183                                sub_frame_size,
3184                                &red_buf,
3185                                f5,
3186                                f2_5,
3187                                self.channels,
3188                                window,
3189                            );
3190                        }
3191                    }
3192                    if fi == 0 {
3193                        self.first_frame_redundancy = redundancy;
3194                    }
3195                    self.prev_redundancy = redundancy && !celt_to_silk;
3196                    prev_mode_frame = Some(OpusMode::SilkOnly);
3197                    self.last_range = rc.rng ^ redundant_rng;
3198                    self.rc_scratch = rc.buf; // reuse the payload buffer next frame
3199                }
3200                self.prev_mode = Some(OpusMode::SilkOnly);
3201                Ok(frame_size)
3202            }
3203
3204            OpusMode::CeltOnly => {
3205                let celt_end_band = Self::celt_end_band_from_toc(toc);
3206                // libopus opus_decoder.c:515 — discard CELT state on a mode change
3207                // unless the previous frame's SILK->CELT redundant frame already
3208                // primed it.
3209                if let Some(pm) = self.prev_mode {
3210                    if pm != OpusMode::CeltOnly && !self.prev_redundancy {
3211                        self.celt_dec.reset();
3212                    }
3213                }
3214                self.prev_redundancy = false;
3215                // Mono packet in a stereo stream => C=1, CC=2 (continuous state).
3216                self.celt_dec.set_stream_channels(packet_channels);
3217
3218                for (fi, payload) in frame_payloads.iter().enumerate() {
3219                    let mut rc =
3220                        RangeCoder::new_decoder_in(std::mem::take(&mut self.rc_scratch), payload);
3221                    let total_bits = (payload.len() * 8) as i32;
3222                    let needed = sub_frame_size * self.channels;
3223                    let out_start = fi * needed;
3224                    let out_end = (out_start + needed).min(output.len());
3225
3226                    if output.len() < out_end {
3227                        return Err(Error::BufferTooSmall("Output buffer too small"));
3228                    }
3229
3230                    if self.channels == 1 {
3231                        self.celt_dec.decode_from_range_coder_with_band_range(
3232                            &mut rc,
3233                            total_bits,
3234                            sub_frame_size,
3235                            &mut output[out_start..out_end],
3236                            0,
3237                            celt_end_band,
3238                        );
3239                        for sample in &mut output[out_start..out_end] {
3240                            *sample = sample.clamp(-1.0, 1.0);
3241                        }
3242                    } else {
3243                        self.celt_dec.decode_from_range_coder_with_band_range(
3244                            &mut rc,
3245                            total_bits,
3246                            sub_frame_size,
3247                            &mut self.w_celt_planar[..needed],
3248                            0,
3249                            celt_end_band,
3250                        );
3251                        for i in 0..sub_frame_size {
3252                            for ch in 0..self.channels {
3253                                let idx = out_start + i * self.channels + ch;
3254                                output[idx] =
3255                                    self.w_celt_planar[ch * sub_frame_size + i].clamp(-1.0, 1.0);
3256                            }
3257                        }
3258                    }
3259                    self.last_range = rc.rng;
3260                    self.rc_scratch = rc.buf; // reuse the payload buffer next frame
3261                }
3262                self.prev_mode = Some(OpusMode::CeltOnly);
3263                Ok(frame_size)
3264            }
3265
3266            OpusMode::Hybrid => {
3267                let internal_sample_rate = 16000;
3268                let internal_frame_size =
3269                    (frame_duration_ms * internal_sample_rate / 1000) as usize;
3270                let celt_end_band = Self::celt_end_band_from_toc(toc);
3271
3272                // Initialised at EQUAL rates too: libopus always runs
3273                // silk_resampler, whose Copy mode delays SILK by
3274                // delay_matrix_dec[in][out] (8k:4, 12k:9, 16k:12 samples) so it
3275                // stays aligned with CELT. Bypassing it at 8/12/16 kHz output
3276                // shifted every SILK sample against libopus's decoder.
3277                if internal_sample_rate != self.prev_internal_rate {
3278                    self.silk_resampler
3279                        .init(internal_sample_rate, self.sampling_rate);
3280                    self.silk_resampler_r
3281                        .init(internal_sample_rate, self.sampling_rate);
3282                    self.prev_internal_rate = internal_sample_rate;
3283                }
3284
3285                // Same SILK stereo/channel handling as the SilkOnly arm: true L/R
3286                // low band via MS->LR for stereo packets; per-packet internal
3287                // channel switch with side-channel/stereo-state resets.
3288                let silk_lr = self.channels == 2 && packet_channels == 2;
3289                self.silk_dec.produce_lr = silk_lr;
3290                let prev_internal_ch = self.silk_dec.n_channels_internal;
3291                if packet_channels as i32 > prev_internal_ch {
3292                    silk::init_decoder::silk_init_decoder(&mut self.silk_dec.channel_state[1]);
3293                }
3294                if self.channels == 2 && packet_channels == 2 && prev_internal_ch == 1 {
3295                    self.silk_dec.s_stereo_pred_prev_q13 = [0; 2];
3296                    self.silk_dec.s_stereo_side = [0; 2];
3297                    self.silk_resampler_r = self.silk_resampler.clone();
3298                }
3299                self.silk_dec.n_channels_internal = packet_channels as i32;
3300
3301                for (fi, payload) in frame_payloads.iter().enumerate() {
3302                    let mut rc =
3303                        RangeCoder::new_decoder_in(std::mem::take(&mut self.rc_scratch), payload);
3304                    let pcm_silk_i16_len = internal_frame_size * self.channels;
3305                    if pcm_silk_i16_len + 2 > self.w_pcm_i16.len() {
3306                        return Err(Error::InvalidPacket("opus: SILK frame size exceeds buffer"));
3307                    }
3308
3309                    // Prepend the previous frame's last two samples (sMid) and
3310                    // decode at offset 2, matching libopus's samplesOut1_tmp[n][2]
3311                    // layout — the resampler is fed from offset 1 (the 1-sample
3312                    // delay line), keeping the SILK low band aligned with the CELT
3313                    // high band exactly as in the reference.
3314                    let s_mid = self.silk_s_mid;
3315                    let ret = {
3316                        let (silk_dec, pcm_i16) = (&mut self.silk_dec, &mut self.w_pcm_i16);
3317                        pcm_i16[0] = s_mid[0];
3318                        pcm_i16[1] = s_mid[1];
3319                        silk_dec.decode(
3320                            &mut rc,
3321                            &mut pcm_i16[2..pcm_silk_i16_len + 2],
3322                            silk::decode_frame::FLAG_DECODE_NORMAL,
3323                            true,
3324                            frame_duration_ms,
3325                            internal_sample_rate,
3326                        )
3327                    };
3328
3329                    if ret < 0 {
3330                        return Err(Error::Internal("SILK decoding failed"));
3331                    }
3332
3333                    let silk_out_len = sub_frame_size * self.channels;
3334                    self.w_silk_out[..silk_out_len].fill(0.0);
3335                    if ret > 0 {
3336                        let decoded_samples = ret as usize;
3337                        if decoded_samples >= 2 {
3338                            self.silk_s_mid[0] = self.w_pcm_i16[decoded_samples];
3339                            self.silk_s_mid[1] = self.w_pcm_i16[decoded_samples + 1];
3340                        }
3341                        let ratio = self.sampling_rate as f64 / internal_sample_rate as f64;
3342                        let out_len =
3343                            ((decoded_samples as f64 * ratio) as usize).min(sub_frame_size);
3344                        debug_assert!(out_len <= self.w_pcm_resampled.len());
3345                        if silk_lr {
3346                            // Stereo low band: L/R from dec_api (already in the
3347                            // 1-sample-delay layout), each through its own resampler.
3348                            self.silk_resampler.process(
3349                                &mut self.w_pcm_resampled[..out_len],
3350                                &self.silk_dec.l_out[..decoded_samples],
3351                                decoded_samples as i32,
3352                            );
3353                            for i in 0..out_len {
3354                                self.w_silk_out[i * 2] = self.w_pcm_resampled[i] as f32 / 32768.0;
3355                            }
3356                            self.silk_resampler_r.process(
3357                                &mut self.w_pcm_resampled[..out_len],
3358                                &self.silk_dec.r_out[..decoded_samples],
3359                                decoded_samples as i32,
3360                            );
3361                            for i in 0..out_len {
3362                                self.w_silk_out[i * 2 + 1] =
3363                                    self.w_pcm_resampled[i] as f32 / 32768.0;
3364                            }
3365                        } else {
3366                            self.silk_resampler.process(
3367                                &mut self.w_pcm_resampled[..out_len],
3368                                &self.w_pcm_i16[1..1 + decoded_samples],
3369                                decoded_samples as i32,
3370                            );
3371                            for i in 0..out_len {
3372                                let v = self.w_pcm_resampled[i] as f32 / 32768.0;
3373                                for ch in 0..self.channels {
3374                                    self.w_silk_out[i * self.channels + ch] = v;
3375                                }
3376                            }
3377                            // Mono packet, stereo output: keep the right-channel
3378                            // resampler continuous (libopus dec_API.c:351-355).
3379                            if self.channels == 2 {
3380                                self.silk_resampler_r.process(
3381                                    &mut self.w_pcm_resampled[..out_len],
3382                                    &self.w_pcm_i16[1..1 + decoded_samples],
3383                                    decoded_samples as i32,
3384                                );
3385                                for i in 0..out_len {
3386                                    self.w_silk_out[i * 2 + 1] =
3387                                        self.w_pcm_resampled[i] as f32 / 32768.0;
3388                                }
3389                            }
3390                        }
3391                    }
3392
3393                    // --- Opus redundancy layer, hybrid form (opus_decoder.c) ---
3394                    // redundancy = bit(12); if set: celt_to_silk = bit(1),
3395                    // redundancy_bytes = uint(256)+2 taken from the END of the
3396                    // packet — the MAIN CELT layer still decodes, but with the
3397                    // range coder's storage shrunk by those bytes (this changes
3398                    // its raw-bit region and tell budget).
3399                    let plen = payload.len();
3400                    let mut redundancy = false;
3401                    let mut celt_to_silk = false;
3402                    let mut red_bytes = 0usize;
3403                    let mut effective_len = plen;
3404                    if rc.tell() + 37 <= (plen as i32) * 8 {
3405                        redundancy = rc.decode_bit_logp(12);
3406                        if redundancy {
3407                            celt_to_silk = rc.decode_bit_logp(1);
3408                            red_bytes = rc.dec_uint(256) as usize + 2;
3409                            if red_bytes <= effective_len {
3410                                effective_len -= red_bytes;
3411                            } else {
3412                                red_bytes = 0;
3413                                redundancy = false;
3414                            }
3415                            if redundancy && (effective_len as i32) * 8 < rc.tell() {
3416                                effective_len = plen;
3417                                red_bytes = 0;
3418                                redundancy = false;
3419                            }
3420                            if redundancy {
3421                                rc.storage -= red_bytes as u32;
3422                            }
3423                        }
3424                    }
3425                    self.run_transition_plc(fi, redundancy);
3426                    let f5 = (self.sampling_rate / 200) as usize;
3427                    let f2_5 = f5 / 2;
3428                    let red_end_band = celt_endband_for_bandwidth(bandwidth);
3429                    let mut red_buf = [0.0f32; 480];
3430                    let mut redundant_rng = 0u32;
3431                    let do_red = redundancy;
3432                    // CELT->SILK: redundant frame decodes BEFORE the main CELT,
3433                    // continuing the prior CELT state (fade-out of previous CELT).
3434                    if do_red && celt_to_silk {
3435                        redundant_rng = self.decode_redundant_celt(
3436                            &payload[plen - red_bytes..],
3437                            false,
3438                            packet_channels,
3439                            red_end_band,
3440                            &mut red_buf[..f5 * self.channels],
3441                        );
3442                    }
3443
3444                    // Main CELT high band. libopus opus_decoder.c:515 — reset CELT
3445                    // on a mode change unless primed by prior SILK->CELT redundancy.
3446                    if fi == 0 {
3447                        if let Some(pm) = self.prev_mode {
3448                            if pm != OpusMode::Hybrid && !self.prev_redundancy {
3449                                self.celt_dec.reset();
3450                            }
3451                        }
3452                    }
3453                    self.celt_dec.set_stream_channels(packet_channels);
3454                    let total_bits = (effective_len * 8) as i32;
3455                    {
3456                        let (celt_dec, celt_planar) = (&mut self.celt_dec, &mut self.w_celt_planar);
3457                        celt_dec.decode_from_range_coder_with_band_range(
3458                            &mut rc,
3459                            total_bits,
3460                            sub_frame_size,
3461                            &mut celt_planar[..silk_out_len],
3462                            17,
3463                            celt_end_band,
3464                        );
3465
3466                        if self.channels == 1 {
3467                            self.w_celt_out[..silk_out_len]
3468                                .copy_from_slice(&self.w_celt_planar[..silk_out_len]);
3469                        } else {
3470                            for i in 0..sub_frame_size {
3471                                for ch in 0..self.channels {
3472                                    self.w_celt_out[i * self.channels + ch] =
3473                                        self.w_celt_planar[ch * sub_frame_size + i];
3474                                }
3475                            }
3476                        }
3477                    }
3478
3479                    let out_start = fi * silk_out_len;
3480                    let total = silk_out_len.min(output.len() - out_start);
3481                    for j in 0..total {
3482                        output[out_start + j] =
3483                            (self.w_silk_out[j] + self.w_celt_out[j]).clamp(-1.0, 1.0);
3484                    }
3485
3486                    // SILK->CELT: reset + decode the redundant frame AFTER the main
3487                    // decode; it primes the CELT state for the upcoming CELT mode.
3488                    if do_red && !celt_to_silk {
3489                        redundant_rng = self.decode_redundant_celt(
3490                            &payload[plen - red_bytes..],
3491                            true,
3492                            packet_channels,
3493                            red_end_band,
3494                            &mut red_buf[..f5 * self.channels],
3495                        );
3496                    }
3497                    if do_red {
3498                        let window = modes::default_mode().window;
3499                        let region = &mut output[out_start..out_start + silk_out_len];
3500                        if celt_to_silk {
3501                            redundancy_fade_start(
3502                                region,
3503                                &red_buf,
3504                                f5,
3505                                f2_5,
3506                                self.channels,
3507                                window,
3508                            );
3509                        } else {
3510                            redundancy_fade_end(
3511                                region,
3512                                sub_frame_size,
3513                                &red_buf,
3514                                f5,
3515                                f2_5,
3516                                self.channels,
3517                                window,
3518                            );
3519                        }
3520                    }
3521                    if fi == 0 {
3522                        self.first_frame_redundancy = redundancy;
3523                    }
3524                    self.prev_redundancy = redundancy && !celt_to_silk;
3525                    self.last_range = rc.rng ^ redundant_rng;
3526                    self.rc_scratch = rc.buf; // reuse the payload buffer next frame
3527                }
3528                self.prev_mode = Some(OpusMode::Hybrid);
3529                Ok(frame_size)
3530            }
3531        };
3532
3533        // A CELT->SILK/hybrid transition is cancelled when the new frame
3534        // carries redundancy (its redundant CELT frame does the fade).
3535        self.transition_pending = 0;
3536        if let Some(t) = self.transition_pcm.take() {
3537            pcm_transition = Some(t);
3538        }
3539        if let (Some(t), Ok(_)) = (pcm_transition.as_ref(), &result) {
3540            if !(mode != OpusMode::CeltOnly && self.first_frame_redundancy) {
3541                let ch = self.channels;
3542                let f2_5 = f5 / 2;
3543                let window = modes::default_mode().window;
3544                let inc = (48000 / self.sampling_rate) as usize;
3545                // smooth_fade(in1, in2, out): out = w*in2 + (1-w)*in1, w = win^2.
3546                if audiosize >= f5 {
3547                    output[..ch * f2_5].copy_from_slice(&t[..ch * f2_5]);
3548                    for c in 0..ch {
3549                        for i in 0..f2_5 {
3550                            let w = window[i * inc] * window[i * inc];
3551                            let idx = (f2_5 + i) * ch + c;
3552                            output[idx] = w * output[idx] + (1.0 - w) * t[idx];
3553                        }
3554                    }
3555                } else {
3556                    // Shorter than 5 ms: fade over the first 2.5 ms anyway.
3557                    for c in 0..ch {
3558                        for i in 0..f2_5 {
3559                            let w = window[i * inc] * window[i * inc];
3560                            let idx = i * ch + c;
3561                            output[idx] = w * output[idx] + (1.0 - w) * t[idx];
3562                        }
3563                    }
3564                }
3565            }
3566        }
3567        result
3568    }
3569}
3570
3571impl OpusDecoder {
3572    #[inline(always)]
3573    fn celt_end_band_from_toc(toc: u8) -> usize {
3574        let mode = modes::default_mode();
3575        let top = mode.eff_ebands;
3576        if mode_from_toc(toc) == OpusMode::CeltOnly && toc >= 0x80 {
3577            const FROM_OPUS_TABLE: [u8; 16] = [
3578                0x80, 0x88, 0x90, 0x98, 0x40, 0x48, 0x50, 0x58, 0x20, 0x28, 0x30, 0x38, 0x00, 0x08,
3579                0x10, 0x18,
3580            ];
3581            let idx = ((toc >> 3) - 16) as usize;
3582            let data0 = FROM_OPUS_TABLE[idx] | (toc & 0x7);
3583            let trim = (data0 >> 5) as usize;
3584            return top.saturating_sub(2 * trim).max(1);
3585        }
3586        // Hybrid: libopus maps the packet bandwidth to a CELT end band
3587        // (opus_decoder.c: SWB -> 19, FB -> 21). Decoding SWB hybrid with 21
3588        // reads two bands the encoder never coded -> range desync every packet.
3589        if mode_from_toc(toc) == OpusMode::Hybrid
3590            && bandwidth_from_toc(toc) == Bandwidth::Superwideband
3591        {
3592            return 19.min(top);
3593        }
3594        top
3595    }
3596
3597    /// Decode a redundant CELT frame (opus_decoder.c "5 ms redundant frame"):
3598    /// start band 0, end band from the packet bandwidth, 5 ms, its own range
3599    /// decoder. Returns the redundant final range; PLANAR output in `buf`
3600    /// (F5 samples per state channel). Only valid at 48 kHz output.
3601    /// The deferred CELT->SILK/hybrid transition concealment (opus_decode_frame:
3602    /// after the redundancy decision, before any CELT decode of the frame).
3603    fn run_transition_plc(&mut self, fi: usize, redundancy: bool) {
3604        let n = std::mem::take(&mut self.transition_pending);
3605        if fi == 0 && n > 0 && !redundancy {
3606            let mut buf = vec![0.0f32; n * self.channels];
3607            self.celt_dec.conceal_lost(n, &mut buf);
3608            self.transition_pcm = Some(buf);
3609        }
3610    }
3611
3612    fn decode_redundant_celt(
3613        &mut self,
3614        red: &[u8],
3615        reset_first: bool,
3616        packet_channels: usize,
3617        end_band: usize,
3618        buf: &mut [f32],
3619    ) -> u32 {
3620        if reset_first {
3621            self.celt_dec.reset();
3622        }
3623        self.celt_dec.set_stream_channels(packet_channels);
3624        let f5 = (self.sampling_rate / 200) as usize;
3625        let mut rrc = RangeCoder::new_decoder_in(std::mem::take(&mut self.red_rc_scratch), red);
3626        let total_bits = (red.len() * 8) as i32;
3627        self.celt_dec
3628            .decode_from_range_coder_with_band_range(&mut rrc, total_bits, f5, buf, 0, end_band);
3629        let rng = rrc.rng;
3630        self.red_rc_scratch = rrc.buf;
3631        rng
3632    }
3633}
3634
3635/// libopus opus_decoder.c bandwidth -> CELT end band for the packet.
3636fn celt_endband_for_bandwidth(bw: Bandwidth) -> usize {
3637    match bw {
3638        Bandwidth::Narrowband => 13,
3639        Bandwidth::Mediumband | Bandwidth::Wideband => 17,
3640        Bandwidth::Superwideband => 19,
3641        _ => 21,
3642    }
3643}
3644
3645/// smooth_fade cross-fades (w = window[i*inc]^2, inc = 48000/Fs) applied to the
3646/// interleaved output region of one frame. `red` is PLANAR (F5 per channel).
3647/// celt_to_silk: redundant frame occupies the START of the frame — first 2.5 ms
3648/// copied verbatim, next 2.5 ms fades redundant -> main.
3649///
3650/// Indexing invariant: `out.len() >= f5 * channels` (writes reach sample
3651/// f5-1 = 2*f2_5-1). A malformed multi-frame packet used to violate this (a
3652/// hostile frame count made the per-frame region tinier than F5, fuzzer-found
3653/// OOB panics here); decode() now rejects such packets up front exactly as C
3654/// libopus does (opus_decode_native's count*packet_frame_size > frame_size ->
3655/// OPUS_BUFFER_TOO_SMALL, and the 120 ms cap of opus_packet_parse_impl), so a
3656/// redundant frame always has >= 10 ms of frame to fade into, as in C.
3657fn redundancy_fade_start(
3658    out: &mut [f32],
3659    red: &[f32],
3660    f5: usize,
3661    f2_5: usize,
3662    channels: usize,
3663    window: &[f32],
3664) {
3665    // smooth_fade steps the 48 kHz window by inc = 48000/Fs (F2.5 = 120 at 48k).
3666    let inc = 120 / f2_5;
3667    for i in 0..f2_5 {
3668        for c in 0..channels {
3669            out[i * channels + c] = red[c * f5 + i];
3670        }
3671    }
3672    for i in 0..f2_5 {
3673        let w = window[i * inc] * window[i * inc];
3674        for c in 0..channels {
3675            let idx = (f2_5 + i) * channels + c;
3676            out[idx] = (1.0 - w) * red[c * f5 + f2_5 + i] + w * out[idx];
3677        }
3678    }
3679}
3680
3681/// SILK->CELT: redundant frame occupies the END of the frame — the last 2.5 ms
3682/// fades main -> redundant (second half of the redundant frame).
3683///
3684/// Indexing invariant: `frame_samples >= f2_5` and `out.len() >=
3685/// frame_samples * channels` (the index `frame_samples - f2_5 + i` would
3686/// otherwise underflow). A malformed multi-frame packet used to violate this
3687/// (fuzzer-found subtract-with-overflow panic here); decode() now rejects such
3688/// packets up front exactly as C libopus does (opus_decode_native's
3689/// count*packet_frame_size > frame_size -> OPUS_BUFFER_TOO_SMALL, plus the
3690/// 120 ms cap of opus_packet_parse_impl), so redundancy only ever runs on
3691/// frames of >= 10 ms, as in C.
3692fn redundancy_fade_end(
3693    out: &mut [f32],
3694    frame_samples: usize,
3695    red: &[f32],
3696    f5: usize,
3697    f2_5: usize,
3698    channels: usize,
3699    window: &[f32],
3700) {
3701    // smooth_fade steps the 48 kHz window by inc = 48000/Fs (F2.5 = 120 at 48k).
3702    let inc = 120 / f2_5;
3703    for i in 0..f2_5 {
3704        let w = window[i * inc] * window[i * inc];
3705        for c in 0..channels {
3706            let idx = (frame_samples - f2_5 + i) * channels + c;
3707            out[idx] = (1.0 - w) * out[idx] + w * red[c * f5 + f2_5 + i];
3708        }
3709    }
3710}
3711
3712// Test helper only: encode() validates against the full frame_size_select list
3713// (this `Fs % frame_size` form rejects 60/100/120 ms at 48 kHz).
3714#[cfg(test)]
3715fn frame_rate_from_params(sampling_rate: i32, frame_size: usize) -> Option<i32> {
3716    let frame_size = frame_size as i32;
3717    if frame_size == 0 || sampling_rate % frame_size != 0 {
3718        return None;
3719    }
3720    Some(sampling_rate / frame_size)
3721}
3722
3723fn gen_toc(mode: OpusMode, frame_rate: i32, bandwidth: Bandwidth, channels: usize) -> u8 {
3724    let mut rate = frame_rate;
3725    let mut period = 0;
3726    while rate < 400 {
3727        rate <<= 1;
3728        period += 1;
3729    }
3730
3731    let mut toc = match mode {
3732        OpusMode::SilkOnly => {
3733            let bw = (bandwidth as i32 - Bandwidth::Narrowband as i32) << 5;
3734            let per = (period - 2) << 3;
3735            (bw | per) as u8
3736        }
3737        OpusMode::CeltOnly => {
3738            let mut tmp = bandwidth as i32 - Bandwidth::Mediumband as i32;
3739            if tmp < 0 {
3740                tmp = 0;
3741            }
3742            let per = period << 3;
3743            (0x80 | (tmp << 5) | per) as u8
3744        }
3745        OpusMode::Hybrid => {
3746            let base_config = if bandwidth == Bandwidth::Superwideband {
3747                12
3748            } else {
3749                14
3750            };
3751            let period_offset = i32::from(frame_rate < 100);
3752            ((base_config + period_offset) << 3) as u8
3753        }
3754    };
3755
3756    if channels == 2 {
3757        toc |= 0x04;
3758    }
3759    toc
3760}
3761
3762fn mode_from_toc(toc: u8) -> OpusMode {
3763    if toc & 0x80 != 0 {
3764        OpusMode::CeltOnly
3765    } else if toc & 0x60 == 0x60 {
3766        OpusMode::Hybrid
3767    } else {
3768        OpusMode::SilkOnly
3769    }
3770}
3771
3772fn bandwidth_from_toc(toc: u8) -> Bandwidth {
3773    let mode = mode_from_toc(toc);
3774    match mode {
3775        OpusMode::SilkOnly => {
3776            let bw_bits = (toc >> 5) & 0x03;
3777            match bw_bits {
3778                0 => Bandwidth::Narrowband,
3779                1 => Bandwidth::Mediumband,
3780                2 => Bandwidth::Wideband,
3781                _ => Bandwidth::Wideband,
3782            }
3783        }
3784        OpusMode::Hybrid => {
3785            let bw_bit = (toc >> 4) & 0x01;
3786            if bw_bit == 0 {
3787                Bandwidth::Superwideband
3788            } else {
3789                Bandwidth::Fullband
3790            }
3791        }
3792        OpusMode::CeltOnly => {
3793            let bw_bits = (toc >> 5) & 0x03;
3794            match bw_bits {
3795                0 => Bandwidth::Mediumband,
3796                1 => Bandwidth::Wideband,
3797                2 => Bandwidth::Superwideband,
3798                3 => Bandwidth::Fullband,
3799                _ => Bandwidth::Fullband,
3800            }
3801        }
3802    }
3803}
3804
3805fn frame_duration_ms_from_toc(toc: u8) -> i32 {
3806    let mode = mode_from_toc(toc);
3807    match mode {
3808        OpusMode::SilkOnly => {
3809            let config = (toc >> 3) & 0x03;
3810            match config {
3811                0 => 10,
3812                1 => 20,
3813                2 => 40,
3814                3 => 60,
3815                _ => 20,
3816            }
3817        }
3818        OpusMode::Hybrid => {
3819            let config = (toc >> 3) & 0x01;
3820            if config == 0 { 10 } else { 20 }
3821        }
3822        OpusMode::CeltOnly => {
3823            let config = (toc >> 3) & 0x03;
3824            match config {
3825                0 => 2,
3826                1 => 5,
3827                2 => 10,
3828                3 => 20,
3829                _ => 20,
3830            }
3831        }
3832    }
3833}
3834
3835fn channels_from_toc(toc: u8) -> usize {
3836    if toc & 0x04 != 0 { 2 } else { 1 }
3837}
3838
3839/// RFC 6716 §3.1 frame-length coding (used by code 2 and VBR code 3): a length
3840/// of 0..=251 is one byte with that value; 252..=1275 is two bytes `b0` (252..255)
3841/// then `b1`, giving `b1*4 + b0`. Returns `(length, bytes_consumed)`.
3842fn read_opus_frame_len(data: &[u8], ptr: usize) -> Result<(usize, usize), Error> {
3843    let b0 = *data
3844        .get(ptr)
3845        .ok_or(Error::InvalidPacket("Opus frame length: truncated"))? as usize;
3846    if b0 < 252 {
3847        Ok((b0, 1))
3848    } else {
3849        let b1 = *data
3850            .get(ptr + 1)
3851            .ok_or(Error::InvalidPacket("Opus frame length: truncated 2-byte"))?
3852            as usize;
3853        Ok((b1 * 4 + b0, 2))
3854    }
3855}
3856
3857#[cfg(test)]
3858mod tests {
3859    use super::*;
3860
3861    fn frame_size_from_toc(toc: u8, sampling_rate: i32) -> Option<usize> {
3862        let mode = mode_from_toc(toc);
3863        match mode {
3864            OpusMode::CeltOnly => {
3865                let period = ((toc >> 3) & 0x03) as i32;
3866                let frame_rate = 400 >> period;
3867                if frame_rate == 0 || sampling_rate % frame_rate != 0 {
3868                    return None;
3869                }
3870                Some((sampling_rate / frame_rate) as usize)
3871            }
3872            OpusMode::SilkOnly => {
3873                let duration_ms = frame_duration_ms_from_toc(toc);
3874                Some((sampling_rate as i64 * duration_ms as i64 / 1000) as usize)
3875            }
3876            OpusMode::Hybrid => {
3877                let duration_ms = frame_duration_ms_from_toc(toc);
3878                Some((sampling_rate as i64 * duration_ms as i64 / 1000) as usize)
3879            }
3880        }
3881    }
3882
3883    #[test]
3884    fn gen_toc_matches_celt_reference_values() {
3885        let sampling_rate = 48_000;
3886        let cases = [
3887            (120usize, 0xE0u8),
3888            (240usize, 0xE8u8),
3889            (480usize, 0xF0u8),
3890            (960usize, 0xF8u8),
3891        ];
3892
3893        for (frame_size, expected_toc) in cases {
3894            let frame_rate = frame_rate_from_params(sampling_rate, frame_size).unwrap();
3895            let toc = gen_toc(OpusMode::CeltOnly, frame_rate, Bandwidth::Fullband, 1);
3896            assert_eq!(
3897                toc, expected_toc,
3898                "frame_size {frame_size} expected TOC {expected_toc:02X} got {toc:02X}"
3899            );
3900            let decoded_size = frame_size_from_toc(toc, sampling_rate).unwrap();
3901            assert_eq!(decoded_size, frame_size);
3902        }
3903
3904        let stereo_toc = gen_toc(
3905            OpusMode::CeltOnly,
3906            frame_rate_from_params(sampling_rate, 960).unwrap(),
3907            Bandwidth::Fullband,
3908            2,
3909        );
3910        assert_eq!(channels_from_toc(stereo_toc), 2);
3911    }
3912
3913    #[test]
3914    fn test_celt_decoder_large_frame_sizes() {
3915        let sampling_rate = 48000;
3916        let channels = 1;
3917
3918        let mut decoder = OpusDecoder::new(sampling_rate, channels).unwrap();
3919
3920        let frame_sizes = [120, 240, 480, 960];
3921
3922        for frame_size in frame_sizes {
3923            let toc = gen_toc(
3924                OpusMode::CeltOnly,
3925                frame_rate_from_params(sampling_rate, frame_size).unwrap(),
3926                Bandwidth::Fullband,
3927                channels,
3928            );
3929            let packet = [toc, 0, 0, 0, 0];
3930
3931            let mut output = vec![0.0f32; frame_size * channels];
3932
3933            let _ = decoder.decode(&packet, frame_size, &mut output);
3934        }
3935
3936        let channels = 2;
3937        let mut decoder = OpusDecoder::new(sampling_rate, channels).unwrap();
3938
3939        for frame_size in frame_sizes {
3940            let toc = gen_toc(
3941                OpusMode::CeltOnly,
3942                frame_rate_from_params(sampling_rate, frame_size).unwrap(),
3943                Bandwidth::Fullband,
3944                channels,
3945            );
3946            let packet = [toc, 0, 0, 0, 0];
3947
3948            let mut output = vec![0.0f32; frame_size * channels];
3949            let _ = decoder.decode(&packet, frame_size, &mut output);
3950        }
3951    }
3952
3953    #[test]
3954    fn test_celt_decoder_edge_case_frame_sizes() {
3955        let sampling_rate = 48000;
3956        let channels = 1;
3957        let mut decoder = OpusDecoder::new(sampling_rate, channels).unwrap();
3958
3959        let edge_sizes = [2048, 2167, 2168, 2169, 2880, 3072];
3960
3961        for frame_size in edge_sizes {
3962            let mut output = vec![0.0f32; frame_size * channels];
3963
3964            let _ = decoder.decode(&[0x80, 0, 0, 0], frame_size, &mut output);
3965        }
3966    }
3967
3968    // Regression test for: "index out of bounds: the len is 48 but the index is 119"
3969    // Root cause: frame_size=48 at 48kHz gives frame_rate=1000, which is not a valid
3970    // Hybrid-mode frame rate but was not validated.  CELT's lm-search then silently
3971    // fell back to lm=0, computed n2=120, and wrote output[119] into a 48-element
3972    // slice.  Triggered via G.729-decoded PCM (8kHz) passed to a 48kHz Opus encoder
3973    // without proper resampling, so the encoder received 48 samples instead of 480.
3974    #[test]
3975    fn test_invalid_small_frame_size_returns_error_not_panic() {
3976        let mut enc = OpusEncoder::new(48000, 2, Application::Voip).unwrap();
3977        enc.bitrate_bps = 64000;
3978        enc.complexity = 5;
3979        enc.use_cbr = true;
3980
3981        // 48 samples at 48kHz = 1ms → frame_rate=1000, invalid for Hybrid mode.
3982        let input = vec![0.0f32; 48 * 2]; // stereo interleaved
3983        let mut output = vec![0u8; 256];
3984
3985        let result = enc.encode(&input, 48, &mut output);
3986        assert!(
3987            result.is_err(),
3988            "encode with invalid frame_size=48 should return Err, not panic"
3989        );
3990    }
3991
3992    // Also verify that the Audio application path (always Hybrid at 48 kHz) rejects
3993    // the same bad frame size.
3994    #[test]
3995    fn test_invalid_small_frame_size_audio_application_returns_error() {
3996        let mut enc = OpusEncoder::new(48000, 1, Application::Audio).unwrap();
3997        let input = vec![0.0f32; 48];
3998        let mut output = vec![0u8; 256];
3999
4000        let result = enc.encode(&input, 48, &mut output);
4001        assert!(
4002            result.is_err(),
4003            "Audio/48kHz encoder with frame_size=48 should return Err"
4004        );
4005    }
4006}