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 n_bytes = cbr_bytes
519            .min(max_data_bytes)
520            .max(1)
521            .min(OPUS_MAX_PACKET_BYTES);
522
523        let init_rc_size = n_bytes - 1;
524        self.rc.reset_for_encode(init_rc_size as u32);
525
526        if mode == OpusMode::SilkOnly || mode == OpusMode::Hybrid {
527            let silk_fs_khz = if mode == OpusMode::Hybrid {
528                16
529            } else {
530                self.sampling_rate.min(16000) / 1000
531            };
532
533            let frame_ms = (frame_size as i32 * 1000) / self.sampling_rate;
534            if !self.silk_initialized || self.silk_enc.s_cmn.fs_khz != silk_fs_khz {
535                let silk_init_bitrate = (((n_bytes - 1) * 8) as i64 * self.sampling_rate as i64
536                    / frame_size as i64) as i32;
537                silk_control_encoder(
538                    state_mut(&mut self.silk_enc),
539                    silk_fs_khz,
540                    frame_ms,
541                    silk_init_bitrate,
542                    self.complexity,
543                );
544                self.silk_enc.s_cmn.use_cbr = if self.use_cbr { 1 } else { 0 };
545
546                self.silk_enc.s_cmn.n_channels = self.channels as i32;
547                self.silk_initialized = true;
548                self.down2_state_first = [0; 2];
549                self.down2_state_second = [0; 2];
550                self.down2_3_state = [0; 6];
551                self.down_1_3_state = silk::resampler::SilkResamplerDown1_3::default();
552            }
553
554            self.silk_enc.s_cmn.use_in_band_fec = if self.use_inband_fec { 1 } else { 0 };
555            self.silk_enc.s_cmn.packet_loss_perc = self.packet_loss_perc.clamp(0, 100);
556
557            self.silk_enc.s_cmn.lbrr_enabled = if self.use_inband_fec { 1 } else { 0 };
558
559            if self.silk_enc.s_cmn.lbrr_gain_increases == 0 {
560                self.silk_enc.s_cmn.lbrr_gain_increases = 2;
561            }
562
563            let hp_freq_smth1 = if mode == OpusMode::CeltOnly {
564                silk_lin2log(60) << 8
565            } else {
566                self.silk_enc.s_cmn.variable_hp_smth1_q15
567            };
568
569            const VARIABLE_HP_SMTH_COEF2_Q16: i32 = 984;
570            self.variable_hp_smth2_q15 = silk_smlawb(
571                self.variable_hp_smth2_q15,
572                hp_freq_smth1 - self.variable_hp_smth2_q15,
573                VARIABLE_HP_SMTH_COEF2_Q16,
574            );
575
576            let cutoff_hz = silk_log2lin(silk_rshift(self.variable_hp_smth2_q15, 8));
577
578            let required_size = frame_size * self.channels;
579            self.buf_filtered.resize(required_size, 0);
580            if self.application == Application::Voip {
581                hp_cutoff(
582                    input,
583                    cutoff_hz,
584                    state_mut(&mut self.buf_filtered),
585                    &mut self.hp_mem,
586                    frame_size,
587                    self.channels,
588                    self.sampling_rate,
589                );
590            } else {
591                for (i, &x) in input.iter().enumerate() {
592                    self.buf_filtered[i] = (x * 32768.0).clamp(-32768.0, 32767.0) as i16;
593                }
594            }
595
596            let input_i16 = state_ref(&self.buf_filtered);
597
598            let silk_input: &[i16] = if mode == OpusMode::SilkOnly && self.sampling_rate > 16000 {
599                if self.sampling_rate == 48000 {
600                    let stage1_size = frame_size / 2;
601                    let mut stage1_buf = [0i16; 480];
602                    silk_resampler_down2(
603                        &mut self.down2_state_first,
604                        &mut stage1_buf[..stage1_size],
605                        input_i16,
606                        frame_size as i32,
607                    );
608                    let silk_frame_size = stage1_size * 2 / 3;
609                    self.buf_silk_input.resize(silk_frame_size, 0);
610                    silk_resampler_down2_3(
611                        &mut self.down2_3_state,
612                        state_mut(&mut self.buf_silk_input),
613                        &stage1_buf[..stage1_size],
614                        stage1_size as i32,
615                    );
616                    state_ref(&self.buf_silk_input)
617                } else if self.sampling_rate == 24000 {
618                    let silk_frame_size = frame_size * 2 / 3;
619                    self.buf_silk_input.resize(silk_frame_size, 0);
620                    silk_resampler_down2_3(
621                        &mut self.down2_3_state,
622                        state_mut(&mut self.buf_silk_input),
623                        input_i16,
624                        frame_size as i32,
625                    );
626                    state_ref(&self.buf_silk_input)
627                } else {
628                    input_i16
629                }
630            } else if mode == OpusMode::SilkOnly && self.channels == 2 {
631                let frame_length = input_i16.len() / 2;
632                self.buf_stereo_mid.resize(frame_length, 0);
633                self.buf_stereo_side.resize(frame_length, 0);
634                for i in 0..frame_length {
635                    let l = input_i16[2 * i] as i32;
636                    let r = input_i16[2 * i + 1] as i32;
637                    self.buf_stereo_mid[i] = ((l + r) / 2) as i16;
638                    self.buf_stereo_side[i] = (l - r) as i16;
639                }
640
641                self.silk_enc.stereo.side.resize(frame_length, 0);
642                self.silk_enc
643                    .stereo
644                    .side
645                    .copy_from_slice(&self.buf_stereo_side[..frame_length]);
646                state_ref(&self.buf_stereo_mid)
647            } else if mode == OpusMode::Hybrid && self.sampling_rate > 16000 {
648                if self.sampling_rate == 48000 {
649                    let silk_frame_size = frame_size / 3;
650                    self.buf_silk_input.resize(silk_frame_size, 0);
651                    silk::resampler::silk_resampler_down_1_3(
652                        &mut self.down_1_3_state,
653                        state_mut(&mut self.buf_silk_input),
654                        input_i16,
655                    );
656                } else {
657                    let silk_frame_size = frame_size * 2 / 3;
658                    self.buf_silk_input.resize(silk_frame_size, 0);
659                    silk_resampler_down2_3(
660                        &mut self.down2_3_state,
661                        state_mut(&mut self.buf_silk_input),
662                        input_i16,
663                        frame_size as i32,
664                    );
665                }
666                state_ref(&self.buf_silk_input)
667            } else {
668                input_i16
669            };
670
671            let mut pn_bytes = 0;
672
673            let silk_rate_for_calc = if mode == OpusMode::Hybrid {
674                16000
675            } else {
676                self.sampling_rate
677            };
678            let silk_frame_len = silk_input.len();
679
680            let silk_bitrate = if mode == OpusMode::Hybrid {
681                let frame_duration_ms = frame_size as i32 * 1000 / self.sampling_rate;
682                let frame20ms = frame_duration_ms >= 20;
683                compute_silk_rate_for_hybrid(self.bitrate_bps, frame20ms)
684            } else {
685                (8i64 * (n_bytes - 1) as i64 * silk_rate_for_calc as i64 / silk_frame_len as i64)
686                    as i32
687            };
688            let silk_max_bits = if mode == OpusMode::Hybrid {
689                let total_max_bits = ((n_bytes - 1) * 8) as i32;
690                if self.use_cbr {
691                    let silk_bits = (silk_bitrate as i64 * silk_frame_len as i64
692                        / silk_rate_for_calc as i64) as i32;
693                    let other_bits = 0i32.max(total_max_bits - silk_bits);
694                    0i32.max(total_max_bits - other_bits * 3 / 4)
695                } else {
696                    let frame_duration_ms = frame_size as i32 * 1000 / self.sampling_rate;
697                    let frame20ms = frame_duration_ms >= 20;
698                    let max_bit_rate = compute_silk_rate_for_hybrid(
699                        total_max_bits * self.sampling_rate / frame_size as i32,
700                        frame20ms,
701                    );
702                    max_bit_rate * frame_size as i32 / self.sampling_rate
703                }
704            } else {
705                ((n_bytes - 1) * 8) as i32
706            };
707            let silk_use_cbr = if mode == OpusMode::Hybrid && self.use_cbr {
708                0
709            } else if self.use_cbr {
710                1
711            } else {
712                0
713            };
714            let ret = silk_encode(
715                state_mut(&mut self.silk_enc),
716                silk_input,
717                silk_input.len(),
718                &mut self.rc,
719                &mut pn_bytes,
720                silk_bitrate,
721                silk_max_bits,
722                silk_use_cbr,
723                1,
724            );
725            if ret != 0 {
726                return Err("SILK encoding failed");
727            }
728        }
729
730        if mode == OpusMode::Hybrid {
731            self.rc.encode_bit_logp(false, 12); // redundancy = 0
732        }
733
734        if mode == OpusMode::Hybrid {
735            let nb_compr_bytes = (n_bytes - 1) as u32;
736            self.rc.shrink(nb_compr_bytes);
737        }
738
739        let silk_ret_bytes = if mode == OpusMode::SilkOnly {
740            ((self.rc.tell() + 7) >> 3) as usize
741        } else {
742            0
743        };
744
745        if mode == OpusMode::CeltOnly || mode == OpusMode::Hybrid {
746            self.celt_enc.complexity = self.complexity;
747            let start_band = if mode == OpusMode::Hybrid { 17 } else { 0 };
748            let total_packet_bits = ((n_bytes - 1) * 8) as i32;
749
750            let celt_input: &[f32] = if self.channels == 1 {
751                input
752            } else {
753                let n = frame_size * self.channels;
754                self.buf_celt_input.resize(n, 0.0);
755                for i in 0..frame_size {
756                    for ch in 0..self.channels {
757                        self.buf_celt_input[ch * frame_size + i] = input[i * self.channels + ch];
758                    }
759                }
760                state_ref(&self.buf_celt_input)
761            };
762
763            if self.rc.tell() <= total_packet_bits {
764                self.celt_enc.encode_with_budget(
765                    celt_input,
766                    frame_size,
767                    &mut self.rc,
768                    start_band,
769                    total_packet_bits,
770                );
771            }
772        }
773
774        self.rc.done();
775
776        if self.rc.error != 0 {
777            return Err("Range coder buffer overflow: encoded data exceeds packet budget");
778        }
779
780        if mode == OpusMode::SilkOnly {
781            let mut ret = silk_ret_bytes.min(self.rc.storage as usize);
782            while ret > 2 && self.rc.buf[ret - 1] == 0 {
783                ret -= 1;
784            }
785
786            let target_total = if self.use_cbr {
787                n_bytes.min(output.len())
788            } else {
789                (ret + 1).min(output.len())
790            };
791
792            let silk_len = ret;
793
794            if !self.use_cbr || silk_len + 1 >= target_total {
795                // VBR or payload fills the target: simple code 0 packet
796                output[0] = toc;
797                let copy_len = silk_len.min(target_total - 1);
798                output[1..1 + copy_len].copy_from_slice(&self.rc.buf[..copy_len]);
799                return Ok((copy_len + 1).min(output.len()));
800            }
801
802            output[0] = toc | 0x03;
803
804            if silk_len + 2 >= target_total {
805                output[1] = 0x01;
806                let copy_len = (target_total - 2).min(silk_len);
807                output[2..2 + copy_len].copy_from_slice(&self.rc.buf[..copy_len]);
808                self.prev_enc_mode = Some(mode);
809                return Ok(target_total.min(output.len()));
810            }
811
812            let pad_amount = target_total - silk_len - 2;
813            output[1] = 0x41;
814
815            let nb_255s = (pad_amount - 1) / 255;
816            let mut ptr = 2;
817            for _ in 0..nb_255s {
818                output[ptr] = 255;
819                ptr += 1;
820            }
821            output[ptr] = (pad_amount - 255 * nb_255s - 1) as u8;
822            ptr += 1;
823
824            output[ptr..ptr + silk_len].copy_from_slice(&self.rc.buf[..silk_len]);
825            ptr += silk_len;
826
827            let fill_end = target_total.min(output.len());
828            for byte in output[ptr..fill_end].iter_mut() {
829                *byte = 0;
830            }
831
832            self.prev_enc_mode = Some(mode);
833            return Ok(target_total.min(output.len()));
834        }
835
836        let payload_len = n_bytes - 1;
837        output[1..1 + payload_len].copy_from_slice(&self.rc.buf[..payload_len]);
838        self.prev_enc_mode = Some(mode);
839        Ok(n_bytes)
840    }
841}
842
843pub struct OpusDecoder {
844    #[cfg(not(feature = "heap"))]
845    celt_dec: CeltDecoder,
846    #[cfg(feature = "heap")]
847    celt_dec: Box<CeltDecoder>,
848    #[cfg(not(feature = "heap"))]
849    silk_dec: silk::dec_api::SilkDecoder,
850    #[cfg(feature = "heap")]
851    silk_dec: Box<silk::dec_api::SilkDecoder>,
852    sampling_rate: i32,
853    channels: usize,
854
855    prev_mode: Option<OpusMode>,
856
857    /// Whether the previous frame had redundancy (mode transition marker).
858    prev_redundancy: bool,
859    frame_size: usize,
860
861    bandwidth: Bandwidth,
862
863    stream_channels: usize,
864
865    silk_resampler: silk::resampler::SilkResampler,
866
867    /// Second resampler instance for stereo channel 1.
868    silk_resampler_2: silk::resampler::SilkResampler,
869
870    prev_internal_rate: i32,
871
872    pub hybrid_skip_celt: bool,
873
874    #[cfg(not(feature = "heap"))]
875    w_pcm_i16: FixedVec<i16, OPUS_PCM_I16>,
876    #[cfg(feature = "heap")]
877    w_pcm_i16: Box<FixedVec<i16, OPUS_PCM_I16>>,
878    #[cfg(not(feature = "heap"))]
879    w_silk_out: FixedVec<f32, OPUS_SUBFRAME_SCRATCH>,
880    #[cfg(feature = "heap")]
881    w_silk_out: Box<FixedVec<f32, OPUS_SUBFRAME_SCRATCH>>,
882    #[cfg(not(feature = "heap"))]
883    w_pcm_resampled: FixedVec<i16, OPUS_SUBFRAME_SCRATCH>,
884    #[cfg(feature = "heap")]
885    w_pcm_resampled: Box<FixedVec<i16, OPUS_SUBFRAME_SCRATCH>>,
886    #[cfg(not(feature = "heap"))]
887    w_celt_planar: FixedVec<f32, OPUS_SUBFRAME_SCRATCH>,
888    #[cfg(feature = "heap")]
889    w_celt_planar: Box<FixedVec<f32, OPUS_SUBFRAME_SCRATCH>>,
890    #[cfg(not(feature = "heap"))]
891    w_celt_out: FixedVec<f32, OPUS_SUBFRAME_SCRATCH>,
892    #[cfg(feature = "heap")]
893    w_celt_out: Box<FixedVec<f32, OPUS_SUBFRAME_SCRATCH>>,
894
895    /// Tail of the previous frame's output, used for smooth_fade at mode
896    /// transitions (libopus pcm_transition + smooth_fade).
897    #[cfg(not(feature = "heap"))]
898    prev_pcm_tail: FixedVec<f32, OPUS_PCM_TAIL>,
899    #[cfg(feature = "heap")]
900    prev_pcm_tail: Box<FixedVec<f32, OPUS_PCM_TAIL>>,
901}
902
903impl OpusDecoder {
904    pub fn new(sampling_rate: i32, channels: usize) -> Result<Self, &'static str> {
905        if ![8000, 12000, 16000, 24000, 48000].contains(&sampling_rate) {
906            return Err("Invalid sampling rate");
907        }
908        if ![1, 2].contains(&channels) {
909            return Err("Invalid number of channels");
910        }
911
912        let mode = modes::default_mode();
913        #[cfg(feature = "heap")]
914        let celt_dec = Box::new(CeltDecoder::new(mode, channels, sampling_rate));
915        #[cfg(not(feature = "heap"))]
916        let celt_dec = CeltDecoder::new(mode, channels, sampling_rate);
917
918        #[cfg(feature = "heap")]
919        let mut silk_dec = Box::new(silk::dec_api::SilkDecoder::new());
920        #[cfg(not(feature = "heap"))]
921        let mut silk_dec = silk::dec_api::SilkDecoder::new();
922        silk_dec.init(sampling_rate.min(16000), channels as i32);
923        silk_dec.channel_state[0].fs_api_hz = sampling_rate;
924
925        Ok(Self {
926            celt_dec,
927            silk_dec,
928            sampling_rate,
929            channels,
930            prev_mode: None,
931            prev_redundancy: false,
932            frame_size: 0,
933            bandwidth: Bandwidth::Auto,
934            stream_channels: channels,
935            silk_resampler: silk::resampler::SilkResampler::default(),
936            silk_resampler_2: silk::resampler::SilkResampler::default(),
937            prev_internal_rate: 0,
938            hybrid_skip_celt: false,
939
940            #[cfg(not(feature = "heap"))]
941            w_pcm_i16: FixedVec::from_value(0i16, 960 * channels),
942            #[cfg(feature = "heap")]
943            w_pcm_i16: Box::new(FixedVec::from_value(0i16, 960 * channels)),
944
945            #[cfg(not(feature = "heap"))]
946            w_silk_out: FixedVec::from_value(0.0f32, OPUS_MAX_SUBFRAME * channels),
947            #[cfg(feature = "heap")]
948            w_silk_out: Box::new(FixedVec::from_value(0.0f32, OPUS_MAX_SUBFRAME * channels)),
949            #[cfg(not(feature = "heap"))]
950            w_pcm_resampled: FixedVec::from_value(0i16, OPUS_MAX_SUBFRAME * channels),
951            #[cfg(feature = "heap")]
952            w_pcm_resampled: Box::new(FixedVec::from_value(0i16, OPUS_MAX_SUBFRAME * channels)),
953            #[cfg(not(feature = "heap"))]
954            w_celt_planar: FixedVec::from_value(0.0f32, OPUS_MAX_SUBFRAME * channels),
955            #[cfg(feature = "heap")]
956            w_celt_planar: Box::new(FixedVec::from_value(0.0f32, OPUS_MAX_SUBFRAME * channels)),
957            #[cfg(not(feature = "heap"))]
958            w_celt_out: FixedVec::from_value(0.0f32, OPUS_MAX_SUBFRAME * channels),
959            #[cfg(feature = "heap")]
960            w_celt_out: Box::new(FixedVec::from_value(0.0f32, OPUS_MAX_SUBFRAME * channels)),
961
962            #[cfg(not(feature = "heap"))]
963            prev_pcm_tail: FixedVec::from_value(0.0f32, 240 * channels),
964            #[cfg(feature = "heap")]
965            prev_pcm_tail: Box::new(FixedVec::from_value(0.0f32, 240 * channels)),
966        })
967    }
968
969    pub fn decode(
970        &mut self,
971        input: &[u8],
972        frame_size: usize,
973        output: &mut [f32],
974    ) -> Result<usize, &'static str> {
975        if input.is_empty() {
976            return Err("Input packet empty");
977        }
978
979        let toc = input[0];
980        let mode = mode_from_toc(toc);
981        let packet_channels = channels_from_toc(toc);
982        let bandwidth = bandwidth_from_toc(toc);
983        let frame_duration_ms = frame_duration_ms_from_toc(toc);
984
985        // A packet of 0 or 1 bytes (ToC only) is a lost/DTX frame. libopus
986        // triggers PLC in this case (opus_decoder.c:315-321). We decode the
987        // frame using the previous mode's concealment.
988        let lost_frame = input.len() <= 1;
989
990        if packet_channels != self.channels {
991            return Err("Channel count mismatch between packet and decoder");
992        }
993
994        let code = toc & 0x03;
995        let frame_count: usize;
996        let frame_payloads: FixedVec<&[u8], OPUS_MAX_PACKET_FRAMES>;
997
998        match code {
999            0 => {
1000                frame_count = 1;
1001                frame_payloads = FixedVec::from_slice(&[&input[1..]]);
1002            }
1003            1 => {
1004                frame_count = 2;
1005                let data_len = input.len() - 1;
1006                // RFC 6716 §3.2.1: code 1 carries two equal-size (CBR) frames,
1007                // so the payload length must be even. libopus rejects odd lengths.
1008                if data_len % 2 != 0 {
1009                    return Err("Code 1: payload length must be even");
1010                }
1011                let half = data_len / 2;
1012                if half == 0 {
1013                    return Err("Code 1: empty frame");
1014                }
1015                frame_payloads = FixedVec::from_slice(&[&input[1..1 + half], &input[1 + half..]]);
1016            }
1017            2 => {
1018                frame_count = 2;
1019                let data = &input[1..];
1020                if data.is_empty() {
1021                    return Err("Code 2 packet has no data");
1022                }
1023                let (first_len, header_size) = parse_frame_size(data)?;
1024                if header_size + first_len > data.len() {
1025                    return Err("Code 2: first frame size exceeds packet");
1026                }
1027                frame_payloads = FixedVec::from_slice(&[
1028                    &data[header_size..header_size + first_len],
1029                    &data[header_size + first_len..],
1030                ]);
1031            }
1032            3 => {
1033                if input.len() < 2 {
1034                    return Err("Code 3 packet too short");
1035                }
1036                let count_byte = input[1];
1037                let n_frames = (count_byte & 0x3F) as usize;
1038                if n_frames < 1 || n_frames > 48 {
1039                    return Err("Code 3: invalid frame count");
1040                }
1041                frame_count = n_frames;
1042                // Bit 6 = padding flag, bit 7 = VBR flag (RFC 6716 §3.2.1).
1043                let padding_flag = (count_byte & 0x40) != 0;
1044                let vbr = (count_byte & 0x80) != 0;
1045
1046                // Parse the optional padding length bytes that follow the count
1047                // byte. The padding *content* (pad_len bytes) lives at the end of
1048                // the packet and is not part of any frame.
1049                let mut ptr = 2usize;
1050                let mut pad_len = 0usize;
1051                if padding_flag {
1052                    loop {
1053                        if ptr >= input.len() {
1054                            return Err("Code 3: padding overflow");
1055                        }
1056                        let p = input[ptr] as usize;
1057                        ptr += 1;
1058                        if p == 255 {
1059                            pad_len += 254;
1060                        } else {
1061                            pad_len += p;
1062                            break;
1063                        }
1064                    }
1065                }
1066                if ptr + pad_len > input.len() {
1067                    return Err("Code 3: padding exceeds packet");
1068                }
1069                let payload_end = input.len() - pad_len;
1070                let payload = &input[ptr..payload_end];
1071
1072                let mut payloads: FixedVec<&[u8], OPUS_MAX_PACKET_FRAMES> = FixedVec::new();
1073                if frame_count == 1 {
1074                    // Single frame: the entire payload region is the frame, both
1075                    // for VBR and CBR (no length prefix is present).
1076                    payloads.push(payload);
1077                } else if vbr {
1078                    // VBR (V=1): per-frame lengths for all frames except the last,
1079                    // which takes the remaining bytes (RFC 6716 §3.2.1).
1080                    let mut cursor = 0usize;
1081                    for i in 0..frame_count {
1082                        if i + 1 < frame_count {
1083                            if cursor >= payload.len() {
1084                                return Err("Code 3: unexpected end in VBR header");
1085                            }
1086                            let (frame_len, header_bytes) =
1087                                parse_frame_size(&payload[cursor..])?;
1088                            cursor += header_bytes;
1089                            if cursor + frame_len > payload.len() {
1090                                return Err("Code 3: frame length exceeds packet");
1091                            }
1092                            payloads.push(&payload[cursor..cursor + frame_len]);
1093                            cursor += frame_len;
1094                        } else {
1095                            // Last frame: remaining bytes, no length prefix.
1096                            if cursor > payload.len() {
1097                                return Err("Code 3: no data for last frame");
1098                            }
1099                            payloads.push(&payload[cursor..]);
1100                        }
1101                    }
1102                } else {
1103                    // CBR (V=0): remaining bytes are split equally into M frames
1104                    // (RFC 6716 §3.2.1: "the remaining bytes are split into M
1105                    // equal chunks").
1106                    if payload.len() % frame_count != 0 {
1107                        return Err("Code 3 CBR: payload not divisible by frame count");
1108                    }
1109                    let frame_len = payload.len() / frame_count;
1110                    for i in 0..frame_count {
1111                        payloads.push(&payload[i * frame_len..(i + 1) * frame_len]);
1112                    }
1113                }
1114                frame_payloads = payloads;
1115            }
1116            _ => unreachable!(),
1117        }
1118
1119        self.frame_size = frame_size;
1120        self.bandwidth = bandwidth;
1121        self.stream_channels = packet_channels;
1122
1123        // Derive the actual per-frame sample count from the TOC, not from the
1124        // caller's frame_size. This prevents panics in bands.rs/celt.rs when
1125        // the caller passes a mismatched frame_size (issue #7 sub-item 1):
1126        // the internal decoders always get the correct geometry.
1127        let toc_frame_size = frame_samples_from_toc(toc, self.sampling_rate)
1128            .ok_or("Invalid TOC for sampling rate")?;
1129        let decoded_total = toc_frame_size * frame_count;
1130        if frame_size < decoded_total {
1131            return Err("frame_size too small for packet");
1132        }
1133        if output.len() < decoded_total * self.channels {
1134            return Err("Output buffer too small for packet");
1135        }
1136        // Zero-fill any extra space the caller provided beyond what the packet
1137        // actually produces, so stale data is never left in the buffer.
1138        if output.len() > decoded_total * self.channels {
1139            for v in &mut output[decoded_total * self.channels..] {
1140                *v = 0.0;
1141            }
1142        }
1143        let sub_frame_size = toc_frame_size;
1144        let sub_output_len = sub_frame_size * self.channels;
1145
1146        // Detect mode transition and reset CELT decoder state to prevent
1147        // cross-mode artifacts (libopus opus_decoder.c:602-604).
1148        // This is the primary fix for issue #8/#9 alignment divergence:
1149        // stale CELT MDCT/prefilter state at SILK↔CELT boundaries causes
1150        // discontinuities that accumulate across transitions.
1151        let mode_transition = match self.prev_mode {
1152            Some(prev) if prev != mode && !self.prev_redundancy => true,
1153            _ => false,
1154        };
1155        if mode_transition {
1156            self.celt_dec.reset_state();
1157        }
1158
1159        // Generate SILK PLC audio for the mode-transition bridge. libopus
1160        // synthesizes 5ms (F5) of pitch-extrapolated audio in the OLD mode
1161        // (opus_decoder.c:387-391) and crossfades it with the new frame. We
1162        // reuse the F5-sized prev_pcm_tail buffer for this bridge.
1163        let f5_bridge = self.sampling_rate as usize / 200; // F5 = Fs/200
1164        if mode_transition
1165            && f5_bridge > 0
1166            && matches!(
1167                self.prev_mode,
1168                Some(OpusMode::SilkOnly) | Some(OpusMode::Hybrid)
1169            )
1170            && self.prev_internal_rate > 0
1171        {
1172            let internal_rate = self.prev_internal_rate;
1173            let plc_internal_len = (10 * internal_rate / 1000) as usize;
1174            let mut plc_rc = RangeCoder::new_decoder(&[]);
1175            let mut plc_i16: FixedVec<i16, OPUS_PCM_I16> =
1176                FixedVec::from_value(0i16, plc_internal_len * self.channels);
1177            let n = self.silk_dec.decode(
1178                &mut plc_rc,
1179                &mut plc_i16,
1180                silk::decode_frame::FLAG_PACKET_LOST,
1181                true,
1182                10,
1183                internal_rate,
1184            );
1185            if n > 0 {
1186                let bridge_ch = f5_bridge * self.channels;
1187                let bridge_len = bridge_ch.min(self.prev_pcm_tail.len());
1188                if internal_rate == self.sampling_rate {
1189                    // No resampling: copy PLC samples directly (ch0 planar).
1190                    let n_us = n as usize;
1191                    for ch in 0..self.channels {
1192                        let src_base = ch * n_us;
1193                        for i in 0..(bridge_len / self.channels).min(n_us) {
1194                            let dst = i * self.channels + ch;
1195                            if dst < bridge_len {
1196                                self.prev_pcm_tail[dst] = plc_i16[src_base + i] as f32 / 32768.0;
1197                            }
1198                        }
1199                    }
1200                } else if self.silk_resampler.is_initialized() {
1201                    // Resample channel 0 to the API rate for the bridge.
1202                    let ratio = self.sampling_rate as f64 / internal_rate as f64;
1203                    let out_len = ((n as f64 * ratio) as usize).min(f5_bridge);
1204                    let n_us = n as usize;
1205                    let mut resampled: FixedVec<i16, OPUS_MAX_FRAME> = FixedVec::from_value(0i16, out_len);
1206                    self.silk_resampler.process(
1207                        &mut resampled,
1208                        &plc_i16[..n_us],
1209                        n,
1210                    );
1211                    for i in 0..out_len {
1212                        if i < bridge_len / self.channels {
1213                            for ch in 0..self.channels {
1214                                self.prev_pcm_tail[i * self.channels + ch] =
1215                                    resampled[i] as f32 / 32768.0;
1216                            }
1217                        }
1218                    }
1219                }
1220            }
1221        }
1222
1223        // Track whether this packet uses Hybrid redundancy.
1224        let mut has_redundancy = false;
1225
1226        match mode {
1227            OpusMode::SilkOnly => {
1228                let internal_sample_rate = match bandwidth {
1229                    Bandwidth::Narrowband => 8000,
1230                    Bandwidth::Mediumband => 12000,
1231                    Bandwidth::Wideband => 16000,
1232                    _ => 16000,
1233                };
1234                let internal_frame_size =
1235                    (frame_duration_ms * internal_sample_rate / 1000) as usize;
1236
1237                if self.sampling_rate != internal_sample_rate
1238                    && internal_sample_rate != self.prev_internal_rate
1239                {
1240                    self.silk_resampler
1241                        .init(internal_sample_rate, self.sampling_rate);
1242                    self.silk_resampler_2
1243                        .init(internal_sample_rate, self.sampling_rate);
1244                }
1245                // Always track the SILK internal rate so the mode-transition
1246                // PLC bridge can be generated (even when no resampling is
1247                // needed, e.g. 16kHz decoder + SILK WB).
1248                self.prev_internal_rate = internal_sample_rate;
1249
1250                for (fi, payload) in frame_payloads.iter().enumerate() {
1251                    let mut rc = RangeCoder::new_decoder(payload);
1252                    let pcm_i16_len = internal_frame_size * self.channels;
1253                    debug_assert!(pcm_i16_len <= self.w_pcm_i16.len());
1254
1255                    let ret = {
1256                        let (silk_dec, pcm_i16) =
1257                            (state_mut(&mut self.silk_dec), state_mut(&mut self.w_pcm_i16));
1258                        let lost_flag = if lost_frame {
1259                            silk::decode_frame::FLAG_PACKET_LOST
1260                        } else {
1261                            silk::decode_frame::FLAG_DECODE_NORMAL
1262                        };
1263                        silk_dec.decode(
1264                            &mut rc,
1265                            &mut pcm_i16[..pcm_i16_len],
1266                            lost_flag,
1267                            true,
1268                            frame_duration_ms,
1269                            internal_sample_rate,
1270                        )
1271                    };
1272
1273                    if ret < 0 {
1274                        return Err("SILK decoding failed");
1275                    }
1276
1277                    let decoded_samples = ret as usize;
1278                    let out_start = fi * sub_output_len;
1279
1280                    // SILK decoder outputs planar: ch0 at [0..fl], ch1 at [fl..2*fl].
1281                    if self.sampling_rate == internal_sample_rate {
1282                        let frames = decoded_samples.min(sub_frame_size);
1283                        for i in 0..frames {
1284                            for ch in 0..self.channels {
1285                                let src = if ch == 0 { i } else { internal_frame_size + i };
1286                                let v = self.w_pcm_i16[src] as f32 / 32768.0;
1287                                let idx = out_start + i * self.channels + ch;
1288                                if idx < output.len() {
1289                                    output[idx] = v;
1290                                }
1291                            }
1292                        }
1293                    } else {
1294                        let ratio = self.sampling_rate as f64 / internal_sample_rate as f64;
1295                        let out_len =
1296                            ((decoded_samples as f64 * ratio) as usize).min(sub_frame_size);
1297                        debug_assert!(out_len * self.channels <= self.w_pcm_resampled.len());
1298                        // Resample channel 0.
1299                        {
1300                            let (res, inp, out) = (
1301                                &mut self.silk_resampler,
1302                                state_ref(&self.w_pcm_i16),
1303                                state_mut(&mut self.w_pcm_resampled),
1304                            );
1305                            res.process(
1306                                &mut out[..out_len],
1307                                &inp[..decoded_samples],
1308                                decoded_samples as i32,
1309                            );
1310                        }
1311                        // Resample channel 1 (stereo only).
1312                        if self.channels == 2 {
1313                            let (res, inp, out) = (
1314                                &mut self.silk_resampler_2,
1315                                state_ref(&self.w_pcm_i16),
1316                                state_mut(&mut self.w_pcm_resampled),
1317                            );
1318                            res.process(
1319                                &mut out[out_len..2 * out_len],
1320                                &inp[internal_frame_size..internal_frame_size + decoded_samples],
1321                                decoded_samples as i32,
1322                            );
1323                        }
1324                        let frames = out_len.min(sub_frame_size);
1325                        for i in 0..frames {
1326                            for ch in 0..self.channels {
1327                                let v = self.w_pcm_resampled[ch * out_len + i] as f32 / 32768.0;
1328                                let idx = out_start + i * self.channels + ch;
1329                                if idx < output.len() {
1330                                    output[idx] = v;
1331                                }
1332                            }
1333                        }
1334                    }
1335                }
1336                decoded_total
1337            }
1338
1339            OpusMode::CeltOnly => {
1340                let celt_end_band = self.celt_end_band_from_toc(toc);
1341
1342                for (fi, payload) in frame_payloads.iter().enumerate() {
1343                    let mut rc = RangeCoder::new_decoder(payload);
1344                    let total_bits = (payload.len() * 8) as i32;
1345                    let needed = sub_frame_size * self.channels;
1346                    let out_start = fi * needed;
1347                    let out_end = (out_start + needed).min(output.len());
1348
1349                    if output.len() < out_end {
1350                        return Err("Output buffer too small");
1351                    }
1352
1353                    if self.channels == 1 {
1354                        self.celt_dec.decode_from_range_coder_with_band_range(
1355                            &mut rc,
1356                            total_bits,
1357                            sub_frame_size,
1358                            &mut output[out_start..out_end],
1359                            0,
1360                            celt_end_band,
1361                        );
1362                        for sample in &mut output[out_start..out_end] {
1363                            *sample = sample.clamp(-1.0, 1.0);
1364                        }
1365                    } else {
1366                        self.celt_dec.decode_from_range_coder_with_band_range(
1367                            &mut rc,
1368                            total_bits,
1369                            sub_frame_size,
1370                            &mut self.w_celt_planar[..needed],
1371                            0,
1372                            celt_end_band,
1373                        );
1374                        for i in 0..sub_frame_size {
1375                            for ch in 0..self.channels {
1376                                let idx = out_start + i * self.channels + ch;
1377                                output[idx] =
1378                                    self.w_celt_planar[ch * sub_frame_size + i].clamp(-1.0, 1.0);
1379                            }
1380                        }
1381                    }
1382                }
1383                decoded_total
1384            }
1385
1386            OpusMode::Hybrid => {
1387                let internal_sample_rate = 16000;
1388                let internal_frame_size =
1389                    (frame_duration_ms * internal_sample_rate / 1000) as usize;
1390                let celt_end_band = self.celt_end_band_from_toc(toc);
1391
1392                if self.sampling_rate != internal_sample_rate
1393                    && internal_sample_rate != self.prev_internal_rate
1394                {
1395                    self.silk_resampler
1396                        .init(internal_sample_rate, self.sampling_rate);
1397                    self.silk_resampler_2
1398                        .init(internal_sample_rate, self.sampling_rate);
1399                }
1400                self.prev_internal_rate = internal_sample_rate;
1401
1402                for (fi, payload) in frame_payloads.iter().enumerate() {
1403                    let mut rc = RangeCoder::new_decoder(payload);
1404                    let pcm_silk_i16_len = internal_frame_size * self.channels;
1405                    debug_assert!(pcm_silk_i16_len <= self.w_pcm_i16.len());
1406
1407                    let ret = {
1408                        let (silk_dec, pcm_i16) =
1409                            (state_mut(&mut self.silk_dec), state_mut(&mut self.w_pcm_i16));
1410                        let lost_flag = if lost_frame {
1411                            silk::decode_frame::FLAG_PACKET_LOST
1412                        } else {
1413                            silk::decode_frame::FLAG_DECODE_NORMAL
1414                        };
1415                        silk_dec.decode(
1416                            &mut rc,
1417                            &mut pcm_i16[..pcm_silk_i16_len],
1418                            lost_flag,
1419                            true,
1420                            frame_duration_ms,
1421                            internal_sample_rate,
1422                        )
1423                    };
1424
1425                    if ret < 0 {
1426                        return Err("SILK decoding failed");
1427                    }
1428
1429                    let silk_out_len = sub_frame_size * self.channels;
1430                    self.w_silk_out[..silk_out_len].fill(0.0);
1431                    if ret > 0 {
1432                        let decoded_samples = ret as usize;
1433                        // SILK decoder outputs planar: ch0 at [0..fl], ch1 at [fl..2*fl].
1434                        if self.sampling_rate == internal_sample_rate {
1435                            let frames = decoded_samples.min(sub_frame_size);
1436                            for i in 0..frames {
1437                                for ch in 0..self.channels {
1438                                    let src = if ch == 0 { i } else { internal_frame_size + i };
1439                                    let v = self.w_pcm_i16[src] as f32 / 32768.0;
1440                                    let idx = i * self.channels + ch;
1441                                    if idx < silk_out_len {
1442                                        self.w_silk_out[idx] = v;
1443                                    }
1444                                }
1445                            }
1446                        } else {
1447                            let ratio = self.sampling_rate as f64 / internal_sample_rate as f64;
1448                            let out_len =
1449                                ((decoded_samples as f64 * ratio) as usize).min(sub_frame_size);
1450                            debug_assert!(out_len * self.channels <= self.w_pcm_resampled.len());
1451                            // Resample channel 0.
1452                            {
1453                                let (res, inp, out) = (
1454                                    &mut self.silk_resampler,
1455                                    state_ref(&self.w_pcm_i16),
1456                                    state_mut(&mut self.w_pcm_resampled),
1457                                );
1458                                res.process(
1459                                    &mut out[..out_len],
1460                                    &inp[..decoded_samples],
1461                                    decoded_samples as i32,
1462                                );
1463                            }
1464                            // Resample channel 1 (stereo only).
1465                            if self.channels == 2 {
1466                                let (res, inp, out) = (
1467                                    &mut self.silk_resampler_2,
1468                                    state_ref(&self.w_pcm_i16),
1469                                    state_mut(&mut self.w_pcm_resampled),
1470                                );
1471                                res.process(
1472                                    &mut out[out_len..2 * out_len],
1473                                    &inp[internal_frame_size..internal_frame_size + decoded_samples],
1474                                    decoded_samples as i32,
1475                                );
1476                            }
1477                            let frames = out_len.min(sub_frame_size);
1478                            for i in 0..frames {
1479                                for ch in 0..self.channels {
1480                                    let v = self.w_pcm_resampled[ch * out_len + i] as f32 / 32768.0;
1481                                    let idx = i * self.channels + ch;
1482                                    if idx < silk_out_len {
1483                                        self.w_silk_out[idx] = v;
1484                                    }
1485                                }
1486                            }
1487                        }
1488                    }
1489
1490                    let total_bits = (payload.len() * 8) as i32;
1491                    let redundancy = rc.decode_bit_logp(12);
1492                    let skip_celt = if redundancy {
1493                        let _celt_to_silk = rc.decode_bit_logp(1);
1494                        has_redundancy = true;
1495                        // When redundancy is present, the redundant CELT frame
1496                        // provides the transition audio. We skip the main CELT
1497                        // decode for this sub-frame (the SILK output stands alone)
1498                        // — a simplified version of libopus's behaviour where the
1499                        // redundant frame is decoded separately and crossfaded.
1500                        true
1501                    } else {
1502                        false
1503                    };
1504
1505                    if skip_celt {
1506                        self.w_celt_out[..silk_out_len].fill(0.0);
1507                    } else {
1508                        let (celt_dec, celt_planar) =
1509                            (state_mut(&mut self.celt_dec), state_mut(&mut self.w_celt_planar));
1510                        celt_dec.decode_from_range_coder_with_band_range(
1511                            &mut rc,
1512                            total_bits,
1513                            sub_frame_size,
1514                            &mut celt_planar[..silk_out_len],
1515                            17,
1516                            celt_end_band,
1517                        );
1518
1519                        if self.channels == 1 {
1520                            self.w_celt_out[..silk_out_len]
1521                                .copy_from_slice(&self.w_celt_planar[..silk_out_len]);
1522                        } else {
1523                            for i in 0..sub_frame_size {
1524                                for ch in 0..self.channels {
1525                                    self.w_celt_out[i * self.channels + ch] =
1526                                        self.w_celt_planar[ch * sub_frame_size + i];
1527                                }
1528                            }
1529                        }
1530                    }
1531
1532                    let out_start = fi * silk_out_len;
1533                    let total = silk_out_len.min(output.len() - out_start);
1534                    for j in 0..total {
1535                        output[out_start + j] =
1536                            (self.w_silk_out[j] + self.w_celt_out[j]).clamp(-1.0, 1.0);
1537                    }
1538                }
1539                decoded_total
1540            }
1541        };
1542
1543        // Apply PLC-style bridging at mode transitions (libopus
1544        // opus_decoder.c:660-679). The first F2_5 of the output is replaced
1545        // with the previous frame's tail (PLC bridge), and the next F2_5 is
1546        // crossfaded between the bridge and the new frame's CELT output.
1547        // F5 = Fs/200, F2_5 = Fs/400.
1548        let f2_5 = self.sampling_rate as usize / 400;
1549        let f5 = f2_5 * 2;
1550        if mode_transition && f5 > 0 && decoded_total >= f5 {
1551            let window = modes::default_mode().window;
1552            let inc = (48000 / self.sampling_rate) as usize;
1553            let f2_5_ch = f2_5 * self.channels;
1554            let f5_ch = f5 * self.channels;
1555            // First F2_5: pure bridging audio from previous frame's tail.
1556            output[..f2_5_ch].copy_from_slice(&self.prev_pcm_tail[..f2_5_ch]);
1557            // Next F2_5: crossfade bridge → new CELT output.
1558            let new_mid: FixedVec<f32, OPUS_PCM_TAIL> = FixedVec::from_slice(&output[f2_5_ch..f5_ch]);
1559            smooth_fade(
1560                &self.prev_pcm_tail[f2_5_ch..f5_ch],
1561                &new_mid,
1562                &mut output[f2_5_ch..f5_ch],
1563                f2_5,
1564                self.channels,
1565                window,
1566                inc,
1567            );
1568        }
1569
1570        // Save the tail of this frame for the next transition (F5 samples).
1571        let tail_len = f5 * self.channels;
1572        let out_total = decoded_total * self.channels;
1573        if out_total >= tail_len && tail_len <= self.prev_pcm_tail.len() {
1574            self.prev_pcm_tail[..tail_len]
1575                .copy_from_slice(&output[out_total - tail_len..out_total]);
1576        }
1577
1578        self.prev_mode = Some(mode);
1579        self.prev_redundancy = has_redundancy;
1580        Ok(decoded_total)
1581    }
1582}
1583
1584impl OpusDecoder {
1585    #[inline(always)]
1586    fn celt_end_band_from_toc(&self, toc: u8) -> usize {
1587        let mode = modes::default_mode();
1588        let top = mode.eff_ebands;
1589        if mode_from_toc(toc) == OpusMode::CeltOnly && toc >= 0x80 {
1590            const FROM_OPUS_TABLE: [u8; 16] = [
1591                0x80, 0x88, 0x90, 0x98, 0x40, 0x48, 0x50, 0x58, 0x20, 0x28, 0x30, 0x38, 0x00, 0x08,
1592                0x10, 0x18,
1593            ];
1594            let idx = ((toc >> 3) - 16) as usize;
1595            let data0 = FROM_OPUS_TABLE[idx] | (toc & 0x7);
1596            let trim = (data0 >> 5) as usize;
1597            return top.saturating_sub(2 * trim).max(1);
1598        }
1599        top
1600    }
1601}
1602
1603fn frame_rate_from_params(sampling_rate: i32, frame_size: usize) -> Option<i32> {
1604    let frame_size = frame_size as i32;
1605    if frame_size == 0 || sampling_rate % frame_size != 0 {
1606        return None;
1607    }
1608    Some(sampling_rate / frame_size)
1609}
1610
1611fn gen_toc(mode: OpusMode, frame_rate: i32, bandwidth: Bandwidth, channels: usize) -> u8 {
1612    let mut rate = frame_rate;
1613    let mut period = 0;
1614    while rate < 400 {
1615        rate <<= 1;
1616        period += 1;
1617    }
1618
1619    let mut toc = match mode {
1620        OpusMode::SilkOnly => {
1621            let bw = (bandwidth as i32 - Bandwidth::Narrowband as i32) << 5;
1622            let per = (period - 2) << 3;
1623            (bw | per) as u8
1624        }
1625        OpusMode::CeltOnly => {
1626            let mut tmp = bandwidth as i32 - Bandwidth::Mediumband as i32;
1627            if tmp < 0 {
1628                tmp = 0;
1629            }
1630            let per = period << 3;
1631            (0x80 | (tmp << 5) | per) as u8
1632        }
1633        OpusMode::Hybrid => {
1634            let base_config = if bandwidth == Bandwidth::Superwideband {
1635                12
1636            } else {
1637                14
1638            };
1639            let period_offset = if frame_rate >= 100 { 0 } else { 1 };
1640            ((base_config + period_offset) << 3) as u8
1641        }
1642    };
1643
1644    if channels == 2 {
1645        toc |= 0x04;
1646    }
1647    toc
1648}
1649
1650fn mode_from_toc(toc: u8) -> OpusMode {
1651    if toc & 0x80 != 0 {
1652        OpusMode::CeltOnly
1653    } else if toc & 0x60 == 0x60 {
1654        OpusMode::Hybrid
1655    } else {
1656        OpusMode::SilkOnly
1657    }
1658}
1659
1660fn bandwidth_from_toc(toc: u8) -> Bandwidth {
1661    let mode = mode_from_toc(toc);
1662    match mode {
1663        OpusMode::SilkOnly => {
1664            let bw_bits = (toc >> 5) & 0x03;
1665            match bw_bits {
1666                0 => Bandwidth::Narrowband,
1667                1 => Bandwidth::Mediumband,
1668                2 => Bandwidth::Wideband,
1669                _ => Bandwidth::Wideband,
1670            }
1671        }
1672        OpusMode::Hybrid => {
1673            let bw_bit = (toc >> 4) & 0x01;
1674            if bw_bit == 0 {
1675                Bandwidth::Superwideband
1676            } else {
1677                Bandwidth::Fullband
1678            }
1679        }
1680        OpusMode::CeltOnly => {
1681            let bw_bits = (toc >> 5) & 0x03;
1682            match bw_bits {
1683                0 => Bandwidth::Mediumband,
1684                1 => Bandwidth::Wideband,
1685                2 => Bandwidth::Superwideband,
1686                3 => Bandwidth::Fullband,
1687                _ => Bandwidth::Fullband,
1688            }
1689        }
1690    }
1691}
1692
1693fn frame_duration_ms_from_toc(toc: u8) -> i32 {
1694    let mode = mode_from_toc(toc);
1695    match mode {
1696        OpusMode::SilkOnly => {
1697            let config = (toc >> 3) & 0x03;
1698            match config {
1699                0 => 10,
1700                1 => 20,
1701                2 => 40,
1702                3 => 60,
1703                _ => 20,
1704            }
1705        }
1706        OpusMode::Hybrid => {
1707            let config = (toc >> 3) & 0x01;
1708            if config == 0 { 10 } else { 20 }
1709        }
1710        OpusMode::CeltOnly => {
1711            let config = (toc >> 3) & 0x03;
1712            match config {
1713                0 => 2,
1714                1 => 5,
1715                2 => 10,
1716                3 => 20,
1717                _ => 20,
1718            }
1719        }
1720    }
1721}
1722
1723/// Compute the per-frame sample count implied by the TOC byte at a given
1724/// sampling rate. For CELT this uses the frame-rate derivation (which handles
1725/// the 2.5 ms case correctly, unlike integer millisecond arithmetic).
1726fn frame_samples_from_toc(toc: u8, sampling_rate: i32) -> Option<usize> {
1727    let mode = mode_from_toc(toc);
1728    match mode {
1729        OpusMode::CeltOnly => {
1730            let period = ((toc >> 3) & 0x03) as i32;
1731            let frame_rate = 400 >> period;
1732            if frame_rate == 0 || sampling_rate % frame_rate != 0 {
1733                return None;
1734            }
1735            Some((sampling_rate / frame_rate) as usize)
1736        }
1737        OpusMode::SilkOnly | OpusMode::Hybrid => {
1738            let duration_ms = frame_duration_ms_from_toc(toc);
1739            Some((sampling_rate as i64 * duration_ms as i64 / 1000) as usize)
1740        }
1741    }
1742}
1743
1744fn channels_from_toc(toc: u8) -> usize {
1745    if toc & 0x04 != 0 { 2 } else { 1 }
1746}
1747
1748/// Crossfade two signals using a squared-sine window (libopus smooth_fade).
1749/// `window` is the 120-sample CELT window at 48 kHz; `inc` = 48000/Fs strides it.
1750fn smooth_fade(
1751    in1: &[f32],
1752    in2: &[f32],
1753    out: &mut [f32],
1754    overlap: usize,
1755    channels: usize,
1756    window: &[f32],
1757    inc: usize,
1758) {
1759    for c in 0..channels {
1760        for i in 0..overlap {
1761            let wi = i * inc;
1762            if wi >= window.len() {
1763                break;
1764            }
1765            let w = window[wi] * window[wi];
1766            out[i * channels + c] = w * in2[i * channels + c] + (1.0 - w) * in1[i * channels + c];
1767        }
1768    }
1769}
1770
1771/// Parse an Opus frame length per RFC 6716 §3.2.1, identical to libopus
1772/// `parse_size()`:
1773///   - `0`: no frame (DTX / lost packet)
1774///   - `1..=251`: length of the frame in bytes (one byte consumed)
1775///   - `252..=255`: a second byte is read; length = `second*4 + first`
1776///
1777/// Returns `(length, bytes_consumed)`.
1778fn parse_frame_size(data: &[u8]) -> Result<(usize, usize), &'static str> {
1779    let first = *data.first().ok_or("truncated frame length")? as usize;
1780    if first < 252 {
1781        Ok((first, 1))
1782    } else {
1783        let second = *data.get(1).ok_or("truncated frame length")? as usize;
1784        Ok((second * 4 + first, 2))
1785    }
1786}
1787
1788#[cfg(all(test, feature = "std"))]
1789mod tests {
1790    use super::*;
1791
1792    fn frame_size_from_toc(toc: u8, sampling_rate: i32) -> Option<usize> {
1793        let mode = mode_from_toc(toc);
1794        match mode {
1795            OpusMode::CeltOnly => {
1796                let period = ((toc >> 3) & 0x03) as i32;
1797                let frame_rate = 400 >> period;
1798                if frame_rate == 0 || sampling_rate % frame_rate != 0 {
1799                    return None;
1800                }
1801                Some((sampling_rate / frame_rate) as usize)
1802            }
1803            OpusMode::SilkOnly => {
1804                let duration_ms = frame_duration_ms_from_toc(toc);
1805                Some((sampling_rate as i64 * duration_ms as i64 / 1000) as usize)
1806            }
1807            OpusMode::Hybrid => {
1808                let duration_ms = frame_duration_ms_from_toc(toc);
1809                Some((sampling_rate as i64 * duration_ms as i64 / 1000) as usize)
1810            }
1811        }
1812    }
1813
1814    #[test]
1815    fn gen_toc_matches_celt_reference_values() {
1816        let sampling_rate = 48_000;
1817        let cases = [
1818            (120usize, 0xE0u8),
1819            (240usize, 0xE8u8),
1820            (480usize, 0xF0u8),
1821            (960usize, 0xF8u8),
1822        ];
1823
1824        for (frame_size, expected_toc) in cases {
1825            let frame_rate = frame_rate_from_params(sampling_rate, frame_size).unwrap();
1826            let toc = gen_toc(OpusMode::CeltOnly, frame_rate, Bandwidth::Fullband, 1);
1827            assert_eq!(
1828                toc, expected_toc,
1829                "frame_size {} expected TOC {:02X} got {:02X}",
1830                frame_size, expected_toc, toc
1831            );
1832            let decoded_size = frame_size_from_toc(toc, sampling_rate).unwrap();
1833            assert_eq!(decoded_size, frame_size);
1834        }
1835
1836        let stereo_toc = gen_toc(
1837            OpusMode::CeltOnly,
1838            frame_rate_from_params(sampling_rate, 960).unwrap(),
1839            Bandwidth::Fullband,
1840            2,
1841        );
1842        assert_eq!(channels_from_toc(stereo_toc), 2);
1843    }
1844
1845    #[test]
1846    fn test_celt_decoder_large_frame_sizes() {
1847        let sampling_rate = 48000;
1848        let channels = 1;
1849
1850        let mut decoder = OpusDecoder::new(sampling_rate, channels).unwrap();
1851
1852        let frame_sizes = [120, 240, 480, 960];
1853
1854        for frame_size in frame_sizes {
1855            let toc = gen_toc(
1856                OpusMode::CeltOnly,
1857                frame_rate_from_params(sampling_rate, frame_size).unwrap(),
1858                Bandwidth::Fullband,
1859                channels,
1860            );
1861            let packet = [toc, 0, 0, 0, 0];
1862
1863            let mut output = vec![0.0f32; frame_size * channels];
1864
1865            let _ = decoder.decode(&packet, frame_size, &mut output);
1866        }
1867
1868        let channels = 2;
1869        let mut decoder = OpusDecoder::new(sampling_rate, channels).unwrap();
1870
1871        for frame_size in frame_sizes {
1872            let toc = gen_toc(
1873                OpusMode::CeltOnly,
1874                frame_rate_from_params(sampling_rate, frame_size).unwrap(),
1875                Bandwidth::Fullband,
1876                channels,
1877            );
1878            let packet = [toc, 0, 0, 0, 0];
1879
1880            let mut output = vec![0.0f32; frame_size * channels];
1881            let _ = decoder.decode(&packet, frame_size, &mut output);
1882        }
1883    }
1884
1885    #[test]
1886    fn test_celt_decoder_edge_case_frame_sizes() {
1887        let sampling_rate = 48000;
1888        let channels = 1;
1889        let mut decoder = OpusDecoder::new(sampling_rate, channels).unwrap();
1890
1891        let edge_sizes = [2048, 2167, 2168, 2169, 2880, 3072];
1892
1893        for frame_size in edge_sizes {
1894            let mut output = vec![0.0f32; frame_size * channels];
1895
1896            let _ = decoder.decode(&[0x80, 0, 0, 0], frame_size, &mut output);
1897        }
1898    }
1899
1900    // Regression test for: "index out of bounds: the len is 48 but the index is 119"
1901    // Root cause: frame_size=48 at 48kHz gives frame_rate=1000, which is not a valid
1902    // Hybrid-mode frame rate but was not validated.  CELT's lm-search then silently
1903    // fell back to lm=0, computed n2=120, and wrote output[119] into a 48-element
1904    // slice.  Triggered via G.729-decoded PCM (8kHz) passed to a 48kHz Opus encoder
1905    // without proper resampling, so the encoder received 48 samples instead of 480.
1906    #[test]
1907    fn test_invalid_small_frame_size_returns_error_not_panic() {
1908        let mut enc = OpusEncoder::new(48000, 2, Application::Voip).unwrap();
1909        enc.bitrate_bps = 64000;
1910        enc.complexity = 5;
1911        enc.use_cbr = true;
1912
1913        // 48 samples at 48kHz = 1ms → frame_rate=1000, invalid for Hybrid mode.
1914        let input = vec![0.0f32; 48 * 2]; // stereo interleaved
1915        let mut output = vec![0u8; 256];
1916
1917        let result = enc.encode(&input, 48, &mut output);
1918        assert!(
1919            result.is_err(),
1920            "encode with invalid frame_size=48 should return Err, not panic"
1921        );
1922    }
1923
1924    // Also verify that the Audio application path (always Hybrid at 48 kHz) rejects
1925    // the same bad frame size.
1926    #[test]
1927    fn test_invalid_small_frame_size_audio_application_returns_error() {
1928        let mut enc = OpusEncoder::new(48000, 1, Application::Audio).unwrap();
1929        let input = vec![0.0f32; 48];
1930        let mut output = vec![0u8; 256];
1931
1932        let result = enc.encode(&input, 48, &mut output);
1933        assert!(
1934            result.is_err(),
1935            "Audio/48kHz encoder with frame_size=48 should return Err"
1936        );
1937    }
1938}