moq_audio/decode/decoder.rs
1//! Opus decoder front end.
2//!
3//! Mirror of [`encode::Encoder`](crate::encode::Encoder): wraps libopus via
4//! [`unsafe_libopus`] and produces interleaved `f32` PCM.
5
6use std::time::Duration;
7
8use unsafe_libopus::{OPUS_OK, OpusDecoder, opus_decode_float, opus_decoder_create, opus_decoder_destroy};
9
10use crate::opus;
11use crate::{Error, Format};
12
13/// Opus packets cap at 120 ms (RFC 6716 ยง2.1.4).
14const MAX_FRAME_MS: usize = 120;
15
16/// Decoder configuration: the PCM layout to emit, plus the subscription's
17/// latency budget.
18///
19/// The mirror of [`encode::Config`](crate::encode::Config): it describes the
20/// output, since the codec's own shape is read from the catalog.
21///
22/// `#[non_exhaustive]`: build via [`Config::new`] (or `default()`) and set the
23/// optional fields, so future knobs don't break callers.
24#[derive(Clone, Debug, Default)]
25#[non_exhaustive]
26pub struct Config {
27 /// How to pack samples in each emitted frame.
28 pub format: Format,
29 /// Sample rate to emit at. `None` uses the codec's native rate from the
30 /// catalog; anything else resamples.
31 pub sample_rate: Option<u32>,
32 /// Channel count to emit. `None` uses the codec's native count; anything
33 /// else is rejected, since remapping isn't implemented.
34 pub channels: Option<u32>,
35 /// Upper bound on buffering before skipping a stalled group.
36 ///
37 /// Forwarded to [`moq_mux::container::Consumer::with_latency`]: if a group is
38 /// stuck and a newer group is more than this far ahead, the consumer skips.
39 /// `None` keeps the moq-mux default of zero, which skips aggressively. Set it
40 /// to the playout buffer you can tolerate (typically tens to a few hundred ms)
41 /// for the best congestion-vs-quality trade-off. The `_max` suffix is a
42 /// reminder that we never *add* latency here: the consumer skips only when
43 /// newer data is already this far ahead. A companion `latency_min` for
44 /// jitter-buffer padding will land in a follow-up.
45 pub latency_max: Option<Duration>,
46}
47
48impl Config {
49 /// A default config: the codec's native rate and channel count, interleaved
50 /// `f32`, and the moq-mux default latency.
51 pub fn new() -> Self {
52 Self::default()
53 }
54}
55
56/// Decodes codec packets into interleaved `f32` PCM.
57///
58/// The bring-your-own-payload layer under [`Consumer`](super::Consumer): use it
59/// when the packets don't come from a plain track subscription.
60pub struct Decoder {
61 inner: *mut OpusDecoder,
62 sample_rate: u32,
63 channel_count: u32,
64 max_frame_size: usize,
65}
66
67// SAFETY: see Encoder.
68unsafe impl Send for Decoder {}
69
70impl Decoder {
71 /// Build a decoder from a catalog [`AudioConfig`](hang::catalog::AudioConfig).
72 ///
73 /// Parses the OpusHead `description` if present; falls back to the catalog's
74 /// declared sample rate / channel count.
75 pub fn new(catalog: &hang::catalog::AudioConfig) -> Result<Self, Error> {
76 let (sample_rate, channel_count) = if let Some(desc) = &catalog.description {
77 let mut buf = desc.as_ref();
78 match moq_mux::codec::opus::Config::parse(&mut buf) {
79 Ok(head) => (head.sample_rate, head.channel_count),
80 Err(_) => (catalog.sample_rate, catalog.channel_count),
81 }
82 } else {
83 (catalog.sample_rate, catalog.channel_count)
84 };
85
86 opus::validate_rate(sample_rate)?;
87 let channels = opus::validate_channels(channel_count)?;
88
89 let mut err = 0i32;
90 // SAFETY: out-pointer is valid; inner is checked for null below.
91 let inner = unsafe { opus_decoder_create(sample_rate as i32, channels, &mut err) };
92 if err != OPUS_OK || inner.is_null() {
93 return Err(opus::error(err, "opus_decoder_create"));
94 }
95
96 let max_frame_size = (sample_rate as usize * MAX_FRAME_MS) / 1000;
97
98 Ok(Self {
99 inner,
100 sample_rate,
101 channel_count,
102 max_frame_size,
103 })
104 }
105
106 /// The rate the codec decodes at, read from the catalog.
107 pub fn sample_rate(&self) -> u32 {
108 self.sample_rate
109 }
110
111 /// The channel count the codec decodes at, read from the catalog.
112 pub fn channel_count(&self) -> u32 {
113 self.channel_count
114 }
115
116 /// Decode one packet into interleaved `f32` PCM.
117 pub fn decode(&mut self, packet: &[u8]) -> Result<Vec<f32>, Error> {
118 let mut out = vec![0.0f32; self.max_frame_size * self.channel_count as usize];
119 // SAFETY: `inner` owns a live OpusDecoder; packet/out slices bound
120 // by the lengths we pass.
121 let samples = unsafe {
122 opus_decode_float(
123 &mut *self.inner,
124 packet.as_ptr(),
125 packet.len() as i32,
126 out.as_mut_ptr(),
127 self.max_frame_size as i32,
128 0,
129 )
130 };
131 if samples < 0 {
132 return Err(opus::error(samples, "opus_decode_float"));
133 }
134 out.truncate(samples as usize * self.channel_count as usize);
135 Ok(out)
136 }
137}
138
139impl Drop for Decoder {
140 fn drop(&mut self) {
141 // SAFETY: `inner` is a live OpusDecoder that nothing else aliases.
142 unsafe { opus_decoder_destroy(self.inner) };
143 }
144}