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] = ®ion[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] = ®ion[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 = ®ion[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}