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_decode,
8    opus_projection_decode_float, opus_projection_decoder_create, opus_projection_decoder_destroy,
9    opus_projection_encode, opus_projection_encode_float, opus_projection_encoder_ctl,
10    opus_projection_encoder_destroy,
11};
12use crate::constants::max_frame_samples_for;
13use crate::error::{Error, Result};
14use crate::types::{Application, Bitrate, SampleRate};
15
16/// Safe wrapper around `OpusProjectionEncoder`.
17pub struct ProjectionEncoder {
18    raw: *mut OpusProjectionEncoder,
19    sample_rate: SampleRate,
20    channels: u8,
21    streams: u8,
22    coupled_streams: u8,
23}
24
25unsafe impl Send for ProjectionEncoder {}
26unsafe impl Sync for ProjectionEncoder {}
27
28impl ProjectionEncoder {
29    /// Create a new projection encoder using the ambisonics helper.
30    ///
31    /// Returns [`Error::BadArg`] for unsupported channel/mapping combinations
32    /// or propagates libopus allocation failures.
33    ///
34    /// # Errors
35    /// Returns [`Error::BadArg`] for invalid arguments or the libopus error produced by
36    /// the underlying create call; [`Error::AllocFail`] if libopus returns a null handle.
37    pub fn new(
38        sample_rate: SampleRate,
39        channels: u8,
40        mapping_family: i32,
41        application: Application,
42    ) -> Result<Self> {
43        let mut err = 0i32;
44        let mut streams = 0i32;
45        let mut coupled = 0i32;
46        let enc = unsafe {
47            opus_projection_ambisonics_encoder_create(
48                sample_rate as i32,
49                i32::from(channels),
50                mapping_family,
51                &raw mut streams,
52                &raw mut coupled,
53                application as i32,
54                &raw mut err,
55            )
56        };
57        if err != 0 {
58            return Err(Error::from_code(err));
59        }
60        if enc.is_null() {
61            return Err(Error::AllocFail);
62        }
63        Ok(Self {
64            raw: enc,
65            sample_rate,
66            channels,
67            streams: u8::try_from(streams).map_err(|_| Error::BadArg)?,
68            coupled_streams: u8::try_from(coupled).map_err(|_| Error::BadArg)?,
69        })
70    }
71
72    fn validate_frame_size(&self, frame_size_per_ch: usize) -> Result<i32> {
73        if frame_size_per_ch == 0 || frame_size_per_ch > max_frame_samples_for(self.sample_rate) {
74            return Err(Error::BadArg);
75        }
76        i32::try_from(frame_size_per_ch).map_err(|_| Error::BadArg)
77    }
78
79    fn ensure_pcm_layout(&self, len: usize, frame_size_per_ch: usize) -> Result<()> {
80        if len != frame_size_per_ch * self.channels as usize {
81            return Err(Error::BadArg);
82        }
83        Ok(())
84    }
85
86    /// Encode interleaved `i16` PCM.
87    ///
88    /// # Errors
89    /// Returns [`Error::InvalidState`] if the encoder handle was freed, [`Error::BadArg`] for
90    /// buffer/layout issues, the libopus error mapped via [`Error::from_code`], or
91    /// [`Error::InternalError`] if libopus reports an impossible packet length.
92    pub fn encode(
93        &mut self,
94        pcm: &[i16],
95        frame_size_per_ch: usize,
96        out: &mut [u8],
97    ) -> Result<usize> {
98        if self.raw.is_null() {
99            return Err(Error::InvalidState);
100        }
101        if out.is_empty() || out.len() > i32::MAX as usize {
102            return Err(Error::BadArg);
103        }
104        self.ensure_pcm_layout(pcm.len(), frame_size_per_ch)?;
105        let frame_size = self.validate_frame_size(frame_size_per_ch)?;
106        let out_len = i32::try_from(out.len()).map_err(|_| Error::BadArg)?;
107        let n = unsafe {
108            opus_projection_encode(
109                self.raw,
110                pcm.as_ptr(),
111                frame_size,
112                out.as_mut_ptr(),
113                out_len,
114            )
115        };
116        if n < 0 {
117            return Err(Error::from_code(n));
118        }
119        usize::try_from(n).map_err(|_| Error::InternalError)
120    }
121
122    /// Encode interleaved `f32` PCM.
123    ///
124    /// # Errors
125    /// Returns [`Error::InvalidState`] if the encoder handle was freed, [`Error::BadArg`] for
126    /// buffer/layout issues, the libopus error mapped via [`Error::from_code`], or
127    /// [`Error::InternalError`] if libopus reports an impossible packet length.
128    pub fn encode_float(
129        &mut self,
130        pcm: &[f32],
131        frame_size_per_ch: usize,
132        out: &mut [u8],
133    ) -> Result<usize> {
134        if self.raw.is_null() {
135            return Err(Error::InvalidState);
136        }
137        if out.is_empty() || out.len() > i32::MAX as usize {
138            return Err(Error::BadArg);
139        }
140        self.ensure_pcm_layout(pcm.len(), frame_size_per_ch)?;
141        let frame_size = self.validate_frame_size(frame_size_per_ch)?;
142        let out_len = i32::try_from(out.len()).map_err(|_| Error::BadArg)?;
143        let n = unsafe {
144            opus_projection_encode_float(
145                self.raw,
146                pcm.as_ptr(),
147                frame_size,
148                out.as_mut_ptr(),
149                out_len,
150            )
151        };
152        if n < 0 {
153            return Err(Error::from_code(n));
154        }
155        usize::try_from(n).map_err(|_| Error::InternalError)
156    }
157
158    /// Set target bitrate for the encoder.
159    ///
160    /// # Errors
161    /// Returns [`Error::InvalidState`] if the encoder handle is invalid or a mapped libopus error.
162    pub fn set_bitrate(&mut self, bitrate: Bitrate) -> Result<()> {
163        self.simple_ctl(OPUS_SET_BITRATE_REQUEST as i32, bitrate.value())
164    }
165
166    /// Query current bitrate configuration.
167    ///
168    /// # Errors
169    /// Returns [`Error::InvalidState`] if the encoder handle is invalid or a mapped libopus error.
170    pub fn bitrate(&mut self) -> Result<Bitrate> {
171        let v = self.get_int_ctl(OPUS_GET_BITRATE_REQUEST as i32)?;
172        Ok(match v {
173            x if x == crate::bindings::OPUS_AUTO => Bitrate::Auto,
174            x if x == OPUS_BITRATE_MAX => Bitrate::Max,
175            other => Bitrate::Custom(other),
176        })
177    }
178
179    /// Size in bytes of the current demixing matrix.
180    ///
181    /// # Errors
182    /// Returns [`Error::InvalidState`] if the encoder handle is invalid or a mapped libopus error.
183    pub fn demixing_matrix_size(&mut self) -> Result<i32> {
184        self.get_int_ctl(OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST as i32)
185    }
186
187    /// Gain (in Q8 dB) of the demixing matrix.
188    ///
189    /// # Errors
190    /// Returns [`Error::InvalidState`] if the encoder handle is invalid or a mapped libopus error.
191    pub fn demixing_matrix_gain(&mut self) -> Result<i32> {
192        self.get_int_ctl(OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN_REQUEST as i32)
193    }
194
195    /// Copy the demixing matrix into `out` and return the number of bytes written.
196    ///
197    /// # Errors
198    /// Returns [`Error::InvalidState`] if the encoder handle is invalid, [`Error::BufferTooSmall`]
199    /// when `out` cannot fit the matrix, a mapped libopus error, or [`Error::InternalError`]
200    /// when libopus reports an invalid matrix size.
201    pub fn write_demixing_matrix(&mut self, out: &mut [u8]) -> Result<usize> {
202        let size = self.demixing_matrix_size()?;
203        if size <= 0 {
204            return Err(Error::InternalError);
205        }
206        let needed = usize::try_from(size).map_err(|_| Error::InternalError)?;
207        if out.len() < needed {
208            return Err(Error::BufferTooSmall);
209        }
210        let r = unsafe {
211            opus_projection_encoder_ctl(
212                self.raw,
213                OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST as i32,
214                out.as_mut_ptr(),
215                size,
216            )
217        };
218        if r != 0 {
219            return Err(Error::from_code(r));
220        }
221        Ok(needed)
222    }
223
224    /// Convenience helper returning the demixing matrix as a newly allocated buffer.
225    ///
226    /// # Errors
227    /// Propagates errors from [`Self::demixing_matrix_size`] and [`Self::write_demixing_matrix`],
228    /// including [`Error::InternalError`] if libopus reports impossible sizes.
229    pub fn demixing_matrix_bytes(&mut self) -> Result<Vec<u8>> {
230        let size = self.demixing_matrix_size()?;
231        let len = usize::try_from(size).map_err(|_| Error::InternalError)?;
232        let mut buf = vec![0u8; len];
233        self.write_demixing_matrix(&mut buf)?;
234        Ok(buf)
235    }
236
237    /// Number of coded streams.
238    #[must_use]
239    pub const fn streams(&self) -> u8 {
240        self.streams
241    }
242
243    /// Number of coupled (stereo) coded streams.
244    #[must_use]
245    pub const fn coupled_streams(&self) -> u8 {
246        self.coupled_streams
247    }
248
249    /// Input channels passed to the encoder.
250    #[must_use]
251    pub const fn channels(&self) -> u8 {
252        self.channels
253    }
254
255    /// Encoder sample rate.
256    #[must_use]
257    pub const fn sample_rate(&self) -> SampleRate {
258        self.sample_rate
259    }
260
261    fn simple_ctl(&mut self, req: i32, val: i32) -> Result<()> {
262        if self.raw.is_null() {
263            return Err(Error::InvalidState);
264        }
265        let r = unsafe { opus_projection_encoder_ctl(self.raw, req, val) };
266        if r != 0 {
267            return Err(Error::from_code(r));
268        }
269        Ok(())
270    }
271
272    fn get_int_ctl(&mut self, req: i32) -> Result<i32> {
273        if self.raw.is_null() {
274            return Err(Error::InvalidState);
275        }
276        let mut v = 0i32;
277        let r = unsafe { opus_projection_encoder_ctl(self.raw, req, &mut v) };
278        if r != 0 {
279            return Err(Error::from_code(r));
280        }
281        Ok(v)
282    }
283}
284
285impl Drop for ProjectionEncoder {
286    fn drop(&mut self) {
287        if !self.raw.is_null() {
288            unsafe { opus_projection_encoder_destroy(self.raw) };
289        }
290    }
291}
292
293/// Safe wrapper around `OpusProjectionDecoder`.
294pub struct ProjectionDecoder {
295    raw: *mut OpusProjectionDecoder,
296    sample_rate: SampleRate,
297    channels: u8,
298    streams: u8,
299    coupled_streams: u8,
300}
301
302unsafe impl Send for ProjectionDecoder {}
303unsafe impl Sync for ProjectionDecoder {}
304
305impl ProjectionDecoder {
306    /// Create a projection decoder given the demixing matrix provided by the encoder.
307    ///
308    /// # Errors
309    /// Returns [`Error::BadArg`] for invalid inputs, `Error::from_code` for libopus failures,
310    /// or [`Error::AllocFail`] if libopus returns a null handle.
311    pub fn new(
312        sample_rate: SampleRate,
313        channels: u8,
314        streams: u8,
315        coupled_streams: u8,
316        demixing_matrix: &[u8],
317    ) -> Result<Self> {
318        if demixing_matrix.is_empty() {
319            return Err(Error::BadArg);
320        }
321        let matrix_len = i32::try_from(demixing_matrix.len()).map_err(|_| Error::BadArg)?;
322        let mut err = 0i32;
323        let dec = unsafe {
324            opus_projection_decoder_create(
325                sample_rate as i32,
326                i32::from(channels),
327                i32::from(streams),
328                i32::from(coupled_streams),
329                demixing_matrix.as_ptr().cast_mut(),
330                matrix_len,
331                &raw mut err,
332            )
333        };
334        if err != 0 {
335            return Err(Error::from_code(err));
336        }
337        if dec.is_null() {
338            return Err(Error::AllocFail);
339        }
340        Ok(Self {
341            raw: dec,
342            sample_rate,
343            channels,
344            streams,
345            coupled_streams,
346        })
347    }
348
349    fn validate_frame_size(&self, frame_size_per_ch: usize) -> Result<i32> {
350        if frame_size_per_ch == 0 || frame_size_per_ch > max_frame_samples_for(self.sample_rate) {
351            return Err(Error::BadArg);
352        }
353        i32::try_from(frame_size_per_ch).map_err(|_| Error::BadArg)
354    }
355
356    fn ensure_output_layout(&self, len: usize, frame_size_per_ch: usize) -> Result<()> {
357        if len != frame_size_per_ch * self.channels as usize {
358            return Err(Error::BadArg);
359        }
360        Ok(())
361    }
362
363    /// Decode into interleaved `i16` PCM.
364    ///
365    /// # Errors
366    /// Returns [`Error::InvalidState`] if the decoder handle was freed, [`Error::BadArg`] for
367    /// buffer/layout issues, a mapped libopus error, or [`Error::InternalError`] if libopus
368    /// reports an impossible decoded sample count.
369    pub fn decode(
370        &mut self,
371        packet: &[u8],
372        out: &mut [i16],
373        frame_size_per_ch: usize,
374        fec: bool,
375    ) -> Result<usize> {
376        if self.raw.is_null() {
377            return Err(Error::InvalidState);
378        }
379        self.ensure_output_layout(out.len(), frame_size_per_ch)?;
380        let frame_size = self.validate_frame_size(frame_size_per_ch)?;
381        let packet_len = if packet.is_empty() {
382            0
383        } else {
384            i32::try_from(packet.len()).map_err(|_| Error::BadArg)?
385        };
386        let n = unsafe {
387            opus_projection_decode(
388                self.raw,
389                if packet.is_empty() {
390                    std::ptr::null()
391                } else {
392                    packet.as_ptr()
393                },
394                packet_len,
395                out.as_mut_ptr(),
396                frame_size,
397                i32::from(fec),
398            )
399        };
400        if n < 0 {
401            return Err(Error::from_code(n));
402        }
403        usize::try_from(n).map_err(|_| Error::InternalError)
404    }
405
406    /// Decode into interleaved `f32` PCM.
407    ///
408    /// # Errors
409    /// Returns [`Error::InvalidState`] if the decoder handle was freed, [`Error::BadArg`] for
410    /// buffer/layout issues, a mapped libopus error, or [`Error::InternalError`] if libopus
411    /// reports an impossible decoded sample count.
412    pub fn decode_float(
413        &mut self,
414        packet: &[u8],
415        out: &mut [f32],
416        frame_size_per_ch: usize,
417        fec: bool,
418    ) -> Result<usize> {
419        if self.raw.is_null() {
420            return Err(Error::InvalidState);
421        }
422        self.ensure_output_layout(out.len(), frame_size_per_ch)?;
423        let frame_size = self.validate_frame_size(frame_size_per_ch)?;
424        let packet_len = if packet.is_empty() {
425            0
426        } else {
427            i32::try_from(packet.len()).map_err(|_| Error::BadArg)?
428        };
429        let n = unsafe {
430            opus_projection_decode_float(
431                self.raw,
432                if packet.is_empty() {
433                    std::ptr::null()
434                } else {
435                    packet.as_ptr()
436                },
437                packet_len,
438                out.as_mut_ptr(),
439                frame_size,
440                i32::from(fec),
441            )
442        };
443        if n < 0 {
444            return Err(Error::from_code(n));
445        }
446        usize::try_from(n).map_err(|_| Error::InternalError)
447    }
448
449    /// Output channel count.
450    #[must_use]
451    pub const fn channels(&self) -> u8 {
452        self.channels
453    }
454
455    /// Number of coded streams expected in the input bitstream.
456    #[must_use]
457    pub const fn streams(&self) -> u8 {
458        self.streams
459    }
460
461    /// Number of coupled coded streams expected in the input bitstream.
462    #[must_use]
463    pub const fn coupled_streams(&self) -> u8 {
464        self.coupled_streams
465    }
466
467    /// Decoder sample rate.
468    #[must_use]
469    pub const fn sample_rate(&self) -> SampleRate {
470        self.sample_rate
471    }
472}
473
474impl Drop for ProjectionDecoder {
475    fn drop(&mut self) {
476        if !self.raw.is_null() {
477            unsafe { opus_projection_decoder_destroy(self.raw) };
478        }
479    }
480}