Skip to main content

oxideav_opus/
silk_packet_encode.rs

1//! SILK-only Opus **packet** encoding — RFC 6716 §3.1 / §4.2.2-§4.2.6.
2//!
3//! Composes the encode-side mirrors into a complete, decoder-ready
4//! Opus packet: the §3.1 TOC byte (code 0, one Opus frame), the
5//! §4.2.3 / §4.2.4 header bits, and one regular SILK frame per 20 ms
6//! time interval (or a single 10 ms frame) written in Table-5 order by
7//! [`crate::silk_decode::encode_silk_frame`], finalized through the
8//! §5.1.5 range-coder termination.
9//!
10//! The produced packets decode end-to-end through
11//! [`crate::decoder::OpusDecoder::decode_packet`] on a fresh decoder:
12//! the per-frame carried state (previous gain / lag / NLSF) is
13//! threaded here exactly the way the packet decoder threads it, so
14//! the per-frame `SilkFrameDecoded` predictions returned to the
15//! caller equal what the decoder reconstructs.
16//!
17//! Scope: **mono and stereo**. LBRR (in-band FEC, §4.2.5) emission is
18//! supported via [`encode_silk_only_packet_mono_with_lbrr`]: LBRR
19//! frames are written ahead of the regular frames with their own
20//! independent carried state, exactly mirroring the decode-side LBRR
21//! walk, and the §4.2.3 / §4.2.4 LBRR flags are derived from which
22//! intervals carry a redundancy script. The **stereo** mid/side
23//! interleave (§4.2.2) is provided by
24//! [`encode_silk_only_packet_stereo`]: per 20 ms interval the mid
25//! SILK frame (carrying the §4.2.7.1 stereo prediction weights and,
26//! when the interval's side channel is not active, the §4.2.7.2
27//! mid-only flag) is written, then the side SILK frame when coded,
28//! with two independent per-channel carried states threaded exactly
29//! the way [`crate::decoder::OpusDecoder`]'s stereo walk threads them.
30
31use crate::decoder::ChannelDecodeState;
32use crate::range_encoder::RangeEncoder;
33use crate::silk_decode::{
34    encode_silk_frame, SilkFrameConfig, SilkFrameDecoded, SilkFrameSymbols, StereoHeaderContext,
35};
36use crate::silk_excitation::SilkFrameSize;
37use crate::silk_header::{silk_frame_count, PerFrameLbrr, SilkChannelHeader, SilkHeaderBits};
38use crate::toc::{Bandwidth, FrameCountCode, Mode, OpusTocByte};
39use crate::Error;
40
41/// Encode one **mono, SILK-only, code-0** Opus packet from per-frame
42/// symbol scripts.
43///
44/// * `bandwidth` — NB / MB / WB (the SILK internal bandwidths).
45/// * `frame_size_tenths_ms` — the §3.1 Opus frame duration: 100, 200,
46///   400, or 600 (10/20/40/60 ms). A 10 ms packet carries one 10 ms
47///   SILK frame; the others carry one 20 ms SILK frame per interval
48///   (1, 2, or 3), and `frames.len()` must match.
49/// * `frames` — one Table-5 symbol script per SILK frame. Each frame's
50///   §4.2.3 VAD bit is derived from its §4.2.7.3 frame type (types
51///   `0..=1` are inactive, `2..=5` active). Frames after the first
52///   must delta-code their first gain (the carried state makes the
53///   first subframe non-independent), exactly as the decoder expects.
54///
55/// Returns the packet bytes plus the per-frame [`SilkFrameDecoded`]
56/// predictions (what a fresh decoder will reconstruct).
57pub fn encode_silk_only_packet_mono(
58    bandwidth: Bandwidth,
59    frame_size_tenths_ms: u16,
60    frames: &[SilkFrameSymbols<'_>],
61) -> Result<(Vec<u8>, Vec<SilkFrameDecoded>), Error> {
62    let n = silk_frame_count(frame_size_tenths_ms).ok_or(Error::MalformedPacket)? as usize;
63    let no_lbrr = vec![None; n];
64    let (packet, regular, _) =
65        encode_silk_only_packet_mono_with_lbrr(bandwidth, frame_size_tenths_ms, frames, &no_lbrr)?;
66    Ok((packet, regular))
67}
68
69/// [`encode_silk_only_packet_mono`] with §4.2.5 LBRR (in-band FEC)
70/// emission: `lbrr[idx]` optionally carries a redundancy script for
71/// SILK frame `idx`'s time interval (a re-encode of the interval the
72/// *previous* packet covered, per §2.1.7). LBRR frames are written
73/// ahead of the regular frames with their own independent carried
74/// state, exactly the way the packet decoder walks them; each LBRR
75/// script's frame type must be active (`2..=5`, §4.2.7.3), its first
76/// gain independent only on the first coded LBRR frame, and its LTP
77/// scaling present only on the first coded LBRR frame.
78///
79/// Returns the packet bytes, the regular per-frame predictions, and
80/// the LBRR per-frame predictions.
81#[allow(clippy::type_complexity)]
82pub fn encode_silk_only_packet_mono_with_lbrr(
83    bandwidth: Bandwidth,
84    frame_size_tenths_ms: u16,
85    frames: &[SilkFrameSymbols<'_>],
86    lbrr: &[Option<SilkFrameSymbols<'_>>],
87) -> Result<
88    (
89        Vec<u8>,
90        Vec<SilkFrameDecoded>,
91        Vec<Option<SilkFrameDecoded>>,
92    ),
93    Error,
94> {
95    let num_silk_frames = silk_frame_count(frame_size_tenths_ms).ok_or(Error::MalformedPacket)?;
96    if frames.len() != num_silk_frames as usize || lbrr.len() != num_silk_frames as usize {
97        return Err(Error::MalformedPacket);
98    }
99    let frame_size = if frame_size_tenths_ms == 100 {
100        SilkFrameSize::TenMs
101    } else {
102        SilkFrameSize::TwentyMs
103    };
104
105    // §3.1 TOC byte: SILK-only, mono, code 0 (one Opus frame).
106    let toc = OpusTocByte::compose_byte(
107        Mode::SilkOnly,
108        bandwidth,
109        frame_size_tenths_ms,
110        false,
111        FrameCountCode::One,
112    )?;
113
114    let mut re = RangeEncoder::new();
115
116    // §4.2.3 / §4.2.4 header bits: VAD per frame from the frame type,
117    // LBRR flags from which intervals carry a redundancy script.
118    let mut vad_flags = 0u8;
119    for (idx, f) in frames.iter().enumerate() {
120        if f.header.frame_type >= 2 {
121            vad_flags |= 1 << idx;
122        }
123    }
124    let mut lbrr_bits = 0u8;
125    for (idx, l) in lbrr.iter().enumerate() {
126        if l.is_some() {
127            lbrr_bits |= 1 << idx;
128        }
129    }
130    let header = SilkHeaderBits {
131        num_silk_frames,
132        mid: SilkChannelHeader {
133            vad_flags,
134            lbrr_flag: lbrr_bits != 0,
135        },
136        side: None,
137        per_frame_lbrr: PerFrameLbrr {
138            mid: lbrr_bits,
139            side: 0,
140        },
141    };
142    header.encode(&mut re)?;
143
144    // §4.2.5 LBRR frames: written ahead of the regular frames, with
145    // their own independent carried state (they form their own
146    // sequence), mirroring the decoder's LBRR walk. Per §4.2.7.3 every
147    // LBRR frame is active-coded.
148    let mut lbrr_prev_gain: Option<u8> = None;
149    let mut lbrr_prev_lag: Option<i32> = None;
150    let mut lbrr_first = true;
151    let mut lbrr_predictions: Vec<Option<SilkFrameDecoded>> = Vec::with_capacity(lbrr.len());
152    for entry in lbrr.iter() {
153        let Some(symbols) = entry else {
154            lbrr_predictions.push(None);
155            continue;
156        };
157        if symbols.header.frame_type < 2 {
158            // §4.2.7.3: LBRR frames use the active PDF.
159            return Err(Error::MalformedPacket);
160        }
161        let cfg = SilkFrameConfig {
162            bandwidth,
163            frame_size,
164            voice_active: true,
165            first_subframe_independent: lbrr_first || lbrr_prev_gain.is_none(),
166            previous_log_gain: lbrr_prev_gain,
167            previous_primary_lag: lbrr_prev_lag,
168            ltp_scaling_present: lbrr_first,
169            lsf_interp_after_reset: lbrr_first,
170            previous_nlsf_q15: None,
171            previous_nlsf_len: 0,
172            stereo: None,
173        };
174        let decoded = encode_silk_frame(&mut re, cfg, symbols)?;
175        lbrr_prev_gain = Some(decoded.gains.last_log_gain());
176        lbrr_prev_lag = Some(decoded.ltp.primary_lag());
177        lbrr_first = false;
178        lbrr_predictions.push(Some(decoded));
179    }
180
181    // §4.2.6 regular SILK frames with the carried state threaded the
182    // same way the packet decoder threads it (fresh-decoder start).
183    let mut prev_gain: Option<u8> = None;
184    let mut prev_lag: Option<i32> = None;
185    let mut prev_nlsf: Option<[i16; crate::silk_lsf_stage2::D_LPC_MAX]> = None;
186    let mut prev_nlsf_len = 0usize;
187    let mut first = true;
188    let mut predictions = Vec::with_capacity(frames.len());
189    for (idx, symbols) in frames.iter().enumerate() {
190        let cfg = SilkFrameConfig {
191            bandwidth,
192            frame_size,
193            voice_active: (vad_flags >> idx) & 1 == 1,
194            first_subframe_independent: first || prev_gain.is_none(),
195            previous_log_gain: prev_gain,
196            previous_primary_lag: prev_lag,
197            ltp_scaling_present: first,
198            lsf_interp_after_reset: first || prev_nlsf.is_none(),
199            previous_nlsf_q15: prev_nlsf,
200            previous_nlsf_len: prev_nlsf_len,
201            stereo: None,
202        };
203        let decoded = encode_silk_frame(&mut re, cfg, symbols)?;
204        prev_gain = Some(decoded.gains.last_log_gain());
205        prev_lag = Some(decoded.ltp.primary_lag());
206        prev_nlsf = Some(decoded.nlsf_q15);
207        prev_nlsf_len = decoded.d_lpc;
208        first = false;
209        predictions.push(decoded);
210    }
211
212    // §5.1.5 finalize; §3.2 code-0 framing = TOC byte + the single
213    // compressed frame.
214    let body = re.finish();
215    let mut packet = Vec::with_capacity(1 + body.len());
216    packet.push(toc);
217    packet.extend_from_slice(&body);
218    Ok((packet, predictions, lbrr_predictions))
219}
220
221/// One 20 ms time interval's symbol scripts for a **stereo** SILK-only
222/// packet (§4.2.2): the mid-channel SILK frame plus, when the side
223/// channel is coded for this interval, the side-channel SILK frame.
224///
225/// Consistency rules enforced by [`encode_silk_only_packet_stereo`]
226/// (mirroring the decode-side §4.2.7.1 / §4.2.7.2 gating):
227///
228/// * `mid.header.stereo` must be `Some` (the §4.2.7.1 weights ride on
229///   every mid frame); `side.header.stereo` must be `None`.
230/// * The interval's **side VAD flag** is derived as "side coded with an
231///   active frame type (`2..=5`)". When it is set, the §4.2.7.2
232///   mid-only flag is *absent* (`mid.header.mid_only_flag == None`).
233///   When it is unset, the flag is *present* and must equal
234///   `side.is_none()`: `Some(true)` skips the side frame entirely,
235///   `Some(false)` codes an inactive side frame (frame type `0..=1`).
236#[derive(Debug, Clone, Copy)]
237pub struct StereoIntervalScripts<'a> {
238    /// The mid-channel frame script (carries the §4.2.7.1 weights and,
239    /// when signalled, the §4.2.7.2 mid-only flag).
240    pub mid: SilkFrameSymbols<'a>,
241    /// The side-channel frame script, or `None` when the side channel
242    /// is not coded for this interval (mid-only).
243    pub side: Option<SilkFrameSymbols<'a>>,
244}
245
246/// The per-frame predictions returned by
247/// [`encode_silk_only_packet_stereo`]: what a fresh
248/// [`crate::decoder::OpusDecoder`] will reconstruct for each channel.
249#[derive(Debug, Clone)]
250pub struct StereoPacketPredictions {
251    /// One decoded mid frame per 20 ms interval.
252    pub mid: Vec<SilkFrameDecoded>,
253    /// One decoded side frame per interval; `None` where the side
254    /// channel is not coded (§4.2.7.2 mid-only).
255    pub side: Vec<Option<SilkFrameDecoded>>,
256}
257
258/// Encode one **stereo, SILK-only, code-0** Opus packet from
259/// per-interval mid/side symbol scripts (§3.1 / §4.2.2-§4.2.6).
260///
261/// Per 20 ms interval (or the single 10 ms interval) the mid SILK frame
262/// is written, then — unless the interval is mid-only — the side SILK
263/// frame, in the §4.2.2 interleaved order, with two independent
264/// per-channel carried states (previous gain / lag / NLSF) threaded
265/// exactly the way the packet decoder threads them. The §4.2.3 header
266/// bits carry both channels' VAD flags: the mid VAD is derived from the
267/// mid frame type, the side VAD from "side coded with an active frame
268/// type" (see [`StereoIntervalScripts`]).
269///
270/// Returns the packet bytes plus the per-channel
271/// [`StereoPacketPredictions`].
272pub fn encode_silk_only_packet_stereo(
273    bandwidth: Bandwidth,
274    frame_size_tenths_ms: u16,
275    intervals: &[StereoIntervalScripts<'_>],
276) -> Result<(Vec<u8>, StereoPacketPredictions), Error> {
277    let n = silk_frame_count(frame_size_tenths_ms).ok_or(Error::MalformedPacket)? as usize;
278    let no_lbrr = vec![StereoIntervalLbrr::default(); n];
279    let (packet, regular, _) = encode_silk_only_packet_stereo_with_lbrr(
280        bandwidth,
281        frame_size_tenths_ms,
282        intervals,
283        &no_lbrr,
284    )?;
285    Ok((packet, regular))
286}
287
288/// One interval's §4.2.5 LBRR (in-band FEC) scripts for
289/// [`encode_silk_only_packet_stereo_with_lbrr`]: an optional mid-channel
290/// redundancy frame and an optional side-channel redundancy frame.
291///
292/// Consistency rules (mirroring the decode-side LBRR walk):
293///
294/// * Both scripts must be active-coded (frame type `2..=5`, §4.2.7.3).
295/// * An LBRR **mid** script carries the §4.2.7.1 weights
296///   (`header.stereo` must be `Some`) and, when the interval has no
297///   side LBRR frame, the §4.2.7.2 mid-only flag, which must be
298///   `Some(true)` (a coded side LBRR frame is forbidden by a set
299///   mid-only flag, and the header LBRR bits already say none follows).
300///   With a side LBRR present the flag is absent (`None`).
301/// * A **side-only** LBRR interval (mid `None`, side `Some`) is legal:
302///   no weights ride on a side frame (§4.2.7.1).
303#[derive(Debug, Clone, Copy, Default)]
304pub struct StereoIntervalLbrr<'a> {
305    /// Mid-channel LBRR script for this interval, if any.
306    pub mid: Option<SilkFrameSymbols<'a>>,
307    /// Side-channel LBRR script for this interval, if any.
308    pub side: Option<SilkFrameSymbols<'a>>,
309}
310
311/// [`encode_silk_only_packet_stereo`] with §4.2.5 LBRR (in-band FEC)
312/// emission: `lbrr[idx]` optionally carries mid / side redundancy
313/// scripts for interval `idx` (re-encodes of the interval the
314/// *previous* packet covered, per §2.1.7). LBRR frames are written
315/// ahead of the regular frames in the same §4.2.2 mid/side interleaved
316/// order, with their own independent per-channel carried states,
317/// exactly the way the packet decoder walks them.
318///
319/// Returns the packet bytes, the regular predictions, and the LBRR
320/// predictions.
321pub fn encode_silk_only_packet_stereo_with_lbrr(
322    bandwidth: Bandwidth,
323    frame_size_tenths_ms: u16,
324    intervals: &[StereoIntervalScripts<'_>],
325    lbrr: &[StereoIntervalLbrr<'_>],
326) -> Result<(Vec<u8>, StereoPacketPredictions, StereoLbrrPredictions), Error> {
327    let num_silk_frames = silk_frame_count(frame_size_tenths_ms).ok_or(Error::MalformedPacket)?;
328    if intervals.len() != num_silk_frames as usize || lbrr.len() != num_silk_frames as usize {
329        return Err(Error::MalformedPacket);
330    }
331    let frame_size = if frame_size_tenths_ms == 100 {
332        SilkFrameSize::TenMs
333    } else {
334        SilkFrameSize::TwentyMs
335    };
336
337    // §3.1 TOC byte: SILK-only, stereo, code 0 (one Opus frame).
338    let toc = OpusTocByte::compose_byte(
339        Mode::SilkOnly,
340        bandwidth,
341        frame_size_tenths_ms,
342        true,
343        FrameCountCode::One,
344    )?;
345
346    let mut re = RangeEncoder::new();
347
348    // §4.2.3 / §4.2.4 header bits. VAD per channel per interval:
349    // mid from the mid frame type; side from "side coded with an
350    // active frame type". LBRR bitmaps from which intervals carry
351    // redundancy scripts.
352    let mut mid_vad_flags = 0u8;
353    let mut side_vad_flags = 0u8;
354    for (idx, iv) in intervals.iter().enumerate() {
355        if iv.mid.header.frame_type >= 2 {
356            mid_vad_flags |= 1 << idx;
357        }
358        if let Some(side) = &iv.side {
359            if side.header.frame_type >= 2 {
360                side_vad_flags |= 1 << idx;
361            }
362        }
363    }
364    let mut mid_lbrr_bits = 0u8;
365    let mut side_lbrr_bits = 0u8;
366    for (idx, l) in lbrr.iter().enumerate() {
367        if l.mid.is_some() {
368            mid_lbrr_bits |= 1 << idx;
369        }
370        if l.side.is_some() {
371            side_lbrr_bits |= 1 << idx;
372        }
373    }
374    let header = SilkHeaderBits {
375        num_silk_frames,
376        mid: SilkChannelHeader {
377            vad_flags: mid_vad_flags,
378            lbrr_flag: mid_lbrr_bits != 0,
379        },
380        side: Some(SilkChannelHeader {
381            vad_flags: side_vad_flags,
382            lbrr_flag: side_lbrr_bits != 0,
383        }),
384        per_frame_lbrr: PerFrameLbrr {
385            mid: mid_lbrr_bits,
386            side: side_lbrr_bits,
387        },
388    };
389    header.encode(&mut re)?;
390
391    // §4.2.5 LBRR frames: per interval, mid (if present) then side (if
392    // present), interleaved per §4.2.2, ahead of every regular frame,
393    // with independent per-channel carried states — the exact mirror of
394    // the decoder's stereo LBRR walk.
395    let mut lbrr_mid_state = ChannelDecodeState::new();
396    let mut lbrr_side_state = ChannelDecodeState::new();
397    let mut lbrr_mid_pred: Vec<Option<SilkFrameDecoded>> = Vec::with_capacity(lbrr.len());
398    let mut lbrr_side_pred: Vec<Option<SilkFrameDecoded>> = Vec::with_capacity(lbrr.len());
399    for entry in lbrr.iter() {
400        let side_lbrr = entry.side.is_some();
401        if let Some(mid_sym) = &entry.mid {
402            if mid_sym.header.frame_type < 2 {
403                // §4.2.7.3: LBRR frames use the active PDF.
404                return Err(Error::MalformedPacket);
405            }
406            // §4.2.7.2: the mid-only flag is present on the mid LBRR
407            // frame iff the interval's side LBRR frame is not coded —
408            // and then it must be set (the header LBRR bits already
409            // promise no side frame follows; a cleared flag would
410            // contradict them).
411            if !side_lbrr && mid_sym.header.mid_only_flag != Some(true) {
412                return Err(Error::MalformedPacket);
413            }
414            let stereo_ctx = StereoHeaderContext {
415                has_mid_only_flag: !side_lbrr,
416            };
417            let decoded = encode_silk_frame(
418                &mut re,
419                lbrr_mid_state.config(bandwidth, frame_size, true, Some(stereo_ctx)),
420                mid_sym,
421            )?;
422            lbrr_mid_state.advance(&decoded);
423            lbrr_mid_pred.push(Some(decoded));
424        } else {
425            lbrr_mid_pred.push(None);
426        }
427        if let Some(side_sym) = &entry.side {
428            if side_sym.header.frame_type < 2 {
429                return Err(Error::MalformedPacket);
430            }
431            let decoded = encode_silk_frame(
432                &mut re,
433                lbrr_side_state.config(bandwidth, frame_size, true, None),
434                side_sym,
435            )?;
436            lbrr_side_state.advance(&decoded);
437            lbrr_side_pred.push(Some(decoded));
438        } else {
439            lbrr_side_pred.push(None);
440        }
441    }
442
443    // §4.2.6 regular SILK frames: per interval, the mid frame then
444    // (unless mid-only) the side frame, with per-channel carried state.
445    let mut mid_state = ChannelDecodeState::new();
446    let mut side_state = ChannelDecodeState::new();
447    let mut mid_pred: Vec<SilkFrameDecoded> = Vec::with_capacity(intervals.len());
448    let mut side_pred: Vec<Option<SilkFrameDecoded>> = Vec::with_capacity(intervals.len());
449    for (idx, iv) in intervals.iter().enumerate() {
450        let side_active = (side_vad_flags >> idx) & 1 == 1;
451        // §4.2.7.2: the mid-only flag is present iff the interval's side
452        // channel is not active; the script must match, and its value
453        // must agree with whether a side script is present.
454        match iv.mid.header.mid_only_flag {
455            Some(flag) => {
456                if side_active || flag != iv.side.is_none() {
457                    return Err(Error::MalformedPacket);
458                }
459            }
460            None => {
461                // No flag ⇒ side VAD must be set ⇒ side frame coded.
462                if !side_active || iv.side.is_none() {
463                    return Err(Error::MalformedPacket);
464                }
465            }
466        }
467        let stereo_ctx = StereoHeaderContext {
468            has_mid_only_flag: !side_active,
469        };
470        let mid_decoded = encode_silk_frame(
471            &mut re,
472            mid_state.config(
473                bandwidth,
474                frame_size,
475                (mid_vad_flags >> idx) & 1 == 1,
476                Some(stereo_ctx),
477            ),
478            &iv.mid,
479        )?;
480        mid_state.advance(&mid_decoded);
481        mid_pred.push(mid_decoded);
482
483        if let Some(side_sym) = &iv.side {
484            let side_decoded = encode_silk_frame(
485                &mut re,
486                side_state.config(bandwidth, frame_size, side_active, None),
487                side_sym,
488            )?;
489            side_state.advance(&side_decoded);
490            side_pred.push(Some(side_decoded));
491        } else {
492            // §4.2.7.2 / §4.5.2: an uncoded side frame leaves the side
493            // carried state untouched (the decoder does not advance it).
494            side_pred.push(None);
495        }
496    }
497
498    // §5.1.5 finalize; §3.2 code-0 framing.
499    let body = re.finish();
500    let mut packet = Vec::with_capacity(1 + body.len());
501    packet.push(toc);
502    packet.extend_from_slice(&body);
503    Ok((
504        packet,
505        StereoPacketPredictions {
506            mid: mid_pred,
507            side: side_pred,
508        },
509        StereoLbrrPredictions {
510            mid: lbrr_mid_pred,
511            side: lbrr_side_pred,
512        },
513    ))
514}
515
516/// The per-interval §4.2.5 LBRR predictions returned by
517/// [`encode_silk_only_packet_stereo_with_lbrr`].
518#[derive(Debug, Clone)]
519pub struct StereoLbrrPredictions {
520    /// One decoded mid LBRR frame per interval that carried one.
521    pub mid: Vec<Option<SilkFrameDecoded>>,
522    /// One decoded side LBRR frame per interval that carried one.
523    pub side: Vec<Option<SilkFrameDecoded>>,
524}
525
526#[cfg(test)]
527mod tests {
528    use super::*;
529    use crate::decoder::{FrameDecodeStatus, OpusDecoder};
530    use crate::range_decoder::RangeDecoder;
531    use crate::silk_decode::decode_silk_frame;
532    use crate::silk_excitation::{shell_block_count, ExcitationSymbols, SHELL_BLOCK_SAMPLES};
533    use crate::silk_frame::SilkHeaderSymbols;
534    use crate::silk_gains::GainSymbol;
535    use crate::silk_ltp::{LagSymbols, LtpSymbols, LTP_MAX_SUBFRAMES};
536
537    /// A tiny deterministic LCG for the packet-level sweeps.
538    struct Lcg(u64);
539    impl Lcg {
540        fn next_u32(&mut self) -> u32 {
541            self.0 = self
542                .0
543                .wrapping_mul(6364136223846793005)
544                .wrapping_add(1442695040888963407);
545            (self.0 >> 32) as u32
546        }
547        fn below(&mut self, n: u32) -> u32 {
548            self.next_u32() % n
549        }
550    }
551
552    /// Owns the per-frame script buffers (the `SilkFrameSymbols` borrow
553    /// slices).
554    struct ScriptBufs {
555        gains: Vec<GainSymbol>,
556        i2: Vec<i8>,
557        lsb_counts: Vec<u8>,
558        e_raw: Vec<i32>,
559        header: SilkHeaderSymbols,
560        lsf_stage1: u8,
561        lsf_interp_w_q2: Option<u8>,
562        ltp: Option<LtpSymbols>,
563        lcg_seed: u8,
564        rate_level: u8,
565    }
566
567    fn random_frame_script(
568        rng: &mut Lcg,
569        bandwidth: Bandwidth,
570        frame_size: SilkFrameSize,
571        first: bool,
572        has_prev_lag: bool,
573    ) -> ScriptBufs {
574        let num_subframes = if frame_size == SilkFrameSize::TenMs {
575            2usize
576        } else {
577            4
578        };
579        let frame_type = rng.below(6) as u8;
580        let voiced = frame_type >= 4;
581        let gains: Vec<GainSymbol> = (0..num_subframes)
582            .map(|k| {
583                if k == 0 && first {
584                    GainSymbol::Independent(rng.below(64) as u8)
585                } else {
586                    GainSymbol::Delta(rng.below(41) as u8)
587                }
588            })
589            .collect();
590        let d_lpc = if bandwidth == Bandwidth::Wb { 16 } else { 10 };
591        let i2: Vec<i8> = (0..d_lpc).map(|_| rng.below(21) as i8 - 10).collect();
592        let ltp = voiced.then(|| {
593            let lag_low_count = match bandwidth {
594                Bandwidth::Nb => 4u32,
595                Bandwidth::Mb => 6,
596                _ => 8,
597            };
598            let lag = if has_prev_lag {
599                if rng.below(2) == 0 {
600                    LagSymbols::RelativeDelta {
601                        delta_index: 1 + rng.below(20) as u8,
602                    }
603                } else {
604                    LagSymbols::RelativeFallback {
605                        lag_high: rng.below(32) as u8,
606                        lag_low: rng.below(lag_low_count) as u8,
607                    }
608                }
609            } else {
610                LagSymbols::Absolute {
611                    lag_high: rng.below(32) as u8,
612                    lag_low: rng.below(lag_low_count) as u8,
613                }
614            };
615            let contour_cells = match (bandwidth, num_subframes) {
616                (Bandwidth::Nb, 2) => 3u32,
617                (Bandwidth::Nb, 4) => 11,
618                (_, 2) => 12,
619                _ => 34,
620            };
621            let periodicity_index = rng.below(3) as u8;
622            let filter_cells = [8u32, 16, 32][periodicity_index as usize];
623            let mut filter_indices = [0u8; LTP_MAX_SUBFRAMES];
624            for f in filter_indices.iter_mut().take(num_subframes) {
625                *f = rng.below(filter_cells) as u8;
626            }
627            LtpSymbols {
628                lag,
629                contour_index: rng.below(contour_cells) as u8,
630                periodicity_index,
631                filter_indices,
632                // ltp_scaling_present == `first` in the packet path.
633                ltp_scaling_index: first.then(|| rng.below(3) as u8),
634            }
635        });
636        let blocks = shell_block_count(bandwidth, frame_size).unwrap();
637        let total = blocks * SHELL_BLOCK_SAMPLES;
638        let mut lsb_counts = vec![0u8; blocks];
639        let mut e_raw = vec![0i32; total];
640        for (b, lc) in lsb_counts.iter_mut().enumerate() {
641            let lsbs = if rng.below(4) == 0 { 1 } else { 0 };
642            *lc = lsbs as u8;
643            let budget = rng.below(17);
644            let base = b * SHELL_BLOCK_SAMPLES;
645            let mut spent = 0u32;
646            while spent < budget {
647                let i = base + rng.below(16) as usize;
648                let add = 1 + rng.below(budget - spent);
649                e_raw[i] += (add << lsbs) as i32;
650                spent += add;
651            }
652            for slot in e_raw[base..base + SHELL_BLOCK_SAMPLES].iter_mut() {
653                if lsbs > 0 {
654                    *slot += (rng.next_u32() & 1) as i32;
655                }
656                if *slot != 0 && rng.below(2) == 0 {
657                    *slot = -*slot;
658                }
659            }
660        }
661        ScriptBufs {
662            gains,
663            i2,
664            lsb_counts,
665            e_raw,
666            header: SilkHeaderSymbols {
667                stereo: None,
668                mid_only_flag: None,
669                frame_type,
670            },
671            lsf_stage1: rng.below(32) as u8,
672            lsf_interp_w_q2: (frame_size == SilkFrameSize::TwentyMs).then(|| rng.below(5) as u8),
673            ltp,
674            lcg_seed: rng.below(4) as u8,
675            rate_level: rng.below(9) as u8,
676        }
677    }
678
679    fn symbols_of(bufs: &ScriptBufs) -> SilkFrameSymbols<'_> {
680        SilkFrameSymbols {
681            header: bufs.header,
682            gains: &bufs.gains,
683            lsf_stage1: bufs.lsf_stage1,
684            lsf_stage2_i2: &bufs.i2,
685            lsf_interp_w_q2: bufs.lsf_interp_w_q2,
686            ltp: bufs.ltp,
687            lcg_seed: bufs.lcg_seed,
688            excitation: ExcitationSymbols {
689                rate_level: bufs.rate_level,
690                lsb_counts: &bufs.lsb_counts,
691                e_raw: &bufs.e_raw,
692            },
693        }
694    }
695
696    /// End-to-end: random mono SILK-only packets (10/20/40/60 ms, all
697    /// bandwidths) produced by the packet encoder decode through a
698    /// fresh `OpusDecoder::decode_packet` with a real-SILK-PCM status
699    /// and the exact §3 sample count, and the frame-level symbols
700    /// decode back (via a parallel `decode_silk_frame` walk) equal to
701    /// the encoder's predictions.
702    #[test]
703    fn packet_encode_decodes_end_to_end() {
704        let mut rng = Lcg(0x0AC4_E701);
705        for round in 0..120 {
706            let bandwidth = match rng.below(3) {
707                0 => Bandwidth::Nb,
708                1 => Bandwidth::Mb,
709                _ => Bandwidth::Wb,
710            };
711            let fs_tenths: u16 = [100u16, 200, 400, 600][rng.below(4) as usize];
712            let frame_size = if fs_tenths == 100 {
713                SilkFrameSize::TenMs
714            } else {
715                SilkFrameSize::TwentyMs
716            };
717            let n = silk_frame_count(fs_tenths).unwrap() as usize;
718            let bufs: Vec<ScriptBufs> = (0..n)
719                .map(|idx| random_frame_script(&mut rng, bandwidth, frame_size, idx == 0, idx > 0))
720                .collect();
721            let scripts: Vec<SilkFrameSymbols<'_>> = bufs.iter().map(symbols_of).collect();
722
723            let (packet, predictions) =
724                encode_silk_only_packet_mono(bandwidth, fs_tenths, &scripts)
725                    .expect("packet encode");
726            assert_eq!(predictions.len(), n);
727
728            // The packet decodes end-to-end on a fresh decoder.
729            let mut dec = OpusDecoder::new();
730            let out = dec.decode_packet(&packet).expect("packet decode");
731            assert_eq!(out.channels, 1, "round {round}");
732            assert_eq!(out.frame_outcomes.len(), 1, "round {round}");
733            assert_eq!(
734                out.frame_outcomes[0].status,
735                FrameDecodeStatus::SilkParamsDecoded,
736                "round {round} bw={bandwidth:?} fs={fs_tenths}"
737            );
738            // §3: 48 kHz output samples for the Opus frame duration.
739            assert_eq!(
740                out.samples_per_channel() as u32,
741                48_000 * fs_tenths as u32 / 10_000,
742                "round {round}"
743            );
744
745            // Frame-level: a parallel Table-5 walk over the packet body
746            // reconstructs exactly the encoder's predictions.
747            let mut rd = RangeDecoder::new(&packet[1..]);
748            let header = SilkHeaderBits::decode(&mut rd, n as u8, false).expect("header bits");
749            assert!(!header.mid.lbrr_flag);
750            let mut prev_gain: Option<u8> = None;
751            let mut prev_lag: Option<i32> = None;
752            let mut prev_nlsf: Option<[i16; crate::silk_lsf_stage2::D_LPC_MAX]> = None;
753            let mut prev_nlsf_len = 0usize;
754            let mut first = true;
755            for (idx, expected) in predictions.iter().enumerate() {
756                let cfg = SilkFrameConfig {
757                    bandwidth,
758                    frame_size,
759                    voice_active: header.mid_vad(idx as u8),
760                    first_subframe_independent: first || prev_gain.is_none(),
761                    previous_log_gain: prev_gain,
762                    previous_primary_lag: prev_lag,
763                    ltp_scaling_present: first,
764                    lsf_interp_after_reset: first || prev_nlsf.is_none(),
765                    previous_nlsf_q15: prev_nlsf,
766                    previous_nlsf_len: prev_nlsf_len,
767                    stereo: None,
768                };
769                let decoded = decode_silk_frame(&mut rd, cfg).expect("frame decode");
770                assert_eq!(&decoded, expected, "round {round} frame {idx}");
771                prev_gain = Some(decoded.gains.last_log_gain());
772                prev_lag = Some(decoded.ltp.primary_lag());
773                prev_nlsf = Some(decoded.nlsf_q15);
774                prev_nlsf_len = decoded.d_lpc;
775                first = false;
776            }
777            assert!(!rd.has_error());
778        }
779    }
780
781    /// LBRR emission closes the FEC loop: a packet carrying §4.2.5
782    /// redundancy scripts still decodes its regular frames end-to-end
783    /// (the LBRR bits shift every later symbol, so this pins the
784    /// range-coder alignment), `decode_packet_fec` recovers real audio
785    /// (`Recovered`), and a no-LBRR packet reports `NoLbrr`.
786    #[test]
787    fn packet_encode_with_lbrr_fec_roundtrip() {
788        use crate::decoder::FecDecodeStatus;
789        let mut rng = Lcg(0xFEC0_0382);
790        for round in 0..60 {
791            let bandwidth = match rng.below(3) {
792                0 => Bandwidth::Nb,
793                1 => Bandwidth::Mb,
794                _ => Bandwidth::Wb,
795            };
796            let fs_tenths: u16 = [100u16, 200, 400, 600][rng.below(4) as usize];
797            let frame_size = if fs_tenths == 100 {
798                SilkFrameSize::TenMs
799            } else {
800                SilkFrameSize::TwentyMs
801            };
802            let n = silk_frame_count(fs_tenths).unwrap() as usize;
803            let bufs: Vec<ScriptBufs> = (0..n)
804                .map(|idx| random_frame_script(&mut rng, bandwidth, frame_size, idx == 0, idx > 0))
805                .collect();
806            let scripts: Vec<SilkFrameSymbols<'_>> = bufs.iter().map(symbols_of).collect();
807
808            // Random non-empty LBRR subset. LBRR scripts must be
809            // active-coded and follow the LBRR carried-state rules, so
810            // build them with the same generator but force an active
811            // frame type and first/subsequent shape per coded order.
812            let mut which = vec![false; n];
813            which[rng.below(n as u32) as usize] = true;
814            for w in which.iter_mut() {
815                if rng.below(2) == 0 {
816                    *w = true;
817                }
818            }
819            let mut lbrr_bufs: Vec<Option<ScriptBufs>> = Vec::with_capacity(n);
820            let mut coded_first = true;
821            for &w in &which {
822                if !w {
823                    lbrr_bufs.push(None);
824                    continue;
825                }
826                let mut b =
827                    random_frame_script(&mut rng, bandwidth, frame_size, coded_first, !coded_first);
828                // Force an active frame type (§4.2.7.3: LBRR is
829                // active-coded) while keeping the generator's LTP shape
830                // consistent with it.
831                if b.header.frame_type < 2 {
832                    b.header.frame_type += 2; // 0/1 -> 2/3 (unvoiced, no LTP)
833                }
834                lbrr_bufs.push(Some(b));
835                coded_first = false;
836            }
837            let lbrr_scripts: Vec<Option<SilkFrameSymbols<'_>>> = lbrr_bufs
838                .iter()
839                .map(|b| b.as_ref().map(symbols_of))
840                .collect();
841
842            let (packet, regular, lbrr_pred) = encode_silk_only_packet_mono_with_lbrr(
843                bandwidth,
844                fs_tenths,
845                &scripts,
846                &lbrr_scripts,
847            )
848            .expect("packet encode with lbrr");
849            assert_eq!(regular.len(), n);
850            assert_eq!(
851                lbrr_pred.iter().filter(|p| p.is_some()).count(),
852                which.iter().filter(|&&w| w).count()
853            );
854
855            // Regular decode still lands (range-coder alignment past
856            // the LBRR frames).
857            let mut dec = OpusDecoder::new();
858            let out = dec.decode_packet(&packet).expect("packet decode");
859            assert_eq!(
860                out.frame_outcomes[0].status,
861                FrameDecodeStatus::SilkParamsDecoded,
862                "round {round} bw={bandwidth:?} fs={fs_tenths}"
863            );
864
865            // FEC recovery from the LBRR we emitted.
866            let mut fec_dec = OpusDecoder::new();
867            let rec = fec_dec.decode_packet_fec(&packet).expect("fec decode");
868            assert_eq!(
869                rec.status,
870                FecDecodeStatus::Recovered,
871                "round {round} bw={bandwidth:?} fs={fs_tenths}"
872            );
873            assert_eq!(
874                rec.pcm.len() as u32,
875                48_000 * fs_tenths as u32 / 10_000,
876                "round {round}"
877            );
878        }
879
880        // A no-LBRR packet reports NoLbrr.
881        let bufs = random_frame_script(
882            &mut rng,
883            Bandwidth::Nb,
884            SilkFrameSize::TwentyMs,
885            true,
886            false,
887        );
888        let script = symbols_of(&bufs);
889        let (packet, _) =
890            encode_silk_only_packet_mono(Bandwidth::Nb, 200, &[script]).expect("encode");
891        let mut dec = OpusDecoder::new();
892        let rec = dec.decode_packet_fec(&packet).expect("fec decode");
893        assert_eq!(rec.status, FecDecodeStatus::NoLbrr);
894    }
895
896    use crate::silk_frame::StereoWeightSymbols;
897
898    fn random_weights(rng: &mut Lcg) -> StereoWeightSymbols {
899        StereoWeightSymbols {
900            n: rng.below(25) as u8,
901            i0: rng.below(3) as u8,
902            i1: rng.below(5) as u8,
903            i2: rng.below(3) as u8,
904            i3: rng.below(5) as u8,
905        }
906    }
907
908    /// Per-interval side-channel coding pattern for the stereo sweeps.
909    #[derive(Clone, Copy, PartialEq)]
910    enum SidePattern {
911        /// Side coded with an active frame type (side VAD set, no
912        /// §4.2.7.2 mid-only flag on the mid frame).
913        Active,
914        /// Side coded but inactive (side VAD clear, mid-only flag
915        /// present and cleared).
916        InactiveCoded,
917        /// Side not coded (mid-only flag present and set).
918        MidOnly,
919    }
920
921    /// Build one stereo interval's (mid, side) script buffers.
922    ///
923    /// `mid_first` / `mid_has_prev_lag` describe the mid channel's
924    /// carried state; `side_first` / `side_has_prev_lag` the side
925    /// channel's (side state only advances on *coded* side frames).
926    #[allow(clippy::too_many_arguments)]
927    fn random_stereo_interval(
928        rng: &mut Lcg,
929        bandwidth: Bandwidth,
930        frame_size: SilkFrameSize,
931        pattern: SidePattern,
932        mid_first: bool,
933        mid_has_prev_lag: bool,
934        side_first: bool,
935        side_has_prev_lag: bool,
936    ) -> (ScriptBufs, Option<ScriptBufs>) {
937        let mut mid = random_frame_script(rng, bandwidth, frame_size, mid_first, mid_has_prev_lag);
938        mid.header.stereo = Some(random_weights(rng));
939        mid.header.mid_only_flag = match pattern {
940            SidePattern::Active => None,
941            SidePattern::InactiveCoded => Some(false),
942            SidePattern::MidOnly => Some(true),
943        };
944        let side = match pattern {
945            SidePattern::MidOnly => None,
946            SidePattern::Active => {
947                let mut s =
948                    random_frame_script(rng, bandwidth, frame_size, side_first, side_has_prev_lag);
949                if s.header.frame_type < 2 {
950                    // Force an active type without disturbing the
951                    // generator's LTP shape (0/1 → 2/3, still unvoiced).
952                    s.header.frame_type += 2;
953                }
954                Some(s)
955            }
956            SidePattern::InactiveCoded => {
957                let mut s =
958                    random_frame_script(rng, bandwidth, frame_size, side_first, side_has_prev_lag);
959                if s.header.frame_type >= 2 {
960                    // Force an inactive type; inactive frames carry no
961                    // §4.2.7.6 LTP.
962                    s.header.frame_type %= 2;
963                    s.ltp = None;
964                }
965                Some(s)
966            }
967        };
968        (mid, side)
969    }
970
971    /// End-to-end: random stereo SILK-only packets across every
972    /// bandwidth × duration × side-coding pattern decode through a fresh
973    /// `OpusDecoder::decode_packet` to real stereo SILK PCM with the
974    /// exact §3 sample count, and a parallel mid/side Table-5 walk
975    /// reconstructs both channels' predictions field-for-field.
976    #[test]
977    fn stereo_packet_encode_decodes_end_to_end() {
978        let mut rng = Lcg(0x57E2_E001);
979        for round in 0..120 {
980            let bandwidth = match rng.below(3) {
981                0 => Bandwidth::Nb,
982                1 => Bandwidth::Mb,
983                _ => Bandwidth::Wb,
984            };
985            let fs_tenths: u16 = [100u16, 200, 400, 600][rng.below(4) as usize];
986            let frame_size = if fs_tenths == 100 {
987                SilkFrameSize::TenMs
988            } else {
989                SilkFrameSize::TwentyMs
990            };
991            let n = silk_frame_count(fs_tenths).unwrap() as usize;
992
993            let mut interval_bufs: Vec<(ScriptBufs, Option<ScriptBufs>)> = Vec::with_capacity(n);
994            let mut patterns: Vec<SidePattern> = Vec::with_capacity(n);
995            let mut side_first = true;
996            let mut side_has_prev = false;
997            for idx in 0..n {
998                let pattern = match rng.below(3) {
999                    0 => SidePattern::Active,
1000                    1 => SidePattern::InactiveCoded,
1001                    _ => SidePattern::MidOnly,
1002                };
1003                let iv = random_stereo_interval(
1004                    &mut rng,
1005                    bandwidth,
1006                    frame_size,
1007                    pattern,
1008                    idx == 0,
1009                    idx > 0,
1010                    side_first,
1011                    side_has_prev,
1012                );
1013                if pattern != SidePattern::MidOnly {
1014                    side_first = false;
1015                    side_has_prev = true;
1016                }
1017                patterns.push(pattern);
1018                interval_bufs.push(iv);
1019            }
1020            let intervals: Vec<StereoIntervalScripts<'_>> = interval_bufs
1021                .iter()
1022                .map(|(m, s)| StereoIntervalScripts {
1023                    mid: symbols_of(m),
1024                    side: s.as_ref().map(symbols_of),
1025                })
1026                .collect();
1027
1028            let (packet, predictions) =
1029                encode_silk_only_packet_stereo(bandwidth, fs_tenths, &intervals)
1030                    .expect("stereo packet encode");
1031            assert_eq!(predictions.mid.len(), n);
1032            assert_eq!(predictions.side.len(), n);
1033
1034            // The packet decodes end-to-end on a fresh decoder.
1035            let mut dec = OpusDecoder::new();
1036            let out = dec.decode_packet(&packet).expect("packet decode");
1037            assert_eq!(out.channels, 2, "round {round}");
1038            assert_eq!(
1039                out.frame_outcomes[0].status,
1040                FrameDecodeStatus::SilkStereoDecoded,
1041                "round {round} bw={bandwidth:?} fs={fs_tenths}"
1042            );
1043            assert_eq!(
1044                out.samples_per_channel() as u32,
1045                48_000 * fs_tenths as u32 / 10_000,
1046                "round {round}"
1047            );
1048            assert_eq!(
1049                out.pcm.len() as u32,
1050                2 * 48_000 * fs_tenths as u32 / 10_000,
1051                "round {round}"
1052            );
1053
1054            // Parallel mid/side Table-5 walk over the packet body
1055            // reconstructs exactly the encoder's predictions.
1056            let mut rd = RangeDecoder::new(&packet[1..]);
1057            let header = SilkHeaderBits::decode(&mut rd, n as u8, true).expect("header bits");
1058            assert!(!header.mid.lbrr_flag);
1059            assert!(header.side.is_some_and(|s| !s.lbrr_flag));
1060            let mut mid_state = crate::decoder::ChannelDecodeState::new();
1061            let mut side_state = crate::decoder::ChannelDecodeState::new();
1062            for (idx, pattern) in patterns.iter().enumerate() {
1063                let side_active = header.side_vad(idx as u8);
1064                assert_eq!(
1065                    side_active,
1066                    *pattern == SidePattern::Active,
1067                    "round {round} interval {idx}"
1068                );
1069                let stereo_ctx = crate::silk_decode::StereoHeaderContext {
1070                    has_mid_only_flag: !side_active,
1071                };
1072                let mid_decoded = decode_silk_frame(
1073                    &mut rd,
1074                    mid_state.config(
1075                        bandwidth,
1076                        frame_size,
1077                        header.mid_vad(idx as u8),
1078                        Some(stereo_ctx),
1079                    ),
1080                )
1081                .expect("mid decode");
1082                assert_eq!(
1083                    &mid_decoded, &predictions.mid[idx],
1084                    "round {round} mid {idx}"
1085                );
1086                let side_coded = side_active || mid_decoded.mid_only_flag == Some(false);
1087                mid_state.advance(&mid_decoded);
1088                if side_coded {
1089                    let side_decoded = decode_silk_frame(
1090                        &mut rd,
1091                        side_state.config(bandwidth, frame_size, side_active, None),
1092                    )
1093                    .expect("side decode");
1094                    assert_eq!(
1095                        Some(&side_decoded),
1096                        predictions.side[idx].as_ref(),
1097                        "round {round} side {idx}"
1098                    );
1099                    side_state.advance(&side_decoded);
1100                } else {
1101                    assert!(predictions.side[idx].is_none(), "round {round} side {idx}");
1102                }
1103            }
1104            assert!(!rd.has_error());
1105        }
1106    }
1107
1108    /// Stereo LBRR emission closes the stereo FEC loop: a packet
1109    /// carrying §4.2.5 mid/side redundancy still decodes its regular
1110    /// frames end-to-end (pinning the range-coder alignment across the
1111    /// interleaved LBRR frames), and `decode_packet_fec` recovers real
1112    /// two-channel audio from the emitted redundancy.
1113    #[test]
1114    fn stereo_packet_encode_with_lbrr_fec_roundtrip() {
1115        use crate::decoder::FecDecodeStatus;
1116        let mut rng = Lcg(0xFEC0_57E2);
1117        for round in 0..60 {
1118            let bandwidth = match rng.below(3) {
1119                0 => Bandwidth::Nb,
1120                1 => Bandwidth::Mb,
1121                _ => Bandwidth::Wb,
1122            };
1123            let fs_tenths: u16 = [100u16, 200, 400, 600][rng.below(4) as usize];
1124            let frame_size = if fs_tenths == 100 {
1125                SilkFrameSize::TenMs
1126            } else {
1127                SilkFrameSize::TwentyMs
1128            };
1129            let n = silk_frame_count(fs_tenths).unwrap() as usize;
1130
1131            // Regular frames: keep the side always actively coded for
1132            // this sweep (the pattern axis is covered by the other test).
1133            let mut interval_bufs: Vec<(ScriptBufs, Option<ScriptBufs>)> = Vec::with_capacity(n);
1134            for idx in 0..n {
1135                interval_bufs.push(random_stereo_interval(
1136                    &mut rng,
1137                    bandwidth,
1138                    frame_size,
1139                    SidePattern::Active,
1140                    idx == 0,
1141                    idx > 0,
1142                    idx == 0,
1143                    idx > 0,
1144                ));
1145            }
1146            let intervals: Vec<StereoIntervalScripts<'_>> = interval_bufs
1147                .iter()
1148                .map(|(m, s)| StereoIntervalScripts {
1149                    mid: symbols_of(m),
1150                    side: s.as_ref().map(symbols_of),
1151                })
1152                .collect();
1153
1154            // LBRR pattern per interval: none / mid-only / mid+side /
1155            // side-only, at least one interval carrying something.
1156            let mut kinds = vec![0u32; n];
1157            kinds[rng.below(n as u32) as usize] = 1 + rng.below(3);
1158            for k in kinds.iter_mut() {
1159                if *k == 0 && rng.below(2) == 0 {
1160                    *k = 1 + rng.below(3);
1161                }
1162            }
1163            let mut lbrr_bufs: Vec<(Option<ScriptBufs>, Option<ScriptBufs>)> =
1164                Vec::with_capacity(n);
1165            let mut mid_first = true;
1166            let mut mid_prev = false;
1167            let mut side_first = true;
1168            let mut side_prev = false;
1169            for &kind in &kinds {
1170                let want_mid = kind == 1 || kind == 2;
1171                let want_side = kind == 2 || kind == 3;
1172                let mid = want_mid.then(|| {
1173                    let mut b =
1174                        random_frame_script(&mut rng, bandwidth, frame_size, mid_first, mid_prev);
1175                    if b.header.frame_type < 2 {
1176                        b.header.frame_type += 2; // active-coded (§4.2.7.3)
1177                    }
1178                    b.header.stereo = Some(random_weights(&mut rng));
1179                    b.header.mid_only_flag = (!want_side).then_some(true);
1180                    mid_first = false;
1181                    mid_prev = true;
1182                    b
1183                });
1184                let side = want_side.then(|| {
1185                    let mut b =
1186                        random_frame_script(&mut rng, bandwidth, frame_size, side_first, side_prev);
1187                    if b.header.frame_type < 2 {
1188                        b.header.frame_type += 2;
1189                    }
1190                    side_first = false;
1191                    side_prev = true;
1192                    b
1193                });
1194                lbrr_bufs.push((mid, side));
1195            }
1196            let lbrr_scripts: Vec<StereoIntervalLbrr<'_>> = lbrr_bufs
1197                .iter()
1198                .map(|(m, s)| StereoIntervalLbrr {
1199                    mid: m.as_ref().map(symbols_of),
1200                    side: s.as_ref().map(symbols_of),
1201                })
1202                .collect();
1203
1204            let (packet, regular, lbrr_pred) = encode_silk_only_packet_stereo_with_lbrr(
1205                bandwidth,
1206                fs_tenths,
1207                &intervals,
1208                &lbrr_scripts,
1209            )
1210            .expect("stereo packet encode with lbrr");
1211            assert_eq!(regular.mid.len(), n);
1212            assert_eq!(
1213                lbrr_pred.mid.iter().filter(|p| p.is_some()).count(),
1214                kinds.iter().filter(|&&k| k == 1 || k == 2).count()
1215            );
1216            assert_eq!(
1217                lbrr_pred.side.iter().filter(|p| p.is_some()).count(),
1218                kinds.iter().filter(|&&k| k == 2 || k == 3).count()
1219            );
1220
1221            // Regular decode still lands (range-coder alignment past the
1222            // interleaved LBRR frames).
1223            let mut dec = OpusDecoder::new();
1224            let out = dec.decode_packet(&packet).expect("packet decode");
1225            assert_eq!(
1226                out.frame_outcomes[0].status,
1227                FrameDecodeStatus::SilkStereoDecoded,
1228                "round {round} bw={bandwidth:?} fs={fs_tenths} kinds={kinds:?}"
1229            );
1230
1231            // FEC recovery from the emitted redundancy: real two-channel
1232            // audio with the exact §3 sample count. The decoder's FEC
1233            // policy needs at least one *mid* LBRR frame to reconstruct a
1234            // stereo signal (a side-only redundancy set has no mid signal
1235            // to unmix against), so an all-side-only pattern reports
1236            // `NoLbrr` while every mid-bearing pattern recovers.
1237            let any_mid_lbrr = kinds.iter().any(|&k| k == 1 || k == 2);
1238            let mut fec_dec = OpusDecoder::new();
1239            let rec = fec_dec.decode_packet_fec(&packet).expect("fec decode");
1240            assert_eq!(
1241                rec.status,
1242                if any_mid_lbrr {
1243                    FecDecodeStatus::Recovered
1244                } else {
1245                    FecDecodeStatus::NoLbrr
1246                },
1247                "round {round} bw={bandwidth:?} fs={fs_tenths} kinds={kinds:?}"
1248            );
1249            assert_eq!(rec.channels, 2, "round {round}");
1250            assert_eq!(
1251                rec.pcm.len() as u32,
1252                2 * 48_000 * fs_tenths as u32 / 10_000,
1253                "round {round}"
1254            );
1255        }
1256    }
1257
1258    /// Stereo shape / consistency violations are rejected: wrong
1259    /// interval count, missing §4.2.7.1 weights, mid-only flag
1260    /// mismatches, an active side frame under a mid-only interval, and
1261    /// inactive LBRR scripts.
1262    #[test]
1263    fn stereo_packet_encode_rejects_inconsistent_scripts() {
1264        let mut rng = Lcg(0xBAD5_7E2E);
1265        let (mid, side) = random_stereo_interval(
1266            &mut rng,
1267            Bandwidth::Wb,
1268            SilkFrameSize::TwentyMs,
1269            SidePattern::Active,
1270            true,
1271            false,
1272            true,
1273            false,
1274        );
1275        let side = side.unwrap();
1276
1277        // Wrong interval count for the duration.
1278        let iv = StereoIntervalScripts {
1279            mid: symbols_of(&mid),
1280            side: Some(symbols_of(&side)),
1281        };
1282        assert!(encode_silk_only_packet_stereo(Bandwidth::Wb, 400, &[iv]).is_err());
1283
1284        // Missing stereo weights on the mid frame.
1285        let mut mid_no_w = symbols_of(&mid);
1286        mid_no_w.header.stereo = None;
1287        let iv = StereoIntervalScripts {
1288            mid: mid_no_w,
1289            side: Some(symbols_of(&side)),
1290        };
1291        assert!(encode_silk_only_packet_stereo(Bandwidth::Wb, 200, &[iv]).is_err());
1292
1293        // Active side but a mid-only flag present on the mid frame.
1294        let mut mid_bad_flag = symbols_of(&mid);
1295        mid_bad_flag.header.mid_only_flag = Some(false);
1296        let iv = StereoIntervalScripts {
1297            mid: mid_bad_flag,
1298            side: Some(symbols_of(&side)),
1299        };
1300        assert!(encode_silk_only_packet_stereo(Bandwidth::Wb, 200, &[iv]).is_err());
1301
1302        // Mid-only flag set but a side script supplied. The side script
1303        // must be inactive for the "flag value vs side presence" check
1304        // to be the failing condition (an active side sets side VAD,
1305        // which is the previous case).
1306        let (mid_mo, _) = random_stereo_interval(
1307            &mut rng,
1308            Bandwidth::Wb,
1309            SilkFrameSize::TwentyMs,
1310            SidePattern::MidOnly,
1311            true,
1312            false,
1313            true,
1314            false,
1315        );
1316        let mut side_inactive = random_frame_script(
1317            &mut rng,
1318            Bandwidth::Wb,
1319            SilkFrameSize::TwentyMs,
1320            true,
1321            false,
1322        );
1323        if side_inactive.header.frame_type >= 2 {
1324            side_inactive.header.frame_type %= 2;
1325            side_inactive.ltp = None;
1326        }
1327        let iv = StereoIntervalScripts {
1328            mid: symbols_of(&mid_mo),
1329            side: Some(symbols_of(&side_inactive)),
1330        };
1331        assert!(encode_silk_only_packet_stereo(Bandwidth::Wb, 200, &[iv]).is_err());
1332
1333        // Stereo weights on a *side* frame are rejected by the
1334        // per-frame encoder (want_stereo mismatch).
1335        let mut side_with_w = symbols_of(&side);
1336        side_with_w.header.stereo = Some(random_weights(&mut rng));
1337        let iv = StereoIntervalScripts {
1338            mid: symbols_of(&mid),
1339            side: Some(side_with_w),
1340        };
1341        assert!(encode_silk_only_packet_stereo(Bandwidth::Wb, 200, &[iv]).is_err());
1342
1343        // Inactive LBRR mid script.
1344        let (mid_ok, side_ok) = random_stereo_interval(
1345            &mut rng,
1346            Bandwidth::Wb,
1347            SilkFrameSize::TwentyMs,
1348            SidePattern::Active,
1349            true,
1350            false,
1351            true,
1352            false,
1353        );
1354        let side_ok = side_ok.unwrap();
1355        let mut lbrr_mid = random_frame_script(
1356            &mut rng,
1357            Bandwidth::Wb,
1358            SilkFrameSize::TwentyMs,
1359            true,
1360            false,
1361        );
1362        if lbrr_mid.header.frame_type >= 2 {
1363            lbrr_mid.header.frame_type %= 2;
1364            lbrr_mid.ltp = None;
1365        }
1366        lbrr_mid.header.stereo = Some(random_weights(&mut rng));
1367        lbrr_mid.header.mid_only_flag = Some(true);
1368        let iv = StereoIntervalScripts {
1369            mid: symbols_of(&mid_ok),
1370            side: Some(symbols_of(&side_ok)),
1371        };
1372        let lbrr = StereoIntervalLbrr {
1373            mid: Some(symbols_of(&lbrr_mid)),
1374            side: None,
1375        };
1376        assert!(
1377            encode_silk_only_packet_stereo_with_lbrr(Bandwidth::Wb, 200, &[iv], &[lbrr]).is_err()
1378        );
1379
1380        // LBRR mid with no side LBRR must carry mid_only == Some(true).
1381        let mut lbrr_mid_bad = random_frame_script(
1382            &mut rng,
1383            Bandwidth::Wb,
1384            SilkFrameSize::TwentyMs,
1385            true,
1386            false,
1387        );
1388        if lbrr_mid_bad.header.frame_type < 2 {
1389            lbrr_mid_bad.header.frame_type += 2;
1390        }
1391        lbrr_mid_bad.header.stereo = Some(random_weights(&mut rng));
1392        lbrr_mid_bad.header.mid_only_flag = Some(false);
1393        let iv = StereoIntervalScripts {
1394            mid: symbols_of(&mid_ok),
1395            side: Some(symbols_of(&side_ok)),
1396        };
1397        let lbrr = StereoIntervalLbrr {
1398            mid: Some(symbols_of(&lbrr_mid_bad)),
1399            side: None,
1400        };
1401        assert!(
1402            encode_silk_only_packet_stereo_with_lbrr(Bandwidth::Wb, 200, &[iv], &[lbrr]).is_err()
1403        );
1404    }
1405
1406    /// Frame-count / duration mismatches are rejected.
1407    #[test]
1408    fn packet_encode_rejects_bad_shape() {
1409        let mut rng = Lcg(7);
1410        let bufs = random_frame_script(
1411            &mut rng,
1412            Bandwidth::Nb,
1413            SilkFrameSize::TwentyMs,
1414            true,
1415            false,
1416        );
1417        let script = symbols_of(&bufs);
1418        // 40 ms needs 2 frames.
1419        assert!(encode_silk_only_packet_mono(Bandwidth::Nb, 400, &[script]).is_err());
1420        // 2.5 ms is not a SILK duration.
1421        let script = symbols_of(&bufs);
1422        assert!(encode_silk_only_packet_mono(Bandwidth::Nb, 25, &[script]).is_err());
1423        // SWB is not a SILK bandwidth.
1424        let script = symbols_of(&bufs);
1425        assert!(encode_silk_only_packet_mono(Bandwidth::Swb, 200, &[script]).is_err());
1426    }
1427}