opus_codec/
multistream.rs

1//! Safe wrappers for the Opus Multistream API (surround and channel-mapped streams)
2
3use crate::bindings::{
4    OPUS_AUTO, OPUS_BANDWIDTH_FULLBAND, OPUS_BANDWIDTH_MEDIUMBAND, OPUS_BANDWIDTH_NARROWBAND,
5    OPUS_BANDWIDTH_SUPERWIDEBAND, OPUS_BANDWIDTH_WIDEBAND, OPUS_BITRATE_MAX,
6    OPUS_GET_BANDWIDTH_REQUEST, OPUS_GET_BITRATE_REQUEST, OPUS_GET_COMPLEXITY_REQUEST,
7    OPUS_GET_DTX_REQUEST, OPUS_GET_FINAL_RANGE_REQUEST, OPUS_GET_FORCE_CHANNELS_REQUEST,
8    OPUS_GET_GAIN_REQUEST, OPUS_GET_IN_DTX_REQUEST, OPUS_GET_INBAND_FEC_REQUEST,
9    OPUS_GET_LAST_PACKET_DURATION_REQUEST, OPUS_GET_LOOKAHEAD_REQUEST,
10    OPUS_GET_MAX_BANDWIDTH_REQUEST, OPUS_GET_PACKET_LOSS_PERC_REQUEST,
11    OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST, OPUS_GET_PITCH_REQUEST,
12    OPUS_GET_SAMPLE_RATE_REQUEST, OPUS_GET_SIGNAL_REQUEST, OPUS_GET_VBR_CONSTRAINT_REQUEST,
13    OPUS_GET_VBR_REQUEST, OPUS_MULTISTREAM_GET_DECODER_STATE_REQUEST,
14    OPUS_MULTISTREAM_GET_ENCODER_STATE_REQUEST, OPUS_RESET_STATE, OPUS_SET_BANDWIDTH_REQUEST,
15    OPUS_SET_BITRATE_REQUEST, OPUS_SET_COMPLEXITY_REQUEST, OPUS_SET_DTX_REQUEST,
16    OPUS_SET_FORCE_CHANNELS_REQUEST, OPUS_SET_GAIN_REQUEST, OPUS_SET_INBAND_FEC_REQUEST,
17    OPUS_SET_MAX_BANDWIDTH_REQUEST, OPUS_SET_PACKET_LOSS_PERC_REQUEST,
18    OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST, OPUS_SET_SIGNAL_REQUEST,
19    OPUS_SET_VBR_CONSTRAINT_REQUEST, OPUS_SET_VBR_REQUEST, OPUS_SIGNAL_MUSIC, OPUS_SIGNAL_VOICE,
20    OpusDecoder, OpusEncoder, OpusMSDecoder, OpusMSEncoder, opus_multistream_decode,
21    opus_multistream_decode_float, opus_multistream_decoder_create, opus_multistream_decoder_ctl,
22    opus_multistream_decoder_destroy, opus_multistream_decoder_get_size,
23    opus_multistream_decoder_init, opus_multistream_encode, opus_multistream_encode_float,
24    opus_multistream_encoder_create, opus_multistream_encoder_ctl,
25    opus_multistream_encoder_destroy, opus_multistream_encoder_get_size,
26    opus_multistream_encoder_init, opus_multistream_surround_encoder_create,
27    opus_multistream_surround_encoder_get_size, opus_multistream_surround_encoder_init,
28};
29use crate::constants::max_frame_samples_for;
30use crate::error::{Error, Result};
31use crate::types::{Application, Bandwidth, Bitrate, Channels, Complexity, SampleRate, Signal};
32use crate::{AlignedBuffer, Ownership, RawHandle};
33use std::marker::PhantomData;
34use std::num::{NonZeroU8, NonZeroUsize};
35use std::ops::{Deref, DerefMut};
36use std::ptr::NonNull;
37
38/// Describes the multistream mapping configuration.
39#[derive(Debug, Clone, Copy)]
40pub struct Mapping<'a> {
41    /// Total input/output channels.
42    pub channels: u8,
43    /// Total number of streams (coupled + uncoupled).
44    pub streams: u8,
45    /// Number of coupled stereo streams (each counts as 2 channels).
46    pub coupled_streams: u8,
47    /// Channel-to-stream mapping table (length == channels).
48    pub mapping: &'a [u8],
49}
50
51impl Mapping<'_> {
52    fn validate_common(&self) -> Result<(usize, usize, usize)> {
53        let channel_count = NonZeroU8::new(self.channels).ok_or(Error::BadArg)?;
54        let channel_count = usize::from(channel_count.get());
55        if self.mapping.len() != channel_count {
56            return Err(Error::BadArg);
57        }
58
59        let streams = NonZeroU8::new(self.streams).ok_or(Error::BadArg)?;
60        let streams = usize::from(streams.get());
61        let coupled = usize::from(self.coupled_streams);
62        if coupled > streams {
63            return Err(Error::BadArg);
64        }
65        if streams + coupled > u8::MAX as usize {
66            return Err(Error::BadArg);
67        }
68        let total_streams = streams + coupled;
69        for &entry in self.mapping {
70            if entry == u8::MAX {
71                continue;
72            }
73            if usize::from(entry) >= total_streams {
74                return Err(Error::BadArg);
75            }
76        }
77        Ok((channel_count, streams, coupled))
78    }
79
80    /// Validate mapping for use with libopus multistream decoder.
81    fn validate_for_decoder(&self) -> Result<()> {
82        self.validate_common()?;
83        Ok(())
84    }
85
86    /// Validate mapping for use with libopus multistream encoder.
87    fn validate_for_encoder(&self) -> Result<()> {
88        let (channel_count, streams, coupled) = self.validate_common()?;
89        if streams + coupled > channel_count {
90            return Err(Error::BadArg);
91        }
92
93        let mut has_left = vec![false; coupled];
94        let mut has_right = vec![false; coupled];
95        let mut has_mono = vec![false; streams.saturating_sub(coupled)];
96        for &entry in self.mapping {
97            if entry == u8::MAX {
98                continue;
99            }
100            let idx = usize::from(entry);
101            if idx < 2 * coupled {
102                let stream = idx / 2;
103                if idx % 2 == 0 {
104                    has_left[stream] = true;
105                } else {
106                    has_right[stream] = true;
107                }
108            } else {
109                let stream = idx - coupled;
110                if stream >= coupled && stream < streams {
111                    has_mono[stream - coupled] = true;
112                }
113            }
114        }
115
116        if has_left.iter().any(|has| !has) || has_right.iter().any(|has| !has) {
117            return Err(Error::BadArg);
118        }
119        if has_mono.iter().any(|has| !has) {
120            return Err(Error::BadArg);
121        }
122        Ok(())
123    }
124}
125
126/// Safe wrapper around `OpusMSEncoder`.
127pub struct MultistreamEncoder {
128    raw: RawHandle<OpusMSEncoder>,
129    sample_rate: SampleRate,
130    channels: u8,
131    streams: u8,
132    coupled_streams: u8,
133}
134
135unsafe impl Send for MultistreamEncoder {}
136unsafe impl Sync for MultistreamEncoder {}
137
138/// Borrowed wrapper around a multistream encoder.
139pub struct MultistreamEncoderRef<'a> {
140    inner: MultistreamEncoder,
141    _marker: PhantomData<&'a mut OpusMSEncoder>,
142}
143
144unsafe impl Send for MultistreamEncoderRef<'_> {}
145unsafe impl Sync for MultistreamEncoderRef<'_> {}
146
147impl MultistreamEncoder {
148    fn from_raw(
149        ptr: NonNull<OpusMSEncoder>,
150        sample_rate: SampleRate,
151        channels: u8,
152        streams: u8,
153        coupled_streams: u8,
154        ownership: Ownership,
155    ) -> Self {
156        Self {
157            raw: RawHandle::new(ptr, ownership, opus_multistream_encoder_destroy),
158            sample_rate,
159            channels,
160            streams,
161            coupled_streams,
162        }
163    }
164
165    /// Size in bytes of a multistream encoder state for external allocation.
166    ///
167    /// # Errors
168    /// Returns [`Error::BadArg`] if the stream counts are invalid or libopus reports
169    /// an impossible size.
170    pub fn size(streams: u8, coupled_streams: u8) -> Result<usize> {
171        let raw = unsafe {
172            opus_multistream_encoder_get_size(i32::from(streams), i32::from(coupled_streams))
173        };
174        if raw <= 0 {
175            return Err(Error::BadArg);
176        }
177        usize::try_from(raw).map_err(|_| Error::InternalError)
178    }
179
180    /// Size in bytes of a surround multistream encoder state for external allocation.
181    ///
182    /// # Errors
183    /// Returns [`Error::BadArg`] if the channel/mapping configuration is invalid.
184    pub fn surround_size(channels: u8, mapping_family: i32) -> Result<usize> {
185        let raw = unsafe {
186            opus_multistream_surround_encoder_get_size(i32::from(channels), mapping_family)
187        };
188        if raw <= 0 {
189            return Err(Error::BadArg);
190        }
191        usize::try_from(raw).map_err(|_| Error::InternalError)
192    }
193
194    /// Initialize a previously allocated multistream encoder state.
195    ///
196    /// # Safety
197    /// The caller must provide a valid pointer to `MultistreamEncoder::size()` bytes,
198    /// aligned to at least `align_of::<usize>()` (malloc-style alignment).
199    ///
200    /// # Errors
201    /// Returns [`Error::BadArg`] if the mapping is invalid or `ptr` is null, or a
202    /// mapped libopus error on failure.
203    pub unsafe fn init_in_place(
204        ptr: *mut OpusMSEncoder,
205        sr: SampleRate,
206        app: Application,
207        mapping: Mapping<'_>,
208    ) -> Result<()> {
209        if ptr.is_null() {
210            return Err(Error::BadArg);
211        }
212        if !crate::opus_ptr_is_aligned(ptr.cast()) {
213            return Err(Error::BadArg);
214        }
215        mapping.validate_for_encoder()?;
216        let r = unsafe {
217            opus_multistream_encoder_init(
218                ptr,
219                sr as i32,
220                i32::from(mapping.channels),
221                i32::from(mapping.streams),
222                i32::from(mapping.coupled_streams),
223                mapping.mapping.as_ptr(),
224                app as i32,
225            )
226        };
227        if r != 0 {
228            return Err(Error::from_code(r));
229        }
230        Ok(())
231    }
232
233    /// Initialize a previously allocated surround multistream encoder state.
234    ///
235    /// # Safety
236    /// The caller must provide a valid pointer to `MultistreamEncoder::surround_size()` bytes,
237    /// aligned to at least `align_of::<usize>()` (malloc-style alignment).
238    ///
239    /// # Errors
240    /// Returns [`Error::BadArg`] for invalid channel counts or a mapped libopus error.
241    pub unsafe fn init_surround_in_place(
242        ptr: *mut OpusMSEncoder,
243        sr: SampleRate,
244        channels: u8,
245        mapping_family: i32,
246        app: Application,
247    ) -> Result<(u8, u8, Vec<u8>)> {
248        if ptr.is_null() || channels == 0 {
249            return Err(Error::BadArg);
250        }
251        if !crate::opus_ptr_is_aligned(ptr.cast()) {
252            return Err(Error::BadArg);
253        }
254        let mut streams = 0i32;
255        let mut coupled = 0i32;
256        let mut mapping = vec![0u8; channels as usize];
257        let r = unsafe {
258            opus_multistream_surround_encoder_init(
259                ptr,
260                sr as i32,
261                i32::from(channels),
262                mapping_family,
263                std::ptr::addr_of_mut!(streams),
264                std::ptr::addr_of_mut!(coupled),
265                mapping.as_mut_ptr(),
266                app as i32,
267            )
268        };
269        if r != 0 {
270            return Err(Error::from_code(r));
271        }
272        Ok((
273            u8::try_from(streams).map_err(|_| Error::BadArg)?,
274            u8::try_from(coupled).map_err(|_| Error::BadArg)?,
275            mapping,
276        ))
277    }
278
279    /// Create a new multistream encoder.
280    ///
281    /// The `mapping.mapping` array describes how input channels are assigned to streams.
282    /// See libopus docs for standard surround layouts.
283    ///
284    /// # Errors
285    /// Returns [`Error::BadArg`] when the mapping dimensions are inconsistent, or
286    /// propagates allocation/configuration failures from libopus.
287    pub fn new(sr: SampleRate, app: Application, mapping: Mapping<'_>) -> Result<Self> {
288        mapping.validate_for_encoder()?;
289        let mut err = 0i32;
290        let enc = unsafe {
291            opus_multistream_encoder_create(
292                sr as i32,
293                i32::from(mapping.channels),
294                i32::from(mapping.streams),
295                i32::from(mapping.coupled_streams),
296                mapping.mapping.as_ptr(),
297                app as i32,
298                std::ptr::addr_of_mut!(err),
299            )
300        };
301        if err != 0 {
302            return Err(Error::from_code(err));
303        }
304        let enc = NonNull::new(enc).ok_or(Error::AllocFail)?;
305        Ok(Self::from_raw(
306            enc,
307            sr,
308            mapping.channels,
309            mapping.streams,
310            mapping.coupled_streams,
311            Ownership::Owned,
312        ))
313    }
314
315    /// Encode interleaved i16 PCM into a multistream Opus packet.
316    ///
317    /// # Errors
318    /// Returns [`Error::InvalidState`] if the encoder handle is invalid, [`Error::BadArg`]
319    /// for buffer mismatches, or the mapped libopus error code.
320    #[allow(clippy::missing_panics_doc)]
321    pub fn encode(
322        &mut self,
323        pcm: &[i16],
324        frame_size_per_ch: usize,
325        out: &mut [u8],
326    ) -> Result<usize> {
327        let frame_size_per_ch = NonZeroUsize::new(frame_size_per_ch).ok_or(Error::BadArg)?;
328        if frame_size_per_ch.get() > max_frame_samples_for(self.sample_rate) {
329            return Err(Error::BadArg);
330        }
331        if pcm.len() != frame_size_per_ch.get() * self.channels as usize {
332            return Err(Error::BadArg);
333        }
334        if out.is_empty() || out.len() > i32::MAX as usize {
335            return Err(Error::BadArg);
336        }
337        let n = unsafe {
338            opus_multistream_encode(
339                self.raw.as_ptr(),
340                pcm.as_ptr(),
341                i32::try_from(frame_size_per_ch.get()).map_err(|_| Error::BadArg)?,
342                out.as_mut_ptr(),
343                i32::try_from(out.len()).map_err(|_| Error::BadArg)?,
344            )
345        };
346        if n < 0 {
347            return Err(Error::from_code(n));
348        }
349        usize::try_from(n).map_err(|_| Error::InternalError)
350    }
351
352    /// Encode interleaved f32 PCM into a multistream Opus packet.
353    ///
354    /// # Errors
355    /// Returns [`Error::InvalidState`] if the encoder handle is invalid, [`Error::BadArg`]
356    /// for buffer mismatches, or the mapped libopus error code.
357    pub fn encode_float(
358        &mut self,
359        pcm: &[f32],
360        frame_size_per_ch: usize,
361        out: &mut [u8],
362    ) -> Result<usize> {
363        let frame_size_per_ch = NonZeroUsize::new(frame_size_per_ch).ok_or(Error::BadArg)?;
364        if frame_size_per_ch.get() > max_frame_samples_for(self.sample_rate) {
365            return Err(Error::BadArg);
366        }
367        if pcm.len() != frame_size_per_ch.get() * self.channels as usize {
368            return Err(Error::BadArg);
369        }
370        if out.is_empty() || out.len() > i32::MAX as usize {
371            return Err(Error::BadArg);
372        }
373        let n = unsafe {
374            opus_multistream_encode_float(
375                self.raw.as_ptr(),
376                pcm.as_ptr(),
377                i32::try_from(frame_size_per_ch.get()).map_err(|_| Error::BadArg)?,
378                out.as_mut_ptr(),
379                i32::try_from(out.len()).map_err(|_| Error::BadArg)?,
380            )
381        };
382        if n < 0 {
383            return Err(Error::from_code(n));
384        }
385        usize::try_from(n).map_err(|_| Error::InternalError)
386    }
387
388    /// Final RNG state from the last encode.
389    ///
390    /// # Errors
391    /// Returns [`Error::InvalidState`] when the encoder handle is null or
392    /// propagates the libopus error.
393    pub fn final_range(&mut self) -> Result<u32> {
394        let mut v: u32 = 0;
395        let r = unsafe {
396            opus_multistream_encoder_ctl(
397                self.raw.as_ptr(),
398                OPUS_GET_FINAL_RANGE_REQUEST as i32,
399                &mut v,
400            )
401        };
402        if r != 0 {
403            return Err(Error::from_code(r));
404        }
405        Ok(v)
406    }
407
408    /// Set target bitrate for the encoder.
409    ///
410    /// # Errors
411    /// Returns [`Error::InvalidState`] if the encoder handle is null or propagates any error
412    /// reported by libopus.
413    pub fn set_bitrate(&mut self, bitrate: Bitrate) -> Result<()> {
414        self.simple_ctl(OPUS_SET_BITRATE_REQUEST as i32, bitrate.value())
415    }
416
417    /// Query the current bitrate target.
418    ///
419    /// # Errors
420    /// Returns [`Error::InvalidState`] if the encoder handle is null, [`Error::InternalError`]
421    /// if the returned value cannot be represented, or propagates any error reported by
422    /// libopus.
423    pub fn bitrate(&mut self) -> Result<Bitrate> {
424        let v = self.get_int_ctl(OPUS_GET_BITRATE_REQUEST as i32)?;
425        Ok(match v {
426            x if x == OPUS_AUTO => Bitrate::Auto,
427            x if x == OPUS_BITRATE_MAX => Bitrate::Max,
428            other => Bitrate::Custom(other),
429        })
430    }
431
432    /// Set encoder complexity in the range 0..=10.
433    ///
434    /// # Errors
435    /// Returns [`Error::InvalidState`] if the encoder handle is null or propagates any error
436    /// reported by libopus.
437    pub fn set_complexity(&mut self, complexity: Complexity) -> Result<()> {
438        self.simple_ctl(
439            OPUS_SET_COMPLEXITY_REQUEST as i32,
440            complexity.value() as i32,
441        )
442    }
443
444    /// Query encoder complexity.
445    ///
446    /// # Errors
447    /// Returns [`Error::InvalidState`] if the encoder handle is null, [`Error::InternalError`]
448    /// if the response is outside the valid range, or propagates any error reported by libopus.
449    pub fn complexity(&mut self) -> Result<Complexity> {
450        let v = self.get_int_ctl(OPUS_GET_COMPLEXITY_REQUEST as i32)?;
451        Ok(Complexity::new(
452            u32::try_from(v).map_err(|_| Error::InternalError)?,
453        ))
454    }
455
456    /// Enable/disable discontinuous transmission (DTX).
457    ///
458    /// # Errors
459    /// Returns [`Error::InvalidState`] if the encoder handle is null or propagates any error
460    /// reported by libopus.
461    pub fn set_dtx(&mut self, enabled: bool) -> Result<()> {
462        self.simple_ctl(OPUS_SET_DTX_REQUEST as i32, i32::from(enabled))
463    }
464
465    /// Query whether DTX is enabled.
466    ///
467    /// # Errors
468    /// Returns [`Error::InvalidState`] if the encoder handle is null or propagates any error
469    /// reported by libopus.
470    pub fn dtx(&mut self) -> Result<bool> {
471        self.get_bool_ctl(OPUS_GET_DTX_REQUEST as i32)
472    }
473
474    /// Query whether the encoder is currently in DTX.
475    ///
476    /// # Errors
477    /// Returns [`Error::InvalidState`] if the encoder handle is null or propagates any error
478    /// reported by libopus.
479    pub fn in_dtx(&mut self) -> Result<bool> {
480        self.get_bool_ctl(OPUS_GET_IN_DTX_REQUEST as i32)
481    }
482
483    /// Enable/disable in-band FEC generation.
484    ///
485    /// # Errors
486    /// Returns [`Error::InvalidState`] if the encoder handle is null or propagates any error
487    /// reported by libopus.
488    pub fn set_inband_fec(&mut self, enabled: bool) -> Result<()> {
489        self.simple_ctl(OPUS_SET_INBAND_FEC_REQUEST as i32, i32::from(enabled))
490    }
491
492    /// Query whether in-band FEC is enabled.
493    ///
494    /// # Errors
495    /// Returns [`Error::InvalidState`] if the encoder handle is null or propagates any error
496    /// reported by libopus.
497    pub fn inband_fec(&mut self) -> Result<bool> {
498        self.get_bool_ctl(OPUS_GET_INBAND_FEC_REQUEST as i32)
499    }
500
501    /// Set expected packet loss percentage (0..=100).
502    ///
503    /// # Errors
504    /// Returns [`Error::BadArg`] when `perc` is outside `0..=100`, [`Error::InvalidState`] if
505    /// the encoder handle is null, or propagates any error reported by libopus.
506    pub fn set_packet_loss_perc(&mut self, perc: i32) -> Result<()> {
507        if !(0..=100).contains(&perc) {
508            return Err(Error::BadArg);
509        }
510        self.simple_ctl(OPUS_SET_PACKET_LOSS_PERC_REQUEST as i32, perc)
511    }
512
513    /// Query expected packet loss percentage.
514    ///
515    /// # Errors
516    /// Returns [`Error::InvalidState`] if the encoder handle is null or propagates any error
517    /// reported by libopus.
518    pub fn packet_loss_perc(&mut self) -> Result<i32> {
519        self.get_int_ctl(OPUS_GET_PACKET_LOSS_PERC_REQUEST as i32)
520    }
521
522    /// Enable/disable variable bitrate.
523    ///
524    /// # Errors
525    /// Returns [`Error::InvalidState`] if the encoder handle is null or propagates any error
526    /// reported by libopus.
527    pub fn set_vbr(&mut self, enabled: bool) -> Result<()> {
528        self.simple_ctl(OPUS_SET_VBR_REQUEST as i32, i32::from(enabled))
529    }
530
531    /// Query VBR status.
532    ///
533    /// # Errors
534    /// Returns [`Error::InvalidState`] if the encoder handle is null or propagates any error
535    /// reported by libopus.
536    pub fn vbr(&mut self) -> Result<bool> {
537        self.get_bool_ctl(OPUS_GET_VBR_REQUEST as i32)
538    }
539
540    /// Constrain VBR to reduce instantaneous bitrate swings.
541    ///
542    /// # Errors
543    /// Returns [`Error::InvalidState`] if the encoder handle is null or propagates any error
544    /// reported by libopus.
545    pub fn set_vbr_constraint(&mut self, constrained: bool) -> Result<()> {
546        self.simple_ctl(
547            OPUS_SET_VBR_CONSTRAINT_REQUEST as i32,
548            i32::from(constrained),
549        )
550    }
551
552    /// Query VBR constraint flag.
553    ///
554    /// # Errors
555    /// Returns [`Error::InvalidState`] if the encoder handle is null or propagates any error
556    /// reported by libopus.
557    pub fn vbr_constraint(&mut self) -> Result<bool> {
558        self.get_bool_ctl(OPUS_GET_VBR_CONSTRAINT_REQUEST as i32)
559    }
560
561    /// Set the maximum bandwidth the encoder may use.
562    ///
563    /// # Errors
564    /// Returns [`Error::InvalidState`] if the encoder handle is null or propagates any error
565    /// reported by libopus.
566    pub fn set_max_bandwidth(&mut self, bw: Bandwidth) -> Result<()> {
567        self.simple_ctl(OPUS_SET_MAX_BANDWIDTH_REQUEST as i32, bw as i32)
568    }
569
570    /// Query the configured maximum bandwidth.
571    ///
572    /// # Errors
573    /// Returns [`Error::InvalidState`] if the encoder handle is null, [`Error::InternalError`]
574    /// if the value cannot be represented, or propagates any error reported by libopus.
575    pub fn max_bandwidth(&mut self) -> Result<Bandwidth> {
576        self.get_bandwidth_ctl(OPUS_GET_MAX_BANDWIDTH_REQUEST as i32)
577    }
578
579    /// Force a specific output bandwidth (overrides automatic selection).
580    ///
581    /// # Errors
582    /// Returns [`Error::InvalidState`] if the encoder handle is null or propagates any error
583    /// reported by libopus.
584    pub fn set_bandwidth(&mut self, bw: Bandwidth) -> Result<()> {
585        self.simple_ctl(OPUS_SET_BANDWIDTH_REQUEST as i32, bw as i32)
586    }
587
588    /// Query the current forced bandwidth, if any.
589    ///
590    /// # Errors
591    /// Returns [`Error::InvalidState`] if the encoder handle is null or [`Error::InternalError`]
592    /// if the value is outside the known set, and propagates any error reported by libopus.
593    pub fn bandwidth(&mut self) -> Result<Bandwidth> {
594        self.get_bandwidth_ctl(OPUS_GET_BANDWIDTH_REQUEST as i32)
595    }
596
597    /// Force mono/stereo output for coupled streams, or `None` for automatic.
598    ///
599    /// # Errors
600    /// Returns [`Error::InvalidState`] if the encoder handle is null or propagates any error
601    /// reported by libopus.
602    pub fn set_force_channels(&mut self, channels: Option<Channels>) -> Result<()> {
603        let value = match channels {
604            Some(Channels::Mono) => 1,
605            Some(Channels::Stereo) => 2,
606            None => OPUS_AUTO,
607        };
608        self.simple_ctl(OPUS_SET_FORCE_CHANNELS_REQUEST as i32, value)
609    }
610
611    /// Query forced channel configuration (if any).
612    ///
613    /// # Errors
614    /// Returns [`Error::InvalidState`] if the encoder handle is null or propagates any error
615    /// reported by libopus.
616    pub fn force_channels(&mut self) -> Result<Option<Channels>> {
617        let v = self.get_int_ctl(OPUS_GET_FORCE_CHANNELS_REQUEST as i32)?;
618        Ok(match v {
619            1 => Some(Channels::Mono),
620            2 => Some(Channels::Stereo),
621            x if x == OPUS_AUTO => None,
622            _ => None,
623        })
624    }
625
626    /// Hint the type of content being encoded (voice/music).
627    ///
628    /// # Errors
629    /// Returns [`Error::InvalidState`] if the encoder handle is null or propagates any error
630    /// reported by libopus.
631    pub fn set_signal(&mut self, signal: Signal) -> Result<()> {
632        self.simple_ctl(OPUS_SET_SIGNAL_REQUEST as i32, signal as i32)
633    }
634
635    /// Query the current signal hint.
636    ///
637    /// # Errors
638    /// Returns [`Error::InvalidState`] if the encoder handle is null, [`Error::InternalError`]
639    /// if the response is not recognized, or propagates any error reported by libopus.
640    pub fn signal(&mut self) -> Result<Signal> {
641        let v = self.get_int_ctl(OPUS_GET_SIGNAL_REQUEST as i32)?;
642        match v {
643            x if x == OPUS_AUTO => Ok(Signal::Auto),
644            x if x == OPUS_SIGNAL_VOICE as i32 => Ok(Signal::Voice),
645            x if x == OPUS_SIGNAL_MUSIC as i32 => Ok(Signal::Music),
646            _ => Err(Error::InternalError),
647        }
648    }
649
650    /// Query the algorithmic lookahead in samples at 48 kHz.
651    ///
652    /// # Errors
653    /// Returns [`Error::InvalidState`] if the encoder handle is null or propagates any error
654    /// reported by libopus.
655    pub fn lookahead(&mut self) -> Result<i32> {
656        self.get_int_ctl(OPUS_GET_LOOKAHEAD_REQUEST as i32)
657    }
658
659    /// Reset the encoder state (retaining configuration).
660    ///
661    /// # Errors
662    /// Returns [`Error::InvalidState`] if the encoder handle is null or propagates any error
663    /// reported by libopus.
664    pub fn reset(&mut self) -> Result<()> {
665        let r = unsafe { opus_multistream_encoder_ctl(self.raw.as_ptr(), OPUS_RESET_STATE as i32) };
666        if r != 0 {
667            return Err(Error::from_code(r));
668        }
669        Ok(())
670    }
671
672    /// Channels of this encoder (interleaved input).
673    #[must_use]
674    pub const fn channels(&self) -> u8 {
675        self.channels
676    }
677    /// Input sampling rate.
678    #[must_use]
679    pub const fn sample_rate(&self) -> SampleRate {
680        self.sample_rate
681    }
682    /// Number of mono streams.
683    #[must_use]
684    pub const fn streams(&self) -> u8 {
685        self.streams
686    }
687    /// Number of coupled streams.
688    #[must_use]
689    pub const fn coupled_streams(&self) -> u8 {
690        self.coupled_streams
691    }
692
693    /// Create a multistream encoder using libopus surround mapping helpers.
694    ///
695    /// # Errors
696    /// Returns [`Error::BadArg`] for invalid channel counts or the mapped libopus
697    /// error when surround initialisation fails.
698    pub fn new_surround(
699        sr: SampleRate,
700        channels: u8,
701        mapping_family: i32,
702        app: Application,
703    ) -> Result<(Self, Vec<u8>)> {
704        if channels == 0 {
705            return Err(Error::BadArg);
706        }
707        let mut err = 0i32;
708        let mut streams = 0i32;
709        let mut coupled = 0i32;
710        let mut mapping = vec![0u8; channels as usize];
711        let enc = unsafe {
712            opus_multistream_surround_encoder_create(
713                sr as i32,
714                i32::from(channels),
715                mapping_family,
716                std::ptr::addr_of_mut!(streams),
717                std::ptr::addr_of_mut!(coupled),
718                mapping.as_mut_ptr(),
719                app as i32,
720                std::ptr::addr_of_mut!(err),
721            )
722        };
723        if err != 0 {
724            return Err(Error::from_code(err));
725        }
726        let enc = NonNull::new(enc).ok_or(Error::AllocFail)?;
727        let streams_u8 = u8::try_from(streams).map_err(|_| Error::BadArg)?;
728        let coupled_u8 = u8::try_from(coupled).map_err(|_| Error::BadArg)?;
729        Ok((
730            Self::from_raw(enc, sr, channels, streams_u8, coupled_u8, Ownership::Owned),
731            mapping,
732        ))
733    }
734
735    /// Borrow a pointer to an individual underlying encoder state for CTLs.
736    ///
737    /// # Safety
738    /// Caller must not outlive the multistream encoder and must ensure the
739    /// returned pointer is only used for immediate FFI calls.
740    ///
741    /// # Errors
742    /// Returns [`Error::InvalidState`] if the encoder handle is invalid or propagates the
743    /// libopus error if retrieving the state fails.
744    pub unsafe fn encoder_state_ptr(&mut self, stream_index: i32) -> Result<*mut OpusEncoder> {
745        let mut state: *mut OpusEncoder = std::ptr::null_mut();
746        let r = unsafe {
747            opus_multistream_encoder_ctl(
748                self.raw.as_ptr(),
749                OPUS_MULTISTREAM_GET_ENCODER_STATE_REQUEST as i32,
750                stream_index,
751                &mut state,
752            )
753        };
754        if r != 0 {
755            return Err(Error::from_code(r));
756        }
757        if state.is_null() {
758            return Err(Error::InternalError);
759        }
760        Ok(state)
761    }
762
763    fn simple_ctl(&mut self, req: i32, val: i32) -> Result<()> {
764        let r = unsafe { opus_multistream_encoder_ctl(self.raw.as_ptr(), req, val) };
765        if r != 0 {
766            return Err(Error::from_code(r));
767        }
768        Ok(())
769    }
770
771    fn get_int_ctl(&mut self, req: i32) -> Result<i32> {
772        let mut v: i32 = 0;
773        let r = unsafe { opus_multistream_encoder_ctl(self.raw.as_ptr(), req, &mut v) };
774        if r != 0 {
775            return Err(Error::from_code(r));
776        }
777        Ok(v)
778    }
779
780    fn get_bool_ctl(&mut self, req: i32) -> Result<bool> {
781        Ok(self.get_int_ctl(req)? != 0)
782    }
783
784    fn get_bandwidth_ctl(&mut self, req: i32) -> Result<Bandwidth> {
785        let v = u32::try_from(self.get_int_ctl(req)?).map_err(|_| Error::InternalError)?;
786        match v {
787            x if x == OPUS_BANDWIDTH_NARROWBAND => Ok(Bandwidth::Narrowband),
788            x if x == OPUS_BANDWIDTH_MEDIUMBAND => Ok(Bandwidth::Mediumband),
789            x if x == OPUS_BANDWIDTH_WIDEBAND => Ok(Bandwidth::Wideband),
790            x if x == OPUS_BANDWIDTH_SUPERWIDEBAND => Ok(Bandwidth::SuperWideband),
791            x if x == OPUS_BANDWIDTH_FULLBAND => Ok(Bandwidth::Fullband),
792            _ => Err(Error::InternalError),
793        }
794    }
795}
796
797impl<'a> MultistreamEncoderRef<'a> {
798    /// Wrap an externally-initialized multistream encoder without taking ownership.
799    ///
800    /// # Safety
801    /// - `ptr` must point to valid, initialized memory of at least [`MultistreamEncoder::size()`] bytes
802    /// - `ptr` must be aligned to at least `align_of::<usize>()` (malloc-style alignment)
803    /// - The memory must remain valid for the lifetime `'a`
804    /// - Caller is responsible for freeing the memory after this wrapper is dropped
805    ///
806    /// Use [`MultistreamEncoder::init_in_place`] to initialize the memory before calling this.
807    #[must_use]
808    pub unsafe fn from_raw(ptr: *mut OpusMSEncoder, sr: SampleRate, mapping: Mapping<'_>) -> Self {
809        debug_assert!(!ptr.is_null(), "from_raw called with null ptr");
810        debug_assert!(crate::opus_ptr_is_aligned(ptr.cast()));
811        debug_assert!(mapping.validate_for_encoder().is_ok());
812        let encoder = MultistreamEncoder::from_raw(
813            unsafe { NonNull::new_unchecked(ptr) },
814            sr,
815            mapping.channels,
816            mapping.streams,
817            mapping.coupled_streams,
818            Ownership::Borrowed,
819        );
820        Self {
821            inner: encoder,
822            _marker: PhantomData,
823        }
824    }
825
826    /// Initialize and wrap an externally allocated buffer.
827    ///
828    /// # Errors
829    /// Returns [`Error::BadArg`] if the buffer is too small, or a mapped libopus error.
830    pub fn init_in(
831        buf: &'a mut AlignedBuffer,
832        sr: SampleRate,
833        app: Application,
834        mapping: Mapping<'_>,
835    ) -> Result<Self> {
836        let required = MultistreamEncoder::size(mapping.streams, mapping.coupled_streams)?;
837        if buf.capacity_bytes() < required {
838            return Err(Error::BadArg);
839        }
840        let ptr = buf.as_mut_ptr::<OpusMSEncoder>();
841        unsafe { MultistreamEncoder::init_in_place(ptr, sr, app, mapping)? };
842        Ok(unsafe { Self::from_raw(ptr, sr, mapping) })
843    }
844
845    /// Initialize a surround encoder in an externally allocated buffer.
846    ///
847    /// # Errors
848    /// Returns [`Error::BadArg`] if the buffer is too small, or a mapped libopus error.
849    pub fn init_in_surround(
850        buf: &'a mut AlignedBuffer,
851        sr: SampleRate,
852        channels: u8,
853        mapping_family: i32,
854        app: Application,
855    ) -> Result<(Self, Vec<u8>)> {
856        let required = MultistreamEncoder::surround_size(channels, mapping_family)?;
857        if buf.capacity_bytes() < required {
858            return Err(Error::BadArg);
859        }
860        let ptr = buf.as_mut_ptr::<OpusMSEncoder>();
861        let (streams, coupled, mapping) = unsafe {
862            MultistreamEncoder::init_surround_in_place(ptr, sr, channels, mapping_family, app)?
863        };
864        let mapping_ref = Mapping {
865            channels,
866            streams,
867            coupled_streams: coupled,
868            mapping: &mapping,
869        };
870        let encoder = unsafe { Self::from_raw(ptr, sr, mapping_ref) };
871        Ok((encoder, mapping))
872    }
873}
874
875impl Deref for MultistreamEncoderRef<'_> {
876    type Target = MultistreamEncoder;
877
878    fn deref(&self) -> &Self::Target {
879        &self.inner
880    }
881}
882
883impl DerefMut for MultistreamEncoderRef<'_> {
884    fn deref_mut(&mut self) -> &mut Self::Target {
885        &mut self.inner
886    }
887}
888
889/// Safe wrapper around `OpusMSDecoder`.
890pub struct MultistreamDecoder {
891    raw: RawHandle<OpusMSDecoder>,
892    sample_rate: SampleRate,
893    channels: u8,
894}
895
896unsafe impl Send for MultistreamDecoder {}
897unsafe impl Sync for MultistreamDecoder {}
898
899/// Borrowed wrapper around a multistream decoder.
900pub struct MultistreamDecoderRef<'a> {
901    inner: MultistreamDecoder,
902    _marker: PhantomData<&'a mut OpusMSDecoder>,
903}
904
905unsafe impl Send for MultistreamDecoderRef<'_> {}
906unsafe impl Sync for MultistreamDecoderRef<'_> {}
907
908impl MultistreamDecoder {
909    fn from_raw(
910        ptr: NonNull<OpusMSDecoder>,
911        sample_rate: SampleRate,
912        channels: u8,
913        ownership: Ownership,
914    ) -> Self {
915        Self {
916            raw: RawHandle::new(ptr, ownership, opus_multistream_decoder_destroy),
917            sample_rate,
918            channels,
919        }
920    }
921
922    /// Size in bytes of a multistream decoder state for external allocation.
923    ///
924    /// # Errors
925    /// Returns [`Error::BadArg`] if the stream counts are invalid or libopus reports
926    /// an impossible size.
927    pub fn size(streams: u8, coupled_streams: u8) -> Result<usize> {
928        let raw = unsafe {
929            opus_multistream_decoder_get_size(i32::from(streams), i32::from(coupled_streams))
930        };
931        if raw <= 0 {
932            return Err(Error::BadArg);
933        }
934        usize::try_from(raw).map_err(|_| Error::InternalError)
935    }
936
937    /// Initialize a previously allocated multistream decoder state.
938    ///
939    /// # Safety
940    /// The caller must provide a valid pointer to `MultistreamDecoder::size()` bytes,
941    /// aligned to at least `align_of::<usize>()` (malloc-style alignment).
942    ///
943    /// # Errors
944    /// Returns [`Error::BadArg`] if the mapping is invalid or `ptr` is null, or a
945    /// mapped libopus error on failure.
946    pub unsafe fn init_in_place(
947        ptr: *mut OpusMSDecoder,
948        sr: SampleRate,
949        mapping: Mapping<'_>,
950    ) -> Result<()> {
951        if ptr.is_null() {
952            return Err(Error::BadArg);
953        }
954        if !crate::opus_ptr_is_aligned(ptr.cast()) {
955            return Err(Error::BadArg);
956        }
957        mapping.validate_for_decoder()?;
958        let r = unsafe {
959            opus_multistream_decoder_init(
960                ptr,
961                sr as i32,
962                i32::from(mapping.channels),
963                i32::from(mapping.streams),
964                i32::from(mapping.coupled_streams),
965                mapping.mapping.as_ptr(),
966            )
967        };
968        if r != 0 {
969            return Err(Error::from_code(r));
970        }
971        Ok(())
972    }
973
974    /// Create a new multistream decoder.
975    ///
976    /// # Errors
977    /// Returns [`Error::BadArg`] when the mapping dimensions are inconsistent, or
978    /// propagates allocation/configuration failures from libopus.
979    pub fn new(sr: SampleRate, mapping: Mapping<'_>) -> Result<Self> {
980        mapping.validate_for_decoder()?;
981        let mut err = 0i32;
982        let dec = unsafe {
983            opus_multistream_decoder_create(
984                sr as i32,
985                i32::from(mapping.channels),
986                i32::from(mapping.streams),
987                i32::from(mapping.coupled_streams),
988                mapping.mapping.as_ptr(),
989                std::ptr::addr_of_mut!(err),
990            )
991        };
992        if err != 0 {
993            return Err(Error::from_code(err));
994        }
995        let dec = NonNull::new(dec).ok_or(Error::AllocFail)?;
996        Ok(Self::from_raw(dec, sr, mapping.channels, Ownership::Owned))
997    }
998
999    /// Decode into interleaved i16 PCM (`frame_size` is per-channel).
1000    ///
1001    /// # Errors
1002    /// Returns [`Error::InvalidState`] if the decoder handle is invalid, [`Error::BadArg`]
1003    /// for buffer mismatches, or the mapped libopus error code.
1004    pub fn decode(
1005        &mut self,
1006        packet: &[u8],
1007        out: &mut [i16],
1008        frame_size_per_ch: usize,
1009        fec: bool,
1010    ) -> Result<usize> {
1011        let frame_size_per_ch = NonZeroUsize::new(frame_size_per_ch).ok_or(Error::BadArg)?;
1012        if frame_size_per_ch.get() > max_frame_samples_for(self.sample_rate) {
1013            return Err(Error::BadArg);
1014        }
1015        if out.len() != frame_size_per_ch.get() * self.channels as usize {
1016            return Err(Error::BadArg);
1017        }
1018        let n = unsafe {
1019            opus_multistream_decode(
1020                self.raw.as_ptr(),
1021                if packet.is_empty() {
1022                    std::ptr::null()
1023                } else {
1024                    packet.as_ptr()
1025                },
1026                if packet.is_empty() {
1027                    0
1028                } else {
1029                    i32::try_from(packet.len()).map_err(|_| Error::BadArg)?
1030                },
1031                out.as_mut_ptr(),
1032                i32::try_from(frame_size_per_ch.get()).map_err(|_| Error::BadArg)?,
1033                i32::from(fec),
1034            )
1035        };
1036        if n < 0 {
1037            return Err(Error::from_code(n));
1038        }
1039        usize::try_from(n).map_err(|_| Error::InternalError)
1040    }
1041
1042    /// Decode into interleaved f32 PCM (`frame_size` is per-channel).
1043    ///
1044    /// # Errors
1045    /// Returns [`Error::InvalidState`] if the decoder handle is invalid, [`Error::BadArg`]
1046    /// for buffer mismatches, or the mapped libopus error code.
1047    pub fn decode_float(
1048        &mut self,
1049        packet: &[u8],
1050        out: &mut [f32],
1051        frame_size_per_ch: usize,
1052        fec: bool,
1053    ) -> Result<usize> {
1054        let frame_size_per_ch = NonZeroUsize::new(frame_size_per_ch).ok_or(Error::BadArg)?;
1055        if frame_size_per_ch.get() > max_frame_samples_for(self.sample_rate) {
1056            return Err(Error::BadArg);
1057        }
1058        if out.len() != frame_size_per_ch.get() * self.channels as usize {
1059            return Err(Error::BadArg);
1060        }
1061        let n = unsafe {
1062            opus_multistream_decode_float(
1063                self.raw.as_ptr(),
1064                if packet.is_empty() {
1065                    std::ptr::null()
1066                } else {
1067                    packet.as_ptr()
1068                },
1069                if packet.is_empty() {
1070                    0
1071                } else {
1072                    i32::try_from(packet.len()).map_err(|_| Error::BadArg)?
1073                },
1074                out.as_mut_ptr(),
1075                i32::try_from(frame_size_per_ch.get()).map_err(|_| Error::BadArg)?,
1076                i32::from(fec),
1077            )
1078        };
1079        if n < 0 {
1080            return Err(Error::from_code(n));
1081        }
1082        usize::try_from(n).map_err(|_| Error::InternalError)
1083    }
1084
1085    /// Final RNG state from the last decode.
1086    ///
1087    /// # Errors
1088    /// Returns [`Error::InvalidState`] when the decoder handle is null or
1089    /// propagates the libopus error.
1090    pub fn final_range(&mut self) -> Result<u32> {
1091        let mut v: u32 = 0;
1092        let r = unsafe {
1093            opus_multistream_decoder_ctl(
1094                self.raw.as_ptr(),
1095                OPUS_GET_FINAL_RANGE_REQUEST as i32,
1096                &mut v,
1097            )
1098        };
1099        if r != 0 {
1100            return Err(Error::from_code(r));
1101        }
1102        Ok(v)
1103    }
1104
1105    /// Reset the decoder to its initial state.
1106    ///
1107    /// # Errors
1108    /// Returns [`Error::InvalidState`] if the decoder handle is null or propagates any error
1109    /// reported by libopus.
1110    pub fn reset(&mut self) -> Result<()> {
1111        let r = unsafe { opus_multistream_decoder_ctl(self.raw.as_ptr(), OPUS_RESET_STATE as i32) };
1112        if r != 0 {
1113            return Err(Error::from_code(r));
1114        }
1115        Ok(())
1116    }
1117
1118    /// Set post-decode gain in Q8 dB units.
1119    ///
1120    /// # Errors
1121    /// Returns [`Error::InvalidState`] if the decoder handle is null or propagates any error
1122    /// reported by libopus.
1123    pub fn set_gain(&mut self, q8_db: i32) -> Result<()> {
1124        self.simple_ctl(OPUS_SET_GAIN_REQUEST as i32, q8_db)
1125    }
1126
1127    /// Query post-decode gain in Q8 dB units.
1128    ///
1129    /// # Errors
1130    /// Returns [`Error::InvalidState`] if the decoder handle is null or propagates any error
1131    /// reported by libopus.
1132    pub fn gain(&mut self) -> Result<i32> {
1133        self.get_int_ctl(OPUS_GET_GAIN_REQUEST as i32)
1134    }
1135
1136    /// Disable or enable phase inversion (CELT stereo decorrelation).
1137    ///
1138    /// # Errors
1139    /// Returns [`Error::InvalidState`] if the decoder handle is null or propagates any error
1140    /// reported by libopus.
1141    pub fn set_phase_inversion_disabled(&mut self, disabled: bool) -> Result<()> {
1142        self.simple_ctl(
1143            OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST as i32,
1144            i32::from(disabled),
1145        )
1146    }
1147
1148    /// Query the phase inversion disabled flag.
1149    ///
1150    /// # Errors
1151    /// Returns [`Error::InvalidState`] if the decoder handle is null or propagates any error
1152    /// reported by libopus.
1153    pub fn phase_inversion_disabled(&mut self) -> Result<bool> {
1154        self.get_bool_ctl(OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST as i32)
1155    }
1156
1157    /// Query decoder output sample rate.
1158    ///
1159    /// # Errors
1160    /// Returns [`Error::InvalidState`] if the decoder handle is null or propagates any error
1161    /// reported by libopus.
1162    pub fn get_sample_rate(&mut self) -> Result<i32> {
1163        self.get_int_ctl(OPUS_GET_SAMPLE_RATE_REQUEST as i32)
1164    }
1165
1166    /// Query the pitch (fundamental period) of the last decoded frame.
1167    ///
1168    /// # Errors
1169    /// Returns [`Error::InvalidState`] if the decoder handle is null or propagates any error
1170    /// reported by libopus.
1171    pub fn get_pitch(&mut self) -> Result<i32> {
1172        self.get_int_ctl(OPUS_GET_PITCH_REQUEST as i32)
1173    }
1174
1175    /// Query the duration (per channel) of the last decoded packet.
1176    ///
1177    /// # Errors
1178    /// Returns [`Error::InvalidState`] if the decoder handle is null or propagates any error
1179    /// reported by libopus.
1180    pub fn get_last_packet_duration(&mut self) -> Result<i32> {
1181        self.get_int_ctl(OPUS_GET_LAST_PACKET_DURATION_REQUEST as i32)
1182    }
1183
1184    /// Output channels (interleaved).
1185    #[must_use]
1186    pub const fn channels(&self) -> u8 {
1187        self.channels
1188    }
1189    /// Output sample rate.
1190    #[must_use]
1191    pub const fn sample_rate(&self) -> SampleRate {
1192        self.sample_rate
1193    }
1194
1195    /// Create a multistream decoder using libopus surround mapping helpers.
1196    ///
1197    /// # Errors
1198    /// Returns [`Error::BadArg`] for invalid channel counts or the mapped libopus
1199    /// error when decoder initialisation fails.
1200    pub fn new_surround(
1201        sr: SampleRate,
1202        channels: u8,
1203        mapping_family: i32,
1204    ) -> Result<(Self, Vec<u8>, u8, u8)> {
1205        if channels == 0 {
1206            return Err(Error::BadArg);
1207        }
1208        let mut err = 0i32;
1209        let mut streams = 0i32;
1210        let mut coupled = 0i32;
1211        let mut mapping = vec![0u8; channels as usize];
1212        // libopus exposes surround helper creation only for encoders; callers
1213        // should use the returned mapping/stream counts to configure this decoder.
1214        let enc = unsafe {
1215            opus_multistream_surround_encoder_create(
1216                sr as i32,
1217                i32::from(channels),
1218                mapping_family,
1219                std::ptr::addr_of_mut!(streams),
1220                std::ptr::addr_of_mut!(coupled),
1221                mapping.as_mut_ptr(),
1222                Application::Audio as i32,
1223                std::ptr::addr_of_mut!(err),
1224            )
1225        };
1226        if !enc.is_null() {
1227            unsafe { opus_multistream_encoder_destroy(enc) };
1228        }
1229        if err != 0 {
1230            return Err(Error::from_code(err));
1231        }
1232        let dec = unsafe {
1233            opus_multistream_decoder_create(
1234                sr as i32,
1235                i32::from(channels),
1236                streams,
1237                coupled,
1238                mapping.as_ptr(),
1239                std::ptr::addr_of_mut!(err),
1240            )
1241        };
1242        if err != 0 {
1243            return Err(Error::from_code(err));
1244        }
1245        let dec = NonNull::new(dec).ok_or(Error::AllocFail)?;
1246        Ok((
1247            Self::from_raw(dec, sr, channels, Ownership::Owned),
1248            mapping,
1249            u8::try_from(streams).map_err(|_| Error::BadArg)?,
1250            u8::try_from(coupled).map_err(|_| Error::BadArg)?,
1251        ))
1252    }
1253
1254    /// Borrow a pointer to an individual underlying decoder state for CTLs.
1255    ///
1256    /// # Safety
1257    /// Caller must not outlive the multistream decoder and must ensure the
1258    /// returned pointer is only used for immediate FFI calls.
1259    ///
1260    /// # Errors
1261    /// Returns [`Error::InvalidState`] if the decoder handle is invalid or propagates the
1262    /// libopus error when retrieving the per-stream state fails.
1263    pub unsafe fn decoder_state_ptr(&mut self, stream_index: i32) -> Result<*mut OpusDecoder> {
1264        let mut state: *mut OpusDecoder = std::ptr::null_mut();
1265        let r = unsafe {
1266            opus_multistream_decoder_ctl(
1267                self.raw.as_ptr(),
1268                OPUS_MULTISTREAM_GET_DECODER_STATE_REQUEST as i32,
1269                stream_index,
1270                &mut state,
1271            )
1272        };
1273        if r != 0 {
1274            return Err(Error::from_code(r));
1275        }
1276        if state.is_null() {
1277            return Err(Error::InternalError);
1278        }
1279        Ok(state)
1280    }
1281
1282    fn simple_ctl(&mut self, req: i32, val: i32) -> Result<()> {
1283        let r = unsafe { opus_multistream_decoder_ctl(self.raw.as_ptr(), req, val) };
1284        if r != 0 {
1285            return Err(Error::from_code(r));
1286        }
1287        Ok(())
1288    }
1289
1290    fn get_int_ctl(&mut self, req: i32) -> Result<i32> {
1291        let mut v: i32 = 0;
1292        let r = unsafe { opus_multistream_decoder_ctl(self.raw.as_ptr(), req, &mut v) };
1293        if r != 0 {
1294            return Err(Error::from_code(r));
1295        }
1296        Ok(v)
1297    }
1298
1299    fn get_bool_ctl(&mut self, req: i32) -> Result<bool> {
1300        Ok(self.get_int_ctl(req)? != 0)
1301    }
1302}
1303
1304impl<'a> MultistreamDecoderRef<'a> {
1305    /// Wrap an externally-initialized multistream decoder without taking ownership.
1306    ///
1307    /// # Safety
1308    /// - `ptr` must point to valid, initialized memory of at least [`MultistreamDecoder::size()`] bytes
1309    /// - `ptr` must be aligned to at least `align_of::<usize>()` (malloc-style alignment)
1310    /// - The memory must remain valid for the lifetime `'a`
1311    /// - Caller is responsible for freeing the memory after this wrapper is dropped
1312    ///
1313    /// Use [`MultistreamDecoder::init_in_place`] to initialize the memory before calling this.
1314    #[must_use]
1315    pub unsafe fn from_raw(ptr: *mut OpusMSDecoder, sr: SampleRate, mapping: Mapping<'_>) -> Self {
1316        debug_assert!(!ptr.is_null(), "from_raw called with null ptr");
1317        debug_assert!(crate::opus_ptr_is_aligned(ptr.cast()));
1318        debug_assert!(mapping.validate_for_decoder().is_ok());
1319        let decoder = MultistreamDecoder::from_raw(
1320            unsafe { NonNull::new_unchecked(ptr) },
1321            sr,
1322            mapping.channels,
1323            Ownership::Borrowed,
1324        );
1325        Self {
1326            inner: decoder,
1327            _marker: PhantomData,
1328        }
1329    }
1330
1331    /// Initialize and wrap an externally allocated buffer.
1332    ///
1333    /// # Errors
1334    /// Returns [`Error::BadArg`] if the buffer is too small, or a mapped libopus error.
1335    pub fn init_in(
1336        buf: &'a mut AlignedBuffer,
1337        sr: SampleRate,
1338        mapping: Mapping<'_>,
1339    ) -> Result<Self> {
1340        let required = MultistreamDecoder::size(mapping.streams, mapping.coupled_streams)?;
1341        if buf.capacity_bytes() < required {
1342            return Err(Error::BadArg);
1343        }
1344        let ptr = buf.as_mut_ptr::<OpusMSDecoder>();
1345        unsafe { MultistreamDecoder::init_in_place(ptr, sr, mapping)? };
1346        Ok(unsafe { Self::from_raw(ptr, sr, mapping) })
1347    }
1348}
1349
1350impl Deref for MultistreamDecoderRef<'_> {
1351    type Target = MultistreamDecoder;
1352
1353    fn deref(&self) -> &Self::Target {
1354        &self.inner
1355    }
1356}
1357
1358impl DerefMut for MultistreamDecoderRef<'_> {
1359    fn deref_mut(&mut self) -> &mut Self::Target {
1360        &mut self.inner
1361    }
1362}
1363
1364#[cfg(test)]
1365mod tests {
1366    use super::*;
1367
1368    #[test]
1369    fn mapping_allows_dropped_channels() {
1370        let mapping = Mapping {
1371            channels: 6,
1372            streams: 2,
1373            coupled_streams: 1,
1374            mapping: &[0, 1, 2, u8::MAX, u8::MAX, u8::MAX],
1375        };
1376        assert!(mapping.validate_for_encoder().is_ok());
1377    }
1378
1379    #[test]
1380    fn mapping_requires_encoder_stream_coverage() {
1381        let mapping = Mapping {
1382            channels: 2,
1383            streams: 1,
1384            coupled_streams: 1,
1385            mapping: &[0, 0],
1386        };
1387        assert!(mapping.validate_for_decoder().is_ok());
1388        assert!(mapping.validate_for_encoder().is_err());
1389    }
1390}