opus_codec/
projection.rs

1//! Safe wrappers for the libopus projection (ambisonics) API
2
3use crate::bindings::{
4    OPUS_BITRATE_MAX, OPUS_GET_BITRATE_REQUEST, OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN_REQUEST,
5    OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST, OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST,
6    OPUS_SET_BITRATE_REQUEST, OpusProjectionDecoder, OpusProjectionEncoder,
7    opus_projection_ambisonics_encoder_create, opus_projection_ambisonics_encoder_get_size,
8    opus_projection_ambisonics_encoder_init, opus_projection_decode, opus_projection_decode_float,
9    opus_projection_decoder_create, opus_projection_decoder_destroy,
10    opus_projection_decoder_get_size, opus_projection_decoder_init, opus_projection_encode,
11    opus_projection_encode_float, opus_projection_encoder_ctl, opus_projection_encoder_destroy,
12};
13use crate::constants::max_frame_samples_for;
14use crate::error::{Error, Result};
15use crate::types::{Application, Bitrate, SampleRate};
16use crate::{AlignedBuffer, Ownership, RawHandle};
17use std::marker::PhantomData;
18use std::num::NonZeroUsize;
19use std::ops::{Deref, DerefMut};
20use std::ptr::NonNull;
21
22/// Safe wrapper around `OpusProjectionEncoder`.
23pub struct ProjectionEncoder {
24    raw: RawHandle<OpusProjectionEncoder>,
25    sample_rate: SampleRate,
26    channels: u8,
27    streams: u8,
28    coupled_streams: u8,
29}
30
31unsafe impl Send for ProjectionEncoder {}
32unsafe impl Sync for ProjectionEncoder {}
33
34/// Borrowed wrapper around a projection encoder state.
35pub struct ProjectionEncoderRef<'a> {
36    inner: ProjectionEncoder,
37    _marker: PhantomData<&'a mut OpusProjectionEncoder>,
38}
39
40unsafe impl Send for ProjectionEncoderRef<'_> {}
41unsafe impl Sync for ProjectionEncoderRef<'_> {}
42
43impl ProjectionEncoder {
44    fn from_raw(
45        ptr: NonNull<OpusProjectionEncoder>,
46        sample_rate: SampleRate,
47        channels: u8,
48        streams: u8,
49        coupled_streams: u8,
50        ownership: Ownership,
51    ) -> Self {
52        Self {
53            raw: RawHandle::new(ptr, ownership, opus_projection_encoder_destroy),
54            sample_rate,
55            channels,
56            streams,
57            coupled_streams,
58        }
59    }
60
61    /// Size in bytes of a projection encoder state for external allocation.
62    ///
63    /// # Errors
64    /// Returns [`Error::BadArg`] if the channel/mapping configuration is invalid.
65    pub fn size(channels: u8, mapping_family: i32) -> Result<usize> {
66        let raw = unsafe {
67            opus_projection_ambisonics_encoder_get_size(i32::from(channels), mapping_family)
68        };
69        if raw <= 0 {
70            return Err(Error::BadArg);
71        }
72        usize::try_from(raw).map_err(|_| Error::InternalError)
73    }
74
75    /// Initialize a previously allocated projection encoder state.
76    ///
77    /// # Safety
78    /// The caller must provide a valid pointer to `ProjectionEncoder::size()` bytes,
79    /// aligned to at least `align_of::<usize>()` (malloc-style alignment).
80    ///
81    /// # Errors
82    /// Returns [`Error::BadArg`] for invalid inputs or a mapped libopus error.
83    pub unsafe fn init_in_place(
84        ptr: *mut OpusProjectionEncoder,
85        sample_rate: SampleRate,
86        channels: u8,
87        mapping_family: i32,
88        application: Application,
89    ) -> Result<(u8, u8)> {
90        if ptr.is_null() || channels == 0 {
91            return Err(Error::BadArg);
92        }
93        if !crate::opus_ptr_is_aligned(ptr.cast()) {
94            return Err(Error::BadArg);
95        }
96        let mut streams = 0i32;
97        let mut coupled = 0i32;
98        let r = unsafe {
99            opus_projection_ambisonics_encoder_init(
100                ptr,
101                sample_rate as i32,
102                i32::from(channels),
103                mapping_family,
104                std::ptr::addr_of_mut!(streams),
105                std::ptr::addr_of_mut!(coupled),
106                application as i32,
107            )
108        };
109        if r != 0 {
110            return Err(Error::from_code(r));
111        }
112        Ok((
113            u8::try_from(streams).map_err(|_| Error::BadArg)?,
114            u8::try_from(coupled).map_err(|_| Error::BadArg)?,
115        ))
116    }
117
118    /// Create a new projection encoder using the ambisonics helper.
119    ///
120    /// Returns [`Error::BadArg`] for unsupported channel/mapping combinations
121    /// or propagates libopus allocation failures.
122    ///
123    /// # Errors
124    /// Returns [`Error::BadArg`] for invalid arguments or the libopus error produced by
125    /// the underlying create call; [`Error::AllocFail`] if libopus returns a null handle.
126    pub fn new(
127        sample_rate: SampleRate,
128        channels: u8,
129        mapping_family: i32,
130        application: Application,
131    ) -> Result<Self> {
132        let mut err = 0i32;
133        let mut streams = 0i32;
134        let mut coupled = 0i32;
135        let enc = unsafe {
136            opus_projection_ambisonics_encoder_create(
137                sample_rate as i32,
138                i32::from(channels),
139                mapping_family,
140                &raw mut streams,
141                &raw mut coupled,
142                application as i32,
143                &raw mut err,
144            )
145        };
146        if err != 0 {
147            return Err(Error::from_code(err));
148        }
149        let enc = NonNull::new(enc).ok_or(Error::AllocFail)?;
150        let streams_u8 = u8::try_from(streams).map_err(|_| Error::BadArg)?;
151        let coupled_u8 = u8::try_from(coupled).map_err(|_| Error::BadArg)?;
152        Ok(Self::from_raw(
153            enc,
154            sample_rate,
155            channels,
156            streams_u8,
157            coupled_u8,
158            Ownership::Owned,
159        ))
160    }
161
162    fn validate_frame_size(&self, frame_size_per_ch: usize) -> Result<i32> {
163        let frame_size = NonZeroUsize::new(frame_size_per_ch).ok_or(Error::BadArg)?;
164        if frame_size.get() > max_frame_samples_for(self.sample_rate) {
165            return Err(Error::BadArg);
166        }
167        i32::try_from(frame_size.get()).map_err(|_| Error::BadArg)
168    }
169
170    fn ensure_pcm_layout(&self, len: usize, frame_size_per_ch: usize) -> Result<()> {
171        if len != frame_size_per_ch * self.channels as usize {
172            return Err(Error::BadArg);
173        }
174        Ok(())
175    }
176
177    /// Encode interleaved `i16` PCM.
178    ///
179    /// # Errors
180    /// Returns [`Error::InvalidState`] if the encoder handle was freed, [`Error::BadArg`] for
181    /// buffer/layout issues, the libopus error mapped via [`Error::from_code`], or
182    /// [`Error::InternalError`] if libopus reports an impossible packet length.
183    pub fn encode(
184        &mut self,
185        pcm: &[i16],
186        frame_size_per_ch: usize,
187        out: &mut [u8],
188    ) -> Result<usize> {
189        if out.is_empty() || out.len() > i32::MAX as usize {
190            return Err(Error::BadArg);
191        }
192        self.ensure_pcm_layout(pcm.len(), frame_size_per_ch)?;
193        let frame_size = self.validate_frame_size(frame_size_per_ch)?;
194        let out_len = i32::try_from(out.len()).map_err(|_| Error::BadArg)?;
195        let n = unsafe {
196            opus_projection_encode(
197                self.raw.as_ptr(),
198                pcm.as_ptr(),
199                frame_size,
200                out.as_mut_ptr(),
201                out_len,
202            )
203        };
204        if n < 0 {
205            return Err(Error::from_code(n));
206        }
207        usize::try_from(n).map_err(|_| Error::InternalError)
208    }
209
210    /// Encode interleaved `f32` PCM.
211    ///
212    /// # Errors
213    /// Returns [`Error::InvalidState`] if the encoder handle was freed, [`Error::BadArg`] for
214    /// buffer/layout issues, the libopus error mapped via [`Error::from_code`], or
215    /// [`Error::InternalError`] if libopus reports an impossible packet length.
216    pub fn encode_float(
217        &mut self,
218        pcm: &[f32],
219        frame_size_per_ch: usize,
220        out: &mut [u8],
221    ) -> Result<usize> {
222        if out.is_empty() || out.len() > i32::MAX as usize {
223            return Err(Error::BadArg);
224        }
225        self.ensure_pcm_layout(pcm.len(), frame_size_per_ch)?;
226        let frame_size = self.validate_frame_size(frame_size_per_ch)?;
227        let out_len = i32::try_from(out.len()).map_err(|_| Error::BadArg)?;
228        let n = unsafe {
229            opus_projection_encode_float(
230                self.raw.as_ptr(),
231                pcm.as_ptr(),
232                frame_size,
233                out.as_mut_ptr(),
234                out_len,
235            )
236        };
237        if n < 0 {
238            return Err(Error::from_code(n));
239        }
240        usize::try_from(n).map_err(|_| Error::InternalError)
241    }
242
243    /// Set target bitrate for the encoder.
244    ///
245    /// # Errors
246    /// Returns [`Error::InvalidState`] if the encoder handle is invalid or a mapped libopus error.
247    pub fn set_bitrate(&mut self, bitrate: Bitrate) -> Result<()> {
248        self.simple_ctl(OPUS_SET_BITRATE_REQUEST as i32, bitrate.value())
249    }
250
251    /// Query current bitrate configuration.
252    ///
253    /// # Errors
254    /// Returns [`Error::InvalidState`] if the encoder handle is invalid or a mapped libopus error.
255    pub fn bitrate(&mut self) -> Result<Bitrate> {
256        let v = self.get_int_ctl(OPUS_GET_BITRATE_REQUEST as i32)?;
257        Ok(match v {
258            x if x == crate::bindings::OPUS_AUTO => Bitrate::Auto,
259            x if x == OPUS_BITRATE_MAX => Bitrate::Max,
260            other => Bitrate::Custom(other),
261        })
262    }
263
264    /// Size in bytes of the current demixing matrix.
265    ///
266    /// # Errors
267    /// Returns [`Error::InvalidState`] if the encoder handle is invalid or a mapped libopus error.
268    pub fn demixing_matrix_size(&mut self) -> Result<i32> {
269        self.get_int_ctl(OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST as i32)
270    }
271
272    /// Gain (in Q8 dB) of the demixing matrix.
273    ///
274    /// # Errors
275    /// Returns [`Error::InvalidState`] if the encoder handle is invalid or a mapped libopus error.
276    pub fn demixing_matrix_gain(&mut self) -> Result<i32> {
277        self.get_int_ctl(OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN_REQUEST as i32)
278    }
279
280    /// Copy the demixing matrix into `out` and return the number of bytes written.
281    ///
282    /// # Errors
283    /// Returns [`Error::InvalidState`] if the encoder handle is invalid, [`Error::BufferTooSmall`]
284    /// when `out` cannot fit the matrix, a mapped libopus error, or [`Error::InternalError`]
285    /// when libopus reports an invalid matrix size.
286    pub fn write_demixing_matrix(&mut self, out: &mut [u8]) -> Result<usize> {
287        let size = self.demixing_matrix_size()?;
288        if size <= 0 {
289            return Err(Error::InternalError);
290        }
291        let needed = usize::try_from(size).map_err(|_| Error::InternalError)?;
292        if out.len() < needed {
293            return Err(Error::BufferTooSmall);
294        }
295        let r = unsafe {
296            opus_projection_encoder_ctl(
297                self.raw.as_ptr(),
298                OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST as i32,
299                out.as_mut_ptr(),
300                size,
301            )
302        };
303        if r != 0 {
304            return Err(Error::from_code(r));
305        }
306        Ok(needed)
307    }
308
309    /// Convenience helper returning the demixing matrix as a newly allocated buffer.
310    ///
311    /// # Errors
312    /// Propagates errors from [`Self::demixing_matrix_size`] and [`Self::write_demixing_matrix`],
313    /// including [`Error::InternalError`] if libopus reports impossible sizes.
314    pub fn demixing_matrix_bytes(&mut self) -> Result<Vec<u8>> {
315        let size = self.demixing_matrix_size()?;
316        let len = usize::try_from(size).map_err(|_| Error::InternalError)?;
317        let mut buf = vec![0u8; len];
318        self.write_demixing_matrix(&mut buf)?;
319        Ok(buf)
320    }
321
322    /// Number of coded streams.
323    #[must_use]
324    pub const fn streams(&self) -> u8 {
325        self.streams
326    }
327
328    /// Number of coupled (stereo) coded streams.
329    #[must_use]
330    pub const fn coupled_streams(&self) -> u8 {
331        self.coupled_streams
332    }
333
334    /// Input channels passed to the encoder.
335    #[must_use]
336    pub const fn channels(&self) -> u8 {
337        self.channels
338    }
339
340    /// Encoder sample rate.
341    #[must_use]
342    pub const fn sample_rate(&self) -> SampleRate {
343        self.sample_rate
344    }
345
346    fn simple_ctl(&mut self, req: i32, val: i32) -> Result<()> {
347        let r = unsafe { opus_projection_encoder_ctl(self.raw.as_ptr(), req, val) };
348        if r != 0 {
349            return Err(Error::from_code(r));
350        }
351        Ok(())
352    }
353
354    fn get_int_ctl(&mut self, req: i32) -> Result<i32> {
355        let mut v = 0i32;
356        let r = unsafe { opus_projection_encoder_ctl(self.raw.as_ptr(), req, &mut v) };
357        if r != 0 {
358            return Err(Error::from_code(r));
359        }
360        Ok(v)
361    }
362}
363
364impl<'a> ProjectionEncoderRef<'a> {
365    /// Wrap an externally-initialized projection encoder without taking ownership.
366    ///
367    /// # Safety
368    /// - `ptr` must point to valid, initialized memory of at least [`ProjectionEncoder::size()`] bytes
369    /// - `ptr` must be aligned to at least `align_of::<usize>()` (malloc-style alignment)
370    /// - The memory must remain valid for the lifetime `'a`
371    /// - Caller is responsible for freeing the memory after this wrapper is dropped
372    ///
373    /// Use [`ProjectionEncoder::init_in_place`] to initialize the memory before calling this.
374    #[must_use]
375    pub unsafe fn from_raw(
376        ptr: *mut OpusProjectionEncoder,
377        sample_rate: SampleRate,
378        channels: u8,
379        streams: u8,
380        coupled_streams: u8,
381    ) -> Self {
382        debug_assert!(!ptr.is_null(), "from_raw called with null ptr");
383        debug_assert!(crate::opus_ptr_is_aligned(ptr.cast()));
384        let encoder = ProjectionEncoder::from_raw(
385            unsafe { NonNull::new_unchecked(ptr) },
386            sample_rate,
387            channels,
388            streams,
389            coupled_streams,
390            Ownership::Borrowed,
391        );
392        Self {
393            inner: encoder,
394            _marker: PhantomData,
395        }
396    }
397
398    /// Initialize and wrap an externally allocated buffer.
399    ///
400    /// # Errors
401    /// Returns [`Error::BadArg`] if the buffer is too small, or a mapped libopus error.
402    pub fn init_in(
403        buf: &'a mut AlignedBuffer,
404        sample_rate: SampleRate,
405        channels: u8,
406        mapping_family: i32,
407        application: Application,
408    ) -> Result<Self> {
409        let required = ProjectionEncoder::size(channels, mapping_family)?;
410        if buf.capacity_bytes() < required {
411            return Err(Error::BadArg);
412        }
413        let ptr = buf.as_mut_ptr::<OpusProjectionEncoder>();
414        let (streams, coupled) = unsafe {
415            ProjectionEncoder::init_in_place(
416                ptr,
417                sample_rate,
418                channels,
419                mapping_family,
420                application,
421            )?
422        };
423        Ok(unsafe { Self::from_raw(ptr, sample_rate, channels, streams, coupled) })
424    }
425}
426
427impl Deref for ProjectionEncoderRef<'_> {
428    type Target = ProjectionEncoder;
429
430    fn deref(&self) -> &Self::Target {
431        &self.inner
432    }
433}
434
435impl DerefMut for ProjectionEncoderRef<'_> {
436    fn deref_mut(&mut self) -> &mut Self::Target {
437        &mut self.inner
438    }
439}
440
441/// Safe wrapper around `OpusProjectionDecoder`.
442pub struct ProjectionDecoder {
443    raw: RawHandle<OpusProjectionDecoder>,
444    sample_rate: SampleRate,
445    channels: u8,
446    streams: u8,
447    coupled_streams: u8,
448}
449
450unsafe impl Send for ProjectionDecoder {}
451unsafe impl Sync for ProjectionDecoder {}
452
453/// Borrowed wrapper around a projection decoder state.
454pub struct ProjectionDecoderRef<'a> {
455    inner: ProjectionDecoder,
456    _marker: PhantomData<&'a mut OpusProjectionDecoder>,
457}
458
459unsafe impl Send for ProjectionDecoderRef<'_> {}
460unsafe impl Sync for ProjectionDecoderRef<'_> {}
461
462impl ProjectionDecoder {
463    fn from_raw(
464        ptr: NonNull<OpusProjectionDecoder>,
465        sample_rate: SampleRate,
466        channels: u8,
467        streams: u8,
468        coupled_streams: u8,
469        ownership: Ownership,
470    ) -> Self {
471        Self {
472            raw: RawHandle::new(ptr, ownership, opus_projection_decoder_destroy),
473            sample_rate,
474            channels,
475            streams,
476            coupled_streams,
477        }
478    }
479
480    /// Size in bytes of a projection decoder state for external allocation.
481    ///
482    /// # Errors
483    /// Returns [`Error::BadArg`] if the channel/stream configuration is invalid.
484    pub fn size(channels: u8, streams: u8, coupled_streams: u8) -> Result<usize> {
485        let raw = unsafe {
486            opus_projection_decoder_get_size(
487                i32::from(channels),
488                i32::from(streams),
489                i32::from(coupled_streams),
490            )
491        };
492        if raw <= 0 {
493            return Err(Error::BadArg);
494        }
495        usize::try_from(raw).map_err(|_| Error::InternalError)
496    }
497
498    /// Initialize a previously allocated projection decoder state.
499    ///
500    /// # Safety
501    /// The caller must provide a valid pointer to `ProjectionDecoder::size()` bytes,
502    /// aligned to at least `align_of::<usize>()` (malloc-style alignment).
503    ///
504    /// # Errors
505    /// Returns [`Error::BadArg`] for invalid inputs or a mapped libopus error.
506    pub unsafe fn init_in_place(
507        ptr: *mut OpusProjectionDecoder,
508        sample_rate: SampleRate,
509        channels: u8,
510        streams: u8,
511        coupled_streams: u8,
512        demixing_matrix: &[u8],
513    ) -> Result<()> {
514        if ptr.is_null() || demixing_matrix.is_empty() {
515            return Err(Error::BadArg);
516        }
517        if !crate::opus_ptr_is_aligned(ptr.cast()) {
518            return Err(Error::BadArg);
519        }
520        let matrix_len = i32::try_from(demixing_matrix.len()).map_err(|_| Error::BadArg)?;
521        let r = unsafe {
522            opus_projection_decoder_init(
523                ptr,
524                sample_rate as i32,
525                i32::from(channels),
526                i32::from(streams),
527                i32::from(coupled_streams),
528                demixing_matrix.as_ptr().cast_mut(),
529                matrix_len,
530            )
531        };
532        if r != 0 {
533            return Err(Error::from_code(r));
534        }
535        Ok(())
536    }
537
538    /// Create a projection decoder given the demixing matrix provided by the encoder.
539    ///
540    /// # Errors
541    /// Returns [`Error::BadArg`] for invalid inputs, `Error::from_code` for libopus failures,
542    /// or [`Error::AllocFail`] if libopus returns a null handle.
543    pub fn new(
544        sample_rate: SampleRate,
545        channels: u8,
546        streams: u8,
547        coupled_streams: u8,
548        demixing_matrix: &[u8],
549    ) -> Result<Self> {
550        if demixing_matrix.is_empty() {
551            return Err(Error::BadArg);
552        }
553        let matrix_len = i32::try_from(demixing_matrix.len()).map_err(|_| Error::BadArg)?;
554        let mut err = 0i32;
555        let dec = unsafe {
556            opus_projection_decoder_create(
557                sample_rate as i32,
558                i32::from(channels),
559                i32::from(streams),
560                i32::from(coupled_streams),
561                demixing_matrix.as_ptr().cast_mut(),
562                matrix_len,
563                &raw mut err,
564            )
565        };
566        if err != 0 {
567            return Err(Error::from_code(err));
568        }
569        let dec = NonNull::new(dec).ok_or(Error::AllocFail)?;
570        Ok(Self::from_raw(
571            dec,
572            sample_rate,
573            channels,
574            streams,
575            coupled_streams,
576            Ownership::Owned,
577        ))
578    }
579
580    fn validate_frame_size(&self, frame_size_per_ch: usize) -> Result<i32> {
581        let frame_size = NonZeroUsize::new(frame_size_per_ch).ok_or(Error::BadArg)?;
582        if frame_size.get() > max_frame_samples_for(self.sample_rate) {
583            return Err(Error::BadArg);
584        }
585        i32::try_from(frame_size.get()).map_err(|_| Error::BadArg)
586    }
587
588    fn ensure_output_layout(&self, len: usize, frame_size_per_ch: usize) -> Result<()> {
589        if len != frame_size_per_ch * self.channels as usize {
590            return Err(Error::BadArg);
591        }
592        Ok(())
593    }
594
595    /// Decode into interleaved `i16` PCM.
596    ///
597    /// # Errors
598    /// Returns [`Error::InvalidState`] if the decoder handle was freed, [`Error::BadArg`] for
599    /// buffer/layout issues, a mapped libopus error, or [`Error::InternalError`] if libopus
600    /// reports an impossible decoded sample count.
601    pub fn decode(
602        &mut self,
603        packet: &[u8],
604        out: &mut [i16],
605        frame_size_per_ch: usize,
606        fec: bool,
607    ) -> Result<usize> {
608        self.ensure_output_layout(out.len(), frame_size_per_ch)?;
609        let frame_size = self.validate_frame_size(frame_size_per_ch)?;
610        let packet_len = if packet.is_empty() {
611            0
612        } else {
613            i32::try_from(packet.len()).map_err(|_| Error::BadArg)?
614        };
615        let n = unsafe {
616            opus_projection_decode(
617                self.raw.as_ptr(),
618                if packet.is_empty() {
619                    std::ptr::null()
620                } else {
621                    packet.as_ptr()
622                },
623                packet_len,
624                out.as_mut_ptr(),
625                frame_size,
626                i32::from(fec),
627            )
628        };
629        if n < 0 {
630            return Err(Error::from_code(n));
631        }
632        usize::try_from(n).map_err(|_| Error::InternalError)
633    }
634
635    /// Decode into interleaved `f32` PCM.
636    ///
637    /// # Errors
638    /// Returns [`Error::InvalidState`] if the decoder handle was freed, [`Error::BadArg`] for
639    /// buffer/layout issues, a mapped libopus error, or [`Error::InternalError`] if libopus
640    /// reports an impossible decoded sample count.
641    pub fn decode_float(
642        &mut self,
643        packet: &[u8],
644        out: &mut [f32],
645        frame_size_per_ch: usize,
646        fec: bool,
647    ) -> Result<usize> {
648        self.ensure_output_layout(out.len(), frame_size_per_ch)?;
649        let frame_size = self.validate_frame_size(frame_size_per_ch)?;
650        let packet_len = if packet.is_empty() {
651            0
652        } else {
653            i32::try_from(packet.len()).map_err(|_| Error::BadArg)?
654        };
655        let n = unsafe {
656            opus_projection_decode_float(
657                self.raw.as_ptr(),
658                if packet.is_empty() {
659                    std::ptr::null()
660                } else {
661                    packet.as_ptr()
662                },
663                packet_len,
664                out.as_mut_ptr(),
665                frame_size,
666                i32::from(fec),
667            )
668        };
669        if n < 0 {
670            return Err(Error::from_code(n));
671        }
672        usize::try_from(n).map_err(|_| Error::InternalError)
673    }
674
675    /// Output channel count.
676    #[must_use]
677    pub const fn channels(&self) -> u8 {
678        self.channels
679    }
680
681    /// Number of coded streams expected in the input bitstream.
682    #[must_use]
683    pub const fn streams(&self) -> u8 {
684        self.streams
685    }
686
687    /// Number of coupled coded streams expected in the input bitstream.
688    #[must_use]
689    pub const fn coupled_streams(&self) -> u8 {
690        self.coupled_streams
691    }
692
693    /// Decoder sample rate.
694    #[must_use]
695    pub const fn sample_rate(&self) -> SampleRate {
696        self.sample_rate
697    }
698}
699
700impl<'a> ProjectionDecoderRef<'a> {
701    /// Wrap an externally-initialized projection decoder without taking ownership.
702    ///
703    /// # Safety
704    /// - `ptr` must point to valid, initialized memory of at least [`ProjectionDecoder::size()`] bytes
705    /// - `ptr` must be aligned to at least `align_of::<usize>()` (malloc-style alignment)
706    /// - The memory must remain valid for the lifetime `'a`
707    /// - Caller is responsible for freeing the memory after this wrapper is dropped
708    ///
709    /// Use [`ProjectionDecoder::init_in_place`] to initialize the memory before calling this.
710    #[must_use]
711    pub unsafe fn from_raw(
712        ptr: *mut OpusProjectionDecoder,
713        sample_rate: SampleRate,
714        channels: u8,
715        streams: u8,
716        coupled_streams: u8,
717    ) -> Self {
718        debug_assert!(!ptr.is_null(), "from_raw called with null ptr");
719        debug_assert!(crate::opus_ptr_is_aligned(ptr.cast()));
720        let decoder = ProjectionDecoder::from_raw(
721            unsafe { NonNull::new_unchecked(ptr) },
722            sample_rate,
723            channels,
724            streams,
725            coupled_streams,
726            Ownership::Borrowed,
727        );
728        Self {
729            inner: decoder,
730            _marker: PhantomData,
731        }
732    }
733
734    /// Initialize and wrap an externally allocated buffer.
735    ///
736    /// # Errors
737    /// Returns [`Error::BadArg`] if the buffer is too small, or a mapped libopus error.
738    pub fn init_in(
739        buf: &'a mut AlignedBuffer,
740        sample_rate: SampleRate,
741        channels: u8,
742        streams: u8,
743        coupled_streams: u8,
744        demixing_matrix: &[u8],
745    ) -> Result<Self> {
746        let required = ProjectionDecoder::size(channels, streams, coupled_streams)?;
747        if buf.capacity_bytes() < required {
748            return Err(Error::BadArg);
749        }
750        let ptr = buf.as_mut_ptr::<OpusProjectionDecoder>();
751        unsafe {
752            ProjectionDecoder::init_in_place(
753                ptr,
754                sample_rate,
755                channels,
756                streams,
757                coupled_streams,
758                demixing_matrix,
759            )?;
760        }
761        Ok(unsafe { Self::from_raw(ptr, sample_rate, channels, streams, coupled_streams) })
762    }
763}
764
765impl Deref for ProjectionDecoderRef<'_> {
766    type Target = ProjectionDecoder;
767
768    fn deref(&self) -> &Self::Target {
769        &self.inner
770    }
771}
772
773impl DerefMut for ProjectionDecoderRef<'_> {
774    fn deref_mut(&mut self) -> &mut Self::Target {
775        &mut self.inner
776    }
777}