opus_pure/multistream.rs
1//! Opus multistream (surround) — port of the core of
2//! `src/opus_multistream_{encoder,decoder}.c`. Wraps N mono/coupled Opus
3//! coders behind a channel-mapping layout so >2-channel audio (quad, 5.1,
4//! 7.1) can be coded as a set of standard Opus streams concatenated with the
5//! self-delimited framing.
6//!
7//! The channel bitrate allocation here is a simple even split across streams
8//! (coupled streams get 2x a mono stream's share) — libopus adds a
9//! surround-masking analysis on top, a quality refinement, not a conformance
10//! requirement. The bitstream layout, mapping, and per-stream Opus coding are
11//! standard, so streams interoperate with libopus.
12
13use crate::{Error, Result};
14
15use crate::encoder::MAX_ENCODING_DEPTH;
16use crate::repacketizer::{Repacketizer, take_self_delimited_into};
17use crate::soft_clip::{float_to_i16, i16_to_float};
18use crate::{Application, Bandwidth, OpusDecoder, OpusEncoder};
19
20/// Vorbis channel layout for mapping family 1, channels 1..=8:
21/// (nb_streams, nb_coupled_streams, channel_mapping).
22const VORBIS_MAPPINGS: [(usize, usize, &[u8]); 8] = [
23 (1, 0, &[0]), // mono
24 (1, 1, &[0, 1]), // stereo
25 (2, 1, &[0, 2, 1]), // 1-d (3.0)
26 (2, 2, &[0, 1, 2, 3]), // quad
27 (3, 2, &[0, 4, 1, 2, 3]), // 5.0
28 (4, 2, &[0, 4, 1, 2, 3, 5]), // 5.1
29 (4, 3, &[0, 4, 1, 2, 3, 5, 6]), // 6.1
30 (5, 3, &[0, 6, 1, 2, 3, 4, 5, 7]), // 7.1
31];
32
33/// How a set of Opus streams becomes a set of output channels.
34///
35/// A multistream packet is several ordinary Opus streams concatenated. Some are
36/// coded as coupled stereo pairs and carry two channels each, the rest carry
37/// one, and the mapping says which of the resulting channels goes where in the
38/// output. Coupled streams always come first, so the channels a decoder
39/// produces are the coupled pairs in order, then the mono streams in order.
40///
41/// [`surround`](Self::surround) builds the standard layout for a channel count
42/// and mapping family, which is how [`OpusMSEncoder`] and [`OpusMSDecoder`]
43/// obtain theirs. The fields are public so a caller can read what a given
44/// channel count works out to, which is what the
45/// [`OpusHead`](crate::OpusHead) for such a stream has to declare.
46#[derive(Debug, Clone)]
47pub struct ChannelLayout {
48 /// The mapping family this layout came from: 0 for mono/stereo, 1 for the
49 /// Vorbis surround orders. Kept so the layout can describe itself to an
50 /// [`OpusHead`](crate::OpusHead) without being asked twice.
51 pub mapping_family: u8,
52 /// Output channels this layout produces, which is what a caller interleaves.
53 pub nb_channels: usize,
54 /// Opus streams in the packet, coupled and uncoupled together.
55 pub nb_streams: usize,
56 /// How many of those streams are coupled stereo pairs. They are the first
57 /// `nb_coupled_streams` of them, and each carries two channels, so the
58 /// streams carry `nb_coupled_streams + nb_streams` channels in total.
59 pub nb_coupled_streams: usize,
60 /// For each output channel, which of the streams' channels feeds it.
61 ///
62 /// Indices `0..2 * nb_coupled_streams` are the coupled pairs, left then
63 /// right; the rest are the mono streams in order. The value 255 marks a
64 /// channel that is left silent. Length is `nb_channels`.
65 pub mapping: Vec<u8>,
66}
67
68impl ChannelLayout {
69 /// Standard layout for a channel count + mapping family (0 = mono/stereo,
70 /// 1 = Vorbis surround for 1..=8 channels).
71 pub fn surround(channels: usize, mapping_family: u8) -> Result<Self> {
72 match mapping_family {
73 0 => {
74 if channels == 1 {
75 Ok(ChannelLayout {
76 mapping_family: 0,
77 nb_channels: 1,
78 nb_streams: 1,
79 nb_coupled_streams: 0,
80 mapping: vec![0],
81 })
82 } else if channels == 2 {
83 Ok(ChannelLayout {
84 mapping_family: 0,
85 nb_channels: 2,
86 nb_streams: 1,
87 nb_coupled_streams: 1,
88 mapping: vec![0, 1],
89 })
90 } else {
91 Err(Error::InvalidArgument(
92 "family 0 supports only 1-2 channels",
93 ))
94 }
95 }
96 1 => {
97 if !(1..=8).contains(&channels) {
98 return Err(Error::InvalidArgument("family 1 supports 1-8 channels"));
99 }
100 let (ns, nc, m) = VORBIS_MAPPINGS[channels - 1];
101 Ok(ChannelLayout {
102 mapping_family: 1,
103 nb_channels: channels,
104 nb_streams: ns,
105 nb_coupled_streams: nc,
106 mapping: m.to_vec(),
107 })
108 }
109 _ => Err(Error::InvalidArgument("unsupported mapping family")),
110 }
111 }
112
113 /// Output-channel indices carrying `target` in the channel mapping.
114 ///
115 /// libopus walks these with a `-1`-sentinel cursor (`get_left_channel` and
116 /// friends, one function per target); an iterator says the same thing once,
117 /// without the sentinel.
118 fn channels_for(&self, target: usize) -> impl Iterator<Item = usize> + '_ {
119 self.mapping
120 .iter()
121 .enumerate()
122 .filter_map(move |(i, &m)| (m as usize == target).then_some(i))
123 }
124
125 /// Mapping value for a coupled stream's left channel.
126 fn left_target(stream_id: usize) -> usize {
127 stream_id * 2
128 }
129 /// Mapping value for a coupled stream's right channel.
130 fn right_target(stream_id: usize) -> usize {
131 stream_id * 2 + 1
132 }
133 /// Mapping value for an uncoupled stream's single channel.
134 fn mono_target(&self, stream_id: usize) -> usize {
135 stream_id + self.nb_coupled_streams
136 }
137}
138
139/// Multistream encoder: one Opus encoder per stream (coupled = stereo, the
140/// rest mono), coded per the channel layout and concatenated self-delimited.
141pub struct OpusMSEncoder {
142 layout: ChannelLayout,
143 encoders: Vec<OpusEncoder>,
144 sample_rate: i32,
145 /// Total target bitrate across all streams; read through
146 /// [`bitrate_bps`](OpusMSEncoder::bitrate_bps), written through
147 /// [`set_bitrate`](OpusMSEncoder::set_bitrate).
148 ///
149 /// Private because the value that matters is the per-stream split derived
150 /// from it, not this number: a writable field here would let a caller set
151 /// it and change nothing.
152 bitrate_bps: i32,
153 /// One stream's channels, de-interleaved out of the caller's input.
154 buf_stream: Vec<f32>,
155 /// One stream's coded packet, before it is framed into the output.
156 buf_packet: Vec<u8>,
157 /// The assembled multistream packet.
158 buf_out: Vec<u8>,
159 /// Float conversion scratch for [`encode_s16`](OpusMSEncoder::encode_s16),
160 /// kept for the same reason [`OpusEncoder`] keeps its own.
161 buf_from_s16: Vec<f32>,
162 /// Reused to apply the self-delimited framing to every stream but the last.
163 framer: Repacketizer,
164}
165
166/// Shows the layout and per-stream settings, not the encoders' coding state.
167impl std::fmt::Debug for OpusMSEncoder {
168 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169 f.debug_struct("OpusMSEncoder")
170 .field("sample_rate", &self.sample_rate)
171 .field("bitrate_bps", &self.bitrate_bps)
172 .field("layout", &self.layout)
173 .finish_non_exhaustive()
174 }
175}
176
177impl OpusMSEncoder {
178 /// Create a surround encoder for `channels` channels.
179 ///
180 /// `mapping_family` selects the layout: 0 for mono or stereo, 1 for the
181 /// Vorbis surround orders (quad, 5.0, 5.1, 6.1, 7.1) up to 8 channels.
182 /// These are the families RFC 7845 defines for an `.opus` file, and the
183 /// same value belongs in the [`OpusHead`](crate::OpusHead) written
184 /// alongside. Any other value, or more than 8 channels, is
185 /// [`Error::InvalidArgument`];
186 /// [`ChannelLayout::surround`] is where that decision is made.
187 pub fn new(
188 sample_rate: i32,
189 channels: usize,
190 mapping_family: u8,
191 application: Application,
192 ) -> Result<Self> {
193 let layout = ChannelLayout::surround(channels, mapping_family)?;
194 let mut encoders = Vec::with_capacity(layout.nb_streams);
195 for s in 0..layout.nb_streams {
196 let ch = if s < layout.nb_coupled_streams { 2 } else { 1 };
197 encoders.push(OpusEncoder::new(sample_rate, ch, application)?);
198 }
199 let mut enc = OpusMSEncoder {
200 layout,
201 encoders,
202 sample_rate,
203 bitrate_bps: 64000 * channels as i32,
204 buf_stream: Vec::new(),
205 buf_packet: Vec::new(),
206 buf_out: Vec::new(),
207 buf_from_s16: Vec::new(),
208 framer: Repacketizer::new(),
209 };
210 enc.set_bitrate(enc.bitrate_bps);
211 Ok(enc)
212 }
213
214 /// Split the total bitrate across streams (each coupled stream gets 2x a
215 /// mono stream's share, matching its 2 channels).
216 pub fn set_bitrate(&mut self, total: i32) {
217 self.bitrate_bps = total;
218 let units = self.layout.nb_coupled_streams * 2
219 + (self.layout.nb_streams - self.layout.nb_coupled_streams);
220 let per_unit = if units > 0 {
221 total / units as i32
222 } else {
223 total
224 };
225 for (s, e) in self.encoders.iter_mut().enumerate() {
226 e.bitrate_bps = if s < self.layout.nb_coupled_streams {
227 per_unit * 2
228 } else {
229 per_unit
230 };
231 }
232 }
233
234 /// Opus streams this encoder writes into each packet. Needed for the
235 /// [`OpusHead`](crate::OpusHead) that describes the stream.
236 pub fn nb_streams(&self) -> usize {
237 self.layout.nb_streams
238 }
239
240 /// The channel layout this encoder codes to.
241 ///
242 /// This is what an [`OpusHead`](crate::OpusHead) for the stream has to
243 /// declare, and [`OpusHead::for_ms_encoder`](crate::OpusHead::for_ms_encoder)
244 /// takes it from here rather than making you derive it a second time.
245 pub fn layout(&self) -> &ChannelLayout {
246 &self.layout
247 }
248
249 /// Total target bitrate across all streams. Set it with
250 /// [`set_bitrate`](Self::set_bitrate).
251 pub fn bitrate_bps(&self) -> i32 {
252 self.bitrate_bps
253 }
254
255 /// The per-stream encoders, so every [`OpusEncoder`] setting is reachable.
256 ///
257 /// A multistream encoder is N ordinary encoders, and rather than mirror ten
258 /// settings here — a list that would go stale the moment `OpusEncoder`
259 /// gained an eleventh — this hands them over. Set the same thing on all of
260 /// them:
261 ///
262 /// ```
263 /// # use opus_pure::{Application, OpusMSEncoder};
264 /// # let mut enc = OpusMSEncoder::new(48_000, 6, 1, Application::Audio)?;
265 /// for e in enc.streams_mut() {
266 /// e.use_inband_fec = true;
267 /// e.packet_loss_perc = 10;
268 /// }
269 /// # Ok::<(), opus_pure::Error>(())
270 /// ```
271 ///
272 /// [`bitrate_bps`](OpusEncoder::bitrate_bps) is the exception: it is split
273 /// across streams from a single total, so set it with
274 /// [`set_bitrate`](Self::set_bitrate) and let the split happen.
275 pub fn streams_mut(&mut self) -> &mut [OpusEncoder] {
276 &mut self.encoders
277 }
278
279 /// The per-stream encoders, for reading. See
280 /// [`streams_mut`](Self::streams_mut) to change their settings.
281 pub fn streams(&self) -> &[OpusEncoder] {
282 &self.encoders
283 }
284
285 /// Encode one frame of interleaved `input` into a multistream packet,
286 /// returning how many bytes of `output` it filled.
287 ///
288 /// `input` holds `frame_size * nb_channels` samples. Unlike
289 /// [`OpusEncoder::encode`], `output.len()` is a plain capacity here and not
290 /// a byte budget: a buffer too small is
291 /// [`Error::BufferTooSmall`], whose `needed`
292 /// tells you exactly how big to make it, rather than a quietly smaller
293 /// packet. Each stream is coded to its own share of
294 /// [`bitrate_bps`](Self::bitrate_bps).
295 pub fn encode(&mut self, input: &[f32], frame_size: usize, output: &mut [u8]) -> Result<usize> {
296 self.encode_native(input, frame_size, output, MAX_ENCODING_DEPTH)
297 }
298
299 /// [`encode`](Self::encode) from 16-bit PCM.
300 ///
301 /// Every stream is told its input came from 16 bits, exactly as
302 /// [`OpusEncoder::encode_s16`] does for a single stream, and for the same
303 /// reason: the digital-silence floor sits at the source's own precision.
304 pub fn encode_s16(
305 &mut self,
306 input: &[i16],
307 frame_size: usize,
308 output: &mut [u8],
309 ) -> Result<usize> {
310 let wanted = frame_size * self.layout.nb_channels;
311 if input.len() < wanted {
312 return Err(Error::InvalidArgument(
313 "input is shorter than frame_size * channels",
314 ));
315 }
316 // Converted through a member rather than a fresh `Vec` per call, the
317 // way `OpusEncoder::encode_s16` does it.
318 let mut converted = std::mem::take(&mut self.buf_from_s16);
319 converted.clear();
320 converted.extend(input[..wanted].iter().copied().map(i16_to_float));
321 let r = self.encode_native(&converted, frame_size, output, 16);
322 self.buf_from_s16 = converted;
323 r
324 }
325
326 fn encode_native(
327 &mut self,
328 input: &[f32],
329 frame_size: usize,
330 output: &mut [u8],
331 api_lsb_depth: i32,
332 ) -> Result<usize> {
333 let nch = self.layout.nb_channels;
334 if input.len() < frame_size * nch {
335 return Err(Error::InvalidArgument(
336 "input is shorter than frame_size * channels",
337 ));
338 }
339
340 // Taken out of `self` so the per-stream encoder below can borrow `self`
341 // mutably at the same time; put back before every return.
342 let mut stream_buf = std::mem::take(&mut self.buf_stream);
343 let mut pkt = std::mem::take(&mut self.buf_packet);
344 let mut out = std::mem::take(&mut self.buf_out);
345 let mut framer = std::mem::take(&mut self.framer);
346 stream_buf.clear();
347 stream_buf.resize(frame_size * 2, 0.0);
348 pkt.clear();
349 pkt.resize(1500 + frame_size, 0);
350 out.clear();
351
352 let result = (|| -> Result<usize> {
353 for s in 0..self.layout.nb_streams {
354 let coupled = s < self.layout.nb_coupled_streams;
355 let sch = if coupled { 2 } else { 1 };
356 // Gather this stream's channels from the interleaved input.
357 if coupled {
358 let l = self
359 .layout
360 .channels_for(ChannelLayout::left_target(s))
361 .next();
362 let r = self
363 .layout
364 .channels_for(ChannelLayout::right_target(s))
365 .next();
366 for i in 0..frame_size {
367 stream_buf[i * 2] = l.map_or(0.0, |c| input[i * nch + c]);
368 stream_buf[i * 2 + 1] = r.map_or(0.0, |c| input[i * nch + c]);
369 }
370 } else {
371 let m = self.layout.channels_for(self.layout.mono_target(s)).next();
372 for i in 0..frame_size {
373 stream_buf[i] = m.map_or(0.0, |c| input[i * nch + c]);
374 }
375 }
376 let n = self.encoders[s].encode_native(
377 &stream_buf[..frame_size * sch],
378 frame_size,
379 &mut pkt,
380 api_lsb_depth,
381 )?;
382 // All streams but the last are self-delimited so the decoder can
383 // find each stream's boundary.
384 if s != self.layout.nb_streams - 1 {
385 framer.clear();
386 framer.cat(&pkt[..n])?;
387 framer.out_self_delimited_into(&mut out)?;
388 } else {
389 out.extend_from_slice(&pkt[..n]);
390 }
391 }
392 if output.len() < out.len() {
393 return Err(Error::buffer_too_small(out.len(), output.len()));
394 }
395 output[..out.len()].copy_from_slice(&out);
396 Ok(out.len())
397 })();
398
399 self.buf_stream = stream_buf;
400 self.buf_packet = pkt;
401 self.buf_out = out;
402 self.framer = framer;
403 result
404 }
405
406 /// The sample rate this encoder was created with.
407 pub fn sample_rate(&self) -> i32 {
408 self.sample_rate
409 }
410
411 /// The number of output channels this encoder codes.
412 pub fn channels(&self) -> usize {
413 self.layout.nb_channels
414 }
415
416 /// Discard every stream's coding state, keeping all settings, as
417 /// [`OpusEncoder::reset_state`] does for one.
418 pub fn reset_state(&mut self) -> Result<()> {
419 for e in &mut self.encoders {
420 e.reset_state()?;
421 }
422 Ok(())
423 }
424}
425
426/// Multistream decoder: decode each stream and remux to the output channels.
427pub struct OpusMSDecoder {
428 layout: ChannelLayout,
429 decoders: Vec<OpusDecoder>,
430 /// One stream's decoded channels, before they are remuxed to the output.
431 buf_stream: Vec<f32>,
432 /// Float scratch [`decode_s16`](OpusMSDecoder::decode_s16) decodes into
433 /// before converting, kept rather than allocated per call.
434 buf_f32: Vec<f32>,
435 /// Scratch buffer for un-delimiting multistream packets.
436 buf_rebuilt: Vec<u8>,
437}
438
439/// Shows the layout, not the decoders' coding state.
440impl std::fmt::Debug for OpusMSDecoder {
441 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
442 f.debug_struct("OpusMSDecoder")
443 .field("layout", &self.layout)
444 .finish_non_exhaustive()
445 }
446}
447
448impl OpusMSDecoder {
449 /// Create a surround decoder for `channels` channels.
450 ///
451 /// `mapping_family` selects the layout, as in
452 /// [`OpusMSEncoder::new`]; for a stream read from a file it is the one
453 /// [`OpusHead::mapping_family`](crate::OpusHead::mapping_family) carries.
454 pub fn new(sample_rate: i32, channels: usize, mapping_family: u8) -> Result<Self> {
455 let layout = ChannelLayout::surround(channels, mapping_family)?;
456 let mut decoders = Vec::with_capacity(layout.nb_streams);
457 for s in 0..layout.nb_streams {
458 let ch = if s < layout.nb_coupled_streams { 2 } else { 1 };
459 decoders.push(OpusDecoder::new(sample_rate, ch)?);
460 }
461 Ok(OpusMSDecoder {
462 layout,
463 decoders,
464 buf_stream: Vec::new(),
465 buf_f32: Vec::new(),
466 buf_rebuilt: Vec::new(),
467 })
468 }
469
470 /// Decode a multistream packet into interleaved `output` (nb_channels per
471 /// sample). Returns the number of samples per channel.
472 /// Decode one multistream packet into interleaved float PCM.
473 ///
474 /// As with [`OpusDecoder::decode`], the output is not bounded by ±1.
475 pub fn decode(
476 &mut self,
477 packet: &[u8],
478 frame_size: usize,
479 output: &mut [f32],
480 ) -> Result<usize> {
481 self.decode_native(packet, frame_size, output, false)
482 }
483
484 /// Decode one multistream packet into interleaved 16-bit PCM.
485 ///
486 /// Each stream is soft-clipped on its own before the channels are remuxed,
487 /// which is where libopus applies it too (`opus_multistream_decode_native`
488 /// hands `soft_clip` to each stream's decoder). Clipping the remuxed result
489 /// instead would let one stream's peak bend another's audio.
490 pub fn decode_s16(
491 &mut self,
492 packet: &[u8],
493 frame_size: usize,
494 output: &mut [i16],
495 ) -> Result<usize> {
496 let nch = self.layout.nb_channels;
497 let capacity = frame_size * nch;
498 if output.len() < capacity {
499 return Err(Error::buffer_too_small(capacity, output.len()));
500 }
501 let mut pcm = std::mem::take(&mut self.buf_f32);
502 pcm.clear();
503 pcm.resize(capacity, 0.0);
504 let result = self.decode_native(packet, frame_size, &mut pcm, true);
505 if let Ok(produced) = result {
506 let n = produced * nch;
507 for (o, &s) in output[..n].iter_mut().zip(&pcm[..n]) {
508 *o = float_to_i16(s);
509 }
510 }
511 self.buf_f32 = pcm;
512 result
513 }
514
515 fn decode_native(
516 &mut self,
517 packet: &[u8],
518 frame_size: usize,
519 output: &mut [f32],
520 soft_clip: bool,
521 ) -> Result<usize> {
522 let nch = self.layout.nb_channels;
523 // Every remux below writes `frame_size * nch` samples. libopus takes the
524 // buffer on trust because C hands it a bare pointer; here the slice
525 // knows its length, so a short one is an error rather than a panic.
526 if output.len() < frame_size * nch {
527 return Err(Error::buffer_too_small(frame_size * nch, output.len()));
528 }
529 // Taken out of `self` so a stream's decoder can borrow `self` mutably
530 // at the same time; put back before the return.
531 let mut buf = std::mem::take(&mut self.buf_stream);
532 let mut rebuilt = std::mem::take(&mut self.buf_rebuilt);
533 buf.clear();
534 buf.resize(frame_size * 2, 0.0);
535 let mut data = packet;
536 let mut produced = frame_size;
537
538 let result = (|| -> Result<usize> {
539 for s in 0..self.layout.nb_streams {
540 let coupled = s < self.layout.nb_coupled_streams;
541 let last = s == self.layout.nb_streams - 1;
542 // The last stream is a normal packet; every earlier one is
543 // self-delimited and must have its length prefix stripped before the
544 // single-stream decoder, which does not understand that framing.
545 let (stream_slice, advance) = if last {
546 (data, data.len())
547 } else {
548 let off = take_self_delimited_into(data, &mut rebuilt)?;
549 (rebuilt.as_slice(), off)
550 };
551 let n = self.decoders[s].decode_native(
552 stream_slice,
553 frame_size,
554 &mut buf,
555 soft_clip,
556 )?;
557 produced = n;
558 // Remux this stream's channel(s) to the output.
559 if coupled {
560 for chan in self.layout.channels_for(ChannelLayout::left_target(s)) {
561 for i in 0..n {
562 output[i * nch + chan] = buf[i * 2];
563 }
564 }
565 for chan in self.layout.channels_for(ChannelLayout::right_target(s)) {
566 for i in 0..n {
567 output[i * nch + chan] = buf[i * 2 + 1];
568 }
569 }
570 } else {
571 for chan in self.layout.channels_for(self.layout.mono_target(s)) {
572 for i in 0..n {
573 output[i * nch + chan] = buf[i];
574 }
575 }
576 }
577 if !last {
578 data = &data[advance..];
579 }
580 }
581 // Unmapped channels (mapping == 255) are silenced.
582 for c in 0..nch {
583 if self.layout.mapping.get(c).copied() == Some(255) {
584 for i in 0..produced {
585 output[i * nch + c] = 0.0;
586 }
587 }
588 }
589 Ok(produced)
590 })();
591 self.buf_stream = buf;
592 self.buf_rebuilt = rebuilt;
593 result
594 }
595
596 /// The channel layout this decoder produces.
597 pub fn layout(&self) -> &ChannelLayout {
598 &self.layout
599 }
600
601 /// Opus streams this decoder expects in each packet.
602 pub fn nb_streams(&self) -> usize {
603 self.layout.nb_streams
604 }
605
606 /// The number of output channels this decoder produces.
607 pub fn channels(&self) -> usize {
608 self.layout.nb_channels
609 }
610
611 /// The sample rate this decoder was created with, in Hz.
612 pub fn sample_rate(&self) -> i32 {
613 self.decoders[0].sample_rate()
614 }
615
616 /// The per-stream decoders, so every [`OpusDecoder`] setting is reachable.
617 ///
618 /// The counterpart of [`OpusMSEncoder::streams_mut`], and the only route to
619 /// [`gain_q8`](OpusDecoder::gain_q8) — which is not a nicety. RFC 7845 §5.1
620 /// says a player SHOULD apply the gain a file declares, and it says nothing
621 /// about mapping family, so a surround stream carrying a non-zero
622 /// [`OpusHead::output_gain_q8`](crate::OpusHead::output_gain_q8) plays at
623 /// the wrong level with no other symptom. A multistream decoder is N
624 /// ordinary decoders and the gain belongs on every one of them:
625 ///
626 /// ```
627 /// # use opus_pure::{OpusHead, OpusMSDecoder};
628 /// # let head = OpusHead::for_layout(
629 /// # &opus_pure::ChannelLayout::surround(6, 1)?, 48_000);
630 /// let mut dec = OpusMSDecoder::new(48_000, 6, head.mapping_family)?;
631 /// for d in dec.streams_mut() {
632 /// d.gain_q8 = head.output_gain_q8 as i32;
633 /// }
634 /// # Ok::<(), opus_pure::Error>(())
635 /// ```
636 ///
637 /// For mono and stereo, [`OpusHead::decoder`](crate::OpusHead::decoder)
638 /// does this for you and there is nothing to remember.
639 pub fn streams_mut(&mut self) -> &mut [OpusDecoder] {
640 &mut self.decoders
641 }
642
643 /// The per-stream decoders, for reading. See
644 /// [`streams_mut`](Self::streams_mut) to change their settings.
645 ///
646 /// [`final_range`](OpusDecoder::final_range) per stream is what a
647 /// conformance check compares against a reference decoder's.
648 pub fn streams(&self) -> &[OpusDecoder] {
649 &self.decoders
650 }
651
652 /// Discard every stream's coding state, as [`OpusDecoder::reset_state`]
653 /// does for one.
654 pub fn reset_state(&mut self) -> Result<()> {
655 for d in &mut self.decoders {
656 d.reset_state()?;
657 }
658 Ok(())
659 }
660}
661
662/// Bandwidth passthrough helper (so callers can cap all streams at once).
663impl OpusMSEncoder {
664 /// Cap the audio bandwidth of every stream at once, as
665 /// [`OpusEncoder::max_bandwidth`] does for one.
666 pub fn set_max_bandwidth(&mut self, bw: Bandwidth) {
667 for e in &mut self.encoders {
668 e.max_bandwidth = bw;
669 }
670 }
671}