moq_audio/decode/
decoder.rs1use 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::pcm;
12use crate::{Error, Format};
13
14const MAX_FRAME_MS: usize = 120;
16
17#[derive(Clone, Debug, Default)]
26#[non_exhaustive]
27pub struct Config {
28 pub format: Format,
30 pub sample_rate: Option<u32>,
33 pub channels: Option<u32>,
36 pub latency_max: Option<Duration>,
47}
48
49impl Config {
50 pub fn new() -> Self {
53 Self::default()
54 }
55}
56
57pub struct Decoder {
62 backend: Backend,
63 sample_rate: u32,
64 channel_count: u32,
65}
66
67enum Backend {
68 Opus(Opus),
69 Pcm { bytes_per_frame: usize },
70}
71
72struct Opus {
73 inner: *mut OpusDecoder,
74 pre_skip_remaining: usize,
75 max_frame_size: usize,
76}
77
78unsafe impl Send for Opus {}
80
81impl Decoder {
82 pub fn new(catalog: &hang::catalog::AudioConfig) -> Result<Self, Error> {
88 match &catalog.codec {
89 hang::catalog::AudioCodec::Opus => Self::new_opus(catalog),
90 hang::catalog::AudioCodec::Pcm => Self::new_pcm(catalog),
91 codec => Err(Error::Unsupported(format!("unsupported audio codec: {codec}"))),
92 }
93 }
94
95 fn new_opus(catalog: &hang::catalog::AudioConfig) -> Result<Self, Error> {
96 let (sample_rate, channel_count, pre_skip) = if let Some(desc) = &catalog.description {
97 let mut buf = desc.as_ref();
98 match moq_mux::codec::opus::Config::parse(&mut buf) {
99 Ok(head) => (head.sample_rate, head.channel_count, head.pre_skip),
100 Err(_) => (catalog.sample_rate, catalog.channel_count, 0),
101 }
102 } else {
103 (catalog.sample_rate, catalog.channel_count, 0)
104 };
105
106 opus::validate_rate(sample_rate)?;
107 let channels = opus::validate_channels(channel_count)?;
108
109 let mut err = 0i32;
110 let inner = unsafe { opus_decoder_create(sample_rate as i32, channels, &mut err) };
112 if err != OPUS_OK || inner.is_null() {
113 return Err(opus::error(err, "opus_decoder_create"));
114 }
115
116 let max_frame_size = (sample_rate as usize * MAX_FRAME_MS) / 1000;
117 let pre_skip_remaining = (pre_skip as usize * sample_rate as usize) / 48_000;
118
119 Ok(Self {
120 backend: Backend::Opus(Opus {
121 inner,
122 pre_skip_remaining,
123 max_frame_size,
124 }),
125 sample_rate,
126 channel_count,
127 })
128 }
129
130 fn new_pcm(catalog: &hang::catalog::AudioConfig) -> Result<Self, Error> {
131 if catalog.sample_rate == 0 {
132 return Err(Error::Unsupported("pcm sample rate must be greater than zero".into()));
133 }
134 if catalog.channel_count == 0 {
135 return Err(Error::Unsupported("pcm channel count must be greater than zero".into()));
136 }
137 if catalog.description.is_some() {
138 return Err(Error::Unsupported("pcm catalog description must be absent".into()));
139 }
140 let bitrate = pcm::bitrate(catalog.sample_rate, catalog.channel_count)?;
141 if catalog.bitrate.is_some_and(|declared| declared != bitrate) {
142 return Err(Error::Unsupported(format!(
143 "pcm catalog bitrate must be {bitrate} bits per second"
144 )));
145 }
146 let bytes_per_frame = pcm::frame_bytes(1, catalog.channel_count)?;
147
148 Ok(Self {
149 backend: Backend::Pcm { bytes_per_frame },
150 sample_rate: catalog.sample_rate,
151 channel_count: catalog.channel_count,
152 })
153 }
154
155 pub fn sample_rate(&self) -> u32 {
157 self.sample_rate
158 }
159
160 pub fn channel_count(&self) -> u32 {
162 self.channel_count
163 }
164
165 pub fn decode(&mut self, packet: &[u8]) -> Result<Vec<f32>, Error> {
167 match &mut self.backend {
168 Backend::Opus(opus) => {
169 let mut out = vec![0.0f32; opus.max_frame_size * self.channel_count as usize];
170 let samples = unsafe {
173 opus_decode_float(
174 &mut *opus.inner,
175 packet.as_ptr(),
176 packet.len() as i32,
177 out.as_mut_ptr(),
178 opus.max_frame_size as i32,
179 0,
180 )
181 };
182 if samples < 0 {
183 return Err(crate::opus::error(samples, "opus_decode_float"));
184 }
185 out.truncate(samples as usize * self.channel_count as usize);
186 let trim_frames = opus.pre_skip_remaining.min(samples as usize);
187 if trim_frames > 0 {
188 let trim_samples = trim_frames * self.channel_count as usize;
189 out.copy_within(trim_samples.., 0);
190 out.truncate(out.len() - trim_samples);
191 opus.pre_skip_remaining -= trim_frames;
192 }
193 Ok(out)
194 }
195 Backend::Pcm { bytes_per_frame } => {
196 if packet.is_empty() || !packet.len().is_multiple_of(*bytes_per_frame) {
197 return Err(Error::Misaligned {
198 got: packet.len(),
199 expected: packet.len().max(1).next_multiple_of(*bytes_per_frame),
200 });
201 }
202
203 Ok(packet
204 .chunks_exact(pcm::BYTES_PER_SAMPLE)
205 .map(|sample| f32::from_le_bytes([sample[0], sample[1], sample[2], sample[3]]))
206 .collect())
207 }
208 }
209 }
210}
211
212impl Drop for Opus {
213 fn drop(&mut self) {
214 unsafe { opus_decoder_destroy(self.inner) };
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn pcm_rejects_incomplete_channel_frame() {
225 let catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Pcm, 48_000, 2);
226 let mut decoder = Decoder::new(&catalog).unwrap();
227
228 assert!(matches!(
229 decoder.decode(&[]),
230 Err(Error::Misaligned { got: 0, expected: 8 })
231 ));
232 assert!(matches!(
233 decoder.decode(&[0; 4]),
234 Err(Error::Misaligned { got: 4, expected: 8 })
235 ));
236 }
237
238 #[test]
239 fn decoder_rejects_unknown_codec() {
240 let catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Unknown("future".into()), 48_000, 2);
241
242 assert!(matches!(Decoder::new(&catalog), Err(Error::Unsupported(_))));
243 }
244
245 #[test]
246 fn pcm_rejects_incorrect_catalog_bitrate() {
247 let mut catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Pcm, 48_000, 2);
248 catalog.bitrate = Some(1);
249
250 assert!(matches!(Decoder::new(&catalog), Err(Error::Unsupported(_))));
251 }
252}