Skip to main content

oxideav_opus/
multistream.rs

1//! Opus multistream packet handling — RFC 7845 §3 + §5.1.1.
2//!
3//! A multichannel Ogg-Opus stream encodes its `M + N` decoded channels
4//! as `N` independent Opus streams (the first `M` of which are stereo).
5//! Every Ogg packet therefore carries `N` separate Opus packets glued
6//! together. RFC 7845 §3 (pp. 4–5) pins the layout:
7//!
8//! > The first (N − 1) Opus packets, if any, are packed one after
9//! > another into the Ogg packet, using the self-delimiting framing from
10//! > Appendix B of \[RFC6716\]. The remaining Opus packet is packed at
11//! > the end of the Ogg packet using the regular, undelimited framing
12//! > from Section 3 of \[RFC6716\].
13//!
14//! This module performs that split — and only the split. It takes a
15//! whole multistream packet plus the stream count `N` (from the
16//! [`crate::opus_head::ChannelMappingTable`]) and recovers the `N`
17//! per-stream Opus packet byte-slices, each of which is a complete Opus
18//! packet directly decodable by [`crate::decoder::OpusDecoder`].
19//!
20//! The actual per-stream decode + channel-map mixing is composed on top
21//! of this split by the multistream decoder; keeping the split as a pure
22//! function makes it independently testable against the §3 framing
23//! rules.
24//!
25//! ## Provenance
26//!
27//! RFC 7845 §3 (pp. 4–5) for the N-packet layout and §5.1.1 for `N`.
28//! The self-delimiting framing it relies on is RFC 6716 Appendix B,
29//! already implemented in [`crate::framing_self_delim`]. No external
30//! library source is consulted.
31
32use crate::framing_self_delim::parse_self_delimited;
33use crate::opus_head::{ChannelMappingTable, OpusHead};
34use crate::Error;
35
36/// One stream's raw Opus packet bytes within a multistream packet.
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub struct StreamPacket<'a> {
39    /// The complete Opus packet bytes for this stream. For the first
40    /// `N − 1` streams these are the bytes the self-delimited parser
41    /// claimed *minus* the Appendix-B extra length fields — i.e. a
42    /// reconstructed regular packet is NOT produced here; instead this
43    /// slice is the self-delimited packet's full extent (TOC through its
44    /// frames), which a stream decoder consumes via the self-delimited
45    /// entry point. The final stream's slice is the undelimited
46    /// remainder.
47    pub bytes: &'a [u8],
48    /// `true` if this stream's bytes use RFC 6716 Appendix-B
49    /// self-delimited framing (the first `N − 1` streams); `false` for
50    /// the final stream, which uses the regular §3 framing.
51    pub self_delimited: bool,
52}
53
54/// Split a multistream Opus packet into its `N` per-stream packets.
55///
56/// `stream_count` is `N` from the channel-mapping table; it MUST be
57/// ≥ 1 (RFC 7845 §5.1.1 item 1 forbids zero). The first `N − 1` streams
58/// are parsed with the Appendix-B self-delimited framing to find each
59/// one's exact byte extent; the final stream is the undelimited
60/// remainder.
61///
62/// Returns [`Error::MalformedPacket`] if `stream_count` is zero, if a
63/// self-delimited sub-packet is malformed, or if the self-delimited
64/// prefixes overrun the buffer leaving nothing for the final stream.
65pub fn split_multistream_packet(
66    packet: &[u8],
67    stream_count: u8,
68) -> Result<Vec<StreamPacket<'_>>, Error> {
69    if stream_count == 0 {
70        return Err(Error::MalformedPacket);
71    }
72    let n = stream_count as usize;
73    let mut streams = Vec::with_capacity(n);
74    let mut offset = 0usize;
75
76    // The first N − 1 streams use self-delimited framing; consume each
77    // one and advance past it.
78    for _ in 0..(n - 1) {
79        if offset >= packet.len() {
80            // Ran out of bytes before reaching the final stream.
81            return Err(Error::MalformedPacket);
82        }
83        let parsed = parse_self_delimited(&packet[offset..])?;
84        let consumed = parsed.consumed;
85        // `parse_self_delimited` guarantees `consumed ≥ 1` on success.
86        streams.push(StreamPacket {
87            bytes: &packet[offset..offset + consumed],
88            self_delimited: true,
89        });
90        offset += consumed;
91    }
92
93    // The final stream is the undelimited remainder. RFC 7845 §3 +
94    // RFC 6716 §3.4 R1: a zero-octet final Opus packet is malformed.
95    if offset >= packet.len() {
96        return Err(Error::MalformedPacket);
97    }
98    streams.push(StreamPacket {
99        bytes: &packet[offset..],
100        self_delimited: false,
101    });
102
103    Ok(streams)
104}
105
106/// Assemble `N` regular (undelimited) per-stream Opus packets into one
107/// multistream packet — the write-side mirror of
108/// [`split_multistream_packet`] (RFC 7845 §3).
109///
110/// Per §3 the first `N − 1` packets are re-framed with the RFC 6716
111/// Appendix-B self-delimiting framing
112/// ([`crate::packet_compose::compose_self_delimited`]) and packed one
113/// after another; the final packet is appended verbatim with its
114/// regular framing. Each input must parse as a complete Opus packet;
115/// §3's equal-duration constraint ("the duration and TOC sequence …
116/// MUST be exactly the same") is enforced across the inputs (same TOC
117/// `config` and frame count).
118///
119/// A code-3 prefix packet is re-framed with its parsed padding
120/// preserved and CBR/VBR chosen from its frame lengths (uniform →
121/// CBR), so the self-delimited form is a valid §3.2-equivalent
122/// encoding of the same frames — byte-identity with the original
123/// code-3 header is not guaranteed for a VBR packet whose lengths
124/// happen to be uniform, but the parsed content always is identical.
125///
126/// Returns [`Error::MalformedPacket`] on an empty stream list, any
127/// unparsable input, or a duration/config mismatch.
128pub fn assemble_multistream_packet(packets: &[&[u8]]) -> Result<Vec<u8>, Error> {
129    use crate::frames::OpusPacket;
130    use crate::packet_compose::compose_self_delimited;
131
132    let n = packets.len();
133    if n == 0 {
134        return Err(Error::MalformedPacket);
135    }
136    // §3: every stream's packet must carry the same duration — pinned
137    // here as "same TOC config and same frame count" (the §3.2 layer
138    // determines the count; the config fixes the per-frame duration).
139    let mut shape: Option<(u8, usize)> = None;
140    let mut out = Vec::new();
141    for (idx, &packet) in packets.iter().enumerate() {
142        let parsed = OpusPacket::parse(packet)?;
143        let config = packet[0] >> 3;
144        let count = parsed.frame_count();
145        match shape {
146            None => shape = Some((config, count)),
147            Some(s) => {
148                if s != (config, count) {
149                    return Err(Error::MalformedPacket);
150                }
151            }
152        }
153        if idx + 1 < n {
154            // Prefix stream: re-frame self-delimited. CBR/VBR and
155            // padding only apply to a code-3 packet, chosen from its
156            // parsed frame lengths / padding.
157            let frames = parsed.frames();
158            let (vbr, padding) =
159                if parsed.toc.frame_count_code == crate::toc::FrameCountCode::Arbitrary {
160                    (
161                        frames.iter().any(|f| f.len() != frames[0].len()),
162                        parsed.padding,
163                    )
164                } else {
165                    (false, 0)
166                };
167            let sd = compose_self_delimited(packet[0], frames, vbr, padding)?;
168            out.extend_from_slice(&sd);
169        } else {
170            // Final stream: regular framing, verbatim.
171            out.extend_from_slice(packet);
172        }
173    }
174    Ok(out)
175}
176
177/// A stateful multistream (multichannel) Opus decoder — RFC 7845 §3 +
178/// §5.1.1.
179///
180/// Wraps `N` independent [`crate::decoder::OpusDecoder`] instances (one
181/// per coded stream) and the [`ChannelMappingTable`] that ties their
182/// outputs to the stream's `C` output channels. Each Ogg packet is split
183/// by [`split_multistream_packet`], every sub-stream is decoded by its
184/// own decoder (so each carries its own inter-frame state), and the
185/// per-stream PCM is assembled into the `C`-channel interleaved output
186/// per the §5.1.1 mapping rule:
187///
188/// * `index < 2*M` → output is decoded channel `index` of coupled
189///   (stereo) stream `index / 2` — left if `index` even, right if odd.
190/// * `2*M ≤ index < 255` → output is mono stream `index − M`.
191/// * `index == 255` → pure silence.
192///
193/// The same decoded channel MAY be routed to several output channels;
194/// some decoded channels MAY be unused — the §5.1.1 mapping is arbitrary.
195#[derive(Debug)]
196pub struct MultistreamDecoder {
197    mapping: ChannelMappingTable,
198    /// One decoder per coded stream (length `N`). The first `M` are
199    /// coupled (stereo) streams; the rest are mono.
200    decoders: Vec<crate::decoder::OpusDecoder>,
201}
202
203impl MultistreamDecoder {
204    /// Build a decoder for the given §5.1.1 channel-mapping table.
205    pub fn new(mapping: ChannelMappingTable) -> Self {
206        let n = mapping.stream_count as usize;
207        let decoders = (0..n).map(|_| crate::decoder::OpusDecoder::new()).collect();
208        MultistreamDecoder { mapping, decoders }
209    }
210
211    /// Build a multistream decoder straight from a parsed
212    /// [`OpusHead`] identification header.
213    pub fn from_head(head: &OpusHead) -> Self {
214        Self::new(head.mapping.clone())
215    }
216
217    /// The §5.1.1 channel-mapping table this decoder was built with.
218    pub fn mapping(&self) -> &ChannelMappingTable {
219        &self.mapping
220    }
221
222    /// Number of output channels `C`.
223    pub fn output_channels(&self) -> u8 {
224        self.mapping.output_channels()
225    }
226
227    /// Reset every per-stream decoder (the §4.5.2 decoder reset, e.g.
228    /// after a container seek).
229    pub fn reset(&mut self) {
230        for d in &mut self.decoders {
231            d.reset();
232        }
233    }
234
235    /// Decode one multistream Ogg packet into `C`-channel interleaved
236    /// 48 kHz PCM.
237    ///
238    /// Splits the packet into its `N` per-stream Opus packets, decodes
239    /// each through its own decoder, and assembles the `C` output
240    /// channels per the §5.1.1 mapping. Index-255 output channels are
241    /// filled with silence.
242    ///
243    /// Returns [`Error::MalformedPacket`] if the split fails or if a
244    /// sub-stream decode fails; the output sample count is taken from the
245    /// first stream (RFC 7845 §3 requires every stream in a packet to
246    /// have the same duration).
247    pub fn decode_packet(&mut self, packet: &[u8]) -> Result<MultistreamAudio, Error> {
248        let streams = split_multistream_packet(packet, self.mapping.stream_count)?;
249        let coupled = self.mapping.coupled_count as usize;
250
251        // Decode every stream. `decoded[s]` is the interleaved PCM of
252        // stream `s` together with its channel count.
253        let mut decoded: Vec<(Vec<i16>, u8)> = Vec::with_capacity(streams.len());
254        for (s, stream) in streams.iter().enumerate() {
255            let dec = &mut self.decoders[s];
256            let audio = if stream.self_delimited {
257                dec.decode_self_delimited_packet(stream.bytes)
258            } else {
259                dec.decode_packet(stream.bytes)
260            }
261            .map_err(|_| Error::MalformedPacket)?;
262            decoded.push((audio.pcm, audio.channels));
263        }
264
265        // RFC 7845 §3: "All of the Opus packets in a single Ogg packet
266        // MUST be constrained to have the same duration." A stream whose
267        // per-channel sample count differs from the first is treated as
268        // malformed (the channel assembly below relies on equal lengths).
269        let samples_per_channel = decoded
270            .first()
271            .map(|(pcm, ch)| pcm.len() / (*ch).max(1) as usize)
272            .unwrap_or(0);
273        for (pcm, ch) in &decoded[1..] {
274            let spc = pcm.len() / (*ch).max(1) as usize;
275            if spc != samples_per_channel {
276                return Err(Error::MalformedPacket);
277            }
278        }
279
280        let c = self.mapping.output_channels() as usize;
281        let mut out = vec![0i16; samples_per_channel * c];
282
283        for (out_ch, &index) in self.mapping.mapping.iter().enumerate() {
284            if index == 255 {
285                // §5.1.1: pure silence; `out` already zeroed.
286                continue;
287            }
288            // Resolve (stream, channel-within-stream) from the index per
289            // §5.1.1: index < 2*M selects coupled (stereo) stream index/2
290            // with L/R by parity; 2*M ≤ index < 255 selects mono stream
291            // index − M.
292            let (stream_idx, chan_in_stream) = if (index as usize) < 2 * coupled {
293                (index as usize / 2, index as usize % 2)
294            } else {
295                ((index as usize) - coupled, 0usize)
296            };
297            let (pcm, ch) = &decoded[stream_idx];
298            // The decoder's interleave width. A coupled stream whose
299            // packet decoded internally mono returns a single channel; in
300            // that case fall back to channel 0 for the requested L/R.
301            let src_channels = (*ch as usize).max(1);
302            let src_chan = if chan_in_stream < src_channels {
303                chan_in_stream
304            } else {
305                0
306            };
307            for sample in 0..samples_per_channel {
308                let src_idx = sample * src_channels + src_chan;
309                let v = pcm.get(src_idx).copied().unwrap_or(0);
310                out[sample * c + out_ch] = v;
311            }
312        }
313
314        Ok(MultistreamAudio {
315            pcm: out,
316            channels: self.mapping.output_channels(),
317            sample_rate_hz: crate::decoder::OUTPUT_SAMPLE_RATE_HZ,
318            samples_per_channel,
319        })
320    }
321}
322
323/// Decoded audio for one multistream Ogg packet: `C`-channel interleaved
324/// 48 kHz PCM.
325#[derive(Debug, Clone, PartialEq, Eq)]
326pub struct MultistreamAudio {
327    /// Interleaved signed 16-bit PCM at 48 kHz, `C` channels. Length is
328    /// `samples_per_channel * channels`.
329    pub pcm: Vec<i16>,
330    /// Output channel count `C`.
331    pub channels: u8,
332    /// Output sample rate (always 48 kHz).
333    pub sample_rate_hz: u32,
334    /// Per-channel 48 kHz sample count.
335    pub samples_per_channel: usize,
336}
337
338#[cfg(test)]
339mod tests {
340    use super::*;
341    use crate::toc::OpusTocByte;
342
343    /// A minimal self-delimited code-0 packet: TOC byte | 1-byte length
344    /// `len` | `len` frame bytes. `config` selects the TOC config so the
345    /// duration is well-defined.
346    fn sd_code0(config: u8, frame: &[u8]) -> Vec<u8> {
347        let toc = (config << 3) & 0xF8; // s = 0 (mono), code 0
348        let mut v = vec![toc];
349        assert!(frame.len() < 252, "test helper only emits 1-byte lengths");
350        v.push(frame.len() as u8);
351        v.extend_from_slice(frame);
352        v
353    }
354
355    /// A regular (undelimited) code-0 packet: TOC byte | frame bytes.
356    fn regular_code0(config: u8, frame: &[u8]) -> Vec<u8> {
357        let toc = (config << 3) & 0xF8;
358        let mut v = vec![toc];
359        v.extend_from_slice(frame);
360        v
361    }
362
363    #[test]
364    fn single_stream_is_whole_packet() {
365        // N = 1 → no self-delimited prefixes; the whole packet is the
366        // final (regular) stream.
367        let pkt = regular_code0(1, &[1, 2, 3, 4]);
368        let streams = split_multistream_packet(&pkt, 1).unwrap();
369        assert_eq!(streams.len(), 1);
370        assert!(!streams[0].self_delimited);
371        assert_eq!(streams[0].bytes, pkt.as_slice());
372    }
373
374    #[test]
375    fn two_streams_split_at_self_delim_boundary() {
376        // N = 2: one self-delimited stream then one regular remainder.
377        let s0 = sd_code0(1, &[0xAA, 0xBB]);
378        let s1 = regular_code0(1, &[0xCC, 0xDD, 0xEE]);
379        let mut pkt = s0.clone();
380        pkt.extend_from_slice(&s1);
381        let streams = split_multistream_packet(&pkt, 2).unwrap();
382        assert_eq!(streams.len(), 2);
383        assert!(streams[0].self_delimited);
384        assert_eq!(streams[0].bytes, s0.as_slice());
385        assert!(!streams[1].self_delimited);
386        assert_eq!(streams[1].bytes, s1.as_slice());
387        // The two halves recover their TOCs.
388        assert_eq!(
389            OpusTocByte::from_byte(streams[0].bytes[0]).frame_size_tenths_ms,
390            OpusTocByte::from_byte(streams[1].bytes[0]).frame_size_tenths_ms
391        );
392    }
393
394    #[test]
395    fn four_streams_5_1_layout() {
396        // A 5.1 layout has N = 4 (2 coupled + 2 mono). Build 3
397        // self-delimited prefixes + 1 regular tail.
398        let s0 = sd_code0(1, &[1]);
399        let s1 = sd_code0(1, &[2, 2]);
400        let s2 = sd_code0(1, &[3, 3, 3]);
401        let s3 = regular_code0(1, &[4, 4, 4, 4]);
402        let mut pkt = Vec::new();
403        for s in [&s0, &s1, &s2] {
404            pkt.extend_from_slice(s);
405        }
406        pkt.extend_from_slice(&s3);
407        let streams = split_multistream_packet(&pkt, 4).unwrap();
408        assert_eq!(streams.len(), 4);
409        assert_eq!(streams[0].bytes, s0.as_slice());
410        assert_eq!(streams[1].bytes, s1.as_slice());
411        assert_eq!(streams[2].bytes, s2.as_slice());
412        assert_eq!(streams[3].bytes, s3.as_slice());
413        assert!(streams[3].bytes.starts_with(&[(1u8 << 3) & 0xF8]));
414    }
415
416    #[test]
417    fn zero_stream_count_rejected() {
418        assert_eq!(
419            split_multistream_packet(&[0x08, 1, 2], 0),
420            Err(Error::MalformedPacket)
421        );
422    }
423
424    #[test]
425    fn missing_final_stream_rejected() {
426        // N = 2 but the self-delimited prefix consumes the whole buffer,
427        // leaving nothing for the final regular stream.
428        let s0 = sd_code0(1, &[0xAA, 0xBB]);
429        assert_eq!(
430            split_multistream_packet(&s0, 2),
431            Err(Error::MalformedPacket)
432        );
433    }
434
435    #[test]
436    fn truncated_self_delim_prefix_rejected() {
437        // A self-delimited length that runs off the end is malformed.
438        let bad = vec![(1u8 << 3) & 0xF8, 200, 1, 2]; // claims 200 bytes
439        assert_eq!(
440            split_multistream_packet(&bad, 2),
441            Err(Error::MalformedPacket)
442        );
443    }
444
445    /// assemble → split roundtrip: three streams (a code-2 prefix, a
446    /// padded code-3 VBR prefix, and a code-3 final packet appended
447    /// verbatim) reassemble into per-stream packets whose parsed frames
448    /// and padding match the originals.
449    #[test]
450    fn assemble_split_roundtrip_mixed_codes() {
451        use crate::frames::OpusPacket;
452        use crate::framing_self_delim::parse_self_delimited;
453        use crate::packet_compose::{compose_packet, compose_packet_code3};
454        use crate::toc::{Bandwidth, FrameCountCode, Mode};
455
456        let toc2 = OpusTocByte::compose_byte(
457            Mode::SilkOnly,
458            Bandwidth::Nb,
459            200,
460            false,
461            FrameCountCode::TwoUnequal,
462        )
463        .unwrap();
464        let toc3 = OpusTocByte::compose_byte(
465            Mode::SilkOnly,
466            Bandwidth::Nb,
467            200,
468            true,
469            FrameCountCode::Arbitrary,
470        )
471        .unwrap();
472        let fa: &[u8] = &[1, 2, 3];
473        let fb: &[u8] = &[4, 5, 6, 7, 8];
474        let p0 = compose_packet(toc2, &[fa, fb]).unwrap();
475        let p1 = compose_packet_code3(toc3, &[fb, fa], true, 300).unwrap();
476        let p2 = compose_packet_code3(toc3, &[fa, fa], false, 0).unwrap();
477
478        let assembled = assemble_multistream_packet(&[&p0, &p1, &p2]).unwrap();
479        let streams = split_multistream_packet(&assembled, 3).unwrap();
480        assert_eq!(streams.len(), 3);
481        assert!(streams[0].self_delimited && streams[1].self_delimited);
482        assert!(!streams[2].self_delimited);
483
484        let s0 = parse_self_delimited(streams[0].bytes).unwrap();
485        assert_eq!(s0.packet.frames(), &[fa, fb]);
486        assert_eq!(s0.packet.padding, 0);
487        let s1 = parse_self_delimited(streams[1].bytes).unwrap();
488        assert_eq!(s1.packet.frames(), &[fb, fa]);
489        assert_eq!(s1.packet.padding, 300);
490        // Final stream is byte-verbatim.
491        assert_eq!(streams[2].bytes, p2.as_slice());
492        let s2 = OpusPacket::parse(streams[2].bytes).unwrap();
493        assert_eq!(s2.frames(), &[fa, fa]);
494    }
495
496    /// assemble rejects an empty stream list, a §3 duration/config
497    /// mismatch, a frame-count mismatch, and unparsable input.
498    #[test]
499    fn assemble_rejects_mismatch_and_garbage() {
500        use crate::packet_compose::compose_packet;
501        use crate::toc::{Bandwidth, FrameCountCode, Mode};
502
503        assert_eq!(
504            assemble_multistream_packet(&[]),
505            Err(Error::MalformedPacket)
506        );
507
508        let toc_20 = OpusTocByte::compose_byte(
509            Mode::SilkOnly,
510            Bandwidth::Nb,
511            200,
512            false,
513            FrameCountCode::One,
514        )
515        .unwrap();
516        let toc_40 = OpusTocByte::compose_byte(
517            Mode::SilkOnly,
518            Bandwidth::Nb,
519            400,
520            false,
521            FrameCountCode::One,
522        )
523        .unwrap();
524        let a = compose_packet(toc_20, &[&[1, 2][..]]).unwrap();
525        let b = compose_packet(toc_40, &[&[3, 4][..]]).unwrap();
526        // Config (duration) mismatch.
527        assert_eq!(
528            assemble_multistream_packet(&[&a, &b]),
529            Err(Error::MalformedPacket)
530        );
531        // Frame-count mismatch at equal config: code 0 vs code 1.
532        let toc_c1 = OpusTocByte::compose_byte(
533            Mode::SilkOnly,
534            Bandwidth::Nb,
535            200,
536            false,
537            FrameCountCode::TwoEqual,
538        )
539        .unwrap();
540        let c = compose_packet(toc_c1, &[&[1, 2][..], &[3, 4][..]]).unwrap();
541        assert_eq!(
542            assemble_multistream_packet(&[&a, &c]),
543            Err(Error::MalformedPacket)
544        );
545        // Unparsable input (empty inner packet → §3.4 R1 EmptyPacket).
546        assert_eq!(
547            assemble_multistream_packet(&[&a, &[][..]]),
548            Err(Error::EmptyPacket)
549        );
550    }
551}