Skip to main content

opus_rs/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2#![allow(unsafe_op_in_unsafe_fn)]
3#![allow(clippy::too_many_arguments)]
4#![allow(clippy::needless_range_loop)]
5
6mod compat;
7mod fixedvec;
8
9pub mod bands;
10pub mod celt;
11pub mod celt_lpc;
12pub mod hp_cutoff;
13pub mod kiss_fft;
14pub mod mdct;
15pub mod modes;
16pub mod pitch;
17pub mod pvq;
18pub mod quant_bands;
19pub mod range_coder;
20pub mod rate;
21pub mod silk;
22
23pub use silk::{SilkResampler, SilkResamplerDown1_3, SilkResamplerDown1_6};
24
25pub use celt::{CeltDecoder, CeltEncoder};
26use hp_cutoff::hp_cutoff;
27use range_coder::RangeCoder;
28use silk::control_codec::silk_control_encoder;
29use silk::enc_api::silk_encode;
30use silk::init_encoder::silk_init_encoder;
31use silk::lin2log::silk_lin2log;
32use silk::log2lin::silk_log2lin;
33use silk::macros::*;
34use silk::resampler::{silk_resampler_down2, silk_resampler_down2_3};
35use silk::structs::SilkEncoderState;
36use crate::fixedvec::FixedVec;
37
38// --- Heap-free buffer capacity constants (worst case: 2 channels). ---
39const OPUS_MAX_CHANNELS: usize = 2;
40/// Largest API frame in samples/channel (120 ms @ 48 kHz = 5760). Used by the
41/// encoder's per-frame input buffers (sized `frame_size * channels`).
42const OPUS_MAX_FRAME: usize = 5760;
43/// Largest *single-frame* samples/channel (60 ms @ 48 kHz = 2880). The decoder's
44/// staging buffers hold one sub-frame at a time, so they're sized to this — not
45/// the full packet. (Halves the decoder footprint vs. a naive 5760/channel.)
46const OPUS_MAX_SUBFRAME: usize = 2880;
47/// Decoder per-sub-frame staging cap: `OPUS_MAX_SUBFRAME * max_channels`.
48const OPUS_SUBFRAME_SCRATCH: usize = OPUS_MAX_SUBFRAME * OPUS_MAX_CHANNELS;
49/// High-pass filter state memory (`channels * 2`).
50const OPUS_HP_MEM: usize = OPUS_MAX_CHANNELS * 2;
51/// Decoder `w_pcm_i16` cap (`960 * max_channels`).
52const OPUS_PCM_I16: usize = 960 * OPUS_MAX_CHANNELS;
53/// Decoder `prev_pcm_tail` cap (`240 * max_channels`).
54const OPUS_PCM_TAIL: usize = 240 * OPUS_MAX_CHANNELS;
55/// Max number of frames encoded in one Opus packet (RFC 6716 caps at 48 for
56/// 2.5 ms codes in a 120 ms packet).
57const OPUS_MAX_PACKET_FRAMES: usize = 48;
58/// RFC 6716 §3.1: a single Opus packet carries at most 1276 bytes of data.
59const OPUS_MAX_PACKET_BYTES: usize = 1276;
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum Application {
63    Voip = 2048,
64    Audio = 2049,
65    RestrictedLowDelay = 2051,
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum Bandwidth {
70    Auto = -1000,
71    Narrowband = 1101,
72    Mediumband = 1102,
73    Wideband = 1103,
74    Superwideband = 1104,
75    Fullband = 1105,
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79enum OpusMode {
80    SilkOnly,
81    Hybrid,
82    CeltOnly,
83}
84
85pub struct OpusEncoder {
86    #[cfg(not(feature = "heap"))]
87    celt_enc: CeltEncoder,
88    #[cfg(feature = "heap")]
89    celt_enc: Box<CeltEncoder>,
90    #[cfg(not(feature = "heap"))]
91    silk_enc: SilkEncoderState,
92    #[cfg(feature = "heap")]
93    silk_enc: Box<SilkEncoderState>,
94    application: Application,
95    sampling_rate: i32,
96    channels: usize,
97    bandwidth: Bandwidth,
98    pub bitrate_bps: i32,
99    pub complexity: i32,
100    pub use_cbr: bool,
101
102    pub use_inband_fec: bool,
103
104    pub packet_loss_perc: i32,
105    silk_initialized: bool,
106    mode: OpusMode,
107    prev_enc_mode: Option<OpusMode>,
108
109    variable_hp_smth2_q15: i32,
110    hp_mem: FixedVec<i32, OPUS_HP_MEM>,
111
112    #[cfg(not(feature = "heap"))]
113    buf_filtered: FixedVec<i16, OPUS_MAX_FRAME>,
114    #[cfg(feature = "heap")]
115    buf_filtered: Box<FixedVec<i16, OPUS_MAX_FRAME>>,
116    #[cfg(not(feature = "heap"))]
117    buf_silk_input: FixedVec<i16, OPUS_MAX_FRAME>,
118    #[cfg(feature = "heap")]
119    buf_silk_input: Box<FixedVec<i16, OPUS_MAX_FRAME>>,
120    #[cfg(not(feature = "heap"))]
121    buf_stereo_mid: FixedVec<i16, OPUS_MAX_FRAME>,
122    #[cfg(feature = "heap")]
123    buf_stereo_mid: Box<FixedVec<i16, OPUS_MAX_FRAME>>,
124    #[cfg(not(feature = "heap"))]
125    buf_stereo_side: FixedVec<i16, OPUS_MAX_FRAME>,
126    #[cfg(feature = "heap")]
127    buf_stereo_side: Box<FixedVec<i16, OPUS_MAX_FRAME>>,
128    #[cfg(not(feature = "heap"))]
129    buf_celt_input: FixedVec<f32, OPUS_MAX_FRAME>,
130    #[cfg(feature = "heap")]
131    buf_celt_input: Box<FixedVec<f32, OPUS_MAX_FRAME>>,
132    down2_state_first: [i32; 2],
133    down2_state_second: [i32; 2],
134    down2_3_state: [i32; 6],
135    down_1_3_state: silk::resampler::SilkResamplerDown1_3,
136
137    rc: RangeCoder,
138}
139
140fn compute_equiv_rate(
141    bitrate: i32,
142    channels: usize,
143    frame_rate: i32,
144    vbr: bool,
145    complexity: i32,
146    loss: i32,
147) -> i32 {
148    let mut equiv = bitrate;
149    if frame_rate > 50 {
150        equiv -= (40 * channels as i32 + 20) * (frame_rate - 50);
151    }
152    if !vbr {
153        equiv -= equiv / 12;
154    }
155    equiv = equiv * (90 + complexity) / 100;
156    if loss > 0 {
157        equiv -= equiv * loss / (12 * loss + 20);
158    }
159    equiv
160}
161
162fn compute_mode_threshold(
163    application: Application,
164    channels: usize,
165    prev_was_celt: bool,
166    has_prev_mode: bool,
167    voice_est: i32,
168) -> i32 {
169    let mode_voice = if channels == 1 { 64000 } else { 44000 };
170    let mode_music = 10000;
171
172    let diff = mode_voice - mode_music;
173    let offset = (voice_est * voice_est * diff) >> 14;
174    let mut threshold = mode_music + offset;
175
176    if application == Application::Voip {
177        threshold += 8000;
178    }
179
180    if has_prev_mode {
181        if prev_was_celt {
182            threshold -= 4000;
183        } else {
184            threshold += 4000;
185        }
186    }
187
188    if application == Application::RestrictedLowDelay {
189        threshold = 0;
190    }
191
192    threshold
193}
194
195fn compute_silk_rate_for_hybrid(rate_bps: i32, frame20ms: bool) -> i32 {
196    const RATE_TABLE: &[(i32, i32, i32)] = &[
197        (0, 0, 0),
198        (12000, 10000, 10000),
199        (16000, 13500, 13500),
200        (20000, 16000, 16000),
201        (24000, 18000, 18000),
202        (32000, 22000, 22000),
203        (64000, 38000, 38000),
204    ];
205    let n = RATE_TABLE.len();
206    let mut i = 1;
207    while i < n && RATE_TABLE[i].0 <= rate_bps {
208        i += 1;
209    }
210    if i == n {
211        let (x_last, r10_last, r20_last) = RATE_TABLE[n - 1];
212        let base = if frame20ms { r20_last } else { r10_last };
213        base + (rate_bps - x_last) / 2
214    } else {
215        let (x0, lo10, lo20) = RATE_TABLE[i - 1];
216        let (x1, hi10, hi20) = RATE_TABLE[i];
217        let (lo, hi) = if frame20ms {
218            (lo20, hi20)
219        } else {
220            (lo10, hi10)
221        };
222        (lo * (x1 - rate_bps) + hi * (rate_bps - x0)) / (x1 - x0)
223    }
224}
225
226#[cfg(all(test, feature = "std"))]
227mod silk_rate_tests {
228    use super::compute_silk_rate_for_hybrid;
229
230    #[test]
231    fn test_reference_table_exact_entries() {
232        assert_eq!(compute_silk_rate_for_hybrid(12000, true), 10000);
233        assert_eq!(compute_silk_rate_for_hybrid(16000, true), 13500);
234        assert_eq!(compute_silk_rate_for_hybrid(20000, true), 16000);
235        assert_eq!(compute_silk_rate_for_hybrid(24000, true), 18000);
236        assert_eq!(compute_silk_rate_for_hybrid(32000, true), 22000);
237        assert_eq!(compute_silk_rate_for_hybrid(64000, true), 38000);
238    }
239
240    #[test]
241    fn test_32kbps_gives_22kbps_silk() {
242        assert_eq!(compute_silk_rate_for_hybrid(32000, true), 22000);
243    }
244
245    #[test]
246    fn test_interpolation_between_table_entries() {
247        let r = compute_silk_rate_for_hybrid(18000, true);
248        assert_eq!(r, 14750);
249    }
250
251    #[test]
252    fn test_above_table_max_gives_half_extra() {
253        let r = compute_silk_rate_for_hybrid(72000, true);
254        assert_eq!(r, 38000 + (72000 - 64000) / 2);
255    }
256}
257
258/// Uniformly borrow a codec-state field that is a `Box<T>` under the `heap`
259/// feature and a plain `T` otherwise, so call sites don't need `#[cfg]` on
260/// every `&self.field` / `&mut self.field`.
261///
262/// Under `heap` the field is a `Box<...>`, so `T: Deref(DerefMut)` resolves to
263/// the box itself and the `Target` is the inner type; without `heap` the field
264/// is the inner type directly (`?Sized` lets `&mut [i16]`-style coercion choose
265/// the slice target).
266#[cfg(feature = "heap")]
267#[inline(always)]
268fn state_ref<T: core::ops::Deref>(b: &T) -> &T::Target {
269    b
270}
271#[cfg(not(feature = "heap"))]
272#[inline(always)]
273fn state_ref<T: ?Sized>(b: &T) -> &T {
274    b
275}
276
277#[cfg(feature = "heap")]
278#[inline(always)]
279fn state_mut<T: core::ops::DerefMut>(b: &mut T) -> &mut T::Target {
280    b
281}
282#[cfg(not(feature = "heap"))]
283#[inline(always)]
284fn state_mut<T: ?Sized>(b: &mut T) -> &mut T {
285    b
286}
287
288impl OpusEncoder {
289    pub fn new(
290        sampling_rate: i32,
291        channels: usize,
292        application: Application,
293    ) -> Result<Self, &'static str> {
294        if ![8000, 12000, 16000, 24000, 48000].contains(&sampling_rate) {
295            return Err("Invalid sampling rate");
296        }
297        if ![1, 2].contains(&channels) {
298            return Err("Invalid number of channels");
299        }
300
301        let mode = modes::default_mode();
302        #[cfg(feature = "heap")]
303        let celt_enc = Box::new(CeltEncoder::new(mode, channels));
304        #[cfg(not(feature = "heap"))]
305        let celt_enc = CeltEncoder::new(mode, channels);
306
307        #[cfg(feature = "heap")]
308        let mut silk_enc = Box::new(SilkEncoderState::default());
309        #[cfg(not(feature = "heap"))]
310        let mut silk_enc = SilkEncoderState::default();
311        if silk_init_encoder(state_mut(&mut silk_enc), 0) != 0 {
312            return Err("SILK encoder initialization failed");
313        }
314
315        let (opus_mode, bw) = match application {
316            Application::Voip => {
317                let bw = match sampling_rate {
318                    8000 => Bandwidth::Narrowband,
319                    12000 => Bandwidth::Mediumband,
320                    16000 => Bandwidth::Wideband,
321                    24000 => Bandwidth::Superwideband,
322                    48000 => Bandwidth::Fullband,
323                    _ => Bandwidth::Narrowband,
324                };
325
326                let mode = if sampling_rate > 16000 {
327                    OpusMode::Hybrid
328                } else {
329                    OpusMode::SilkOnly
330                };
331                (mode, bw)
332            }
333            Application::RestrictedLowDelay => {
334                let bw = match sampling_rate {
335                    8000 => Bandwidth::Narrowband,
336                    12000 => Bandwidth::Mediumband,
337                    16000 => Bandwidth::Wideband,
338                    24000 => Bandwidth::Superwideband,
339                    _ => Bandwidth::Fullband,
340                };
341                (OpusMode::CeltOnly, bw)
342            }
343            Application::Audio => {
344                if sampling_rate <= 16000 {
345                    let bw = match sampling_rate {
346                        8000 => Bandwidth::Narrowband,
347                        12000 => Bandwidth::Mediumband,
348                        _ => Bandwidth::Wideband,
349                    };
350                    (OpusMode::SilkOnly, bw)
351                } else {
352                    let bw = match sampling_rate {
353                        24000 => Bandwidth::Superwideband,
354                        _ => Bandwidth::Fullband,
355                    };
356                    (OpusMode::Hybrid, bw)
357                }
358            }
359        };
360
361        use silk::lin2log::silk_lin2log;
362        let variable_hp_smth2_q15 = silk_lin2log(60) << 8;
363
364        Ok(Self {
365            celt_enc,
366            silk_enc,
367            application,
368            sampling_rate,
369            channels,
370            bandwidth: bw,
371            bitrate_bps: 64000,
372            complexity: 9,
373            use_cbr: false,
374            use_inband_fec: false,
375            packet_loss_perc: 0,
376            silk_initialized: false,
377            prev_enc_mode: None,
378            mode: opus_mode,
379            variable_hp_smth2_q15,
380            hp_mem: FixedVec::from_value(0, channels * 2),
381
382            #[cfg(not(feature = "heap"))]
383            buf_filtered: FixedVec::new(),
384            #[cfg(feature = "heap")]
385            buf_filtered: Box::new(FixedVec::new()),
386            #[cfg(not(feature = "heap"))]
387            buf_silk_input: FixedVec::new(),
388            #[cfg(feature = "heap")]
389            buf_silk_input: Box::new(FixedVec::new()),
390            #[cfg(not(feature = "heap"))]
391            buf_stereo_mid: FixedVec::new(),
392            #[cfg(feature = "heap")]
393            buf_stereo_mid: Box::new(FixedVec::new()),
394            #[cfg(not(feature = "heap"))]
395            buf_stereo_side: FixedVec::new(),
396            #[cfg(feature = "heap")]
397            buf_stereo_side: Box::new(FixedVec::new()),
398            #[cfg(not(feature = "heap"))]
399            buf_celt_input: FixedVec::new(),
400            #[cfg(feature = "heap")]
401            buf_celt_input: Box::new(FixedVec::new()),
402            down2_state_first: [0; 2],
403            down2_state_second: [0; 2],
404            down2_3_state: [0; 6],
405            down_1_3_state: silk::resampler::SilkResamplerDown1_3::default(),
406            rc: RangeCoder::new_encoder(1),
407        })
408    }
409
410    pub fn enable_hybrid_mode(&mut self) -> Result<(), &'static str> {
411        if self.sampling_rate != 24000 && self.sampling_rate != 48000 {
412            return Err("Hybrid mode requires 24kHz or 48kHz sampling rate");
413        }
414        let bw = if self.sampling_rate == 48000 {
415            Bandwidth::Fullband
416        } else {
417            Bandwidth::Superwideband
418        };
419        self.mode = OpusMode::Hybrid;
420        self.bandwidth = bw;
421        self.silk_initialized = false;
422        Ok(())
423    }
424
425    pub fn encode(
426        &mut self,
427        input: &[f32],
428        frame_size: usize,
429        output: &mut [u8],
430    ) -> Result<usize, &'static str> {
431        if output.len() < 2 {
432            return Err("Output buffer too small");
433        }
434
435        let frame_rate = frame_rate_from_params(self.sampling_rate, frame_size)
436            .ok_or("Invalid frame size for sampling rate")?;
437
438        // Mode selection: match C's opus_encode_native() behavior.
439        // C reference auto-selects between SILK_ONLY and CELT_ONLY; Hybrid is
440        // produced afterwards by bandwidth overrides (SILK-only + FB/SWB → Hybrid).
441        let mut mode = if self.application == Application::RestrictedLowDelay {
442            OpusMode::CeltOnly
443        } else {
444            let equiv = compute_equiv_rate(
445                self.bitrate_bps,
446                self.channels,
447                frame_rate,
448                !self.use_cbr,
449                self.complexity,
450                self.packet_loss_perc,
451            );
452            let prev_was_celt = self.prev_enc_mode == Some(OpusMode::CeltOnly);
453            let has_prev_mode = self.prev_enc_mode.is_some();
454            let voice_est = match self.application {
455                Application::Voip => 115,
456                Application::Audio => 48,
457                Application::RestrictedLowDelay => 0,
458            };
459            let threshold = compute_mode_threshold(
460                self.application,
461                self.channels,
462                prev_was_celt,
463                has_prev_mode,
464                voice_est,
465            );
466            if equiv >= threshold && self.sampling_rate >= 24000 {
467                OpusMode::CeltOnly
468            } else {
469                OpusMode::SilkOnly
470            }
471        };
472
473        let curr_bw = self.bandwidth;
474        if mode == OpusMode::SilkOnly
475            && (curr_bw == Bandwidth::Superwideband || curr_bw == Bandwidth::Fullband)
476        {
477            mode = OpusMode::Hybrid;
478        }
479        if mode == OpusMode::Hybrid
480            && (curr_bw == Bandwidth::Narrowband
481                || curr_bw == Bandwidth::Mediumband
482                || curr_bw == Bandwidth::Wideband)
483        {
484            mode = OpusMode::SilkOnly;
485        }
486
487        if mode == OpusMode::CeltOnly {
488            match frame_rate {
489                400 | 200 | 100 | 50 => {}
490                _ => return Err("Unsupported frame size for CELT-only mode"),
491            }
492        }
493
494        if mode == OpusMode::Hybrid {
495            match frame_rate {
496                100 | 50 => {}
497                _ => return Err("Unsupported frame size for Hybrid mode"),
498            }
499        }
500
501        if mode == OpusMode::SilkOnly {
502            match frame_rate {
503                400 | 200 | 100 | 50 | 25 => {}
504                _ => return Err("Unsupported frame size for SILK-only mode"),
505            }
506        }
507
508        let toc = gen_toc(mode, frame_rate, self.bandwidth, self.channels);
509        output[0] = toc;
510
511        let target_bits =
512            (self.bitrate_bps as i64 * frame_size as i64 / self.sampling_rate as i64) as i32;
513        let cbr_bytes = ((target_bits + 4) / 8) as usize;
514        let max_data_bytes = output.len();
515
516        // Cap at the Opus per-packet maximum (RFC 6716); the range coder's buffer
517        // is heap-free and sized to this constant.
518        let mut n_bytes = cbr_bytes
519            .min(max_data_bytes)
520            .max(1)
521            .min(OPUS_MAX_PACKET_BYTES);
522        let init_rc_size = n_bytes - 1;
523        self.rc.reset_for_encode(init_rc_size as u32);
524
525        if mode == OpusMode::SilkOnly || mode == OpusMode::Hybrid {
526            let silk_fs_khz = if mode == OpusMode::Hybrid {
527                16
528            } else {
529                self.sampling_rate.min(16000) / 1000
530            };
531
532            let frame_ms = (frame_size as i32 * 1000) / self.sampling_rate;
533            if !self.silk_initialized || self.silk_enc.s_cmn.fs_khz != silk_fs_khz {
534                let silk_init_bitrate = (((n_bytes - 1) * 8) as i64 * self.sampling_rate as i64
535                    / frame_size as i64) as i32;
536                silk_control_encoder(
537                    state_mut(&mut self.silk_enc),
538                    silk_fs_khz,
539                    frame_ms,
540                    silk_init_bitrate,
541                    self.complexity,
542                );
543                self.silk_enc.s_cmn.use_cbr = if self.use_cbr { 1 } else { 0 };
544
545                self.silk_enc.s_cmn.n_channels = self.channels as i32;
546                self.silk_initialized = true;
547                self.down2_state_first = [0; 2];
548                self.down2_state_second = [0; 2];
549                self.down2_3_state = [0; 6];
550                self.down_1_3_state = silk::resampler::SilkResamplerDown1_3::default();
551            }
552
553            self.silk_enc.s_cmn.use_in_band_fec = if self.use_inband_fec { 1 } else { 0 };
554            self.silk_enc.s_cmn.packet_loss_perc = self.packet_loss_perc.clamp(0, 100);
555
556            self.silk_enc.s_cmn.lbrr_enabled = if self.use_inband_fec { 1 } else { 0 };
557
558            if self.silk_enc.s_cmn.lbrr_gain_increases == 0 {
559                self.silk_enc.s_cmn.lbrr_gain_increases = 2;
560            }
561
562            let hp_freq_smth1 = if mode == OpusMode::CeltOnly {
563                silk_lin2log(60) << 8
564            } else {
565                self.silk_enc.s_cmn.variable_hp_smth1_q15
566            };
567
568            const VARIABLE_HP_SMTH_COEF2_Q16: i32 = 984;
569            self.variable_hp_smth2_q15 = silk_smlawb(
570                self.variable_hp_smth2_q15,
571                hp_freq_smth1 - self.variable_hp_smth2_q15,
572                VARIABLE_HP_SMTH_COEF2_Q16,
573            );
574
575            let cutoff_hz = silk_log2lin(silk_rshift(self.variable_hp_smth2_q15, 8));
576
577            let required_size = frame_size * self.channels;
578            self.buf_filtered.resize(required_size, 0);
579            if self.application == Application::Voip {
580                hp_cutoff(
581                    input,
582                    cutoff_hz,
583                    state_mut(&mut self.buf_filtered),
584                    &mut self.hp_mem,
585                    frame_size,
586                    self.channels,
587                    self.sampling_rate,
588                );
589            } else {
590                for (i, &x) in input.iter().enumerate() {
591                    self.buf_filtered[i] = (x * 32768.0).clamp(-32768.0, 32767.0) as i16;
592                }
593            }
594
595            let input_i16 = state_ref(&self.buf_filtered);
596
597            let silk_input: &[i16] = if mode == OpusMode::SilkOnly && self.sampling_rate > 16000 {
598                if self.sampling_rate == 48000 {
599                    let stage1_size = frame_size / 2;
600                    let mut stage1_buf = [0i16; 480];
601                    silk_resampler_down2(
602                        &mut self.down2_state_first,
603                        &mut stage1_buf[..stage1_size],
604                        input_i16,
605                        frame_size as i32,
606                    );
607                    let silk_frame_size = stage1_size * 2 / 3;
608                    self.buf_silk_input.resize(silk_frame_size, 0);
609                    silk_resampler_down2_3(
610                        &mut self.down2_3_state,
611                        state_mut(&mut self.buf_silk_input),
612                        &stage1_buf[..stage1_size],
613                        stage1_size as i32,
614                    );
615                    state_ref(&self.buf_silk_input)
616                } else if self.sampling_rate == 24000 {
617                    let silk_frame_size = frame_size * 2 / 3;
618                    self.buf_silk_input.resize(silk_frame_size, 0);
619                    silk_resampler_down2_3(
620                        &mut self.down2_3_state,
621                        state_mut(&mut self.buf_silk_input),
622                        input_i16,
623                        frame_size as i32,
624                    );
625                    state_ref(&self.buf_silk_input)
626                } else {
627                    input_i16
628                }
629            } else if mode == OpusMode::SilkOnly && self.channels == 2 {
630                let frame_length = input_i16.len() / 2;
631                self.buf_stereo_mid.resize(frame_length, 0);
632                self.buf_stereo_side.resize(frame_length, 0);
633                for i in 0..frame_length {
634                    let l = input_i16[2 * i] as i32;
635                    let r = input_i16[2 * i + 1] as i32;
636                    self.buf_stereo_mid[i] = ((l + r) / 2) as i16;
637                    self.buf_stereo_side[i] = (l - r) as i16;
638                }
639
640                self.silk_enc.stereo.side.resize(frame_length, 0);
641                self.silk_enc
642                    .stereo
643                    .side
644                    .copy_from_slice(&self.buf_stereo_side[..frame_length]);
645                state_ref(&self.buf_stereo_mid)
646            } else if mode == OpusMode::Hybrid && self.sampling_rate > 16000 {
647                if self.sampling_rate == 48000 {
648                    let silk_frame_size = frame_size / 3;
649                    self.buf_silk_input.resize(silk_frame_size, 0);
650                    silk::resampler::silk_resampler_down_1_3(
651                        &mut self.down_1_3_state,
652                        state_mut(&mut self.buf_silk_input),
653                        input_i16,
654                    );
655                } else {
656                    let silk_frame_size = frame_size * 2 / 3;
657                    self.buf_silk_input.resize(silk_frame_size, 0);
658                    silk_resampler_down2_3(
659                        &mut self.down2_3_state,
660                        state_mut(&mut self.buf_silk_input),
661                        input_i16,
662                        frame_size as i32,
663                    );
664                }
665                state_ref(&self.buf_silk_input)
666            } else {
667                input_i16
668            };
669
670            let mut pn_bytes = 0;
671
672            let silk_rate_for_calc = if mode == OpusMode::Hybrid {
673                16000
674            } else {
675                self.sampling_rate
676            };
677            let silk_frame_len = silk_input.len();
678
679            let silk_bitrate = if mode == OpusMode::Hybrid {
680                let frame_duration_ms = frame_size as i32 * 1000 / self.sampling_rate;
681                let frame20ms = frame_duration_ms >= 20;
682                compute_silk_rate_for_hybrid(self.bitrate_bps, frame20ms)
683            } else {
684                (8i64 * (n_bytes - 1) as i64 * silk_rate_for_calc as i64 / silk_frame_len as i64)
685                    as i32
686            };
687            let silk_max_bits = if mode == OpusMode::Hybrid {
688                let total_max_bits = ((n_bytes - 1) * 8) as i32;
689                if self.use_cbr {
690                    let silk_bits = (silk_bitrate as i64 * silk_frame_len as i64
691                        / silk_rate_for_calc as i64) as i32;
692                    let other_bits = 0i32.max(total_max_bits - silk_bits);
693                    0i32.max(total_max_bits - other_bits * 3 / 4)
694                } else {
695                    let frame_duration_ms = frame_size as i32 * 1000 / self.sampling_rate;
696                    let frame20ms = frame_duration_ms >= 20;
697                    let max_bit_rate = compute_silk_rate_for_hybrid(
698                        total_max_bits * self.sampling_rate / frame_size as i32,
699                        frame20ms,
700                    );
701                    max_bit_rate * frame_size as i32 / self.sampling_rate
702                }
703            } else {
704                ((n_bytes - 1) * 8) as i32
705            };
706            let silk_use_cbr = if mode == OpusMode::Hybrid && self.use_cbr {
707                0
708            } else if self.use_cbr {
709                1
710            } else {
711                0
712            };
713            let ret = silk_encode(
714                state_mut(&mut self.silk_enc),
715                silk_input,
716                silk_input.len(),
717                &mut self.rc,
718                &mut pn_bytes,
719                silk_bitrate,
720                silk_max_bits,
721                silk_use_cbr,
722                1,
723            );
724            if ret != 0 {
725                return Err("SILK encoding failed");
726            }
727        }
728
729        if mode == OpusMode::Hybrid {
730            self.rc.encode_bit_logp(false, 12); // redundancy = 0
731        }
732
733        if mode == OpusMode::Hybrid {
734            let nb_compr_bytes = (n_bytes - 1) as u32;
735            self.rc.shrink(nb_compr_bytes);
736        }
737
738        let silk_ret_bytes = if mode == OpusMode::SilkOnly {
739            ((self.rc.tell() + 7) >> 3) as usize
740        } else {
741            0
742        };
743
744        // libopus parity: adjust nbCompressedBytes for CELT/Hybrid based on tell (opus_encoder.c:1913-1921)
745        // tmp = bitrate*frame_size + tell*Fs; nbCompressed = (tmp+4*Fs)/(8*Fs)
746        if mode != OpusMode::SilkOnly {
747            let tell = self.rc.tell();
748            if tell > 1 {
749                let tmp = self.bitrate_bps as i64 * frame_size as i64 + tell as i64 * self.sampling_rate as i64;
750                let adjusted = ((tmp + 4 * self.sampling_rate as i64) / (8 * self.sampling_rate as i64)) as usize;
751                let new_n = adjusted.min(max_data_bytes).max(1).min(OPUS_MAX_PACKET_BYTES);
752                if new_n < n_bytes {
753                    n_bytes = new_n;
754                    // shrink range coder to new size (keep SILK bytes, trim tail)
755                    let new_payload = n_bytes - 1;
756                    self.rc.shrink(new_payload as u32);
757                }
758            }
759        }
760
761        if mode == OpusMode::CeltOnly || mode == OpusMode::Hybrid {
762            self.celt_enc.complexity = self.complexity;
763            let start_band = if mode == OpusMode::Hybrid { 17 } else { 0 };
764            let total_packet_bits = ((n_bytes - 1) * 8) as i32;
765            // Propagate bitrate/VBR to CeltEncoder for accurate VBR handling (libopus parity)
766            let celt_bitrate = if mode == OpusMode::Hybrid {
767                let frame_ms = frame_size as i32 * 1000 / self.sampling_rate;
768                let frame20ms = frame_ms >= 20;
769                let silk_rate = compute_silk_rate_for_hybrid(self.bitrate_bps, frame20ms);
770                (self.bitrate_bps - silk_rate).max(8000)
771            } else {
772                self.bitrate_bps
773            };
774            self.celt_enc.set_bitrate(celt_bitrate);
775            self.celt_enc.set_vbr(!self.use_cbr);
776            // libopus: Hybrid VBR is unconstrained (can steal from SILK), CELT-only constrained
777            self.celt_enc.set_constrained_vbr(mode == OpusMode::CeltOnly);
778
779            let celt_input: &[f32] = if self.channels == 1 {
780                input
781            } else {
782                let n = frame_size * self.channels;
783                self.buf_celt_input.resize(n, 0.0);
784                for i in 0..frame_size {
785                    for ch in 0..self.channels {
786                        self.buf_celt_input[ch * frame_size + i] = input[i * self.channels + ch];
787                    }
788                }
789                state_ref(&self.buf_celt_input)
790            };
791
792            if self.rc.tell() <= total_packet_bits {
793                let is_vbr = !self.use_cbr;
794                self.celt_enc.encode_with_budget_vbr(
795                    celt_input,
796                    frame_size,
797                    &mut self.rc,
798                    start_band,
799                    total_packet_bits,
800                    is_vbr,
801                );
802            }
803        }
804
805        self.rc.done();
806
807        if self.rc.error != 0 {
808            return Err("Range coder buffer overflow: encoded data exceeds packet budget");
809        }
810
811        if mode == OpusMode::SilkOnly {
812            let mut ret = silk_ret_bytes.min(self.rc.storage as usize);
813            while ret > 2 && self.rc.buf[ret - 1] == 0 {
814                ret -= 1;
815            }
816
817            let target_total = if self.use_cbr {
818                n_bytes.min(output.len())
819            } else {
820                (ret + 1).min(output.len())
821            };
822
823            let silk_len = ret;
824
825            if !self.use_cbr || silk_len + 1 >= target_total {
826                // VBR or payload fills the target: simple code 0 packet
827                output[0] = toc;
828                let copy_len = silk_len.min(target_total - 1);
829                output[1..1 + copy_len].copy_from_slice(&self.rc.buf[..copy_len]);
830                return Ok((copy_len + 1).min(output.len()));
831            }
832
833            output[0] = toc | 0x03;
834
835            if silk_len + 2 >= target_total {
836                output[1] = 0x01;
837                let copy_len = (target_total - 2).min(silk_len);
838                output[2..2 + copy_len].copy_from_slice(&self.rc.buf[..copy_len]);
839                self.prev_enc_mode = Some(mode);
840                return Ok(target_total.min(output.len()));
841            }
842
843            let pad_amount = target_total - silk_len - 2;
844            output[1] = 0x41;
845
846            let nb_255s = (pad_amount - 1) / 255;
847            let mut ptr = 2;
848            for _ in 0..nb_255s {
849                output[ptr] = 255;
850                ptr += 1;
851            }
852            output[ptr] = (pad_amount - 255 * nb_255s - 1) as u8;
853            ptr += 1;
854
855            output[ptr..ptr + silk_len].copy_from_slice(&self.rc.buf[..silk_len]);
856            ptr += silk_len;
857
858            let fill_end = target_total.min(output.len());
859            for byte in output[ptr..fill_end].iter_mut() {
860                *byte = 0;
861            }
862
863            self.prev_enc_mode = Some(mode);
864            return Ok(target_total.min(output.len()));
865        }
866
867        // For CELT/Hybrid, respect possible VBR shrink performed by CeltEncoder (e.g. silence)
868        let payload_len = (self.rc.storage as usize).min(output.len() - 1);
869        output[1..1 + payload_len].copy_from_slice(&self.rc.buf[..payload_len]);
870        self.prev_enc_mode = Some(mode);
871        Ok(payload_len + 1)
872    }
873}
874
875pub struct OpusDecoder {
876    #[cfg(not(feature = "heap"))]
877    celt_dec: CeltDecoder,
878    #[cfg(feature = "heap")]
879    celt_dec: Box<CeltDecoder>,
880    #[cfg(not(feature = "heap"))]
881    silk_dec: silk::dec_api::SilkDecoder,
882    #[cfg(feature = "heap")]
883    silk_dec: Box<silk::dec_api::SilkDecoder>,
884    sampling_rate: i32,
885    channels: usize,
886
887    prev_mode: Option<OpusMode>,
888
889    /// Whether the previous frame had redundancy (mode transition marker).
890    prev_redundancy: bool,
891    frame_size: usize,
892
893    bandwidth: Bandwidth,
894
895    stream_channels: usize,
896
897    silk_resampler: silk::resampler::SilkResampler,
898
899    /// Second resampler instance for stereo channel 1.
900    silk_resampler_2: silk::resampler::SilkResampler,
901
902    prev_internal_rate: i32,
903
904    pub hybrid_skip_celt: bool,
905
906    #[cfg(not(feature = "heap"))]
907    w_pcm_i16: FixedVec<i16, OPUS_PCM_I16>,
908    #[cfg(feature = "heap")]
909    w_pcm_i16: Box<FixedVec<i16, OPUS_PCM_I16>>,
910    #[cfg(not(feature = "heap"))]
911    w_silk_out: FixedVec<f32, OPUS_SUBFRAME_SCRATCH>,
912    #[cfg(feature = "heap")]
913    w_silk_out: Box<FixedVec<f32, OPUS_SUBFRAME_SCRATCH>>,
914    #[cfg(not(feature = "heap"))]
915    w_pcm_resampled: FixedVec<i16, OPUS_SUBFRAME_SCRATCH>,
916    #[cfg(feature = "heap")]
917    w_pcm_resampled: Box<FixedVec<i16, OPUS_SUBFRAME_SCRATCH>>,
918    #[cfg(not(feature = "heap"))]
919    w_celt_planar: FixedVec<f32, OPUS_SUBFRAME_SCRATCH>,
920    #[cfg(feature = "heap")]
921    w_celt_planar: Box<FixedVec<f32, OPUS_SUBFRAME_SCRATCH>>,
922    #[cfg(not(feature = "heap"))]
923    w_celt_out: FixedVec<f32, OPUS_SUBFRAME_SCRATCH>,
924    #[cfg(feature = "heap")]
925    w_celt_out: Box<FixedVec<f32, OPUS_SUBFRAME_SCRATCH>>,
926
927    /// Tail of the previous frame's output, used for smooth_fade at mode
928    /// transitions (libopus pcm_transition + smooth_fade).
929    #[cfg(not(feature = "heap"))]
930    prev_pcm_tail: FixedVec<f32, OPUS_PCM_TAIL>,
931    #[cfg(feature = "heap")]
932    prev_pcm_tail: Box<FixedVec<f32, OPUS_PCM_TAIL>>,
933}
934
935impl OpusDecoder {
936    pub fn new(sampling_rate: i32, channels: usize) -> Result<Self, &'static str> {
937        if ![8000, 12000, 16000, 24000, 48000].contains(&sampling_rate) {
938            return Err("Invalid sampling rate");
939        }
940        if ![1, 2].contains(&channels) {
941            return Err("Invalid number of channels");
942        }
943
944        let mode = modes::default_mode();
945        #[cfg(feature = "heap")]
946        let celt_dec = Box::new(CeltDecoder::new(mode, channels, sampling_rate));
947        #[cfg(not(feature = "heap"))]
948        let celt_dec = CeltDecoder::new(mode, channels, sampling_rate);
949
950        #[cfg(feature = "heap")]
951        let mut silk_dec = Box::new(silk::dec_api::SilkDecoder::new());
952        #[cfg(not(feature = "heap"))]
953        let mut silk_dec = silk::dec_api::SilkDecoder::new();
954        silk_dec.init(sampling_rate.min(16000), channels as i32);
955        silk_dec.channel_state[0].fs_api_hz = sampling_rate;
956
957        Ok(Self {
958            celt_dec,
959            silk_dec,
960            sampling_rate,
961            channels,
962            prev_mode: None,
963            prev_redundancy: false,
964            frame_size: 0,
965            bandwidth: Bandwidth::Auto,
966            stream_channels: channels,
967            silk_resampler: silk::resampler::SilkResampler::default(),
968            silk_resampler_2: silk::resampler::SilkResampler::default(),
969            prev_internal_rate: 0,
970            hybrid_skip_celt: false,
971
972            #[cfg(not(feature = "heap"))]
973            w_pcm_i16: FixedVec::from_value(0i16, 960 * channels),
974            #[cfg(feature = "heap")]
975            w_pcm_i16: Box::new(FixedVec::from_value(0i16, 960 * channels)),
976
977            #[cfg(not(feature = "heap"))]
978            w_silk_out: FixedVec::from_value(0.0f32, OPUS_MAX_SUBFRAME * channels),
979            #[cfg(feature = "heap")]
980            w_silk_out: Box::new(FixedVec::from_value(0.0f32, OPUS_MAX_SUBFRAME * channels)),
981            #[cfg(not(feature = "heap"))]
982            w_pcm_resampled: FixedVec::from_value(0i16, OPUS_MAX_SUBFRAME * channels),
983            #[cfg(feature = "heap")]
984            w_pcm_resampled: Box::new(FixedVec::from_value(0i16, OPUS_MAX_SUBFRAME * channels)),
985            #[cfg(not(feature = "heap"))]
986            w_celt_planar: FixedVec::from_value(0.0f32, OPUS_MAX_SUBFRAME * channels),
987            #[cfg(feature = "heap")]
988            w_celt_planar: Box::new(FixedVec::from_value(0.0f32, OPUS_MAX_SUBFRAME * channels)),
989            #[cfg(not(feature = "heap"))]
990            w_celt_out: FixedVec::from_value(0.0f32, OPUS_MAX_SUBFRAME * channels),
991            #[cfg(feature = "heap")]
992            w_celt_out: Box::new(FixedVec::from_value(0.0f32, OPUS_MAX_SUBFRAME * channels)),
993
994            #[cfg(not(feature = "heap"))]
995            prev_pcm_tail: FixedVec::from_value(0.0f32, 240 * channels),
996            #[cfg(feature = "heap")]
997            prev_pcm_tail: Box::new(FixedVec::from_value(0.0f32, 240 * channels)),
998        })
999    }
1000
1001    pub fn decode(
1002        &mut self,
1003        input: &[u8],
1004        frame_size: usize,
1005        output: &mut [f32],
1006    ) -> Result<usize, &'static str> {
1007        if input.is_empty() {
1008            return Err("Input packet empty");
1009        }
1010
1011        let toc = input[0];
1012        let mode = mode_from_toc(toc);
1013        let packet_channels = channels_from_toc(toc);
1014        let bandwidth = bandwidth_from_toc(toc);
1015        let frame_duration_ms = frame_duration_ms_from_toc(toc);
1016
1017        // A packet of 0 or 1 bytes (ToC only) is a lost/DTX frame. libopus
1018        // triggers PLC in this case (opus_decoder.c:315-321). We decode the
1019        // frame using the previous mode's concealment.
1020        let lost_frame = input.len() <= 1;
1021
1022        if packet_channels != self.channels {
1023            return Err("Channel count mismatch between packet and decoder");
1024        }
1025
1026        let code = toc & 0x03;
1027        let frame_count: usize;
1028        let frame_payloads: FixedVec<&[u8], OPUS_MAX_PACKET_FRAMES>;
1029
1030        match code {
1031            0 => {
1032                frame_count = 1;
1033                frame_payloads = FixedVec::from_slice(&[&input[1..]]);
1034            }
1035            1 => {
1036                frame_count = 2;
1037                let data_len = input.len() - 1;
1038                // RFC 6716 §3.2.1: code 1 carries two equal-size (CBR) frames,
1039                // so the payload length must be even. libopus rejects odd lengths.
1040                if data_len % 2 != 0 {
1041                    return Err("Code 1: payload length must be even");
1042                }
1043                let half = data_len / 2;
1044                if half == 0 {
1045                    return Err("Code 1: empty frame");
1046                }
1047                frame_payloads = FixedVec::from_slice(&[&input[1..1 + half], &input[1 + half..]]);
1048            }
1049            2 => {
1050                frame_count = 2;
1051                let data = &input[1..];
1052                if data.is_empty() {
1053                    return Err("Code 2 packet has no data");
1054                }
1055                let (first_len, header_size) = parse_frame_size(data)?;
1056                if header_size + first_len > data.len() {
1057                    return Err("Code 2: first frame size exceeds packet");
1058                }
1059                frame_payloads = FixedVec::from_slice(&[
1060                    &data[header_size..header_size + first_len],
1061                    &data[header_size + first_len..],
1062                ]);
1063            }
1064            3 => {
1065                if input.len() < 2 {
1066                    return Err("Code 3 packet too short");
1067                }
1068                let count_byte = input[1];
1069                let n_frames = (count_byte & 0x3F) as usize;
1070                if n_frames < 1 || n_frames > 48 {
1071                    return Err("Code 3: invalid frame count");
1072                }
1073                frame_count = n_frames;
1074                // Bit 6 = padding flag, bit 7 = VBR flag (RFC 6716 §3.2.1).
1075                let padding_flag = (count_byte & 0x40) != 0;
1076                let vbr = (count_byte & 0x80) != 0;
1077
1078                // Parse the optional padding length bytes that follow the count
1079                // byte. The padding *content* (pad_len bytes) lives at the end of
1080                // the packet and is not part of any frame.
1081                let mut ptr = 2usize;
1082                let mut pad_len = 0usize;
1083                if padding_flag {
1084                    loop {
1085                        if ptr >= input.len() {
1086                            return Err("Code 3: padding overflow");
1087                        }
1088                        let p = input[ptr] as usize;
1089                        ptr += 1;
1090                        if p == 255 {
1091                            pad_len += 254;
1092                        } else {
1093                            pad_len += p;
1094                            break;
1095                        }
1096                    }
1097                }
1098                if ptr + pad_len > input.len() {
1099                    return Err("Code 3: padding exceeds packet");
1100                }
1101                let payload_end = input.len() - pad_len;
1102                let payload = &input[ptr..payload_end];
1103
1104                let mut payloads: FixedVec<&[u8], OPUS_MAX_PACKET_FRAMES> = FixedVec::new();
1105                if frame_count == 1 {
1106                    // Single frame: the entire payload region is the frame, both
1107                    // for VBR and CBR (no length prefix is present).
1108                    payloads.push(payload);
1109                } else if vbr {
1110                    // VBR (V=1): per-frame lengths for all frames except the last,
1111                    // which takes the remaining bytes (RFC 6716 §3.2.1).
1112                    let mut cursor = 0usize;
1113                    for i in 0..frame_count {
1114                        if i + 1 < frame_count {
1115                            if cursor >= payload.len() {
1116                                return Err("Code 3: unexpected end in VBR header");
1117                            }
1118                            let (frame_len, header_bytes) =
1119                                parse_frame_size(&payload[cursor..])?;
1120                            cursor += header_bytes;
1121                            if cursor + frame_len > payload.len() {
1122                                return Err("Code 3: frame length exceeds packet");
1123                            }
1124                            payloads.push(&payload[cursor..cursor + frame_len]);
1125                            cursor += frame_len;
1126                        } else {
1127                            // Last frame: remaining bytes, no length prefix.
1128                            if cursor > payload.len() {
1129                                return Err("Code 3: no data for last frame");
1130                            }
1131                            payloads.push(&payload[cursor..]);
1132                        }
1133                    }
1134                } else {
1135                    // CBR (V=0): remaining bytes are split equally into M frames
1136                    // (RFC 6716 §3.2.1: "the remaining bytes are split into M
1137                    // equal chunks").
1138                    if payload.len() % frame_count != 0 {
1139                        return Err("Code 3 CBR: payload not divisible by frame count");
1140                    }
1141                    let frame_len = payload.len() / frame_count;
1142                    for i in 0..frame_count {
1143                        payloads.push(&payload[i * frame_len..(i + 1) * frame_len]);
1144                    }
1145                }
1146                frame_payloads = payloads;
1147            }
1148            _ => unreachable!(),
1149        }
1150
1151        self.frame_size = frame_size;
1152        self.bandwidth = bandwidth;
1153        self.stream_channels = packet_channels;
1154
1155        // Derive the actual per-frame sample count from the TOC, not from the
1156        // caller's frame_size. This prevents panics in bands.rs/celt.rs when
1157        // the caller passes a mismatched frame_size (issue #7 sub-item 1):
1158        // the internal decoders always get the correct geometry.
1159        let toc_frame_size = frame_samples_from_toc(toc, self.sampling_rate)
1160            .ok_or("Invalid TOC for sampling rate")?;
1161        let decoded_total = toc_frame_size * frame_count;
1162        if frame_size < decoded_total {
1163            return Err("frame_size too small for packet");
1164        }
1165        if output.len() < decoded_total * self.channels {
1166            return Err("Output buffer too small for packet");
1167        }
1168        // Zero-fill any extra space the caller provided beyond what the packet
1169        // actually produces, so stale data is never left in the buffer.
1170        if output.len() > decoded_total * self.channels {
1171            for v in &mut output[decoded_total * self.channels..] {
1172                *v = 0.0;
1173            }
1174        }
1175        let sub_frame_size = toc_frame_size;
1176        let sub_output_len = sub_frame_size * self.channels;
1177
1178        // Detect mode transition and reset CELT decoder state to prevent
1179        // cross-mode artifacts (libopus opus_decoder.c:602-604).
1180        // This is the primary fix for issue #8/#9 alignment divergence:
1181        // stale CELT MDCT/prefilter state at SILK↔CELT boundaries causes
1182        // discontinuities that accumulate across transitions.
1183        let mode_transition = match self.prev_mode {
1184            Some(prev) if prev != mode && !self.prev_redundancy => true,
1185            _ => false,
1186        };
1187        if mode_transition {
1188            self.celt_dec.reset_state();
1189        }
1190
1191        // Generate SILK PLC audio for the mode-transition bridge. libopus
1192        // synthesizes 5ms (F5) of pitch-extrapolated audio in the OLD mode
1193        // (opus_decoder.c:387-391) and crossfades it with the new frame. We
1194        // reuse the F5-sized prev_pcm_tail buffer for this bridge.
1195        let f5_bridge = self.sampling_rate as usize / 200; // F5 = Fs/200
1196        if mode_transition
1197            && f5_bridge > 0
1198            && matches!(
1199                self.prev_mode,
1200                Some(OpusMode::SilkOnly) | Some(OpusMode::Hybrid)
1201            )
1202            && self.prev_internal_rate > 0
1203        {
1204            let internal_rate = self.prev_internal_rate;
1205            let plc_internal_len = (10 * internal_rate / 1000) as usize;
1206            let mut plc_rc = RangeCoder::new_decoder(&[]);
1207            let mut plc_i16: FixedVec<i16, OPUS_PCM_I16> =
1208                FixedVec::from_value(0i16, plc_internal_len * self.channels);
1209            let n = self.silk_dec.decode(
1210                &mut plc_rc,
1211                &mut plc_i16,
1212                silk::decode_frame::FLAG_PACKET_LOST,
1213                true,
1214                10,
1215                internal_rate,
1216            );
1217            if n > 0 {
1218                let bridge_ch = f5_bridge * self.channels;
1219                let bridge_len = bridge_ch.min(self.prev_pcm_tail.len());
1220                if internal_rate == self.sampling_rate {
1221                    // No resampling: copy PLC samples directly (ch0 planar).
1222                    let n_us = n as usize;
1223                    for ch in 0..self.channels {
1224                        let src_base = ch * n_us;
1225                        for i in 0..(bridge_len / self.channels).min(n_us) {
1226                            let dst = i * self.channels + ch;
1227                            if dst < bridge_len {
1228                                self.prev_pcm_tail[dst] = plc_i16[src_base + i] as f32 / 32768.0;
1229                            }
1230                        }
1231                    }
1232                } else if self.silk_resampler.is_initialized() {
1233                    // Resample channel 0 to the API rate for the bridge.
1234                    let ratio = self.sampling_rate as f64 / internal_rate as f64;
1235                    let out_len = ((n as f64 * ratio) as usize).min(f5_bridge);
1236                    let n_us = n as usize;
1237                    let mut resampled: FixedVec<i16, OPUS_MAX_FRAME> = FixedVec::from_value(0i16, out_len);
1238                    self.silk_resampler.process(
1239                        &mut resampled,
1240                        &plc_i16[..n_us],
1241                        n,
1242                    );
1243                    for i in 0..out_len {
1244                        if i < bridge_len / self.channels {
1245                            for ch in 0..self.channels {
1246                                self.prev_pcm_tail[i * self.channels + ch] =
1247                                    resampled[i] as f32 / 32768.0;
1248                            }
1249                        }
1250                    }
1251                }
1252            }
1253        }
1254
1255        // Track whether this packet uses Hybrid redundancy.
1256        let mut has_redundancy = false;
1257
1258        match mode {
1259            OpusMode::SilkOnly => {
1260                let internal_sample_rate = match bandwidth {
1261                    Bandwidth::Narrowband => 8000,
1262                    Bandwidth::Mediumband => 12000,
1263                    Bandwidth::Wideband => 16000,
1264                    _ => 16000,
1265                };
1266                let internal_frame_size =
1267                    (frame_duration_ms * internal_sample_rate / 1000) as usize;
1268
1269                if self.sampling_rate != internal_sample_rate
1270                    && internal_sample_rate != self.prev_internal_rate
1271                {
1272                    self.silk_resampler
1273                        .init(internal_sample_rate, self.sampling_rate);
1274                    self.silk_resampler_2
1275                        .init(internal_sample_rate, self.sampling_rate);
1276                }
1277                // Always track the SILK internal rate so the mode-transition
1278                // PLC bridge can be generated (even when no resampling is
1279                // needed, e.g. 16kHz decoder + SILK WB).
1280                self.prev_internal_rate = internal_sample_rate;
1281
1282                for (fi, payload) in frame_payloads.iter().enumerate() {
1283                    let mut rc = RangeCoder::new_decoder(payload);
1284                    let pcm_i16_len = internal_frame_size * self.channels;
1285                    debug_assert!(pcm_i16_len <= self.w_pcm_i16.len());
1286
1287                    let ret = {
1288                        let (silk_dec, pcm_i16) =
1289                            (state_mut(&mut self.silk_dec), state_mut(&mut self.w_pcm_i16));
1290                        let lost_flag = if lost_frame {
1291                            silk::decode_frame::FLAG_PACKET_LOST
1292                        } else {
1293                            silk::decode_frame::FLAG_DECODE_NORMAL
1294                        };
1295                        silk_dec.decode(
1296                            &mut rc,
1297                            &mut pcm_i16[..pcm_i16_len],
1298                            lost_flag,
1299                            true,
1300                            frame_duration_ms,
1301                            internal_sample_rate,
1302                        )
1303                    };
1304
1305                    if ret < 0 {
1306                        return Err("SILK decoding failed");
1307                    }
1308
1309                    let decoded_samples = ret as usize;
1310                    let out_start = fi * sub_output_len;
1311
1312                    // SILK decoder outputs planar: ch0 at [0..fl], ch1 at [fl..2*fl].
1313                    if self.sampling_rate == internal_sample_rate {
1314                        let frames = decoded_samples.min(sub_frame_size);
1315                        for i in 0..frames {
1316                            for ch in 0..self.channels {
1317                                let src = if ch == 0 { i } else { internal_frame_size + i };
1318                                let v = self.w_pcm_i16[src] as f32 / 32768.0;
1319                                let idx = out_start + i * self.channels + ch;
1320                                if idx < output.len() {
1321                                    output[idx] = v;
1322                                }
1323                            }
1324                        }
1325                    } else {
1326                        let ratio = self.sampling_rate as f64 / internal_sample_rate as f64;
1327                        let out_len =
1328                            ((decoded_samples as f64 * ratio) as usize).min(sub_frame_size);
1329                        debug_assert!(out_len * self.channels <= self.w_pcm_resampled.len());
1330                        // Resample channel 0.
1331                        {
1332                            let (res, inp, out) = (
1333                                &mut self.silk_resampler,
1334                                state_ref(&self.w_pcm_i16),
1335                                state_mut(&mut self.w_pcm_resampled),
1336                            );
1337                            res.process(
1338                                &mut out[..out_len],
1339                                &inp[..decoded_samples],
1340                                decoded_samples as i32,
1341                            );
1342                        }
1343                        // Resample channel 1 (stereo only).
1344                        if self.channels == 2 {
1345                            let (res, inp, out) = (
1346                                &mut self.silk_resampler_2,
1347                                state_ref(&self.w_pcm_i16),
1348                                state_mut(&mut self.w_pcm_resampled),
1349                            );
1350                            res.process(
1351                                &mut out[out_len..2 * out_len],
1352                                &inp[internal_frame_size..internal_frame_size + decoded_samples],
1353                                decoded_samples as i32,
1354                            );
1355                        }
1356                        let frames = out_len.min(sub_frame_size);
1357                        for i in 0..frames {
1358                            for ch in 0..self.channels {
1359                                let v = self.w_pcm_resampled[ch * out_len + i] as f32 / 32768.0;
1360                                let idx = out_start + i * self.channels + ch;
1361                                if idx < output.len() {
1362                                    output[idx] = v;
1363                                }
1364                            }
1365                        }
1366                    }
1367                }
1368                decoded_total
1369            }
1370
1371            OpusMode::CeltOnly => {
1372                let celt_end_band = self.celt_end_band_from_toc(toc);
1373
1374                for (fi, payload) in frame_payloads.iter().enumerate() {
1375                    let mut rc = RangeCoder::new_decoder(payload);
1376                    let total_bits = (payload.len() * 8) as i32;
1377                    let needed = sub_frame_size * self.channels;
1378                    let out_start = fi * needed;
1379                    let out_end = (out_start + needed).min(output.len());
1380
1381                    if output.len() < out_end {
1382                        return Err("Output buffer too small");
1383                    }
1384
1385                    if self.channels == 1 {
1386                        self.celt_dec.decode_from_range_coder_with_band_range(
1387                            &mut rc,
1388                            total_bits,
1389                            sub_frame_size,
1390                            &mut output[out_start..out_end],
1391                            0,
1392                            celt_end_band,
1393                        );
1394                        for sample in &mut output[out_start..out_end] {
1395                            *sample = sample.clamp(-1.0, 1.0);
1396                        }
1397                    } else {
1398                        self.celt_dec.decode_from_range_coder_with_band_range(
1399                            &mut rc,
1400                            total_bits,
1401                            sub_frame_size,
1402                            &mut self.w_celt_planar[..needed],
1403                            0,
1404                            celt_end_band,
1405                        );
1406                        for i in 0..sub_frame_size {
1407                            for ch in 0..self.channels {
1408                                let idx = out_start + i * self.channels + ch;
1409                                output[idx] =
1410                                    self.w_celt_planar[ch * sub_frame_size + i].clamp(-1.0, 1.0);
1411                            }
1412                        }
1413                    }
1414                }
1415                decoded_total
1416            }
1417
1418            OpusMode::Hybrid => {
1419                let internal_sample_rate = 16000;
1420                let internal_frame_size =
1421                    (frame_duration_ms * internal_sample_rate / 1000) as usize;
1422                let celt_end_band = self.celt_end_band_from_toc(toc);
1423
1424                if self.sampling_rate != internal_sample_rate
1425                    && internal_sample_rate != self.prev_internal_rate
1426                {
1427                    self.silk_resampler
1428                        .init(internal_sample_rate, self.sampling_rate);
1429                    self.silk_resampler_2
1430                        .init(internal_sample_rate, self.sampling_rate);
1431                }
1432                self.prev_internal_rate = internal_sample_rate;
1433
1434                for (fi, payload) in frame_payloads.iter().enumerate() {
1435                    let mut rc = RangeCoder::new_decoder(payload);
1436                    let pcm_silk_i16_len = internal_frame_size * self.channels;
1437                    debug_assert!(pcm_silk_i16_len <= self.w_pcm_i16.len());
1438
1439                    let ret = {
1440                        let (silk_dec, pcm_i16) =
1441                            (state_mut(&mut self.silk_dec), state_mut(&mut self.w_pcm_i16));
1442                        let lost_flag = if lost_frame {
1443                            silk::decode_frame::FLAG_PACKET_LOST
1444                        } else {
1445                            silk::decode_frame::FLAG_DECODE_NORMAL
1446                        };
1447                        silk_dec.decode(
1448                            &mut rc,
1449                            &mut pcm_i16[..pcm_silk_i16_len],
1450                            lost_flag,
1451                            true,
1452                            frame_duration_ms,
1453                            internal_sample_rate,
1454                        )
1455                    };
1456
1457                    if ret < 0 {
1458                        return Err("SILK decoding failed");
1459                    }
1460
1461                    let silk_out_len = sub_frame_size * self.channels;
1462                    self.w_silk_out[..silk_out_len].fill(0.0);
1463                    if ret > 0 {
1464                        let decoded_samples = ret as usize;
1465                        // SILK decoder outputs planar: ch0 at [0..fl], ch1 at [fl..2*fl].
1466                        if self.sampling_rate == internal_sample_rate {
1467                            let frames = decoded_samples.min(sub_frame_size);
1468                            for i in 0..frames {
1469                                for ch in 0..self.channels {
1470                                    let src = if ch == 0 { i } else { internal_frame_size + i };
1471                                    let v = self.w_pcm_i16[src] as f32 / 32768.0;
1472                                    let idx = i * self.channels + ch;
1473                                    if idx < silk_out_len {
1474                                        self.w_silk_out[idx] = v;
1475                                    }
1476                                }
1477                            }
1478                        } else {
1479                            let ratio = self.sampling_rate as f64 / internal_sample_rate as f64;
1480                            let out_len =
1481                                ((decoded_samples as f64 * ratio) as usize).min(sub_frame_size);
1482                            debug_assert!(out_len * self.channels <= self.w_pcm_resampled.len());
1483                            // Resample channel 0.
1484                            {
1485                                let (res, inp, out) = (
1486                                    &mut self.silk_resampler,
1487                                    state_ref(&self.w_pcm_i16),
1488                                    state_mut(&mut self.w_pcm_resampled),
1489                                );
1490                                res.process(
1491                                    &mut out[..out_len],
1492                                    &inp[..decoded_samples],
1493                                    decoded_samples as i32,
1494                                );
1495                            }
1496                            // Resample channel 1 (stereo only).
1497                            if self.channels == 2 {
1498                                let (res, inp, out) = (
1499                                    &mut self.silk_resampler_2,
1500                                    state_ref(&self.w_pcm_i16),
1501                                    state_mut(&mut self.w_pcm_resampled),
1502                                );
1503                                res.process(
1504                                    &mut out[out_len..2 * out_len],
1505                                    &inp[internal_frame_size..internal_frame_size + decoded_samples],
1506                                    decoded_samples as i32,
1507                                );
1508                            }
1509                            let frames = out_len.min(sub_frame_size);
1510                            for i in 0..frames {
1511                                for ch in 0..self.channels {
1512                                    let v = self.w_pcm_resampled[ch * out_len + i] as f32 / 32768.0;
1513                                    let idx = i * self.channels + ch;
1514                                    if idx < silk_out_len {
1515                                        self.w_silk_out[idx] = v;
1516                                    }
1517                                }
1518                            }
1519                        }
1520                    }
1521
1522                    let total_bits = (payload.len() * 8) as i32;
1523                    let redundancy = rc.decode_bit_logp(12);
1524                    let skip_celt = if redundancy {
1525                        let _celt_to_silk = rc.decode_bit_logp(1);
1526                        has_redundancy = true;
1527                        // When redundancy is present, the redundant CELT frame
1528                        // provides the transition audio. We skip the main CELT
1529                        // decode for this sub-frame (the SILK output stands alone)
1530                        // — a simplified version of libopus's behaviour where the
1531                        // redundant frame is decoded separately and crossfaded.
1532                        true
1533                    } else {
1534                        false
1535                    };
1536
1537                    if skip_celt {
1538                        self.w_celt_out[..silk_out_len].fill(0.0);
1539                    } else {
1540                        let (celt_dec, celt_planar) =
1541                            (state_mut(&mut self.celt_dec), state_mut(&mut self.w_celt_planar));
1542                        celt_dec.decode_from_range_coder_with_band_range(
1543                            &mut rc,
1544                            total_bits,
1545                            sub_frame_size,
1546                            &mut celt_planar[..silk_out_len],
1547                            17,
1548                            celt_end_band,
1549                        );
1550
1551                        if self.channels == 1 {
1552                            self.w_celt_out[..silk_out_len]
1553                                .copy_from_slice(&self.w_celt_planar[..silk_out_len]);
1554                        } else {
1555                            for i in 0..sub_frame_size {
1556                                for ch in 0..self.channels {
1557                                    self.w_celt_out[i * self.channels + ch] =
1558                                        self.w_celt_planar[ch * sub_frame_size + i];
1559                                }
1560                            }
1561                        }
1562                    }
1563
1564                    let out_start = fi * silk_out_len;
1565                    let total = silk_out_len.min(output.len() - out_start);
1566                    for j in 0..total {
1567                        output[out_start + j] =
1568                            (self.w_silk_out[j] + self.w_celt_out[j]).clamp(-1.0, 1.0);
1569                    }
1570                }
1571                decoded_total
1572            }
1573        };
1574
1575        // Apply PLC-style bridging at mode transitions (libopus
1576        // opus_decoder.c:660-679). The first F2_5 of the output is replaced
1577        // with the previous frame's tail (PLC bridge), and the next F2_5 is
1578        // crossfaded between the bridge and the new frame's CELT output.
1579        // F5 = Fs/200, F2_5 = Fs/400.
1580        let f2_5 = self.sampling_rate as usize / 400;
1581        let f5 = f2_5 * 2;
1582        if mode_transition && f5 > 0 && decoded_total >= f5 {
1583            let window = modes::default_mode().window;
1584            let inc = (48000 / self.sampling_rate) as usize;
1585            let f2_5_ch = f2_5 * self.channels;
1586            let f5_ch = f5 * self.channels;
1587            // First F2_5: pure bridging audio from previous frame's tail.
1588            output[..f2_5_ch].copy_from_slice(&self.prev_pcm_tail[..f2_5_ch]);
1589            // Next F2_5: crossfade bridge → new CELT output.
1590            let new_mid: FixedVec<f32, OPUS_PCM_TAIL> = FixedVec::from_slice(&output[f2_5_ch..f5_ch]);
1591            smooth_fade(
1592                &self.prev_pcm_tail[f2_5_ch..f5_ch],
1593                &new_mid,
1594                &mut output[f2_5_ch..f5_ch],
1595                f2_5,
1596                self.channels,
1597                window,
1598                inc,
1599            );
1600        }
1601
1602        // Save the tail of this frame for the next transition (F5 samples).
1603        let tail_len = f5 * self.channels;
1604        let out_total = decoded_total * self.channels;
1605        if out_total >= tail_len && tail_len <= self.prev_pcm_tail.len() {
1606            self.prev_pcm_tail[..tail_len]
1607                .copy_from_slice(&output[out_total - tail_len..out_total]);
1608        }
1609
1610        self.prev_mode = Some(mode);
1611        self.prev_redundancy = has_redundancy;
1612        Ok(decoded_total)
1613    }
1614}
1615
1616impl OpusDecoder {
1617    #[inline(always)]
1618    fn celt_end_band_from_toc(&self, toc: u8) -> usize {
1619        let mode = modes::default_mode();
1620        let top = mode.eff_ebands;
1621        if mode_from_toc(toc) == OpusMode::CeltOnly && toc >= 0x80 {
1622            const FROM_OPUS_TABLE: [u8; 16] = [
1623                0x80, 0x88, 0x90, 0x98, 0x40, 0x48, 0x50, 0x58, 0x20, 0x28, 0x30, 0x38, 0x00, 0x08,
1624                0x10, 0x18,
1625            ];
1626            let idx = ((toc >> 3) - 16) as usize;
1627            let data0 = FROM_OPUS_TABLE[idx] | (toc & 0x7);
1628            let trim = (data0 >> 5) as usize;
1629            return top.saturating_sub(2 * trim).max(1);
1630        }
1631        top
1632    }
1633}
1634
1635fn frame_rate_from_params(sampling_rate: i32, frame_size: usize) -> Option<i32> {
1636    let frame_size = frame_size as i32;
1637    if frame_size == 0 || sampling_rate % frame_size != 0 {
1638        return None;
1639    }
1640    Some(sampling_rate / frame_size)
1641}
1642
1643fn gen_toc(mode: OpusMode, frame_rate: i32, bandwidth: Bandwidth, channels: usize) -> u8 {
1644    let mut rate = frame_rate;
1645    let mut period = 0;
1646    while rate < 400 {
1647        rate <<= 1;
1648        period += 1;
1649    }
1650
1651    let mut toc = match mode {
1652        OpusMode::SilkOnly => {
1653            let bw = (bandwidth as i32 - Bandwidth::Narrowband as i32) << 5;
1654            let per = (period - 2) << 3;
1655            (bw | per) as u8
1656        }
1657        OpusMode::CeltOnly => {
1658            let mut tmp = bandwidth as i32 - Bandwidth::Mediumband as i32;
1659            if tmp < 0 {
1660                tmp = 0;
1661            }
1662            let per = period << 3;
1663            (0x80 | (tmp << 5) | per) as u8
1664        }
1665        OpusMode::Hybrid => {
1666            let base_config = if bandwidth == Bandwidth::Superwideband {
1667                12
1668            } else {
1669                14
1670            };
1671            let period_offset = if frame_rate >= 100 { 0 } else { 1 };
1672            ((base_config + period_offset) << 3) as u8
1673        }
1674    };
1675
1676    if channels == 2 {
1677        toc |= 0x04;
1678    }
1679    toc
1680}
1681
1682fn mode_from_toc(toc: u8) -> OpusMode {
1683    if toc & 0x80 != 0 {
1684        OpusMode::CeltOnly
1685    } else if toc & 0x60 == 0x60 {
1686        OpusMode::Hybrid
1687    } else {
1688        OpusMode::SilkOnly
1689    }
1690}
1691
1692fn bandwidth_from_toc(toc: u8) -> Bandwidth {
1693    let mode = mode_from_toc(toc);
1694    match mode {
1695        OpusMode::SilkOnly => {
1696            let bw_bits = (toc >> 5) & 0x03;
1697            match bw_bits {
1698                0 => Bandwidth::Narrowband,
1699                1 => Bandwidth::Mediumband,
1700                2 => Bandwidth::Wideband,
1701                _ => Bandwidth::Wideband,
1702            }
1703        }
1704        OpusMode::Hybrid => {
1705            let bw_bit = (toc >> 4) & 0x01;
1706            if bw_bit == 0 {
1707                Bandwidth::Superwideband
1708            } else {
1709                Bandwidth::Fullband
1710            }
1711        }
1712        OpusMode::CeltOnly => {
1713            let bw_bits = (toc >> 5) & 0x03;
1714            match bw_bits {
1715                0 => Bandwidth::Mediumband,
1716                1 => Bandwidth::Wideband,
1717                2 => Bandwidth::Superwideband,
1718                3 => Bandwidth::Fullband,
1719                _ => Bandwidth::Fullband,
1720            }
1721        }
1722    }
1723}
1724
1725fn frame_duration_ms_from_toc(toc: u8) -> i32 {
1726    let mode = mode_from_toc(toc);
1727    match mode {
1728        OpusMode::SilkOnly => {
1729            let config = (toc >> 3) & 0x03;
1730            match config {
1731                0 => 10,
1732                1 => 20,
1733                2 => 40,
1734                3 => 60,
1735                _ => 20,
1736            }
1737        }
1738        OpusMode::Hybrid => {
1739            let config = (toc >> 3) & 0x01;
1740            if config == 0 { 10 } else { 20 }
1741        }
1742        OpusMode::CeltOnly => {
1743            let config = (toc >> 3) & 0x03;
1744            match config {
1745                0 => 2,
1746                1 => 5,
1747                2 => 10,
1748                3 => 20,
1749                _ => 20,
1750            }
1751        }
1752    }
1753}
1754
1755/// Compute the per-frame sample count implied by the TOC byte at a given
1756/// sampling rate. For CELT this uses the frame-rate derivation (which handles
1757/// the 2.5 ms case correctly, unlike integer millisecond arithmetic).
1758fn frame_samples_from_toc(toc: u8, sampling_rate: i32) -> Option<usize> {
1759    let mode = mode_from_toc(toc);
1760    match mode {
1761        OpusMode::CeltOnly => {
1762            let period = ((toc >> 3) & 0x03) as i32;
1763            let frame_rate = 400 >> period;
1764            if frame_rate == 0 || sampling_rate % frame_rate != 0 {
1765                return None;
1766            }
1767            Some((sampling_rate / frame_rate) as usize)
1768        }
1769        OpusMode::SilkOnly | OpusMode::Hybrid => {
1770            let duration_ms = frame_duration_ms_from_toc(toc);
1771            Some((sampling_rate as i64 * duration_ms as i64 / 1000) as usize)
1772        }
1773    }
1774}
1775
1776fn channels_from_toc(toc: u8) -> usize {
1777    if toc & 0x04 != 0 { 2 } else { 1 }
1778}
1779
1780/// Crossfade two signals using a squared-sine window (libopus smooth_fade).
1781/// `window` is the 120-sample CELT window at 48 kHz; `inc` = 48000/Fs strides it.
1782fn smooth_fade(
1783    in1: &[f32],
1784    in2: &[f32],
1785    out: &mut [f32],
1786    overlap: usize,
1787    channels: usize,
1788    window: &[f32],
1789    inc: usize,
1790) {
1791    for c in 0..channels {
1792        for i in 0..overlap {
1793            let wi = i * inc;
1794            if wi >= window.len() {
1795                break;
1796            }
1797            let w = window[wi] * window[wi];
1798            out[i * channels + c] = w * in2[i * channels + c] + (1.0 - w) * in1[i * channels + c];
1799        }
1800    }
1801}
1802
1803/// Parse an Opus frame length per RFC 6716 §3.2.1, identical to libopus
1804/// `parse_size()`:
1805///   - `0`: no frame (DTX / lost packet)
1806///   - `1..=251`: length of the frame in bytes (one byte consumed)
1807///   - `252..=255`: a second byte is read; length = `second*4 + first`
1808///
1809/// Returns `(length, bytes_consumed)`.
1810fn parse_frame_size(data: &[u8]) -> Result<(usize, usize), &'static str> {
1811    let first = *data.first().ok_or("truncated frame length")? as usize;
1812    if first < 252 {
1813        Ok((first, 1))
1814    } else {
1815        let second = *data.get(1).ok_or("truncated frame length")? as usize;
1816        Ok((second * 4 + first, 2))
1817    }
1818}
1819
1820#[cfg(all(test, feature = "std"))]
1821mod tests {
1822    use super::*;
1823
1824    fn frame_size_from_toc(toc: u8, sampling_rate: i32) -> Option<usize> {
1825        let mode = mode_from_toc(toc);
1826        match mode {
1827            OpusMode::CeltOnly => {
1828                let period = ((toc >> 3) & 0x03) as i32;
1829                let frame_rate = 400 >> period;
1830                if frame_rate == 0 || sampling_rate % frame_rate != 0 {
1831                    return None;
1832                }
1833                Some((sampling_rate / frame_rate) as usize)
1834            }
1835            OpusMode::SilkOnly => {
1836                let duration_ms = frame_duration_ms_from_toc(toc);
1837                Some((sampling_rate as i64 * duration_ms as i64 / 1000) as usize)
1838            }
1839            OpusMode::Hybrid => {
1840                let duration_ms = frame_duration_ms_from_toc(toc);
1841                Some((sampling_rate as i64 * duration_ms as i64 / 1000) as usize)
1842            }
1843        }
1844    }
1845
1846    #[test]
1847    fn gen_toc_matches_celt_reference_values() {
1848        let sampling_rate = 48_000;
1849        let cases = [
1850            (120usize, 0xE0u8),
1851            (240usize, 0xE8u8),
1852            (480usize, 0xF0u8),
1853            (960usize, 0xF8u8),
1854        ];
1855
1856        for (frame_size, expected_toc) in cases {
1857            let frame_rate = frame_rate_from_params(sampling_rate, frame_size).unwrap();
1858            let toc = gen_toc(OpusMode::CeltOnly, frame_rate, Bandwidth::Fullband, 1);
1859            assert_eq!(
1860                toc, expected_toc,
1861                "frame_size {} expected TOC {:02X} got {:02X}",
1862                frame_size, expected_toc, toc
1863            );
1864            let decoded_size = frame_size_from_toc(toc, sampling_rate).unwrap();
1865            assert_eq!(decoded_size, frame_size);
1866        }
1867
1868        let stereo_toc = gen_toc(
1869            OpusMode::CeltOnly,
1870            frame_rate_from_params(sampling_rate, 960).unwrap(),
1871            Bandwidth::Fullband,
1872            2,
1873        );
1874        assert_eq!(channels_from_toc(stereo_toc), 2);
1875    }
1876
1877    #[test]
1878    fn test_celt_decoder_large_frame_sizes() {
1879        let sampling_rate = 48000;
1880        let channels = 1;
1881
1882        let mut decoder = OpusDecoder::new(sampling_rate, channels).unwrap();
1883
1884        let frame_sizes = [120, 240, 480, 960];
1885
1886        for frame_size in frame_sizes {
1887            let toc = gen_toc(
1888                OpusMode::CeltOnly,
1889                frame_rate_from_params(sampling_rate, frame_size).unwrap(),
1890                Bandwidth::Fullband,
1891                channels,
1892            );
1893            let packet = [toc, 0, 0, 0, 0];
1894
1895            let mut output = vec![0.0f32; frame_size * channels];
1896
1897            let _ = decoder.decode(&packet, frame_size, &mut output);
1898        }
1899
1900        let channels = 2;
1901        let mut decoder = OpusDecoder::new(sampling_rate, channels).unwrap();
1902
1903        for frame_size in frame_sizes {
1904            let toc = gen_toc(
1905                OpusMode::CeltOnly,
1906                frame_rate_from_params(sampling_rate, frame_size).unwrap(),
1907                Bandwidth::Fullband,
1908                channels,
1909            );
1910            let packet = [toc, 0, 0, 0, 0];
1911
1912            let mut output = vec![0.0f32; frame_size * channels];
1913            let _ = decoder.decode(&packet, frame_size, &mut output);
1914        }
1915    }
1916
1917    #[test]
1918    fn test_celt_decoder_edge_case_frame_sizes() {
1919        let sampling_rate = 48000;
1920        let channels = 1;
1921        let mut decoder = OpusDecoder::new(sampling_rate, channels).unwrap();
1922
1923        let edge_sizes = [2048, 2167, 2168, 2169, 2880, 3072];
1924
1925        for frame_size in edge_sizes {
1926            let mut output = vec![0.0f32; frame_size * channels];
1927
1928            let _ = decoder.decode(&[0x80, 0, 0, 0], frame_size, &mut output);
1929        }
1930    }
1931
1932    // Regression test for: "index out of bounds: the len is 48 but the index is 119"
1933    // Root cause: frame_size=48 at 48kHz gives frame_rate=1000, which is not a valid
1934    // Hybrid-mode frame rate but was not validated.  CELT's lm-search then silently
1935    // fell back to lm=0, computed n2=120, and wrote output[119] into a 48-element
1936    // slice.  Triggered via G.729-decoded PCM (8kHz) passed to a 48kHz Opus encoder
1937    // without proper resampling, so the encoder received 48 samples instead of 480.
1938    #[test]
1939    fn test_invalid_small_frame_size_returns_error_not_panic() {
1940        let mut enc = OpusEncoder::new(48000, 2, Application::Voip).unwrap();
1941        enc.bitrate_bps = 64000;
1942        enc.complexity = 5;
1943        enc.use_cbr = true;
1944
1945        // 48 samples at 48kHz = 1ms → frame_rate=1000, invalid for Hybrid mode.
1946        let input = vec![0.0f32; 48 * 2]; // stereo interleaved
1947        let mut output = vec![0u8; 256];
1948
1949        let result = enc.encode(&input, 48, &mut output);
1950        assert!(
1951            result.is_err(),
1952            "encode with invalid frame_size=48 should return Err, not panic"
1953        );
1954    }
1955
1956    // Also verify that the Audio application path (always Hybrid at 48 kHz) rejects
1957    // the same bad frame size.
1958    #[test]
1959    fn test_invalid_small_frame_size_audio_application_returns_error() {
1960        let mut enc = OpusEncoder::new(48000, 1, Application::Audio).unwrap();
1961        let input = vec![0.0f32; 48];
1962        let mut output = vec![0u8; 256];
1963
1964        let result = enc.encode(&input, 48, &mut output);
1965        assert!(
1966            result.is_err(),
1967            "Audio/48kHz encoder with frame_size=48 should return Err"
1968        );
1969    }
1970}