Skip to main content

opus_decoder/
lib.rs

1//! Public API for the pure Rust Opus decoder port.
2//!
3//! The crate keeps the CELT and SILK internals close to libopus while exposing
4//! stable packet-to-PCM decoding entry points.
5
6#![forbid(unsafe_code)]
7#![allow(
8    clippy::erasing_op,
9    clippy::identity_op,
10    clippy::precedence,
11    clippy::int_plus_one
12)]
13
14macro_rules! debug_trace {
15    ($($arg:tt)*) => {};
16}
17
18mod celt;
19pub(crate) mod compare;
20mod entropy;
21mod error;
22mod multistream;
23mod packet;
24mod silk;
25use crate::entropy::EcDec;
26use core::sync::atomic::{AtomicUsize, Ordering};
27
28pub(crate) use error::Error;
29pub use multistream::OpusMultistreamDecoder;
30
31static TRACE_DECODE_PACKET_IDX: AtomicUsize = AtomicUsize::new(0);
32
33/// High-level single-stream Opus decoder.
34///
35/// This wrapper exposes the stable public API for packet-to-PCM decoding while
36/// keeping CELT and SILK state management internal to the crate.
37pub struct OpusDecoder {
38    decoder: Decoder,
39    float_scratch: Vec<i16>,
40    loss_count: u32,
41    last_packet_duration: usize,
42}
43
44impl OpusDecoder {
45    /// Maximum decoded frame size per channel at 48 kHz.
46    pub const MAX_FRAME_SIZE_48K: usize = Decoder::MAX_FRAME_SIZE_48K;
47
48    /// Create a new decoder.
49    ///
50    /// `sample_rate` must be `8000`, `12000`, `16000`, `24000`, or `48000`,
51    /// and `channels` must be `1` or `2`.
52    ///
53    /// # Examples
54    ///
55    /// ```rust
56    /// use opus_decoder::OpusDecoder;
57    ///
58    /// let decoder = OpusDecoder::new(48_000, 2)?;
59    /// # let _ = decoder;
60    /// # Ok::<(), opus_decoder::OpusError>(())
61    /// ```
62    pub fn new(sample_rate: u32, channels: usize) -> Result<Self, OpusError> {
63        let channels =
64            u8::try_from(channels).map_err(|_| OpusError::InvalidArgument("channels"))?;
65        let decoder = Decoder::new(sample_rate, channels).map_err(OpusError::from)?;
66
67        Ok(Self {
68            decoder,
69            float_scratch: Vec::new(),
70            loss_count: 0,
71            last_packet_duration: 0,
72        })
73    }
74
75    /// Return the maximum decoded frame size per channel for this output rate.
76    pub fn max_frame_size_per_channel(&self) -> usize {
77        self.decoder.max_frame_size_per_channel()
78    }
79
80    /// Mirror internal PLC bookkeeping into the public wrapper.
81    ///
82    /// Parameters: none.
83    /// Returns: nothing.
84    fn sync_state_from_decoder(&mut self) {
85        self.loss_count = self.decoder.loss_count;
86        self.last_packet_duration = self.decoder.last_packet_duration;
87    }
88
89    /// Decode a packet into 16-bit PCM samples (interleaved if stereo).
90    ///
91    /// Empty `packet` input triggers packet loss concealment using the previous
92    /// decoder state. Returns the number of decoded samples per channel.
93    /// - `fec`: reserved for future in-band FEC support. Currently treated as
94    ///   packet loss concealment (PLC) when `true`. Pass `false` for normal decode.
95    ///
96    /// # Examples
97    ///
98    /// ```rust,no_run
99    /// use opus_decoder::OpusDecoder;
100    ///
101    /// let mut decoder = OpusDecoder::new(48_000, 2)?;
102    /// let packet = std::fs::read("frame.opus")?;
103    /// let mut pcm = vec![0i16; 960 * 2];
104    /// let samples = decoder.decode(&packet, &mut pcm, false)?;
105    /// # let _ = samples;
106    /// # Ok::<(), Box<dyn std::error::Error>>(())
107    /// ```
108    pub fn decode(
109        &mut self,
110        packet: &[u8],
111        pcm: &mut [i16],
112        fec: bool,
113    ) -> Result<usize, OpusError> {
114        let samples_per_channel = if packet.is_empty() || fec {
115            self.decoder.decode(None, pcm).map_err(OpusError::from)?
116        } else {
117            self.decoder
118                .decode(Some(packet), pcm)
119                .map_err(OpusError::from)?
120        };
121        self.sync_state_from_decoder();
122        Ok(samples_per_channel)
123    }
124
125    /// Decode a packet into f32 PCM samples (interleaved if stereo).
126    ///
127    /// Empty `packet` input triggers packet loss concealment using the previous
128    /// decoder state. Returns the number of decoded samples per channel.
129    /// - `fec`: reserved for future in-band FEC support. Currently treated as
130    ///   packet loss concealment (PLC) when `true`. Pass `false` for normal decode.
131    ///
132    /// # Examples
133    ///
134    /// ```rust,no_run
135    /// use opus_decoder::OpusDecoder;
136    ///
137    /// let mut decoder = OpusDecoder::new(48_000, 1)?;
138    /// let packet = std::fs::read("frame.opus")?;
139    /// let mut pcm = vec![0.0f32; 960];
140    /// let samples = decoder.decode_float(&packet, &mut pcm, false)?;
141    /// # let _ = samples;
142    /// # Ok::<(), Box<dyn std::error::Error>>(())
143    /// ```
144    pub fn decode_float(
145        &mut self,
146        packet: &[u8],
147        pcm: &mut [f32],
148        fec: bool,
149    ) -> Result<usize, OpusError> {
150        let samples_per_channel_hint = if packet.is_empty() || fec {
151            self.decoder.last_packet_duration
152        } else {
153            packet::parse_packet(packet)
154                .map_err(OpusError::from)?
155                .samples_per_channel(self.decoder.fs_hz())
156        };
157        let needed = samples_per_channel_hint * self.decoder.channels() as usize;
158        if pcm.len() < needed {
159            return Err(OpusError::BufferTooSmall);
160        }
161
162        if self.float_scratch.len() < needed {
163            self.float_scratch.resize(needed, 0);
164        }
165
166        let samples_per_channel = self
167            .decoder
168            .decode(
169                if packet.is_empty() || fec {
170                    None
171                } else {
172                    Some(packet)
173                },
174                &mut self.float_scratch[..needed],
175            )
176            .map_err(OpusError::from)?;
177        self.sync_state_from_decoder();
178        let written = samples_per_channel * self.decoder.channels() as usize;
179        for (dst, src) in pcm.iter_mut().zip(self.float_scratch[..written].iter()) {
180            *dst = f32::from(*src) / 32768.0;
181        }
182
183        Ok(samples_per_channel)
184    }
185
186    /// Reset decoder state (e.g. after packet loss or seek).
187    ///
188    /// Parameters: none.
189    /// Returns: nothing.
190    pub fn reset(&mut self) {
191        self.decoder.reset();
192        self.loss_count = 0;
193        self.last_packet_duration = 0;
194    }
195
196    /// Return the last range-coder final state observed by the decoder.
197    pub fn final_range(&self) -> u32 {
198        self.decoder.final_range()
199    }
200
201    /// Return the last CELT recursive split count.
202    #[doc(hidden)]
203    pub fn last_split_count(&self) -> usize {
204        self.decoder.last_split_count()
205    }
206
207    /// Return the current deemphasis memory for channel 0.
208    #[doc(hidden)]
209    pub fn deemph_mem(&self) -> f32 {
210        self.decoder.deemph_mem()
211    }
212
213    /// Return whether the last CELT frame used the transient path.
214    #[doc(hidden)]
215    pub fn last_is_transient(&self) -> bool {
216        self.decoder.last_is_transient()
217    }
218
219    /// Return whether the last decoded packet carried SILK redundancy.
220    #[doc(hidden)]
221    pub fn last_had_redundancy(&self) -> bool {
222        self.decoder.last_had_redundancy()
223    }
224
225    /// Return whether the last decoded SILK redundancy was CELT-to-SILK.
226    #[doc(hidden)]
227    pub fn last_celt_to_silk(&self) -> bool {
228        self.decoder.last_celt_to_silk()
229    }
230}
231
232/// Errors returned by the public Opus decoding API.
233#[derive(Debug, thiserror::Error)]
234pub enum OpusError {
235    /// The provided Opus packet is malformed or internally inconsistent.
236    #[error("invalid packet")]
237    InvalidPacket,
238    /// The decoder hit an internal unsupported or unexpected state.
239    #[error("internal error")]
240    InternalError,
241    /// The output PCM buffer is too small for the decoded frame.
242    #[error("buffer too small")]
243    BufferTooSmall,
244    /// One of the public API arguments is invalid.
245    #[error("invalid argument: {0}")]
246    InvalidArgument(&'static str),
247}
248
249impl From<Error> for OpusError {
250    fn from(value: Error) -> Self {
251        match value {
252            Error::InvalidSampleRate(_) => Self::InvalidArgument("sample_rate"),
253            Error::InvalidChannels(_) => Self::InvalidArgument("channels"),
254            Error::PacketTooLarge { .. } | Error::BadPacket => Self::InvalidPacket,
255            Error::OutputTooSmall { .. } => Self::BufferTooSmall,
256            Error::NotImplemented => Self::InternalError,
257        }
258    }
259}
260
261#[derive(Debug, Clone)]
262pub(crate) struct Decoder {
263    fs_hz: u32,
264    channels: u8,
265    celt: celt::CeltDecoder,
266    silk: silk::SilkDecoder,
267    last_packet_split_count: usize,
268    last_final_range: u32,
269    last_had_redundancy: bool,
270    last_celt_to_silk: bool,
271    prev_mode: Option<OpusMode>,
272    prev_redundancy: bool,
273    loss_count: u32,
274    last_packet_duration: usize,
275    last_output: Vec<i16>,
276}
277
278#[derive(Debug, Clone, Copy, PartialEq, Eq)]
279pub(crate) enum OpusMode {
280    SilkOnly,
281    Hybrid,
282    CeltOnly,
283}
284
285impl Decoder {
286    pub(crate) const MAX_FRAME_SIZE_48K: usize = 5760; // 120 ms @ 48 kHz
287
288    pub(crate) fn new(fs_hz: u32, channels: u8) -> Result<Self, Error> {
289        if !matches!(fs_hz, 8000 | 12000 | 16000 | 24000 | 48000) {
290            return Err(Error::InvalidSampleRate(fs_hz));
291        }
292        if !matches!(channels, 1 | 2) {
293            return Err(Error::InvalidChannels(channels));
294        }
295        Ok(Self {
296            fs_hz,
297            channels,
298            celt: celt::CeltDecoder::new(fs_hz, channels),
299            silk: silk::SilkDecoder::new(fs_hz, channels),
300            last_packet_split_count: 0,
301            last_final_range: 0,
302            last_had_redundancy: false,
303            last_celt_to_silk: false,
304            prev_mode: None,
305            prev_redundancy: false,
306            loss_count: 0,
307            last_packet_duration: 0,
308            last_output: Vec::new(),
309        })
310    }
311
312    pub(crate) fn fs_hz(&self) -> u32 {
313        self.fs_hz
314    }
315
316    pub(crate) fn channels(&self) -> u8 {
317        self.channels
318    }
319
320    pub(crate) fn max_frame_size_per_channel(&self) -> usize {
321        // Opus internally decodes 48 kHz and can output other Fs by deterministic
322        // downsampling/decimation (libopus behavior).
323        match self.fs_hz {
324            8000 => 960,
325            12000 => 1440,
326            16000 => 1920,
327            24000 => 2880,
328            48000 => Self::MAX_FRAME_SIZE_48K,
329            _ => 0, // guarded in new()
330        }
331    }
332
333    /// Conceal a lost non-CELT frame by fading the previous PCM.
334    ///
335    /// Params: per-channel `frame_size`, consecutive `loss_count`, and mutable `out`.
336    /// Returns: nothing; `out` receives interleaved concealed PCM.
337    fn conceal_with_fade(&self, frame_size: usize, loss_count: u32, out: &mut [i16]) {
338        let channels = self.channels as usize;
339        let needed = frame_size * channels;
340        if self.last_output.len() < needed {
341            out[..needed].fill(0);
342            return;
343        }
344
345        let fade = 0.9f32.powi((loss_count.min(10) + 1) as i32);
346        for (dst, src) in out[..needed]
347            .iter_mut()
348            .zip(self.last_output[..needed].iter())
349        {
350            let sample = f32::from(*src) * fade;
351            *dst = sample.round().clamp(i16::MIN as f32, i16::MAX as f32) as i16;
352        }
353    }
354
355    /// Persist the most recent decoded interleaved PCM frame.
356    ///
357    /// Params: decoded `out` buffer and `samples_per_channel` written into it.
358    /// Returns: nothing; the decoder keeps a copy for future PLC fallback.
359    fn store_last_output(&mut self, out: &[i16], samples_per_channel: usize) {
360        let written = samples_per_channel * self.channels as usize;
361        self.last_output.clear();
362        self.last_output.extend_from_slice(&out[..written]);
363    }
364
365    /// Decode a lost packet using CELT PLC or a safe fade fallback.
366    ///
367    /// Params: mutable interleaved `out` buffer.
368    /// Returns: concealed sample count per channel.
369    fn decode_lost_packet(&mut self, out: &mut [i16]) -> Result<usize, Error> {
370        let samples_per_channel = self.last_packet_duration;
371        if samples_per_channel == 0 {
372            self.last_final_range = 0;
373            return Ok(0);
374        }
375
376        let needed = samples_per_channel * self.channels as usize;
377        if out.len() < needed {
378            return Err(Error::OutputTooSmall {
379                needed,
380                got: out.len(),
381            });
382        }
383
384        out[..needed].fill(0);
385        match self.prev_mode {
386            Some(OpusMode::CeltOnly) => {
387                let concealed = self
388                    .celt
389                    .decode_lost(samples_per_channel, self.channels as usize);
390                for (dst, src) in out[..needed].iter_mut().zip(concealed.iter()) {
391                    *dst = src.round().clamp(i16::MIN as f32, i16::MAX as f32) as i16;
392                }
393            }
394            Some(OpusMode::SilkOnly | OpusMode::Hybrid) => {
395                self.silk
396                    .decode_lost(samples_per_channel, out, self.loss_count)?;
397            }
398            _ => {
399                self.conceal_with_fade(samples_per_channel, self.loss_count, out);
400            }
401        }
402
403        self.loss_count = self.loss_count.saturating_add(1);
404        self.last_packet_split_count = 0;
405        self.last_final_range = 0;
406        self.last_had_redundancy = false;
407        self.last_celt_to_silk = false;
408        self.prev_redundancy = false;
409        self.store_last_output(out, samples_per_channel);
410        Ok(samples_per_channel)
411    }
412
413    pub(crate) fn reset(&mut self) {
414        // Placeholder. The real implementation will reset SILK/CELT state.
415        self.celt.reset();
416        self.silk.reset();
417        self.last_packet_split_count = 0;
418        self.last_final_range = 0;
419        self.last_had_redundancy = false;
420        self.last_celt_to_silk = false;
421        self.prev_mode = None;
422        self.prev_redundancy = false;
423        self.loss_count = 0;
424        self.last_packet_duration = 0;
425        self.last_output.clear();
426    }
427
428    /// Return range coder final state for conformance comparison.
429    pub(crate) fn final_range(&self) -> u32 {
430        self.last_final_range
431    }
432
433    /// Return recursive split count from last decoded frame.
434    ///
435    /// This is a debug metric used for CELT split-path diagnostics.
436    pub(crate) fn last_split_count(&self) -> usize {
437        self.last_packet_split_count
438    }
439
440    /// Return deemphasis memory for channel 0 (debug metric).
441    ///
442    /// This is used for CELT synthesis diagnostics in mono vectors.
443    pub(crate) fn deemph_mem(&self) -> f32 {
444        self.celt.deemph_mem(0)
445    }
446
447    /// Return whether the last CELT frame was transient.
448    ///
449    /// This is a compatibility helper for the local conformance harness.
450    pub(crate) fn last_is_transient(&self) -> bool {
451        false
452    }
453
454    /// Return whether the last decoded packet carried SILK redundancy.
455    ///
456    /// This follows the top-level transition bookkeeping used by the decoder.
457    pub(crate) fn last_had_redundancy(&self) -> bool {
458        self.last_had_redundancy
459    }
460
461    /// Return whether the last decoded SILK redundancy was CELT-to-SILK.
462    ///
463    /// This is a debug helper for transition tracing in the conformance harness.
464    pub(crate) fn last_celt_to_silk(&self) -> bool {
465        self.last_celt_to_silk
466    }
467
468    /// Decode one Opus packet to interleaved i16 PCM.
469    ///
470    /// - `packet=None` triggers PLC (packet loss concealment).
471    /// - `out` must be large enough for the maximum frame size (120 ms).
472    /// - Returns the number of samples per channel written.
473    pub(crate) fn decode(
474        &mut self,
475        packet: Option<&[u8]>,
476        out: &mut [i16],
477    ) -> Result<usize, Error> {
478        let packet_idx = TRACE_DECODE_PACKET_IDX.fetch_add(1, Ordering::SeqCst);
479        let (toc, samples_per_channel_needed) = match packet {
480            Some(packet) => {
481                let pp = packet::parse_packet(packet)?;
482                (Some(pp.toc), pp.samples_per_channel(self.fs_hz))
483            }
484            None => (None, self.last_packet_duration),
485        };
486
487        let needed = samples_per_channel_needed * self.channels as usize;
488        if out.len() < needed {
489            return Err(Error::OutputTooSmall {
490                needed,
491                got: out.len(),
492            });
493        }
494
495        let Some(toc) = toc else {
496            return self.decode_lost_packet(out);
497        };
498
499        self.celt.reset_loss_count();
500        let config = (toc >> 3) & 0x1f;
501        let mode = match config {
502            0..=11 => OpusMode::SilkOnly,
503            12..=15 => OpusMode::Hybrid,
504            16..=31 => OpusMode::CeltOnly,
505            _ => unreachable!(),
506        };
507        // Parse again to get per-frame slices.
508        let pp = packet::parse_packet(packet.unwrap())?;
509        self.last_packet_split_count = 0;
510        self.last_final_range = 0;
511
512        let transition = self.prev_mode.is_some()
513            && ((mode == OpusMode::CeltOnly
514                && self.prev_mode != Some(OpusMode::CeltOnly)
515                && !self.prev_redundancy)
516                || (mode != OpusMode::CeltOnly && self.prev_mode == Some(OpusMode::CeltOnly)));
517        let transition_samples = (self.fs_hz as usize) / 200;
518        let transition_overlap = (self.fs_hz as usize) / 400;
519        let channels = self.channels as usize;
520        let celt_transition = if transition && mode == OpusMode::CeltOnly {
521            vec![0i16; transition_samples * channels]
522        } else {
523            Vec::new()
524        };
525        let mut apply_celt_transition = transition && mode == OpusMode::CeltOnly;
526        let mut reset_silk = transition && self.prev_mode == Some(OpusMode::CeltOnly);
527        let mut had_redundancy = false;
528        let mut last_celt_to_silk = false;
529        let mut written_per_channel = 0usize;
530        for &frame in pp.frames().iter() {
531            let frame_samples_48k = pp.samples_per_frame_48k;
532            let out_frame = &mut out[written_per_channel * self.channels as usize..];
533            match mode {
534                OpusMode::CeltOnly => {
535                    if apply_celt_transition {
536                        self.celt.reset();
537                    }
538                    // CELT frame sizes are specified in 48 kHz samples; this is enough
539                    // to drive the CELT-side LM selection.
540                    let mut ec = EcDec::new(frame);
541                    let celt_frame = self.celt.decode_frame_with_ec(
542                        frame,
543                        &mut ec,
544                        frame_samples_48k,
545                        config,
546                        pp.packet_channels,
547                        packet_idx,
548                        out_frame,
549                        false,
550                    )?;
551                    if apply_celt_transition {
552                        apply_transition_fade_i16(
553                            &celt_transition,
554                            &mut out_frame[..celt_frame.samples_per_channel * channels],
555                            transition_overlap.min(celt_frame.samples_per_channel / 2),
556                            channels,
557                            self.celt.window(),
558                            self.fs_hz,
559                        );
560                        apply_celt_transition = false;
561                    }
562                    self.last_packet_split_count += self.celt.last_split_count();
563                    self.last_final_range = self.celt.final_range();
564                    written_per_channel += celt_frame.samples_per_channel;
565                }
566                OpusMode::SilkOnly => {
567                    if reset_silk {
568                        self.silk.reset();
569                        reset_silk = false;
570                    }
571                    let packet_frame = frame;
572                    let silk_frame = self.silk.decode_frame(
573                        packet_frame,
574                        frame_samples_48k,
575                        config,
576                        pp.packet_channels,
577                        packet_idx,
578                        out_frame,
579                    )?;
580                    last_celt_to_silk = silk_frame.celt_to_silk;
581                    let mut redundancy_rng = 0u32;
582                    if silk_frame.consumed_redundancy {
583                        let redundancy_data =
584                            &packet_frame[packet_frame.len() - silk_frame.redundancy_bytes..];
585                        let redundancy_frame_size_48k = 240usize;
586                        let redundancy_samples = (self.fs_hz as usize) / 200;
587                        let redundancy_end_band = silk_redundancy_end_band(config);
588                        let mut redundancy_out =
589                            vec![0i16; redundancy_samples * self.channels as usize];
590                        if !silk_frame.celt_to_silk {
591                            self.celt.reset();
592                        }
593                        self.celt.set_start_band(0);
594                        self.celt.set_end_band(redundancy_end_band);
595                        let redundancy_frame = self.celt.decode_frame(
596                            redundancy_data,
597                            redundancy_frame_size_48k,
598                            config,
599                            pp.packet_channels,
600                            packet_idx,
601                            &mut redundancy_out,
602                        )?;
603                        self.celt.clear_end_band();
604                        redundancy_rng = self.celt.final_range();
605                        if !silk_frame.celt_to_silk {
606                            let overlap = (self.fs_hz as usize) / 400;
607                            let channels = self.channels as usize;
608                            let silk_tail_start =
609                                (silk_frame.samples_per_channel - overlap) * channels;
610                            let silk_tail_end = silk_frame.samples_per_channel * channels;
611                            let redundancy_start = overlap * channels;
612                            let redundancy_end = redundancy_start + overlap * channels;
613                            let silk_tail = out_frame[silk_tail_start..silk_tail_end].to_vec();
614                            smooth_fade_i16(
615                                &silk_tail,
616                                &redundancy_out[redundancy_start..redundancy_end],
617                                &mut out_frame[silk_tail_start..silk_tail_end],
618                                overlap,
619                                channels,
620                                self.celt.window(),
621                                self.fs_hz,
622                            );
623                        } else {
624                            let overlap = (self.fs_hz as usize) / 400;
625                            let channels = self.channels as usize;
626                            let frame_len = silk_frame.samples_per_channel * channels;
627                            apply_transition_fade_i16(
628                                &redundancy_out,
629                                &mut out_frame[..frame_len],
630                                overlap.min(redundancy_frame.samples_per_channel / 2),
631                                channels,
632                                self.celt.window(),
633                                self.fs_hz,
634                            );
635                        }
636                    }
637                    had_redundancy = silk_frame.consumed_redundancy && !silk_frame.celt_to_silk;
638                    self.last_final_range = self.silk.final_range() ^ redundancy_rng;
639                    written_per_channel += silk_frame.samples_per_channel;
640                }
641                OpusMode::Hybrid => {
642                    let mut ec = EcDec::new(frame);
643                    let silk_frame = self.silk.decode_frame_with_ec(
644                        frame,
645                        &mut ec,
646                        frame_samples_48k,
647                        config,
648                        pp.packet_channels,
649                        true,
650                        packet_idx,
651                        out_frame,
652                    )?;
653                    let redundancy = if ec.tell() + 17 + 20 <= (frame.len() as i32) * 8 {
654                        ec.dec_bit_logp(12)
655                    } else {
656                        false
657                    };
658                    let mut celt_to_silk = false;
659                    let mut redundancy_bytes = 0usize;
660                    if redundancy {
661                        celt_to_silk = ec.dec_bit_logp(1);
662                        redundancy_bytes = ec.dec_uint(256) as usize + 2;
663                        ec.shrink_storage(redundancy_bytes);
664                    }
665                    let mut celt_to_silk_audio = Vec::new();
666                    let mut celt_to_silk_samples = 0usize;
667                    let mut redundant_rng = 0u32;
668                    let apply_celt_to_silk_audio = redundancy
669                        && celt_to_silk
670                        && (self.prev_mode != Some(OpusMode::SilkOnly) || self.prev_redundancy);
671                    let reset_main_celt = self.prev_mode.is_some()
672                        && self.prev_mode != Some(mode)
673                        && !self.prev_redundancy;
674                    if redundancy && celt_to_silk {
675                        let redundancy_samples = (self.fs_hz as usize) / 200;
676                        let redundancy_data = &frame[frame.len() - redundancy_bytes..];
677                        self.celt.set_start_band(0);
678                        celt_to_silk_audio = vec![0i16; redundancy_samples * channels];
679                        let redundancy_frame = self.celt.decode_frame(
680                            redundancy_data,
681                            240,
682                            config,
683                            pp.packet_channels,
684                            packet_idx,
685                            &mut celt_to_silk_audio,
686                        )?;
687                        self.last_packet_split_count += self.celt.last_split_count();
688                        celt_to_silk_samples = redundancy_frame.samples_per_channel;
689                        redundant_rng = self.celt.final_range();
690                    }
691                    if reset_main_celt {
692                        self.celt.reset();
693                    }
694                    self.celt.set_start_band(17);
695                    let celt_frame = match self.celt.decode_frame_with_ec(
696                        frame,
697                        &mut ec,
698                        frame_samples_48k,
699                        config,
700                        pp.packet_channels,
701                        packet_idx,
702                        out_frame,
703                        true,
704                    ) {
705                        Ok(frame) => frame,
706                        Err(err) => {
707                            self.celt.set_start_band(0);
708                            return Err(err);
709                        }
710                    };
711                    self.celt.set_start_band(0);
712                    debug_assert_eq!(
713                        silk_frame.samples_per_channel,
714                        celt_frame.samples_per_channel
715                    );
716                    self.last_packet_split_count += self.celt.last_split_count();
717                    let main_celt_rng = self.celt.final_range();
718                    self.last_final_range = main_celt_rng;
719                    if redundancy && !celt_to_silk {
720                        let channels = self.channels as usize;
721                        let redundancy_samples = (self.fs_hz as usize) / 200;
722                        let overlap = (self.fs_hz as usize) / 400;
723                        let frame_len = celt_frame.samples_per_channel * channels;
724                        let redundancy_data = &frame[frame.len() - redundancy_bytes..];
725                        let mut redundancy_out = vec![0i16; redundancy_samples * channels];
726                        self.celt.reset();
727                        self.celt.set_start_band(0);
728                        let redundancy_frame = self.celt.decode_frame(
729                            redundancy_data,
730                            240,
731                            config,
732                            pp.packet_channels,
733                            packet_idx,
734                            &mut redundancy_out,
735                        )?;
736                        self.last_packet_split_count += self.celt.last_split_count();
737                        let redundant_rng = self.celt.final_range();
738                        let fade_len = overlap * channels;
739                        let tail_start = frame_len.saturating_sub(fade_len);
740                        let tail_end = tail_start + fade_len;
741                        let redundancy_start = fade_len;
742                        let redundancy_end = redundancy_start + fade_len;
743                        if tail_end <= out_frame.len()
744                            && redundancy_end <= redundancy_out.len()
745                            && redundancy_frame.samples_per_channel == redundancy_samples
746                        {
747                            let celt_tail = out_frame[tail_start..tail_end].to_vec();
748                            smooth_fade_i16(
749                                &celt_tail,
750                                &redundancy_out[redundancy_start..redundancy_end],
751                                &mut out_frame[tail_start..tail_end],
752                                overlap,
753                                channels,
754                                self.celt.window(),
755                                self.fs_hz,
756                            );
757                        }
758                        self.last_final_range ^= redundant_rng;
759                    }
760                    if redundancy && celt_to_silk {
761                        if apply_celt_to_silk_audio {
762                            let overlap = (self.fs_hz as usize) / 400;
763                            let frame_len = celt_frame.samples_per_channel * channels;
764                            apply_transition_fade_i16(
765                                &celt_to_silk_audio,
766                                &mut out_frame[..frame_len],
767                                overlap.min(celt_to_silk_samples / 2),
768                                channels,
769                                self.celt.window(),
770                                self.fs_hz,
771                            );
772                        }
773                        self.last_final_range ^= redundant_rng;
774                    }
775                    last_celt_to_silk = celt_to_silk;
776                    had_redundancy = redundancy && !celt_to_silk;
777                    written_per_channel += silk_frame.samples_per_channel;
778                }
779            }
780        }
781
782        self.prev_mode = Some(mode);
783        self.last_had_redundancy = had_redundancy;
784        self.last_celt_to_silk = last_celt_to_silk;
785        self.prev_redundancy = had_redundancy;
786        self.loss_count = 0;
787        self.last_packet_duration = written_per_channel;
788        self.store_last_output(out, written_per_channel);
789        Ok(written_per_channel)
790    }
791}
792
793/// Crossfade SILK PCM with redundant CELT PCM using the CELT overlap window.
794///
795/// Params: previous `in1`, incoming `in2`, mutable `out`, overlap length,
796/// interleaved `channels`, CELT `window`, and output sampling rate `fs_hz`.
797/// Returns: nothing; `out` is updated in-place.
798fn smooth_fade_i16(
799    in1: &[i16],
800    in2: &[i16],
801    out: &mut [i16],
802    overlap: usize,
803    channels: usize,
804    window: &[f32],
805    fs_hz: u32,
806) {
807    let inc = (48_000 / fs_hz) as usize;
808    for c in 0..channels {
809        for i in 0..overlap {
810            let w = window[i * inc] * window[i * inc];
811            let idx = i * channels + c;
812            let mixed = w * in2[idx] as f32 + (1.0 - w) * in1[idx] as f32;
813            out[idx] = mixed.round().clamp(i16::MIN as f32, i16::MAX as f32) as i16;
814        }
815    }
816}
817
818/// Apply the SILK-to-CELT transition prefix and crossfade.
819///
820/// Params: previous-mode `transition` PCM, mutable decoded `pcm`, fade length
821/// `overlap`, interleaved `channels`, CELT `window`, and output rate `fs_hz`.
822/// Returns: nothing; `pcm` is updated in-place.
823fn apply_transition_fade_i16(
824    transition: &[i16],
825    pcm: &mut [i16],
826    overlap: usize,
827    channels: usize,
828    window: &[f32],
829    fs_hz: u32,
830) {
831    if overlap == 0 || channels == 0 {
832        return;
833    }
834
835    let prefix_len = overlap * channels;
836    let copy_len = prefix_len.min(transition.len()).min(pcm.len());
837    pcm[..copy_len].copy_from_slice(&transition[..copy_len]);
838
839    let fade_available = (transition.len().saturating_sub(prefix_len))
840        .min(pcm.len().saturating_sub(prefix_len))
841        / channels;
842    if fade_available == 0 {
843        return;
844    }
845
846    let fade_samples = fade_available.min(overlap);
847    let fade_len = fade_samples * channels;
848    let fade_start = prefix_len;
849    let fade_end = fade_start + fade_len;
850    let incoming = pcm[fade_start..fade_end].to_vec();
851    smooth_fade_i16(
852        &transition[fade_start..fade_end],
853        &incoming,
854        &mut pcm[fade_start..fade_end],
855        fade_samples,
856        channels,
857        window,
858        fs_hz,
859    );
860}
861
862/// Map SILK packet config to CELT redundancy end band.
863///
864/// Params: Opus TOC `config`.
865/// Returns: exclusive CELT end band matching libopus packet bandwidth.
866fn silk_redundancy_end_band(config: u8) -> usize {
867    match config {
868        0..=3 => 13,
869        4..=11 => 17,
870        12..=13 => 19,
871        14..=15 => 21,
872        _ => 21,
873    }
874}