1#![deny(unsafe_code)]
33#![allow(clippy::needless_range_loop)]
34
35pub mod alaw;
36pub mod mulaw;
37pub mod tables;
38
39use oxideav_core::{CodecCapabilities, CodecId, CodecTag};
40use oxideav_core::{CodecInfo, CodecRegistry};
41
42pub const CODEC_ID_MULAW: &str = "pcm_mulaw";
44
45pub const CODEC_ID_ALAW: &str = "pcm_alaw";
47
48pub const MULAW_ALIASES: &[&str] = &["pcm_mulaw", "ulaw", "g711u"];
50
51pub const ALAW_ALIASES: &[&str] = &["pcm_alaw", "alaw", "g711a"];
53
54pub fn register(reg: &mut CodecRegistry) {
56 for (idx, alias) in MULAW_ALIASES.iter().enumerate() {
59 let caps = CodecCapabilities::audio("g711_mulaw_sw")
60 .with_lossy(true)
61 .with_intra_only(true);
62 let mut info = CodecInfo::new(CodecId::new(*alias))
63 .capabilities(caps)
64 .decoder(mulaw::make_decoder)
65 .encoder(mulaw::make_encoder);
66 if idx == 0 {
67 info = info.tag(CodecTag::wave_format(0x0007));
69 }
70 reg.register(info);
71 }
72
73 for (idx, alias) in ALAW_ALIASES.iter().enumerate() {
75 let caps = CodecCapabilities::audio("g711_alaw_sw")
76 .with_lossy(true)
77 .with_intra_only(true);
78 let mut info = CodecInfo::new(CodecId::new(*alias))
79 .capabilities(caps)
80 .decoder(alaw::make_decoder)
81 .encoder(alaw::make_encoder);
82 if idx == 0 {
83 info = info.tag(CodecTag::wave_format(0x0006));
85 }
86 reg.register(info);
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use oxideav_core::{CodecParameters, Frame, Packet, SampleFormat, TimeBase};
94
95 fn params(id: &str) -> CodecParameters {
96 let mut p = CodecParameters::audio(CodecId::new(id));
97 p.sample_rate = Some(8_000);
98 p.channels = Some(1);
99 p.sample_format = Some(SampleFormat::S16);
100 p
101 }
102
103 #[test]
104 fn register_all_aliases() {
105 let mut reg = CodecRegistry::new();
106 register(&mut reg);
107 for alias in MULAW_ALIASES.iter().chain(ALAW_ALIASES.iter()) {
108 let id = CodecId::new(*alias);
109 assert!(reg.has_decoder(&id), "no decoder for alias {alias}");
110 assert!(reg.has_encoder(&id), "no encoder for alias {alias}");
111 }
112 }
113
114 #[test]
115 fn mulaw_aliases_resolve_to_same_impl() {
116 let mut reg = CodecRegistry::new();
117 register(&mut reg);
118 let input = vec![0x55u8, 0xAA, 0x80, 0x00];
121 let mut results = Vec::new();
122 for alias in MULAW_ALIASES {
123 let p = params(alias);
124 let mut dec = reg.make_decoder(&p).expect("make_decoder");
125 let pkt = Packet::new(0, TimeBase::new(1, 8_000), input.clone());
126 dec.send_packet(&pkt).unwrap();
127 let Frame::Audio(af) = dec.receive_frame().unwrap() else {
128 panic!("expected audio frame");
129 };
130 results.push(af.data[0].clone());
131 }
132 for r in &results[1..] {
133 assert_eq!(r, &results[0]);
134 }
135 }
136
137 #[test]
138 fn mulaw_roundtrip_samples() {
139 let mut reg = CodecRegistry::new();
140 register(&mut reg);
141 let p = params(CODEC_ID_MULAW);
142 let mut enc = reg.make_encoder(&p).expect("make_encoder");
143 let mut dec = reg.make_decoder(&p).expect("make_decoder");
144
145 let samples: Vec<i16> = vec![0, 1, -1, 100, -100, 10000, -10000, 32000, -32000];
146 let mut pcm_bytes = Vec::with_capacity(samples.len() * 2);
147 for &s in &samples {
148 pcm_bytes.extend_from_slice(&s.to_le_bytes());
149 }
150 let input = Frame::Audio(oxideav_core::AudioFrame {
151 format: SampleFormat::S16,
152 channels: 1,
153 sample_rate: 8_000,
154 samples: samples.len() as u32,
155 pts: Some(0),
156 time_base: TimeBase::new(1, 8_000),
157 data: vec![pcm_bytes],
158 });
159 enc.send_frame(&input).unwrap();
160 let pkt = enc.receive_packet().unwrap();
161 assert_eq!(pkt.data.len(), samples.len());
162 dec.send_packet(&pkt).unwrap();
163 let Frame::Audio(af) = dec.receive_frame().unwrap() else {
164 panic!("expected audio frame");
165 };
166 assert_eq!(af.samples as usize, samples.len());
167 }
168}