wavekat_core/codec/
g711.rs1pub const PCMU_PAYLOAD_TYPE: u8 = 0;
20pub const PCMA_PAYLOAD_TYPE: u8 = 8;
22
23pub const G711_SAMPLE_RATE: u32 = 8000;
26pub const G711_FRAME_SAMPLES: usize = 160;
29
30const CLIP: i32 = 32635;
31const BIAS: i32 = 0x84;
32const SIGN_BIT: u8 = 0x80;
33const QUANT_MASK: u8 = 0x0F;
34const SEG_SHIFT: u8 = 4;
35const SEG_MASK: u8 = 0x70;
36
37#[inline]
43fn seg_for(pcm: i32) -> u32 {
44 if pcm < 0x100 {
45 0
46 } else {
47 31 - (pcm as u32).leading_zeros() - 7
48 }
49}
50
51pub fn linear_to_ulaw(pcm: i16) -> u8 {
53 let mut pcm = pcm as i32;
54 let sign = if pcm < 0 {
55 pcm = -pcm;
56 0x7F
57 } else {
58 0xFF
59 };
60 if pcm > CLIP {
61 pcm = CLIP;
62 }
63 pcm += BIAS;
64
65 let seg = seg_for(pcm);
66 let mantissa = ((pcm >> (seg + 3)) & 0x0F) as u8;
67 let coded = ((seg as u8) << 4) | mantissa;
68 coded ^ sign
69}
70
71pub fn ulaw_to_linear(ulaw: u8) -> i16 {
73 let ulaw = !ulaw;
74 let sign = (ulaw & SIGN_BIT) != 0;
75 let exponent = (ulaw & SEG_MASK) >> SEG_SHIFT;
76 let mantissa = ulaw & QUANT_MASK;
77 let mut sample = (((mantissa as i32) << 3) + BIAS) << exponent;
78 sample -= BIAS;
79 if sign {
80 -sample as i16
81 } else {
82 sample as i16
83 }
84}
85
86pub fn linear_to_alaw(pcm: i16) -> u8 {
88 let (pcm, mask) = if pcm >= 0 {
89 (pcm as i32, 0xD5u8)
90 } else {
91 (((!pcm) as i32) & 0x7FFF, 0x55u8)
92 };
93
94 let seg = seg_for(pcm);
95 let mantissa = if seg < 1 {
96 ((pcm >> 4) & 0x0F) as u8
97 } else {
98 ((pcm >> (seg + 3)) & 0x0F) as u8
99 };
100 let coded = ((seg as u8) << 4) | mantissa;
101 coded ^ mask
102}
103
104pub fn alaw_to_linear(alaw: u8) -> i16 {
110 let alaw = alaw ^ 0x55;
111 let sign_set = (alaw & SIGN_BIT) != 0;
112 let exponent = (alaw & SEG_MASK) >> SEG_SHIFT;
113 let mantissa = alaw & QUANT_MASK;
114 let mut sample = ((mantissa as i32) << 4) + 8;
115 if exponent != 0 {
116 sample = (sample + 0x100) << (exponent - 1);
117 }
118 if sign_set {
119 sample as i16
120 } else {
121 -sample as i16
122 }
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129pub enum G711Codec {
130 Pcmu,
132 Pcma,
134}
135
136impl G711Codec {
137 pub fn payload_type(self) -> u8 {
140 match self {
141 G711Codec::Pcmu => PCMU_PAYLOAD_TYPE,
142 G711Codec::Pcma => PCMA_PAYLOAD_TYPE,
143 }
144 }
145
146 pub fn from_payload_type(pt: u8) -> Option<Self> {
150 match pt {
151 PCMU_PAYLOAD_TYPE => Some(G711Codec::Pcmu),
152 PCMA_PAYLOAD_TYPE => Some(G711Codec::Pcma),
153 _ => None,
154 }
155 }
156
157 pub fn encode(self, pcm: &[i16], out: &mut Vec<u8>) {
160 out.reserve(pcm.len());
161 match self {
162 G711Codec::Pcmu => out.extend(pcm.iter().map(|&s| linear_to_ulaw(s))),
163 G711Codec::Pcma => out.extend(pcm.iter().map(|&s| linear_to_alaw(s))),
164 }
165 }
166
167 pub fn decode(self, encoded: &[u8], out: &mut Vec<i16>) {
170 out.reserve(encoded.len());
171 match self {
172 G711Codec::Pcmu => out.extend(encoded.iter().map(|&b| ulaw_to_linear(b))),
173 G711Codec::Pcma => out.extend(encoded.iter().map(|&b| alaw_to_linear(b))),
174 }
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn ulaw_round_trip_silence() {
184 assert_eq!(linear_to_ulaw(0), 0xFF);
185 let s = ulaw_to_linear(0xFF);
190 assert_eq!(linear_to_ulaw(s), 0xFF);
191 }
192
193 #[test]
194 fn alaw_round_trip_silence() {
195 let encoded = linear_to_alaw(0);
196 let s = alaw_to_linear(encoded);
197 assert_eq!(linear_to_alaw(s), encoded);
198 }
199
200 #[test]
201 fn ulaw_handles_full_scale() {
202 assert_eq!(linear_to_ulaw(i16::MAX), 0x80);
203 assert_eq!(linear_to_ulaw(i16::MIN), 0x00);
204 }
205
206 #[test]
207 fn alaw_handles_full_scale() {
208 assert_eq!(linear_to_alaw(i16::MAX), 0xD5 ^ 0x7F);
209 assert_eq!(linear_to_alaw(i16::MIN), 0x55 ^ 0x7F);
210 }
211
212 #[test]
213 fn codec_encode_decode_length_matches_samples() {
214 let pcm: Vec<i16> = (0..160).map(|i| (i * 200) as i16).collect();
215 let mut encoded = Vec::new();
216 G711Codec::Pcmu.encode(&pcm, &mut encoded);
217 assert_eq!(encoded.len(), pcm.len());
218 let mut decoded = Vec::new();
219 G711Codec::Pcmu.decode(&encoded, &mut decoded);
220 assert_eq!(decoded.len(), encoded.len());
221 }
222
223 #[test]
224 fn payload_type_round_trips() {
225 assert_eq!(G711Codec::from_payload_type(0), Some(G711Codec::Pcmu));
226 assert_eq!(G711Codec::from_payload_type(8), Some(G711Codec::Pcma));
227 assert_eq!(G711Codec::from_payload_type(127), None);
228 assert_eq!(G711Codec::Pcmu.payload_type(), 0);
229 assert_eq!(G711Codec::Pcma.payload_type(), 8);
230 }
231
232 #[test]
233 fn ulaw_round_trip_preserves_loud_samples_within_codec_step() {
234 let inputs: &[i16] = &[1000, -1000, 8000, -8000, 16000, -16000];
235 for &s in inputs {
236 let encoded = linear_to_ulaw(s);
237 let decoded = ulaw_to_linear(encoded);
238 let diff = (s as i32 - decoded as i32).abs();
239 assert!(
240 diff < 400,
241 "μ-law round-trip drift too large: {s} → {decoded} (Δ={diff})"
242 );
243 }
244 }
245
246 #[test]
247 fn alaw_round_trip_preserves_loud_samples_within_codec_step() {
248 let inputs: &[i16] = &[1000, -1000, 8000, -8000, 16000, -16000];
249 for &s in inputs {
250 let encoded = linear_to_alaw(s);
251 let decoded = alaw_to_linear(encoded);
252 let diff = (s as i32 - decoded as i32).abs();
253 assert!(
254 diff < 400,
255 "A-law round-trip drift too large: {s} → {decoded} (Δ={diff})"
256 );
257 }
258 }
259
260 #[test]
261 fn ulaw_decode_is_a_fixed_point_for_every_codeword() {
262 for b in 0u8..=255 {
270 let mid = ulaw_to_linear(b);
271 let again = ulaw_to_linear(linear_to_ulaw(mid));
272 assert_eq!(again, mid, "μ-law decode not fixed-point at {b:#x}");
273 }
274 }
275
276 #[test]
277 fn alaw_decode_is_a_fixed_point_for_every_codeword() {
278 for b in 0u8..=255 {
279 let mid = alaw_to_linear(b);
280 let again = alaw_to_linear(linear_to_alaw(mid));
281 assert_eq!(again, mid, "A-law decode not fixed-point at {b:#x}");
282 }
283 }
284
285 #[test]
286 fn ulaw_decode_covers_full_codeword_space_without_panic() {
287 for b in 0u8..=255 {
290 let _ = ulaw_to_linear(b);
291 }
292 }
293
294 #[test]
295 fn alaw_decode_covers_full_codeword_space_without_panic() {
296 for b in 0u8..=255 {
297 let _ = alaw_to_linear(b);
298 }
299 }
300
301 #[test]
302 fn ulaw_zero_is_distinct_from_full_scale() {
303 assert_ne!(linear_to_ulaw(0), linear_to_ulaw(i16::MAX));
306 assert_ne!(linear_to_ulaw(0), linear_to_ulaw(i16::MIN));
307 }
308
309 #[test]
310 fn alaw_zero_is_distinct_from_full_scale() {
311 assert_ne!(linear_to_alaw(0), linear_to_alaw(i16::MAX));
312 assert_ne!(linear_to_alaw(0), linear_to_alaw(i16::MIN));
313 }
314
315 #[test]
316 fn pcmu_and_pcma_produce_different_bytes_for_the_same_input() {
317 let s = 12345i16;
322 assert_ne!(linear_to_ulaw(s), linear_to_alaw(s));
323 }
324
325 #[test]
326 fn codec_enum_dispatches_to_the_right_path() {
327 let pcm = vec![1000i16, -2000, 3000];
330
331 let mut a = Vec::new();
332 G711Codec::Pcmu.encode(&pcm, &mut a);
333 let mut b = Vec::new();
334 for &s in &pcm {
335 b.push(linear_to_ulaw(s));
336 }
337 assert_eq!(a, b);
338
339 let mut c = Vec::new();
340 G711Codec::Pcma.encode(&pcm, &mut c);
341 let mut d = Vec::new();
342 for &s in &pcm {
343 d.push(linear_to_alaw(s));
344 }
345 assert_eq!(c, d);
346 }
347
348 #[test]
349 fn slice_encode_then_decode_recovers_signal_within_codec_drift() {
350 let samples: Vec<i16> = (0..G711_FRAME_SAMPLES)
355 .map(|i| {
356 let t = i as f32 / G711_SAMPLE_RATE as f32;
357 ((t * 440.0 * 2.0 * std::f32::consts::PI).sin() * 8000.0) as i16
358 })
359 .collect();
360
361 for codec in [G711Codec::Pcmu, G711Codec::Pcma] {
362 let mut encoded = Vec::new();
363 codec.encode(&samples, &mut encoded);
364 assert_eq!(encoded.len(), G711_FRAME_SAMPLES);
365
366 let mut decoded = Vec::new();
367 codec.decode(&encoded, &mut decoded);
368 assert_eq!(decoded.len(), G711_FRAME_SAMPLES);
369
370 let mean_abs_error: f64 = samples
371 .iter()
372 .zip(decoded.iter())
373 .map(|(a, b)| (*a as i32 - *b as i32).abs() as f64)
374 .sum::<f64>()
375 / samples.len() as f64;
376 assert!(
379 mean_abs_error < 200.0,
380 "{codec:?}: mean abs error {mean_abs_error} too high"
381 );
382 }
383 }
384
385 #[test]
386 fn encode_appends_rather_than_replacing() {
387 let mut buf = vec![0xFFu8, 0xFEu8];
391 let pcm = vec![0i16; 3];
392 G711Codec::Pcmu.encode(&pcm, &mut buf);
393 assert_eq!(buf.len(), 5);
394 assert_eq!(&buf[..2], &[0xFF, 0xFE]);
395 }
396
397 #[test]
398 fn payload_type_constants_match_rfc3551() {
399 assert_eq!(PCMU_PAYLOAD_TYPE, 0);
403 assert_eq!(PCMA_PAYLOAD_TYPE, 8);
404 assert_eq!(G711_SAMPLE_RATE, 8000);
405 assert_eq!(G711_FRAME_SAMPLES, 160);
406 }
407}