Skip to main content

opus_codec/
decoder.rs

1//! Opus decoder implementation with safe wrappers
2
3#[cfg(feature = "dred")]
4use crate::bindings::OPUS_SET_DNN_BLOB_REQUEST;
5use crate::bindings::{
6    OPUS_GET_FINAL_RANGE_REQUEST, OPUS_GET_GAIN_REQUEST, OPUS_GET_LAST_PACKET_DURATION_REQUEST,
7    OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST, OPUS_GET_PITCH_REQUEST,
8    OPUS_GET_SAMPLE_RATE_REQUEST, OPUS_RESET_STATE, OPUS_SET_GAIN_REQUEST,
9    OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST, OpusDecoder, opus_decode, opus_decode_float,
10    opus_decoder_create, opus_decoder_ctl, opus_decoder_destroy, opus_decoder_get_nb_samples,
11    opus_decoder_get_size, opus_decoder_init,
12};
13use crate::constants::{is_frame_size_2_5ms_aligned, max_frame_samples_for};
14use crate::error::{Error, Result};
15use crate::packet;
16use crate::types::{Bandwidth, Channels, SampleRate};
17use crate::{AlignedBuffer, Ownership, RawHandle};
18use std::marker::PhantomData;
19use std::num::NonZeroUsize;
20use std::ops::Deref;
21use std::ptr::{self, NonNull};
22
23#[cfg(feature = "dred")]
24struct RetainedDnnBlob {
25    data: Box<[u32]>,
26    len: i32,
27}
28
29#[cfg(feature = "dred")]
30impl RetainedDnnBlob {
31    fn parts(&self) -> (*const u8, i32) {
32        (self.data.as_ptr().cast::<u8>(), self.len)
33    }
34}
35
36/// Safe wrapper around a libopus `OpusDecoder`.
37pub struct Decoder {
38    raw: RawHandle<OpusDecoder>,
39    sample_rate: SampleRate,
40    channels: Channels,
41    // External-weight builds retain pointers into DNN blobs. Keep each copy,
42    // including copies used by failed non-transactional load attempts, until
43    // after the C decoder has been destroyed. Field declaration order makes
44    // `raw` drop before this storage.
45    #[cfg(feature = "dred")]
46    dnn_blobs: Vec<RetainedDnnBlob>,
47    #[cfg(feature = "dred")]
48    active_dnn_blob: Option<usize>,
49}
50
51unsafe impl Send for Decoder {}
52
53/// Borrowed wrapper around a decoder state.
54///
55/// The owning handle cannot be moved out of this borrowed wrapper:
56///
57/// ```compile_fail
58/// use opus_codec::decoder::DecoderRef;
59/// use opus_codec::Decoder;
60/// fn extract<'a>(state: &mut DecoderRef<'a>, replacement: Decoder) -> Decoder {
61///     std::mem::replace(&mut **state, replacement)
62/// }
63/// ```
64pub struct DecoderRef<'a> {
65    inner: Decoder,
66    #[cfg(feature = "dred")]
67    active_dnn_blob: Option<(*const u8, i32)>,
68    _marker: PhantomData<&'a mut OpusDecoder>,
69}
70
71unsafe impl Send for DecoderRef<'_> {}
72
73impl Decoder {
74    fn from_raw(
75        ptr: NonNull<OpusDecoder>,
76        sample_rate: SampleRate,
77        channels: Channels,
78        ownership: Ownership,
79    ) -> Self {
80        Self {
81            raw: RawHandle::new(ptr, ownership, opus_decoder_destroy),
82            sample_rate,
83            channels,
84            #[cfg(feature = "dred")]
85            dnn_blobs: Vec::new(),
86            #[cfg(feature = "dred")]
87            active_dnn_blob: None,
88        }
89    }
90
91    /// Size in bytes of a decoder state for external allocation.
92    ///
93    /// # Errors
94    /// Returns [`Error::BadArg`] if the channel count is invalid or libopus reports
95    /// an impossible size.
96    pub fn size(channels: Channels) -> Result<usize> {
97        let raw = unsafe { opus_decoder_get_size(channels.as_i32()) };
98        if raw <= 0 {
99            return Err(Error::BadArg);
100        }
101        usize::try_from(raw).map_err(|_| Error::InternalError)
102    }
103
104    /// Initialize a previously allocated decoder state.
105    ///
106    /// # Safety
107    /// The caller must provide a valid pointer to `Decoder::size()` bytes,
108    /// aligned to at least `align_of::<usize>()` (malloc-style alignment).
109    ///
110    /// # Errors
111    /// Returns [`Error::BadArg`] if `ptr` is null, or a mapped libopus error.
112    pub unsafe fn init_in_place(
113        ptr: *mut OpusDecoder,
114        sample_rate: SampleRate,
115        channels: Channels,
116    ) -> Result<()> {
117        if ptr.is_null() {
118            return Err(Error::BadArg);
119        }
120        if !crate::opus_ptr_is_aligned(ptr.cast()) {
121            return Err(Error::BadArg);
122        }
123        let r = unsafe { opus_decoder_init(ptr, sample_rate.as_i32(), channels.as_i32()) };
124        if r != 0 {
125            return Err(Error::from_code(r));
126        }
127        Ok(())
128    }
129
130    /// Create a new decoder for a given sample rate and channel layout.
131    ///
132    /// # Errors
133    /// Returns an error if allocation fails or arguments are invalid.
134    pub fn new(sample_rate: SampleRate, channels: Channels) -> Result<Self> {
135        // Validate sample rate
136        if !sample_rate.is_valid() {
137            return Err(Error::BadArg);
138        }
139
140        let mut error = 0i32;
141        let decoder = unsafe {
142            opus_decoder_create(
143                sample_rate.as_i32(),
144                channels.as_i32(),
145                std::ptr::addr_of_mut!(error),
146            )
147        };
148
149        if error != 0 {
150            return Err(Error::from_code(error));
151        }
152
153        let decoder = NonNull::new(decoder).ok_or(Error::AllocFail)?;
154
155        Ok(Self::from_raw(
156            decoder,
157            sample_rate,
158            channels,
159            Ownership::Owned,
160        ))
161    }
162
163    /// Decode a packet into 16-bit PCM.
164    ///
165    /// - `input`: Opus packet bytes. Pass empty slice to invoke PLC.
166    /// - `output`: Interleaved output buffer sized to `frame_size * channels`.
167    /// - `fec`: Enable in-band FEC if available.
168    ///
169    /// # Errors
170    /// Returns [`Error::InvalidState`] if the decoder handle is invalid, [`Error::BadArg`]
171    /// for invalid buffer sizes or frame sizes, or a mapped libopus error via
172    /// [`Error::from_code`].
173    pub fn decode(&mut self, input: &[u8], output: &mut [i16], fec: bool) -> Result<usize> {
174        // Errors: InvalidState, BadArg, or libopus error mapped.
175        // Validate buffer sizes up-front
176        if !input.is_empty() && input.len() > i32::MAX as usize {
177            return Err(Error::BadArg);
178        }
179        if output.is_empty() {
180            return Err(Error::BadArg);
181        }
182        if !output.len().is_multiple_of(self.channels.as_usize()) {
183            return Err(Error::BadArg);
184        }
185        let frame_size = output.len() / self.channels.as_usize();
186        let frame_size = NonZeroUsize::new(frame_size).ok_or(Error::BadArg)?;
187        let max_frame = max_frame_samples_for(self.sample_rate);
188        if frame_size.get() > max_frame {
189            return Err(Error::BadArg);
190        }
191        // libopus requires PLC/FEC frame sizes to be multiples of 2.5 ms.
192        if (input.is_empty() || fec)
193            && !is_frame_size_2_5ms_aligned(frame_size.get(), self.sample_rate)
194        {
195            return Err(Error::BadArg);
196        }
197
198        let input_len_i32 = if input.is_empty() {
199            0
200        } else {
201            i32::try_from(input.len()).map_err(|_| Error::BadArg)?
202        };
203        let frame_size_i32 = i32::try_from(frame_size.get()).map_err(|_| Error::BadArg)?;
204
205        let result = unsafe {
206            opus_decode(
207                self.raw.as_ptr(),
208                if input.is_empty() {
209                    ptr::null()
210                } else {
211                    input.as_ptr()
212                },
213                input_len_i32,
214                output.as_mut_ptr(),
215                frame_size_i32,
216                i32::from(fec),
217            )
218        };
219
220        if result < 0 {
221            return Err(Error::from_code(result));
222        }
223
224        usize::try_from(result).map_err(|_| Error::InternalError)
225    }
226
227    /// Decode a packet into `f32` PCM.
228    ///
229    /// See [`Self::decode`] for parameter semantics.
230    ///
231    /// # Errors
232    /// Returns [`Error::InvalidState`] if the decoder handle is invalid, [`Error::BadArg`]
233    /// for invalid buffer sizes or frame sizes, or a mapped libopus error via
234    /// [`Error::from_code`].
235    pub fn decode_float(&mut self, input: &[u8], output: &mut [f32], fec: bool) -> Result<usize> {
236        // Validate buffer sizes up-front
237        if !input.is_empty() && input.len() > i32::MAX as usize {
238            return Err(Error::BadArg);
239        }
240        if output.is_empty() {
241            return Err(Error::BadArg);
242        }
243        if !output.len().is_multiple_of(self.channels.as_usize()) {
244            return Err(Error::BadArg);
245        }
246        let frame_size = output.len() / self.channels.as_usize();
247        let frame_size = NonZeroUsize::new(frame_size).ok_or(Error::BadArg)?;
248        let max_frame = max_frame_samples_for(self.sample_rate);
249        if frame_size.get() > max_frame {
250            return Err(Error::BadArg);
251        }
252        // libopus requires PLC/FEC frame sizes to be multiples of 2.5 ms.
253        if (input.is_empty() || fec)
254            && !is_frame_size_2_5ms_aligned(frame_size.get(), self.sample_rate)
255        {
256            return Err(Error::BadArg);
257        }
258
259        let input_len_i32 = if input.is_empty() {
260            0
261        } else {
262            i32::try_from(input.len()).map_err(|_| Error::BadArg)?
263        };
264        let frame_size_i32 = i32::try_from(frame_size.get()).map_err(|_| Error::BadArg)?;
265
266        let result = unsafe {
267            opus_decode_float(
268                self.raw.as_ptr(),
269                if input.is_empty() {
270                    ptr::null()
271                } else {
272                    input.as_ptr()
273                },
274                input_len_i32,
275                output.as_mut_ptr(),
276                frame_size_i32,
277                i32::from(fec),
278            )
279        };
280
281        if result < 0 {
282            return Err(Error::from_code(result));
283        }
284
285        usize::try_from(result).map_err(|_| Error::InternalError)
286    }
287
288    /// Return the number of samples (per channel) in an Opus `packet` at this decoder's rate.
289    ///
290    /// # Errors
291    /// Returns [`Error::InvalidState`] if the decoder is invalid, [`Error::BadArg`] for
292    /// overlong input, or a mapped libopus error.
293    pub fn packet_samples(&self, packet: &[u8]) -> Result<usize> {
294        // Errors: InvalidState or libopus error mapped.
295        if packet.is_empty() {
296            return Err(Error::BadArg);
297        }
298        if packet.len() > i32::MAX as usize {
299            return Err(Error::BadArg);
300        }
301        let len_i32 = i32::try_from(packet.len()).map_err(|_| Error::BadArg)?;
302        let result =
303            unsafe { opus_decoder_get_nb_samples(self.raw.as_ptr(), packet.as_ptr(), len_i32) };
304
305        if result < 0 {
306            return Err(Error::from_code(result));
307        }
308
309        usize::try_from(result).map_err(|_| Error::InternalError)
310    }
311
312    /// Return the bandwidth encoded in an Opus `packet`.
313    ///
314    /// # Errors
315    /// Returns [`Error::InvalidState`] if the decoder is invalid, or [`Error::InvalidPacket`]
316    /// if the packet cannot be parsed.
317    pub fn packet_bandwidth(&self, packet: &[u8]) -> Result<Bandwidth> {
318        // Errors: InvalidState or InvalidPacket.
319        packet::packet_bandwidth(packet)
320    }
321
322    /// Return the number of channels described by an Opus `packet`.
323    ///
324    /// # Errors
325    /// Returns [`Error::InvalidState`] if the decoder is invalid, or [`Error::InvalidPacket`]
326    /// if the packet cannot be parsed.
327    pub fn packet_channels(&self, packet: &[u8]) -> Result<Channels> {
328        // Errors: InvalidState or InvalidPacket.
329        packet::packet_channels(packet)
330    }
331
332    /// Reset the decoder to its initial state.
333    ///
334    /// # Errors
335    /// Returns [`Error::InvalidState`] if the decoder is invalid, or a mapped libopus error
336    /// if resetting fails.
337    pub fn reset(&mut self) -> Result<()> {
338        // Errors: InvalidState or request failure.
339        // OPUS_RESET_STATE takes no additional argument. Passing extras is undefined behavior.
340        let result = unsafe { opus_decoder_ctl(self.raw.as_ptr(), OPUS_RESET_STATE as i32) };
341
342        if result != 0 {
343            return Err(Error::from_code(result));
344        }
345
346        #[cfg(feature = "dred")]
347        self.reload_active_dnn_blob()?;
348        Ok(())
349    }
350
351    /// The decoder's configured sample rate.
352    #[must_use]
353    pub const fn sample_rate(&self) -> SampleRate {
354        self.sample_rate
355    }
356
357    /// The decoder's channel configuration.
358    #[must_use]
359    pub const fn channels(&self) -> Channels {
360        self.channels
361    }
362
363    #[cfg_attr(not(feature = "dred"), allow(dead_code))]
364    pub(crate) fn as_mut_ptr(&mut self) -> *mut OpusDecoder {
365        self.raw.as_ptr()
366    }
367
368    /// Query decoder output sample rate.
369    ///
370    /// # Errors
371    /// Returns [`Error::InvalidState`] if the decoder is invalid, or a mapped libopus error.
372    pub fn get_sample_rate(&mut self) -> Result<i32> {
373        self.get_int_ctl(OPUS_GET_SAMPLE_RATE_REQUEST as i32)
374    }
375
376    /// Query pitch (fundamental period) of the last decoded frame (in samples at 48 kHz domain).
377    ///
378    /// # Errors
379    /// Returns [`Error::InvalidState`] if the decoder is invalid, or a mapped libopus error.
380    pub fn get_pitch(&mut self) -> Result<i32> {
381        self.get_int_ctl(OPUS_GET_PITCH_REQUEST as i32)
382    }
383
384    /// Duration (per channel) of the last decoded packet.
385    ///
386    /// # Errors
387    /// Returns [`Error::InvalidState`] if the decoder is invalid, or a mapped libopus error.
388    pub fn get_last_packet_duration(&mut self) -> Result<i32> {
389        self.get_int_ctl(OPUS_GET_LAST_PACKET_DURATION_REQUEST as i32)
390    }
391
392    /// Final RNG state after the last decode.
393    ///
394    /// # Errors
395    /// Returns [`Error::InvalidState`] if the decoder is invalid, or a mapped libopus error.
396    pub fn final_range(&mut self) -> Result<u32> {
397        let mut v: u32 = 0;
398        let r = unsafe {
399            opus_decoder_ctl(
400                self.raw.as_ptr(),
401                OPUS_GET_FINAL_RANGE_REQUEST as i32,
402                &mut v,
403            )
404        };
405        if r != 0 {
406            return Err(Error::from_code(r));
407        }
408        Ok(v)
409    }
410
411    /// Set post-decode gain in Q8 dB units.
412    ///
413    /// # Errors
414    /// Returns [`Error::InvalidState`] if the decoder is invalid, or a mapped libopus error.
415    pub fn set_gain(&mut self, q8_db: i32) -> Result<()> {
416        self.simple_ctl(OPUS_SET_GAIN_REQUEST as i32, q8_db)
417    }
418    /// Query post-decode gain in Q8 dB units.
419    ///
420    /// # Errors
421    /// Returns [`Error::InvalidState`] if the decoder is invalid, or a mapped libopus error.
422    pub fn gain(&mut self) -> Result<i32> {
423        self.get_int_ctl(OPUS_GET_GAIN_REQUEST as i32)
424    }
425
426    /// Returns true if phase inversion is disabled (CELT stereo decorrelation).
427    ///
428    /// # Errors
429    /// Returns [`Error::InvalidState`] if the decoder is invalid, or a mapped libopus error.
430    pub fn phase_inversion_disabled(&mut self) -> Result<bool> {
431        Ok(self.get_int_ctl(OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST as i32)? != 0)
432    }
433
434    /// Disable/enable phase inversion (CELT stereo decorrelation).
435    ///
436    /// # Errors
437    /// Returns [`Error::InvalidState`] if the decoder is invalid, or a mapped libopus error.
438    pub fn set_phase_inversion_disabled(&mut self, disabled: bool) -> Result<()> {
439        self.simple_ctl(
440            OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST as i32,
441            i32::from(disabled),
442        )
443    }
444
445    #[cfg(feature = "dred")]
446    /// Set DNN blob for DRED (feature-gated; will error if unsupported).
447    ///
448    /// # Safety
449    /// `ptr` must be valid for reads of `len` bytes for the duration of this call and point to a
450    /// complete, correctly formatted libopus DNN blob. The bytes are copied into aligned storage
451    /// owned by the decoder, so the caller's allocation need not remain alive after this returns.
452    /// Some external-weight libopus builds do not safely handle malformed model records.
453    ///
454    /// # Errors
455    /// Returns [`Error::BadArg`] if `ptr` is null or `len` is non-positive,
456    /// [`Error::InvalidState`] if the decoder is invalid, or a mapped libopus error.
457    pub unsafe fn set_dnn_blob(&mut self, ptr: *const u8, len: i32) -> Result<()> {
458        let blob_index = unsafe { self.retain_dnn_blob_copy(ptr, len)? };
459        let (owned_ptr, owned_len) = self.dnn_blobs[blob_index].parts();
460        if let Err(error) = unsafe { self.apply_dnn_blob(owned_ptr, owned_len) } {
461            if error == Error::Unimplemented {
462                // An unsupported CTL never inspected or retained the pointer.
463                // Other failures may leave model fields pointing into the blob.
464                let removed = self.dnn_blobs.pop();
465                debug_assert!(removed.is_some());
466            }
467            return Err(error);
468        }
469        self.active_dnn_blob = Some(blob_index);
470        Ok(())
471    }
472
473    #[cfg(feature = "dred")]
474    unsafe fn retain_dnn_blob_copy(&mut self, ptr: *const u8, len: i32) -> Result<usize> {
475        if ptr.is_null() || len <= 0 {
476            return Err(Error::BadArg);
477        }
478        let byte_len = usize::try_from(len).map_err(|_| Error::BadArg)?;
479        let word_len = byte_len.div_ceil(std::mem::size_of::<u32>());
480        let mut blob = vec![0u32; word_len].into_boxed_slice();
481        unsafe {
482            std::ptr::copy_nonoverlapping(ptr, blob.as_mut_ptr().cast::<u8>(), byte_len);
483        }
484        let index = self.dnn_blobs.len();
485        self.dnn_blobs.push(RetainedDnnBlob { data: blob, len });
486        Ok(index)
487    }
488
489    #[cfg(feature = "dred")]
490    unsafe fn apply_dnn_blob(&mut self, ptr: *const u8, len: i32) -> Result<()> {
491        let r = unsafe {
492            opus_decoder_ctl(
493                self.raw.as_ptr(),
494                OPUS_SET_DNN_BLOB_REQUEST as i32,
495                ptr,
496                len,
497            )
498        };
499        if r != 0 {
500            return Err(Error::from_code(r));
501        }
502        Ok(())
503    }
504
505    #[cfg(feature = "dred")]
506    fn reload_active_dnn_blob(&mut self) -> Result<()> {
507        let Some(index) = self.active_dnn_blob else {
508            return Ok(());
509        };
510        let (ptr, len) = self.dnn_blobs[index].parts();
511        unsafe { self.apply_dnn_blob(ptr, len) }
512    }
513
514    // --- internal helpers for CTLs ---
515    fn simple_ctl(&mut self, req: i32, val: i32) -> Result<()> {
516        let r = unsafe { opus_decoder_ctl(self.raw.as_ptr(), req, val) };
517        if r != 0 {
518            return Err(Error::from_code(r));
519        }
520        Ok(())
521    }
522    fn get_int_ctl(&mut self, req: i32) -> Result<i32> {
523        let mut v: i32 = 0;
524        let r = unsafe { opus_decoder_ctl(self.raw.as_ptr(), req, &mut v) };
525        if r != 0 {
526            return Err(Error::from_code(r));
527        }
528        Ok(v)
529    }
530}
531
532impl<'a> DecoderRef<'a> {
533    /// Wrap an externally-initialized decoder without taking ownership.
534    ///
535    /// # Safety
536    /// - `ptr` must point to valid, initialized memory of at least [`Decoder::size()`] bytes
537    /// - `ptr` must be aligned to at least `align_of::<usize>()` (malloc-style alignment)
538    /// - `sample_rate` and `channels` must exactly match the decoder state already stored at `ptr`
539    /// - The memory must remain valid for the lifetime `'a`
540    /// - Caller is responsible for freeing the memory after this wrapper is dropped
541    /// - If the external state already uses runtime-loaded DNN weights, register that blob again
542    ///   through `DecoderRef::set_dnn_blob` before calling `DecoderRef::reset`
543    ///
544    /// Passing mismatched metadata is undefined behavior: later safe methods may validate buffer
545    /// sizes against the wrong channel/rate and then call libopus with out-of-bounds buffers.
546    ///
547    /// # Panics
548    /// Panics if `ptr` is null or not pointer-aligned.
549    ///
550    /// Use [`Decoder::init_in_place`] to initialize the memory before calling this.
551    #[must_use]
552    pub unsafe fn from_raw(
553        ptr: *mut OpusDecoder,
554        sample_rate: SampleRate,
555        channels: Channels,
556    ) -> Self {
557        let decoder = Decoder::from_raw(
558            crate::checked_non_null(ptr, "DecoderRef::from_raw"),
559            sample_rate,
560            channels,
561            Ownership::Borrowed,
562        );
563        Self {
564            inner: decoder,
565            #[cfg(feature = "dred")]
566            active_dnn_blob: None,
567            _marker: PhantomData,
568        }
569    }
570
571    /// Initialize and wrap an externally allocated buffer.
572    ///
573    /// # Errors
574    /// Returns [`Error::BadArg`] if the buffer is too small, or a mapped libopus error.
575    pub fn init_in(
576        buf: &'a mut AlignedBuffer,
577        sample_rate: SampleRate,
578        channels: Channels,
579    ) -> Result<Self> {
580        let required = Decoder::size(channels)?;
581        if buf.capacity_bytes() < required {
582            return Err(Error::BadArg);
583        }
584        let ptr = buf.as_mut_ptr::<OpusDecoder>();
585        unsafe { Decoder::init_in_place(ptr, sample_rate, channels)? };
586        Ok(unsafe { Self::from_raw(ptr, sample_rate, channels) })
587    }
588
589    delegate_ref_mut_methods! {
590        fn decode(input: &[u8], output: &mut [i16], fec: bool) -> Result<usize>;
591        fn decode_float(input: &[u8], output: &mut [f32], fec: bool) -> Result<usize>;
592        fn get_sample_rate() -> Result<i32>;
593        fn get_pitch() -> Result<i32>;
594        fn get_last_packet_duration() -> Result<i32>;
595        fn final_range() -> Result<u32>;
596        fn set_gain(q8_db: i32) -> Result<()>;
597        fn gain() -> Result<i32>;
598        fn phase_inversion_disabled() -> Result<bool>;
599        fn set_phase_inversion_disabled(disabled: bool) -> Result<()>;
600    }
601
602    /// Reset the decoder and restore the last successfully registered external DNN model.
603    ///
604    /// # Errors
605    /// Returns a mapped libopus error if the reset or model restoration fails.
606    pub fn reset(&mut self) -> Result<()> {
607        self.inner.reset()?;
608        #[cfg(feature = "dred")]
609        if let Some((ptr, len)) = self.active_dnn_blob {
610            unsafe { self.inner.apply_dnn_blob(ptr, len)? };
611        }
612        Ok(())
613    }
614
615    #[cfg(feature = "dred")]
616    /// Load an external DNN blob into this borrowed decoder state.
617    ///
618    /// Unlike [`Decoder::set_dnn_blob`], a borrowed wrapper cannot attach owned storage to the
619    /// external state. This method therefore passes the caller's allocation directly to libopus.
620    ///
621    /// # Safety
622    /// - `ptr` must point to `len` readable bytes containing a complete, correctly formatted
623    ///   libopus DNN blob, and must be aligned to at least `align_of::<u32>()`.
624    /// - The allocation must remain fixed and readable until the external decoder state is
625    ///   destroyed or will never be used again, even if this method returns an error. Dropping
626    ///   this Rust wrapper alone does not end that requirement.
627    ///
628    /// # Errors
629    /// Returns [`Error::BadArg`] for invalid pointer metadata or alignment, or a mapped libopus
630    /// error when loading fails.
631    pub unsafe fn set_dnn_blob(&mut self, ptr: *const u8, len: i32) -> Result<()> {
632        if ptr.is_null() || len <= 0 || !ptr.addr().is_multiple_of(std::mem::align_of::<u32>()) {
633            return Err(Error::BadArg);
634        }
635        unsafe { self.inner.apply_dnn_blob(ptr, len)? };
636        self.active_dnn_blob = Some((ptr, len));
637        Ok(())
638    }
639}
640
641impl Deref for DecoderRef<'_> {
642    type Target = Decoder;
643
644    fn deref(&self) -> &Self::Target {
645        &self.inner
646    }
647}
648
649#[cfg(all(test, feature = "dred"))]
650mod tests {
651    use super::*;
652    use crate::types::{Channels, SampleRate};
653
654    #[test]
655    fn dnn_blob_is_copied_into_retained_aligned_storage() {
656        let mut decoder = Decoder::new(SampleRate::Hz48000, Channels::Mono).unwrap();
657        let source = [0u8, 1, 2, 3, 4];
658        let unaligned = unsafe { source.as_ptr().add(1) };
659
660        let index = unsafe { decoder.retain_dnn_blob_copy(unaligned, 4) }.unwrap();
661        let (retained, len) = decoder.dnn_blobs[index].parts();
662
663        assert_eq!(len, 4);
664        assert_eq!((retained as usize) % std::mem::align_of::<u32>(), 0);
665        assert_eq!(
666            unsafe { std::slice::from_raw_parts(retained, 4) },
667            &source[1..]
668        );
669        assert_eq!(decoder.dnn_blobs.len(), 1);
670    }
671}