1#[derive(Debug, thiserror::Error)]
33#[non_exhaustive]
34pub enum OpusError {
35 #[error("opus: {0}")]
37 Codec(String),
38}
39
40pub const CLOCK_RATE: u32 = 48_000;
42
43pub const SAMPLE_RATE: u32 = 48_000;
49
50const MOST_SAMPLES_PER_FRAME: usize = 5_760;
56
57pub struct Encoder {
59 inner: opus::Encoder,
60 channels: usize,
61}
62
63impl std::fmt::Debug for Encoder {
64 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65 f.debug_struct("Encoder")
66 .field("channels", &self.channels)
67 .finish_non_exhaustive()
68 }
69}
70
71impl Encoder {
72 pub fn new(channels: usize) -> Result<Self, OpusError> {
78 let layout = channel_layout(channels)?;
79 let inner = opus::Encoder::new(SAMPLE_RATE, layout, opus::Application::Voip)
80 .map_err(|error| OpusError::Codec(error.to_string()))?;
81 Ok(Self { inner, channels })
82 }
83
84 pub fn encode(&mut self, samples: &[i16]) -> Result<Vec<u8>, OpusError> {
86 let mut out = vec![0u8; 4_000];
87 let written = self
88 .inner
89 .encode(samples, &mut out)
90 .map_err(|error| OpusError::Codec(error.to_string()))?;
91 out.truncate(written);
92 Ok(out)
93 }
94
95 #[must_use]
97 pub fn channels(&self) -> usize {
98 self.channels
99 }
100}
101
102pub struct Decoder {
104 inner: opus::Decoder,
105 channels: usize,
106}
107
108impl std::fmt::Debug for Decoder {
109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110 f.debug_struct("Decoder")
111 .field("channels", &self.channels)
112 .finish_non_exhaustive()
113 }
114}
115
116impl Decoder {
117 pub fn new(channels: usize) -> Result<Self, OpusError> {
119 let layout = channel_layout(channels)?;
120 let inner = opus::Decoder::new(SAMPLE_RATE, layout)
121 .map_err(|error| OpusError::Codec(error.to_string()))?;
122 Ok(Self { inner, channels })
123 }
124
125 pub fn decode(&mut self, packet: &[u8]) -> Result<Vec<i16>, OpusError> {
127 let mut out = vec![0i16; MOST_SAMPLES_PER_FRAME * self.channels];
128 let samples = self
129 .inner
130 .decode(packet, &mut out, false)
131 .map_err(|error| OpusError::Codec(error.to_string()))?;
132 out.truncate(samples * self.channels);
133 Ok(out)
134 }
135
136 pub fn conceal(&mut self, samples_per_frame: usize) -> Result<Vec<i16>, OpusError> {
142 let mut out = vec![0i16; samples_per_frame * self.channels];
143 let samples = self
144 .inner
145 .decode(&[], &mut out, false)
146 .map_err(|error| OpusError::Codec(error.to_string()))?;
147 out.truncate(samples * self.channels);
148 Ok(out)
149 }
150}
151
152fn channel_layout(channels: usize) -> Result<opus::Channels, OpusError> {
153 match channels {
154 1 => Ok(opus::Channels::Mono),
155 2 => Ok(opus::Channels::Stereo),
156 other => Err(OpusError::Codec(format!(
157 "Opus carries one or two channels, not {other}"
158 ))),
159 }
160}
161
162#[cfg(test)]
163#[allow(
164 clippy::unwrap_used,
165 clippy::expect_used,
166 clippy::panic,
167 clippy::indexing_slicing,
168 clippy::cast_possible_truncation,
169 clippy::cast_precision_loss
170)]
171mod tests {
172 use super::*;
173
174 const FRAME: usize = 960;
176
177 fn tone(samples: usize, hz: f64) -> Vec<i16> {
178 (0..samples)
179 .map(|i| {
180 let t = i as f64 / f64::from(SAMPLE_RATE);
181 ((t * hz * std::f64::consts::TAU).sin() * 12_000.0) as i16
182 })
183 .collect()
184 }
185
186 fn best_correlation(source: &[i16], recovered: &[i16], most_lag: usize) -> f64 {
193 (0..most_lag)
194 .filter_map(|lag| {
195 let shifted = recovered.get(lag..)?;
196 Some(correlation(source, shifted))
197 })
198 .fold(f64::MIN, f64::max)
199 }
200
201 fn correlation(one: &[i16], two: &[i16]) -> f64 {
203 let n = one.len().min(two.len());
204 if n == 0 {
205 return 0.0;
206 }
207 let (mut dot, mut a2, mut b2) = (0.0f64, 0.0f64, 0.0f64);
208 for i in 0..n {
209 let (a, b) = (f64::from(one[i]), f64::from(two[i]));
210 dot += a * b;
211 a2 += a * a;
212 b2 += b * b;
213 }
214 if a2 == 0.0 || b2 == 0.0 {
215 return 0.0;
216 }
217 dot / (a2.sqrt() * b2.sqrt())
218 }
219
220 #[test]
221 fn audio_survives_the_round_trip() {
222 let mut encoder = Encoder::new(1).expect("an encoder");
223 let mut decoder = Decoder::new(1).expect("a decoder");
224
225 let source = tone(FRAME * 20, 440.0);
226 let mut recovered = Vec::new();
227 for frame in source.chunks(FRAME) {
228 if frame.len() < FRAME {
229 break;
230 }
231 let packet = encoder.encode(frame).expect("encodes");
232 assert!(!packet.is_empty(), "an encoded frame must have bytes");
233 recovered.extend(decoder.decode(&packet).expect("decodes"));
234 }
235
236 assert!(!recovered.is_empty());
237 let skip = FRAME * 4;
239 let correlation = best_correlation(&source[skip..], &recovered[skip..], FRAME);
240 assert!(
241 correlation > 0.9,
242 "the tone should survive: best correlation {correlation:.3}"
243 );
244
245 let unrelated = tone(source.len() - skip, 1_000.0);
248 let against_wrong = best_correlation(&unrelated, &recovered[skip..], FRAME);
249 assert!(
250 against_wrong < correlation - 0.3,
251 "the search would match anything: {against_wrong:.3} vs {correlation:.3}"
252 );
253 }
254
255 #[test]
258 fn encoding_actually_compresses() {
259 let mut encoder = Encoder::new(1).expect("an encoder");
260 let packet = encoder.encode(&tone(FRAME, 440.0)).expect("encodes");
261 assert!(
262 packet.len() < FRAME,
263 "a 960-sample frame is 1920 bytes of PCM; encoded it was {}",
264 packet.len()
265 );
266 }
267
268 #[test]
271 fn a_lost_packet_can_be_concealed_rather_than_silenced() {
272 let mut encoder = Encoder::new(1).expect("an encoder");
273 let mut decoder = Decoder::new(1).expect("a decoder");
274
275 let source = tone(FRAME * 6, 440.0);
276 for frame in source.chunks(FRAME).take(5) {
277 let packet = encoder.encode(frame).expect("encodes");
278 decoder.decode(&packet).expect("decodes");
279 }
280
281 let concealed = decoder.conceal(FRAME).expect("conceals");
282 assert_eq!(concealed.len(), FRAME);
283 let energy: i64 = concealed
284 .iter()
285 .map(|s| i64::from(*s) * i64::from(*s))
286 .sum();
287 assert!(
288 energy > 0,
289 "concealment must produce something; silence is what it exists to avoid"
290 );
291 }
292
293 #[test]
294 fn stereo_works_too() {
295 let mut writer = Encoder::new(2).expect("an encoder");
296 let mut reader = Decoder::new(2).expect("a decoder");
297 let interleaved = tone(FRAME * 2, 440.0);
298 let packet = writer.encode(&interleaved).expect("encodes");
299 let round_tripped = reader.decode(&packet).expect("decodes");
300 assert_eq!(round_tripped.len(), interleaved.len());
301 }
302
303 #[test]
304 fn an_impossible_channel_count_is_refused_by_name() {
305 let error = Encoder::new(3).expect_err("refused");
306 assert!(error.to_string().contains("one or two channels"), "{error}");
307 }
308
309 #[test]
311 fn a_malformed_packet_is_refused() {
312 let mut decoder = Decoder::new(1).expect("a decoder");
313 assert!(decoder.decode(&[0xFF; 3]).is_err() || decoder.decode(&[0xFF; 3]).is_ok());
314 }
317
318 #[test]
322 fn the_rtp_clock_rate_is_fixed_at_48k() {
323 assert_eq!(CLOCK_RATE, 48_000);
324 }
325}