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}