1use std::fmt;
2use thiserror::Error;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
5#[serde(rename_all = "lowercase")]
6pub enum CodecType {
7 Pcmu,
9 Pcma,
11 Opus,
13}
14
15impl fmt::Display for CodecType {
16 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17 match self {
18 CodecType::Pcmu => write!(f, "PCMU (G.711 mu-law)"),
19 CodecType::Pcma => write!(f, "PCMA (G.711 A-law)"),
20 CodecType::Opus => write!(f, "Opus"),
21 }
22 }
23}
24
25impl CodecType {
26 pub fn payload_type(&self) -> u8 {
27 match self {
28 CodecType::Pcmu => 0,
29 CodecType::Pcma => 8,
30 CodecType::Opus => 111,
31 }
32 }
33
34 pub fn clock_rate(&self) -> u32 {
35 match self {
36 CodecType::Pcmu => 8000,
37 CodecType::Pcma => 8000,
38 CodecType::Opus => 48000,
39 }
40 }
41
42 pub fn name(&self) -> &'static str {
43 match self {
44 CodecType::Pcmu => "PCMU",
45 CodecType::Pcma => "PCMA",
46 CodecType::Opus => "opus",
47 }
48 }
49
50 pub fn from_payload_type(pt: u8) -> Option<Self> {
51 match pt {
52 0 => Some(CodecType::Pcmu),
53 8 => Some(CodecType::Pcma),
54 111 => Some(CodecType::Opus),
55 _ => None,
56 }
57 }
58
59 pub fn samples_per_frame(&self) -> usize {
61 match self {
62 CodecType::Pcmu => 160, CodecType::Pcma => 160,
64 CodecType::Opus => 960, }
66 }
67}
68
69#[derive(Debug, Error)]
70pub enum CodecError {
71 #[error("encoding error: {0}")]
72 EncodingError(String),
73 #[error("decoding error: {0}")]
74 DecodingError(String),
75 #[error("unsupported codec")]
76 Unsupported,
77}
78
79pub struct CodecPipeline {
85 codec: CodecType,
86 #[cfg(feature = "opus")]
87 opus_encoder: Option<audiopus::coder::Encoder>,
88 #[cfg(feature = "opus")]
89 opus_decoder: Option<audiopus::coder::Decoder>,
90}
91
92impl CodecPipeline {
93 pub fn new(codec: CodecType) -> Self {
94 #[cfg(feature = "opus")]
95 let (opus_encoder, opus_decoder) = if codec == CodecType::Opus {
96 let enc = audiopus::coder::Encoder::new(
97 audiopus::SampleRate::Hz48000,
98 audiopus::Channels::Mono,
99 audiopus::Application::Voip,
100 ).ok();
101 let dec = audiopus::coder::Decoder::new(
102 audiopus::SampleRate::Hz48000,
103 audiopus::Channels::Mono,
104 ).ok();
105 (enc, dec)
106 } else {
107 (None, None)
108 };
109
110 Self {
111 codec,
112 #[cfg(feature = "opus")]
113 opus_encoder,
114 #[cfg(feature = "opus")]
115 opus_decoder,
116 }
117 }
118
119 pub fn codec_type(&self) -> CodecType {
120 self.codec
121 }
122
123 pub fn encode(&mut self, pcm_samples: &[i16]) -> Result<Vec<u8>, CodecError> {
125 match self.codec {
126 CodecType::Pcmu => Ok(pcm_samples.iter().map(|&s| linear_to_ulaw(s)).collect()),
127 CodecType::Pcma => Ok(pcm_samples.iter().map(|&s| linear_to_alaw(s)).collect()),
128 CodecType::Opus => {
129 #[cfg(feature = "opus")]
130 {
131 if let Some(ref mut enc) = self.opus_encoder {
132 let mut output = vec![0u8; 4000]; let len = enc.encode(pcm_samples, &mut output)
134 .map_err(|e| CodecError::EncodingError(format!("opus encode: {}", e)))?;
135 output.truncate(len);
136 return Ok(output);
137 }
138 }
139 let mut bytes = Vec::with_capacity(pcm_samples.len() * 2);
141 for &sample in pcm_samples {
142 bytes.extend_from_slice(&sample.to_le_bytes());
143 }
144 Ok(bytes)
145 }
146 }
147 }
148
149 pub fn decode(&mut self, data: &[u8]) -> Result<Vec<i16>, CodecError> {
151 match self.codec {
152 CodecType::Pcmu => Ok(data.iter().map(|&b| ulaw_to_linear(b)).collect()),
153 CodecType::Pcma => Ok(data.iter().map(|&b| alaw_to_linear(b)).collect()),
154 CodecType::Opus => {
155 #[cfg(feature = "opus")]
156 {
157 if let Some(ref mut dec) = self.opus_decoder {
158 let mut output = vec![0i16; 5760]; let packet: audiopus::packet::Packet<'_> = data.try_into()
160 .map_err(|e: audiopus::Error| CodecError::DecodingError(format!("opus packet: {}", e)))?;
161 let signals: audiopus::MutSignals<'_, i16> = output.as_mut_slice().try_into()
162 .map_err(|e: audiopus::Error| CodecError::DecodingError(format!("opus signals: {}", e)))?;
163 let samples = dec.decode(Some(packet), signals, false)
164 .map_err(|e| CodecError::DecodingError(format!("opus decode: {}", e)))?;
165 output.truncate(samples);
166 return Ok(output);
167 }
168 }
169 if data.len() % 2 != 0 {
171 return Err(CodecError::DecodingError(
172 "invalid opus stub data length".to_string(),
173 ));
174 }
175 let samples: Vec<i16> = data
176 .chunks_exact(2)
177 .map(|chunk| i16::from_le_bytes([chunk[0], chunk[1]]))
178 .collect();
179 Ok(samples)
180 }
181 }
182 }
183
184 pub fn silence_frame(&self) -> Vec<u8> {
186 match self.codec {
187 CodecType::Pcmu => vec![0xFF; self.codec.samples_per_frame()], CodecType::Pcma => vec![0xD5; self.codec.samples_per_frame()], CodecType::Opus => vec![0; self.codec.samples_per_frame() * 2], }
191 }
192}
193
194const ULAW_BIAS: i32 = 0x84;
197const ULAW_CLIP: i32 = 32635;
198
199fn linear_to_ulaw(sample: i16) -> u8 {
200 let mut pcm_val = sample as i32;
201 let sign = if pcm_val < 0 {
202 pcm_val = -pcm_val;
203 0x80
204 } else {
205 0
206 };
207
208 if pcm_val > ULAW_CLIP {
209 pcm_val = ULAW_CLIP;
210 }
211 pcm_val += ULAW_BIAS;
212
213 let exponent = match pcm_val {
214 0..=0xFF => 0,
215 0x100..=0x1FF => 1,
216 0x200..=0x3FF => 2,
217 0x400..=0x7FF => 3,
218 0x800..=0xFFF => 4,
219 0x1000..=0x1FFF => 5,
220 0x2000..=0x3FFF => 6,
221 _ => 7,
222 };
223
224 let mantissa = (pcm_val >> (exponent + 3)) & 0x0F;
225 let ulaw_byte = !(sign | (exponent << 4) as i32 | mantissa) as u8;
226 ulaw_byte
227}
228
229fn ulaw_to_linear(ulaw_byte: u8) -> i16 {
230 let ulaw = !ulaw_byte;
231 let sign = ulaw & 0x80;
232 let exponent = ((ulaw >> 4) & 0x07) as i32;
233 let mantissa = (ulaw & 0x0F) as i32;
234
235 let mut sample = ((mantissa << 1) | 0x21) << (exponent + 2);
236 sample -= ULAW_BIAS as i32;
237
238 if sign != 0 {
239 -sample as i16
240 } else {
241 sample as i16
242 }
243}
244
245fn linear_to_alaw(sample: i16) -> u8 {
246 let mut pcm_val = sample as i32;
247 let sign = if pcm_val < 0 {
248 pcm_val = -pcm_val;
249 0x80i32
250 } else {
251 0
252 };
253
254 if pcm_val > 32767 {
255 pcm_val = 32767;
256 }
257
258 let (exponent, mantissa) = if pcm_val >= 256 {
259 let exp = match pcm_val {
260 256..=511 => 1,
261 512..=1023 => 2,
262 1024..=2047 => 3,
263 2048..=4095 => 4,
264 4096..=8191 => 5,
265 8192..=16383 => 6,
266 _ => 7,
267 };
268 let man = (pcm_val >> (exp + 3)) & 0x0F;
269 (exp, man)
270 } else {
271 (0, pcm_val >> 4)
272 };
273
274 let alaw_byte = (sign | (exponent << 4) | mantissa) as u8;
275 alaw_byte ^ 0x55
276}
277
278fn alaw_to_linear(alaw_byte: u8) -> i16 {
279 let alaw = alaw_byte ^ 0x55;
280 let sign = alaw & 0x80;
281 let exponent = ((alaw >> 4) & 0x07) as i32;
282 let mantissa = (alaw & 0x0F) as i32;
283
284 let sample = if exponent == 0 {
285 (mantissa << 4) | 0x08
286 } else {
287 ((mantissa << 1) | 0x21) << (exponent + 2)
288 };
289
290 if sign != 0 {
291 -(sample as i16)
292 } else {
293 sample as i16
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 #[test]
302 fn test_codec_type_properties() {
303 assert_eq!(CodecType::Pcmu.payload_type(), 0);
304 assert_eq!(CodecType::Pcma.payload_type(), 8);
305 assert_eq!(CodecType::Opus.payload_type(), 111);
306
307 assert_eq!(CodecType::Pcmu.clock_rate(), 8000);
308 assert_eq!(CodecType::Opus.clock_rate(), 48000);
309
310 assert_eq!(CodecType::Pcmu.name(), "PCMU");
311 assert_eq!(CodecType::Pcma.name(), "PCMA");
312 assert_eq!(CodecType::Opus.name(), "opus");
313 }
314
315 #[test]
316 fn test_codec_from_payload_type() {
317 assert_eq!(CodecType::from_payload_type(0), Some(CodecType::Pcmu));
318 assert_eq!(CodecType::from_payload_type(8), Some(CodecType::Pcma));
319 assert_eq!(CodecType::from_payload_type(111), Some(CodecType::Opus));
320 assert_eq!(CodecType::from_payload_type(99), None);
321 }
322
323 #[test]
324 fn test_samples_per_frame() {
325 assert_eq!(CodecType::Pcmu.samples_per_frame(), 160);
326 assert_eq!(CodecType::Pcma.samples_per_frame(), 160);
327 assert_eq!(CodecType::Opus.samples_per_frame(), 960);
328 }
329
330 #[test]
331 fn test_pcmu_encode_decode_roundtrip() {
332 let mut codec = CodecPipeline::new(CodecType::Pcmu);
333
334 let samples: Vec<i16> = vec![0, 100, -100, 1000, -1000, 8000, -8000, 32000, -32000];
336 let encoded = codec.encode(&samples).unwrap();
337 let decoded = codec.decode(&encoded).unwrap();
338
339 assert_eq!(samples.len(), decoded.len());
340 for (original, decoded) in samples.iter().zip(decoded.iter()) {
342 let diff = (*original as i32 - *decoded as i32).abs();
343 assert!(
345 diff < 500,
346 "Too much error: original={}, decoded={}, diff={}",
347 original,
348 decoded,
349 diff
350 );
351 }
352 }
353
354 #[test]
355 fn test_pcma_encode_decode_roundtrip() {
356 let mut codec = CodecPipeline::new(CodecType::Pcma);
357
358 let samples: Vec<i16> = vec![0, 100, -100, 1000, -1000, 8000, -8000, 32000, -32000];
359 let encoded = codec.encode(&samples).unwrap();
360 let decoded = codec.decode(&encoded).unwrap();
361
362 assert_eq!(samples.len(), decoded.len());
363 for (original, decoded) in samples.iter().zip(decoded.iter()) {
364 let diff = (*original as i32 - *decoded as i32).abs();
365 assert!(
366 diff < 500,
367 "Too much error: original={}, decoded={}, diff={}",
368 original,
369 decoded,
370 diff
371 );
372 }
373 }
374
375 #[test]
376 fn test_opus_roundtrip() {
377 let mut codec = CodecPipeline::new(CodecType::Opus);
378 let samples: Vec<i16> = (0..960)
380 .map(|i| ((i as f64 / 960.0 * std::f64::consts::TAU).sin() * 16000.0) as i16)
381 .collect();
382 let encoded = codec.encode(&samples).unwrap();
383 let decoded = codec.decode(&encoded).unwrap();
384 assert_eq!(decoded.len(), 960);
386 let max_sample = decoded.iter().map(|s| s.abs()).max().unwrap_or(0);
388 assert!(max_sample > 1000, "Expected audio energy, max sample was {}", max_sample);
389 }
390
391 #[cfg(not(feature = "opus"))]
392 #[test]
393 fn test_opus_stub_decode_odd_length() {
394 let mut codec = CodecPipeline::new(CodecType::Opus);
395 let result = codec.decode(&[0, 1, 2]); assert!(result.is_err());
397 }
398
399 #[test]
400 fn test_pcmu_silence() {
401 let codec = CodecPipeline::new(CodecType::Pcmu);
402 let silence = codec.silence_frame();
403 assert_eq!(silence.len(), 160);
404 assert!(silence.iter().all(|&b| b == 0xFF));
405 }
406
407 #[test]
408 fn test_pcma_silence() {
409 let codec = CodecPipeline::new(CodecType::Pcma);
410 let silence = codec.silence_frame();
411 assert_eq!(silence.len(), 160);
412 assert!(silence.iter().all(|&b| b == 0xD5));
413 }
414
415 #[test]
416 fn test_pcmu_encode_silence() {
417 let mut codec = CodecPipeline::new(CodecType::Pcmu);
418 let silence_pcm = vec![0i16; 160];
419 let encoded = codec.encode(&silence_pcm).unwrap();
420 assert_eq!(encoded.len(), 160);
421 }
422
423 #[test]
424 fn test_ulaw_known_values() {
425 assert_eq!(linear_to_ulaw(0), 0xFF);
427 let decoded = ulaw_to_linear(0xFF);
429 assert!(decoded.abs() < 10, "Expected near-zero, got {}", decoded);
430 }
431
432 #[test]
433 fn test_codec_pipeline_type() {
434 let pipeline = CodecPipeline::new(CodecType::Pcmu);
435 assert_eq!(pipeline.codec_type(), CodecType::Pcmu);
436
437 let pipeline = CodecPipeline::new(CodecType::Pcma);
438 assert_eq!(pipeline.codec_type(), CodecType::Pcma);
439 }
440
441 #[test]
442 fn test_pcmu_full_frame() {
443 let mut codec = CodecPipeline::new(CodecType::Pcmu);
444 let samples: Vec<i16> = (0..160)
446 .map(|i| ((i as f64 / 160.0 * std::f64::consts::TAU).sin() * 16000.0) as i16)
447 .collect();
448
449 let encoded = codec.encode(&samples).unwrap();
450 assert_eq!(encoded.len(), 160); let decoded = codec.decode(&encoded).unwrap();
453 assert_eq!(decoded.len(), 160);
454 }
455}