Skip to main content

opus_codec/
encoder.rs

1//! Opus encoder implementation with safe wrappers
2
3use crate::bindings::{
4    OPUS_AUTO, OPUS_BANDWIDTH_FULLBAND, OPUS_BITRATE_MAX, OPUS_GET_BANDWIDTH_REQUEST,
5    OPUS_GET_BITRATE_REQUEST, OPUS_GET_COMPLEXITY_REQUEST, OPUS_GET_DTX_REQUEST,
6    OPUS_GET_EXPERT_FRAME_DURATION_REQUEST, OPUS_GET_FINAL_RANGE_REQUEST,
7    OPUS_GET_FORCE_CHANNELS_REQUEST, OPUS_GET_IN_DTX_REQUEST, OPUS_GET_INBAND_FEC_REQUEST,
8    OPUS_GET_LOOKAHEAD_REQUEST, OPUS_GET_LSB_DEPTH_REQUEST, OPUS_GET_MAX_BANDWIDTH_REQUEST,
9    OPUS_GET_PACKET_LOSS_PERC_REQUEST, OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST,
10    OPUS_GET_PREDICTION_DISABLED_REQUEST, OPUS_GET_SIGNAL_REQUEST, OPUS_GET_VBR_CONSTRAINT_REQUEST,
11    OPUS_GET_VBR_REQUEST, OPUS_SET_BANDWIDTH_REQUEST, OPUS_SET_BITRATE_REQUEST,
12    OPUS_SET_COMPLEXITY_REQUEST, OPUS_SET_DTX_REQUEST, OPUS_SET_EXPERT_FRAME_DURATION_REQUEST,
13    OPUS_SET_FORCE_CHANNELS_REQUEST, OPUS_SET_INBAND_FEC_REQUEST, OPUS_SET_LSB_DEPTH_REQUEST,
14    OPUS_SET_MAX_BANDWIDTH_REQUEST, OPUS_SET_PACKET_LOSS_PERC_REQUEST,
15    OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST, OPUS_SET_PREDICTION_DISABLED_REQUEST,
16    OPUS_SET_SIGNAL_REQUEST, OPUS_SET_VBR_CONSTRAINT_REQUEST, OPUS_SET_VBR_REQUEST, OpusEncoder,
17    opus_encode, opus_encode_float, opus_encoder_create, opus_encoder_ctl, opus_encoder_destroy,
18    opus_encoder_get_size, opus_encoder_init,
19};
20#[cfg(feature = "dred")]
21use crate::bindings::{
22    OPUS_GET_DRED_DURATION_REQUEST, OPUS_SET_DNN_BLOB_REQUEST, OPUS_SET_DRED_DURATION_REQUEST,
23};
24use crate::constants::max_frame_samples_for;
25use crate::error::{Error, Result};
26use crate::types::{
27    Application, Bandwidth, Bitrate, Channels, Complexity, ExpertFrameDuration, SampleRate, Signal,
28};
29use crate::{AlignedBuffer, Ownership, RawHandle};
30use std::marker::PhantomData;
31use std::num::NonZeroUsize;
32use std::ops::Deref;
33use std::ptr::NonNull;
34
35#[cfg(feature = "dred")]
36struct RetainedDnnBlob {
37    data: Box<[u32]>,
38    len: i32,
39}
40
41#[cfg(feature = "dred")]
42impl RetainedDnnBlob {
43    fn parts(&self) -> (*const u8, i32) {
44        (self.data.as_ptr().cast::<u8>(), self.len)
45    }
46}
47
48/// Safe wrapper around a libopus `OpusEncoder`.
49pub struct Encoder {
50    raw: RawHandle<OpusEncoder>,
51    sample_rate: SampleRate,
52    channels: Channels,
53    // External-weight builds retain pointers into DNN blobs. Keep each copy,
54    // including copies used by failed non-transactional load attempts, until
55    // after the C encoder has been destroyed. Field declaration order makes
56    // `raw` drop before this storage.
57    #[cfg(feature = "dred")]
58    dnn_blobs: Vec<RetainedDnnBlob>,
59    #[cfg(feature = "dred")]
60    active_dnn_blob: Option<usize>,
61}
62
63unsafe impl Send for Encoder {}
64
65/// Borrowed wrapper around an encoder state.
66///
67/// The owning handle cannot be moved out of this borrowed wrapper:
68///
69/// ```compile_fail
70/// use opus_codec::encoder::EncoderRef;
71/// use opus_codec::Encoder;
72/// fn extract<'a>(state: &mut EncoderRef<'a>, replacement: Encoder) -> Encoder {
73///     std::mem::replace(&mut **state, replacement)
74/// }
75/// ```
76pub struct EncoderRef<'a> {
77    inner: Encoder,
78    #[cfg(feature = "dred")]
79    active_dnn_blob: Option<(*const u8, i32)>,
80    _marker: PhantomData<&'a mut OpusEncoder>,
81}
82
83unsafe impl Send for EncoderRef<'_> {}
84
85impl Encoder {
86    fn from_raw(
87        ptr: NonNull<OpusEncoder>,
88        sample_rate: SampleRate,
89        channels: Channels,
90        ownership: Ownership,
91    ) -> Self {
92        Self {
93            raw: RawHandle::new(ptr, ownership, opus_encoder_destroy),
94            sample_rate,
95            channels,
96            #[cfg(feature = "dred")]
97            dnn_blobs: Vec::new(),
98            #[cfg(feature = "dred")]
99            active_dnn_blob: None,
100        }
101    }
102
103    /// Size in bytes of an encoder state for external allocation.
104    ///
105    /// # Errors
106    /// Returns [`Error::BadArg`] if the channel count is invalid or libopus reports
107    /// an impossible size.
108    pub fn size(channels: Channels) -> Result<usize> {
109        let raw = unsafe { opus_encoder_get_size(channels.as_i32()) };
110        if raw <= 0 {
111            return Err(Error::BadArg);
112        }
113        usize::try_from(raw).map_err(|_| Error::InternalError)
114    }
115
116    /// Initialize a previously allocated encoder state.
117    ///
118    /// # Safety
119    /// The caller must provide a valid pointer to `Encoder::size()` bytes,
120    /// aligned to at least `align_of::<usize>()` (malloc-style alignment).
121    ///
122    /// # Errors
123    /// Returns [`Error::BadArg`] if `ptr` is null, or a mapped libopus error.
124    pub unsafe fn init_in_place(
125        ptr: *mut OpusEncoder,
126        sample_rate: SampleRate,
127        channels: Channels,
128        application: Application,
129    ) -> Result<()> {
130        if ptr.is_null() {
131            return Err(Error::BadArg);
132        }
133        if !crate::opus_ptr_is_aligned(ptr.cast()) {
134            return Err(Error::BadArg);
135        }
136        let r = unsafe {
137            opus_encoder_init(
138                ptr,
139                sample_rate.as_i32(),
140                channels.as_i32(),
141                application as i32,
142            )
143        };
144        if r != 0 {
145            return Err(Error::from_code(r));
146        }
147        Ok(())
148    }
149
150    /// Create a new encoder.
151    ///
152    /// # Errors
153    /// Returns an error if allocation fails or arguments are invalid.
154    pub fn new(
155        sample_rate: SampleRate,
156        channels: Channels,
157        application: Application,
158    ) -> Result<Self> {
159        // Validate sample rate
160        if !sample_rate.is_valid() {
161            return Err(Error::BadArg);
162        }
163
164        let mut error = 0i32;
165        let encoder = unsafe {
166            opus_encoder_create(
167                sample_rate.as_i32(),
168                channels.as_i32(),
169                application as i32,
170                std::ptr::addr_of_mut!(error),
171            )
172        };
173
174        if error != 0 {
175            return Err(Error::from_code(error));
176        }
177
178        let encoder = NonNull::new(encoder).ok_or(Error::AllocFail)?;
179
180        Ok(Self::from_raw(
181            encoder,
182            sample_rate,
183            channels,
184            Ownership::Owned,
185        ))
186    }
187
188    /// Encode 16-bit PCM into an Opus packet.
189    ///
190    /// # Errors
191    /// Returns [`Error::InvalidState`] if the encoder is invalid, [`Error::BadArg`] for
192    /// invalid buffer sizes or frame size, or a mapped libopus error.
193    pub fn encode(&mut self, input: &[i16], output: &mut [u8]) -> Result<usize> {
194        // Validate input buffer size
195        if input.is_empty() {
196            return Err(Error::BadArg);
197        }
198
199        // Ensure input buffer is properly sized for the number of channels
200        if !input.len().is_multiple_of(self.channels.as_usize()) {
201            return Err(Error::BadArg);
202        }
203
204        let frame_size = input.len() / self.channels.as_usize();
205        let frame_size = NonZeroUsize::new(frame_size).ok_or(Error::BadArg)?;
206        // Validate frame size is within Opus limits for the configured sample rate
207        if frame_size.get() > max_frame_samples_for(self.sample_rate) {
208            return Err(Error::BadArg);
209        }
210
211        // Validate output buffer size
212        if output.is_empty() {
213            return Err(Error::BadArg);
214        }
215        if output.len() > i32::MAX as usize {
216            return Err(Error::BadArg);
217        }
218
219        let frame_size_i32 = i32::try_from(frame_size.get()).map_err(|_| Error::BadArg)?;
220        let out_len_i32 = i32::try_from(output.len()).map_err(|_| Error::BadArg)?;
221        let result = unsafe {
222            opus_encode(
223                self.raw.as_ptr(),
224                input.as_ptr(),
225                frame_size_i32,
226                output.as_mut_ptr(),
227                out_len_i32,
228            )
229        };
230
231        if result < 0 {
232            return Err(Error::from_code(result));
233        }
234
235        usize::try_from(result).map_err(|_| Error::InternalError)
236    }
237
238    /// Encode 16-bit PCM, capping output to `max_data_bytes`.
239    ///
240    /// This uses the normal libopus `opus_encode` path and passes `max_data_bytes`
241    /// as the output limit, so it constrains packet size without enabling a separate
242    /// encoder mode. It does not itself enable FEC; use `set_inband_fec(true)` and
243    /// `set_packet_loss_perc(…)` to actually make the encoder produce FEC.
244    ///
245    /// # Errors
246    /// Returns [`Error::InvalidState`] if the encoder is invalid, [`Error::BadArg`] for
247    /// invalid buffer sizes or frame size, or a mapped libopus error.
248    pub fn encode_limited(
249        &mut self,
250        input: &[i16],
251        output: &mut [u8],
252        max_data_bytes: usize,
253    ) -> Result<usize> {
254        // Validate input buffer size
255        if input.is_empty() {
256            return Err(Error::BadArg);
257        }
258
259        // Ensure input buffer is properly sized for the number of channels
260        if !input.len().is_multiple_of(self.channels.as_usize()) {
261            return Err(Error::BadArg);
262        }
263
264        let frame_size = input.len() / self.channels.as_usize();
265        let frame_size = NonZeroUsize::new(frame_size).ok_or(Error::BadArg)?;
266        // Validate frame size is within Opus limits for the configured sample rate
267        if frame_size.get() > max_frame_samples_for(self.sample_rate) {
268            return Err(Error::BadArg);
269        }
270
271        // Validate output buffer size
272        if output.is_empty() {
273            return Err(Error::BadArg);
274        }
275        if output.len() > i32::MAX as usize {
276            return Err(Error::BadArg);
277        }
278        // Validate max_data_bytes parameter
279        if max_data_bytes == 0 || max_data_bytes > output.len() {
280            return Err(Error::BadArg);
281        }
282
283        let frame_size_i32 = i32::try_from(frame_size.get()).map_err(|_| Error::BadArg)?;
284        let max_bytes_i32 = i32::try_from(max_data_bytes).map_err(|_| Error::BadArg)?;
285        let result = unsafe {
286            opus_encode(
287                self.raw.as_ptr(),
288                input.as_ptr(),
289                frame_size_i32,
290                output.as_mut_ptr(),
291                max_bytes_i32,
292            )
293        };
294
295        if result < 0 {
296            return Err(Error::from_code(result));
297        }
298
299        usize::try_from(result).map_err(|_| Error::InternalError)
300    }
301
302    /// Encode f32 PCM into an Opus packet.
303    ///
304    /// # Errors
305    /// Returns [`Error::InvalidState`] if the encoder is invalid, [`Error::BadArg`] for
306    /// invalid buffer sizes or frame size, or a mapped libopus error.
307    pub fn encode_float(&mut self, input: &[f32], output: &mut [u8]) -> Result<usize> {
308        if input.is_empty() {
309            return Err(Error::BadArg);
310        }
311        if !input.len().is_multiple_of(self.channels.as_usize()) {
312            return Err(Error::BadArg);
313        }
314        let frame_size = input.len() / self.channels.as_usize();
315        let frame_size = NonZeroUsize::new(frame_size).ok_or(Error::BadArg)?;
316        if frame_size.get() > max_frame_samples_for(self.sample_rate) {
317            return Err(Error::BadArg);
318        }
319        if output.is_empty() || output.len() > i32::MAX as usize {
320            return Err(Error::BadArg);
321        }
322        let frame_i32 = i32::try_from(frame_size.get()).map_err(|_| Error::BadArg)?;
323        let out_len_i32 = i32::try_from(output.len()).map_err(|_| Error::BadArg)?;
324        let n = unsafe {
325            opus_encode_float(
326                self.raw.as_ptr(),
327                input.as_ptr(),
328                frame_i32,
329                output.as_mut_ptr(),
330                out_len_i32,
331            )
332        };
333        if n < 0 {
334            return Err(Error::from_code(n));
335        }
336        usize::try_from(n).map_err(|_| Error::InternalError)
337    }
338
339    // ===== Common encoder CTLs =====
340
341    /// Enable/disable in-band FEC generation (decoder can recover from losses).
342    ///
343    /// # Errors
344    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
345    pub fn set_inband_fec(&mut self, enabled: bool) -> Result<()> {
346        self.simple_ctl(OPUS_SET_INBAND_FEC_REQUEST as i32, i32::from(enabled))
347    }
348    /// Query in-band FEC setting.
349    ///
350    /// # Errors
351    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
352    pub fn inband_fec(&mut self) -> Result<bool> {
353        self.get_bool_ctl(OPUS_GET_INBAND_FEC_REQUEST as i32)
354    }
355
356    /// Hint expected packet loss percentage [0..=100].
357    ///
358    /// # Errors
359    /// Returns [`Error::InvalidState`] if the encoder is invalid, [`Error::BadArg`] for out-of-range values,
360    /// or a mapped libopus error.
361    pub fn set_packet_loss_perc(&mut self, perc: i32) -> Result<()> {
362        if !(0..=100).contains(&perc) {
363            return Err(Error::BadArg);
364        }
365        self.simple_ctl(OPUS_SET_PACKET_LOSS_PERC_REQUEST as i32, perc)
366    }
367    /// Query packet loss percentage hint.
368    ///
369    /// # Errors
370    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
371    pub fn packet_loss_perc(&mut self) -> Result<i32> {
372        self.get_int_ctl(OPUS_GET_PACKET_LOSS_PERC_REQUEST as i32)
373    }
374
375    /// Enable/disable DTX (discontinuous transmission).
376    ///
377    /// # Errors
378    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
379    pub fn set_dtx(&mut self, enabled: bool) -> Result<()> {
380        self.simple_ctl(OPUS_SET_DTX_REQUEST as i32, i32::from(enabled))
381    }
382    /// Query DTX setting.
383    ///
384    /// # Errors
385    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
386    pub fn dtx(&mut self) -> Result<bool> {
387        self.get_bool_ctl(OPUS_GET_DTX_REQUEST as i32)
388    }
389    /// Returns true if encoder is currently in DTX.
390    ///
391    /// # Errors
392    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
393    pub fn in_dtx(&mut self) -> Result<bool> {
394        self.get_bool_ctl(OPUS_GET_IN_DTX_REQUEST as i32)
395    }
396
397    /// Constrain VBR to reduce instant bitrate swings.
398    ///
399    /// # Errors
400    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
401    pub fn set_vbr_constraint(&mut self, constrained: bool) -> Result<()> {
402        self.simple_ctl(
403            OPUS_SET_VBR_CONSTRAINT_REQUEST as i32,
404            i32::from(constrained),
405        )
406    }
407    /// Query VBR constraint setting.
408    ///
409    /// # Errors
410    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
411    pub fn vbr_constraint(&mut self) -> Result<bool> {
412        self.get_bool_ctl(OPUS_GET_VBR_CONSTRAINT_REQUEST as i32)
413    }
414
415    /// Set maximum audio bandwidth the encoder may use.
416    ///
417    /// # Errors
418    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
419    pub fn set_max_bandwidth(&mut self, bw: Bandwidth) -> Result<()> {
420        self.simple_ctl(OPUS_SET_MAX_BANDWIDTH_REQUEST as i32, bw as i32)
421    }
422    /// Query max bandwidth.
423    ///
424    /// # Errors
425    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
426    pub fn max_bandwidth(&mut self) -> Result<Bandwidth> {
427        self.get_bandwidth_ctl(OPUS_GET_MAX_BANDWIDTH_REQUEST as i32)
428    }
429
430    /// Force a specific bandwidth (overrides automatic).
431    ///
432    /// # Errors
433    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
434    pub fn set_bandwidth(&mut self, bw: Bandwidth) -> Result<()> {
435        self.simple_ctl(OPUS_SET_BANDWIDTH_REQUEST as i32, bw as i32)
436    }
437    /// Query current forced bandwidth.
438    ///
439    /// # Errors
440    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
441    pub fn bandwidth(&mut self) -> Result<Bandwidth> {
442        self.get_bandwidth_ctl(OPUS_GET_BANDWIDTH_REQUEST as i32)
443    }
444
445    /// Force mono/stereo output, or None for automatic.
446    ///
447    /// # Errors
448    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
449    pub fn set_force_channels(&mut self, channels: Option<Channels>) -> Result<()> {
450        let val = match channels {
451            Some(Channels::Mono) => 1,
452            Some(Channels::Stereo) => 2,
453            None => crate::bindings::OPUS_AUTO,
454        };
455        self.simple_ctl(OPUS_SET_FORCE_CHANNELS_REQUEST as i32, val)
456    }
457    /// Query forced channels.
458    ///
459    /// # Errors
460    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
461    pub fn force_channels(&mut self) -> Result<Option<Channels>> {
462        let v = self.get_int_ctl(OPUS_GET_FORCE_CHANNELS_REQUEST as i32)?;
463        Ok(match v {
464            1 => Some(Channels::Mono),
465            2 => Some(Channels::Stereo),
466            _ => None,
467        })
468    }
469
470    /// Hint content type (voice or music).
471    ///
472    /// # Errors
473    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
474    pub fn set_signal(&mut self, signal: Signal) -> Result<()> {
475        self.simple_ctl(OPUS_SET_SIGNAL_REQUEST as i32, signal as i32)
476    }
477    /// Query current signal hint.
478    ///
479    /// # Errors
480    /// Returns [`Error::InvalidState`] if the encoder is invalid, [`Error::InternalError`] if the
481    /// response is not recognized, or a mapped libopus error.
482    pub fn signal(&mut self) -> Result<Signal> {
483        let v = self.get_int_ctl(OPUS_GET_SIGNAL_REQUEST as i32)?;
484        match v {
485            x if x == OPUS_AUTO => Ok(Signal::Auto),
486            x if x == crate::bindings::OPUS_SIGNAL_VOICE as i32 => Ok(Signal::Voice),
487            x if x == crate::bindings::OPUS_SIGNAL_MUSIC as i32 => Ok(Signal::Music),
488            _ => Err(Error::InternalError),
489        }
490    }
491
492    /// Encoder algorithmic lookahead in samples at this encoder's configured sample rate.
493    ///
494    /// # Errors
495    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
496    pub fn lookahead(&mut self) -> Result<i32> {
497        self.get_int_ctl(OPUS_GET_LOOKAHEAD_REQUEST as i32)
498    }
499    /// Final RNG state from the last encode (debugging/bitstream id).
500    ///
501    /// # Errors
502    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
503    pub fn final_range(&mut self) -> Result<u32> {
504        let mut val: u32 = 0;
505        let r = unsafe {
506            opus_encoder_ctl(
507                self.raw.as_ptr(),
508                OPUS_GET_FINAL_RANGE_REQUEST as i32,
509                &mut val,
510            )
511        };
512        if r != 0 {
513            return Err(Error::from_code(r));
514        }
515        Ok(val)
516    }
517
518    /// Set input LSB depth (typically 16-24 bits).
519    ///
520    /// # Errors
521    /// Returns [`Error::InvalidState`] if the encoder is invalid, [`Error::BadArg`] for an
522    /// out-of-range bit depth, or a mapped libopus error.
523    pub fn set_lsb_depth(&mut self, bits: i32) -> Result<()> {
524        if !(8..=24).contains(&bits) {
525            return Err(Error::BadArg);
526        }
527        self.simple_ctl(OPUS_SET_LSB_DEPTH_REQUEST as i32, bits)
528    }
529    /// Query input LSB depth.
530    ///
531    /// # Errors
532    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
533    pub fn lsb_depth(&mut self) -> Result<i32> {
534        self.get_int_ctl(OPUS_GET_LSB_DEPTH_REQUEST as i32)
535    }
536
537    /// Set expert frame duration choice.
538    ///
539    /// # Errors
540    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
541    pub fn set_expert_frame_duration(&mut self, dur: ExpertFrameDuration) -> Result<()> {
542        self.simple_ctl(OPUS_SET_EXPERT_FRAME_DURATION_REQUEST as i32, dur as i32)
543    }
544    /// Query expert frame duration.
545    ///
546    /// # Errors
547    /// Returns [`Error::InvalidState`] if the encoder is invalid, [`Error::InternalError`] if the
548    /// response is not recognized, or a mapped libopus error.
549    pub fn expert_frame_duration(&mut self) -> Result<ExpertFrameDuration> {
550        let v = self.get_int_ctl(OPUS_GET_EXPERT_FRAME_DURATION_REQUEST as i32)?;
551        let vu = u32::try_from(v).map_err(|_| Error::InternalError)?;
552        match vu {
553            x if x == crate::bindings::OPUS_FRAMESIZE_ARG => Ok(ExpertFrameDuration::Auto),
554            x if x == crate::bindings::OPUS_FRAMESIZE_2_5_MS => Ok(ExpertFrameDuration::Ms2_5),
555            x if x == crate::bindings::OPUS_FRAMESIZE_5_MS => Ok(ExpertFrameDuration::Ms5),
556            x if x == crate::bindings::OPUS_FRAMESIZE_10_MS => Ok(ExpertFrameDuration::Ms10),
557            x if x == crate::bindings::OPUS_FRAMESIZE_20_MS => Ok(ExpertFrameDuration::Ms20),
558            x if x == crate::bindings::OPUS_FRAMESIZE_40_MS => Ok(ExpertFrameDuration::Ms40),
559            x if x == crate::bindings::OPUS_FRAMESIZE_60_MS => Ok(ExpertFrameDuration::Ms60),
560            x if x == crate::bindings::OPUS_FRAMESIZE_80_MS => Ok(ExpertFrameDuration::Ms80),
561            x if x == crate::bindings::OPUS_FRAMESIZE_100_MS => Ok(ExpertFrameDuration::Ms100),
562            x if x == crate::bindings::OPUS_FRAMESIZE_120_MS => Ok(ExpertFrameDuration::Ms120),
563            _ => Err(Error::InternalError),
564        }
565    }
566
567    /// Disable/enable inter-frame prediction (expert option).
568    ///
569    /// # Errors
570    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
571    pub fn set_prediction_disabled(&mut self, disabled: bool) -> Result<()> {
572        self.simple_ctl(
573            OPUS_SET_PREDICTION_DISABLED_REQUEST as i32,
574            i32::from(disabled),
575        )
576    }
577    /// Query prediction disabled flag.
578    ///
579    /// # Errors
580    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
581    pub fn prediction_disabled(&mut self) -> Result<bool> {
582        self.get_bool_ctl(OPUS_GET_PREDICTION_DISABLED_REQUEST as i32)
583    }
584
585    /// Disable/enable phase inversion (stereo decorrelation) in CELT (expert option).
586    ///
587    /// # Errors
588    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
589    pub fn set_phase_inversion_disabled(&mut self, disabled: bool) -> Result<()> {
590        self.simple_ctl(
591            OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST as i32,
592            i32::from(disabled),
593        )
594    }
595    /// Query phase inversion disabled flag.
596    ///
597    /// # Errors
598    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
599    pub fn phase_inversion_disabled(&mut self) -> Result<bool> {
600        self.get_bool_ctl(OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST as i32)
601    }
602
603    #[cfg(feature = "dred")]
604    /// Set the maximum number of 10-ms Deep Redundancy (DRED) frames.
605    ///
606    /// A value of zero disables DRED. Libopus validates the supported upper bound.
607    ///
608    /// # Errors
609    /// Returns [`Error::BadArg`] if `frames_10ms` is outside the supported range, or a mapped
610    /// libopus error if DRED is unavailable in the linked encoder.
611    pub fn set_dred_duration(&mut self, frames_10ms: i32) -> Result<()> {
612        self.simple_ctl(OPUS_SET_DRED_DURATION_REQUEST as i32, frames_10ms)
613    }
614
615    #[cfg(feature = "dred")]
616    /// Query the configured maximum number of 10-ms DRED frames.
617    ///
618    /// # Errors
619    /// Returns a mapped libopus error if the encoder is invalid or DRED is unavailable.
620    pub fn dred_duration(&mut self) -> Result<i32> {
621        self.get_int_ctl(OPUS_GET_DRED_DURATION_REQUEST as i32)
622    }
623
624    #[cfg(feature = "dred")]
625    /// Load an external DNN model blob into this encoder.
626    ///
627    /// # Safety
628    /// `ptr` must be valid for reads of `len` bytes for the duration of this call and point to a
629    /// complete, correctly formatted libopus DNN blob. The bytes are copied into aligned storage
630    /// owned by the encoder, so the caller's allocation need not remain alive after this returns.
631    /// Some external-weight libopus builds do not safely handle malformed model records.
632    ///
633    /// # Errors
634    /// Returns [`Error::BadArg`] if `ptr` is null or `len` is non-positive, or a mapped libopus
635    /// error when loading fails. Embedded-weight libopus builds return [`Error::Unimplemented`].
636    pub unsafe fn set_dnn_blob(&mut self, ptr: *const u8, len: i32) -> Result<()> {
637        let blob_index = unsafe { self.retain_dnn_blob_copy(ptr, len)? };
638        let (owned_ptr, owned_len) = self.dnn_blobs[blob_index].parts();
639        if let Err(error) = unsafe { self.apply_dnn_blob(owned_ptr, owned_len) } {
640            if error == Error::Unimplemented {
641                // An unsupported CTL never inspected or retained the pointer.
642                // Other failures may leave model fields pointing into the blob.
643                let removed = self.dnn_blobs.pop();
644                debug_assert!(removed.is_some());
645            }
646            return Err(error);
647        }
648        self.active_dnn_blob = Some(blob_index);
649        Ok(())
650    }
651
652    #[cfg(feature = "dred")]
653    unsafe fn retain_dnn_blob_copy(&mut self, ptr: *const u8, len: i32) -> Result<usize> {
654        if ptr.is_null() || len <= 0 {
655            return Err(Error::BadArg);
656        }
657        let byte_len = usize::try_from(len).map_err(|_| Error::BadArg)?;
658        let word_len = byte_len.div_ceil(std::mem::size_of::<u32>());
659        let mut blob = vec![0u32; word_len].into_boxed_slice();
660        unsafe {
661            std::ptr::copy_nonoverlapping(ptr, blob.as_mut_ptr().cast::<u8>(), byte_len);
662        }
663        let index = self.dnn_blobs.len();
664        self.dnn_blobs.push(RetainedDnnBlob { data: blob, len });
665        Ok(index)
666    }
667
668    #[cfg(feature = "dred")]
669    unsafe fn apply_dnn_blob(&mut self, ptr: *const u8, len: i32) -> Result<()> {
670        let r = unsafe {
671            opus_encoder_ctl(
672                self.raw.as_ptr(),
673                OPUS_SET_DNN_BLOB_REQUEST as i32,
674                ptr,
675                len,
676            )
677        };
678        if r != 0 {
679            return Err(Error::from_code(r));
680        }
681        Ok(())
682    }
683
684    #[cfg(feature = "dred")]
685    fn reload_active_dnn_blob(&mut self) -> Result<()> {
686        let Some(index) = self.active_dnn_blob else {
687            return Ok(());
688        };
689        let (ptr, len) = self.dnn_blobs[index].parts();
690        unsafe { self.apply_dnn_blob(ptr, len) }
691    }
692
693    // --- internal helpers ---
694    fn simple_ctl(&mut self, req: i32, val: i32) -> Result<()> {
695        let r = unsafe { opus_encoder_ctl(self.raw.as_ptr(), req, val) };
696        if r != 0 {
697            return Err(Error::from_code(r));
698        }
699        Ok(())
700    }
701    fn get_bool_ctl(&mut self, req: i32) -> Result<bool> {
702        Ok(self.get_int_ctl(req)? != 0)
703    }
704    fn get_int_ctl(&mut self, req: i32) -> Result<i32> {
705        let mut v: i32 = 0;
706        let r = unsafe { opus_encoder_ctl(self.raw.as_ptr(), req, &mut v) };
707        if r != 0 {
708            return Err(Error::from_code(r));
709        }
710        Ok(v)
711    }
712    fn get_bandwidth_ctl(&mut self, req: i32) -> Result<Bandwidth> {
713        let v = self.get_int_ctl(req)?;
714        let vu = u32::try_from(v).map_err(|_| Error::InternalError)?;
715        match vu {
716            x if x == crate::bindings::OPUS_BANDWIDTH_NARROWBAND => Ok(Bandwidth::Narrowband),
717            x if x == crate::bindings::OPUS_BANDWIDTH_MEDIUMBAND => Ok(Bandwidth::Mediumband),
718            x if x == crate::bindings::OPUS_BANDWIDTH_WIDEBAND => Ok(Bandwidth::Wideband),
719            x if x == crate::bindings::OPUS_BANDWIDTH_SUPERWIDEBAND => Ok(Bandwidth::SuperWideband),
720            x if x == OPUS_BANDWIDTH_FULLBAND => Ok(Bandwidth::Fullband),
721            _ => Err(Error::InternalError),
722        }
723    }
724
725    /// Set target bitrate.
726    ///
727    /// # Errors
728    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
729    pub fn set_bitrate(&mut self, bitrate: Bitrate) -> Result<()> {
730        let result = unsafe {
731            opus_encoder_ctl(
732                self.raw.as_ptr(),
733                OPUS_SET_BITRATE_REQUEST as i32,
734                bitrate.value(),
735            )
736        };
737
738        if result != 0 {
739            return Err(Error::from_code(result));
740        }
741
742        Ok(())
743    }
744
745    /// Query current bitrate.
746    ///
747    /// # Errors
748    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
749    pub fn bitrate(&mut self) -> Result<Bitrate> {
750        let mut bitrate = 0i32;
751        let result = unsafe {
752            opus_encoder_ctl(
753                self.raw.as_ptr(),
754                OPUS_GET_BITRATE_REQUEST as i32,
755                &mut bitrate,
756            )
757        };
758
759        if result != 0 {
760            return Err(Error::from_code(result));
761        }
762
763        match bitrate {
764            OPUS_AUTO => Ok(Bitrate::Auto),
765            OPUS_BITRATE_MAX => Ok(Bitrate::Max),
766            bps => Ok(Bitrate::Custom(bps)),
767        }
768    }
769
770    /// Set encoder complexity [0..=10].
771    ///
772    /// # Errors
773    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
774    pub fn set_complexity(&mut self, complexity: Complexity) -> Result<()> {
775        let result = unsafe {
776            opus_encoder_ctl(
777                self.raw.as_ptr(),
778                OPUS_SET_COMPLEXITY_REQUEST as i32,
779                complexity.value() as i32,
780            )
781        };
782
783        if result != 0 {
784            return Err(Error::from_code(result));
785        }
786
787        Ok(())
788    }
789
790    /// Query encoder complexity.
791    ///
792    /// # Errors
793    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
794    pub fn complexity(&mut self) -> Result<Complexity> {
795        let mut complexity = 0i32;
796        let result = unsafe {
797            opus_encoder_ctl(
798                self.raw.as_ptr(),
799                OPUS_GET_COMPLEXITY_REQUEST as i32,
800                &mut complexity,
801            )
802        };
803
804        if result != 0 {
805            return Err(Error::from_code(result));
806        }
807
808        let complexity = u32::try_from(complexity).map_err(|_| Error::InternalError)?;
809        Complexity::try_new(complexity).ok_or(Error::InternalError)
810    }
811
812    /// Enable or disable VBR.
813    ///
814    /// # Errors
815    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
816    pub fn set_vbr(&mut self, enabled: bool) -> Result<()> {
817        let vbr = i32::from(enabled);
818        let result =
819            unsafe { opus_encoder_ctl(self.raw.as_ptr(), OPUS_SET_VBR_REQUEST as i32, vbr) };
820
821        if result != 0 {
822            return Err(Error::from_code(result));
823        }
824
825        Ok(())
826    }
827
828    /// Query VBR status.
829    ///
830    /// # Errors
831    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
832    pub fn vbr(&mut self) -> Result<bool> {
833        let mut vbr = 0i32;
834        let result =
835            unsafe { opus_encoder_ctl(self.raw.as_ptr(), OPUS_GET_VBR_REQUEST as i32, &mut vbr) };
836
837        if result != 0 {
838            return Err(Error::from_code(result));
839        }
840
841        Ok(vbr != 0)
842    }
843
844    /// The encoder's configured sample rate.
845    #[must_use]
846    pub const fn sample_rate(&self) -> SampleRate {
847        self.sample_rate
848    }
849
850    /// The encoder's channel configuration.
851    #[must_use]
852    pub const fn channels(&self) -> Channels {
853        self.channels
854    }
855
856    /// Reset the encoder to its initial state (same config, cleared history).
857    ///
858    /// # Errors
859    /// Returns [`Error::InvalidState`] if the encoder is invalid, or a mapped libopus error.
860    pub fn reset(&mut self) -> Result<()> {
861        let r = unsafe {
862            opus_encoder_ctl(self.raw.as_ptr(), crate::bindings::OPUS_RESET_STATE as i32)
863        };
864        if r != 0 {
865            return Err(Error::from_code(r));
866        }
867        #[cfg(feature = "dred")]
868        self.reload_active_dnn_blob()?;
869        Ok(())
870    }
871}
872
873impl<'a> EncoderRef<'a> {
874    /// Wrap an externally-initialized encoder without taking ownership.
875    ///
876    /// # Safety
877    /// - `ptr` must point to valid, initialized memory of at least [`Encoder::size()`] bytes
878    /// - `ptr` must be aligned to at least `align_of::<usize>()` (malloc-style alignment)
879    /// - `sample_rate` and `channels` must exactly match the encoder state already stored at `ptr`
880    /// - The memory must remain valid for the lifetime `'a`
881    /// - Caller is responsible for freeing the memory after this wrapper is dropped
882    /// - If the external state already uses runtime-loaded DNN weights, register that blob again
883    ///   through `EncoderRef::set_dnn_blob` before calling `EncoderRef::reset`
884    ///
885    /// Passing mismatched metadata is undefined behavior: later safe methods may validate buffer
886    /// sizes against the wrong channel/rate and then call libopus with out-of-bounds buffers.
887    ///
888    /// # Panics
889    /// Panics if `ptr` is null or not pointer-aligned.
890    ///
891    /// Use [`Encoder::init_in_place`] to initialize the memory before calling this.
892    #[must_use]
893    pub unsafe fn from_raw(
894        ptr: *mut OpusEncoder,
895        sample_rate: SampleRate,
896        channels: Channels,
897    ) -> Self {
898        let encoder = Encoder::from_raw(
899            crate::checked_non_null(ptr, "EncoderRef::from_raw"),
900            sample_rate,
901            channels,
902            Ownership::Borrowed,
903        );
904        Self {
905            inner: encoder,
906            #[cfg(feature = "dred")]
907            active_dnn_blob: None,
908            _marker: PhantomData,
909        }
910    }
911
912    /// Initialize and wrap an externally allocated buffer.
913    ///
914    /// # Errors
915    /// Returns [`Error::BadArg`] if the buffer is too small, or a mapped libopus error.
916    pub fn init_in(
917        buf: &'a mut AlignedBuffer,
918        sample_rate: SampleRate,
919        channels: Channels,
920        application: Application,
921    ) -> Result<Self> {
922        let required = Encoder::size(channels)?;
923        if buf.capacity_bytes() < required {
924            return Err(Error::BadArg);
925        }
926        let ptr = buf.as_mut_ptr::<OpusEncoder>();
927        unsafe { Encoder::init_in_place(ptr, sample_rate, channels, application)? };
928        Ok(unsafe { Self::from_raw(ptr, sample_rate, channels) })
929    }
930
931    delegate_ref_mut_methods! {
932        fn encode(input: &[i16], output: &mut [u8]) -> Result<usize>;
933        fn encode_limited(input: &[i16], output: &mut [u8], max_data_bytes: usize) -> Result<usize>;
934        fn encode_float(input: &[f32], output: &mut [u8]) -> Result<usize>;
935        fn set_inband_fec(enabled: bool) -> Result<()>;
936        fn inband_fec() -> Result<bool>;
937        fn set_packet_loss_perc(perc: i32) -> Result<()>;
938        fn packet_loss_perc() -> Result<i32>;
939        fn set_dtx(enabled: bool) -> Result<()>;
940        fn dtx() -> Result<bool>;
941        fn in_dtx() -> Result<bool>;
942        fn set_vbr_constraint(constrained: bool) -> Result<()>;
943        fn vbr_constraint() -> Result<bool>;
944        fn set_max_bandwidth(bw: Bandwidth) -> Result<()>;
945        fn max_bandwidth() -> Result<Bandwidth>;
946        fn set_bandwidth(bw: Bandwidth) -> Result<()>;
947        fn bandwidth() -> Result<Bandwidth>;
948        fn set_force_channels(channels: Option<Channels>) -> Result<()>;
949        fn force_channels() -> Result<Option<Channels>>;
950        fn set_signal(signal: Signal) -> Result<()>;
951        fn signal() -> Result<Signal>;
952        fn lookahead() -> Result<i32>;
953        fn final_range() -> Result<u32>;
954        fn set_lsb_depth(bits: i32) -> Result<()>;
955        fn lsb_depth() -> Result<i32>;
956        fn set_expert_frame_duration(dur: ExpertFrameDuration) -> Result<()>;
957        fn expert_frame_duration() -> Result<ExpertFrameDuration>;
958        fn set_prediction_disabled(disabled: bool) -> Result<()>;
959        fn prediction_disabled() -> Result<bool>;
960        fn set_phase_inversion_disabled(disabled: bool) -> Result<()>;
961        fn phase_inversion_disabled() -> Result<bool>;
962        #[cfg(feature = "dred")]
963        fn set_dred_duration(frames_10ms: i32) -> Result<()>;
964        #[cfg(feature = "dred")]
965        fn dred_duration() -> Result<i32>;
966        fn set_bitrate(bitrate: Bitrate) -> Result<()>;
967        fn bitrate() -> Result<Bitrate>;
968        fn set_complexity(complexity: Complexity) -> Result<()>;
969        fn complexity() -> Result<Complexity>;
970        fn set_vbr(enabled: bool) -> Result<()>;
971        fn vbr() -> Result<bool>;
972    }
973
974    /// Reset the encoder and restore the last successfully registered external DNN model.
975    ///
976    /// # Errors
977    /// Returns a mapped libopus error if the reset or model restoration fails.
978    pub fn reset(&mut self) -> Result<()> {
979        self.inner.reset()?;
980        #[cfg(feature = "dred")]
981        if let Some((ptr, len)) = self.active_dnn_blob {
982            unsafe { self.inner.apply_dnn_blob(ptr, len)? };
983        }
984        Ok(())
985    }
986
987    #[cfg(feature = "dred")]
988    /// Load an external DNN blob into this borrowed encoder state.
989    ///
990    /// Unlike [`Encoder::set_dnn_blob`], a borrowed wrapper cannot retain storage beyond the
991    /// wrapper's lifetime. This method therefore passes the caller's allocation to libopus.
992    ///
993    /// # Safety
994    /// - `ptr` must point to `len` readable bytes containing a complete, correctly formatted
995    ///   libopus DNN blob, and must be aligned to at least `align_of::<u32>()`.
996    /// - The allocation must remain fixed and readable until the external encoder state is
997    ///   destroyed or will never be used again, even if this method returns an error. Dropping
998    ///   this Rust wrapper alone does not end that requirement.
999    ///
1000    /// # Errors
1001    /// Returns [`Error::BadArg`] for invalid pointer metadata or alignment, or a mapped libopus
1002    /// error when loading fails. Embedded-weight libopus builds return [`Error::Unimplemented`].
1003    pub unsafe fn set_dnn_blob(&mut self, ptr: *const u8, len: i32) -> Result<()> {
1004        if ptr.is_null() || len <= 0 || !ptr.addr().is_multiple_of(std::mem::align_of::<u32>()) {
1005            return Err(Error::BadArg);
1006        }
1007        unsafe { self.inner.apply_dnn_blob(ptr, len)? };
1008        self.active_dnn_blob = Some((ptr, len));
1009        Ok(())
1010    }
1011}
1012
1013impl Deref for EncoderRef<'_> {
1014    type Target = Encoder;
1015
1016    fn deref(&self) -> &Self::Target {
1017        &self.inner
1018    }
1019}
1020
1021#[cfg(all(test, feature = "dred"))]
1022mod tests {
1023    use super::*;
1024
1025    #[test]
1026    fn dnn_blob_is_copied_into_retained_aligned_storage() {
1027        let mut encoder =
1028            Encoder::new(SampleRate::Hz48000, Channels::Mono, Application::Audio).unwrap();
1029        let source = [0u8, 1, 2, 3, 4];
1030        let unaligned = unsafe { source.as_ptr().add(1) };
1031
1032        let index = unsafe { encoder.retain_dnn_blob_copy(unaligned, 4) }.unwrap();
1033        let (retained, len) = encoder.dnn_blobs[index].parts();
1034
1035        assert_eq!(len, 4);
1036        assert_eq!((retained as usize) % std::mem::align_of::<u32>(), 0);
1037        assert_eq!(
1038            unsafe { std::slice::from_raw_parts(retained, 4) },
1039            &source[1..]
1040        );
1041        assert_eq!(encoder.dnn_blobs.len(), 1);
1042    }
1043}