gemini_adk_fluent_rs/telephony/
g711.rs1pub fn linear_to_ulaw(sample: i16) -> u8 {
17 const BIAS: i32 = 0x84; const CLIP: i32 = 32_635;
19
20 let sign: u8 = if sample < 0 { 0x80 } else { 0 };
21 let mut magnitude = (sample as i32).abs().min(CLIP) + BIAS;
22
23 let mut segment: u8 = 0;
26 let mut probe = magnitude >> 8;
27 while probe > 0 && segment < 7 {
28 segment += 1;
29 probe >>= 1;
30 }
31
32 magnitude >>= segment + 3;
33 let mantissa = (magnitude & 0x0F) as u8;
34 !(sign | (segment << 4) | mantissa)
36}
37
38pub fn ulaw_to_linear(byte: u8) -> i16 {
40 let byte = !byte;
41 let sign = byte & 0x80;
42 let segment = (byte >> 4) & 0x07;
43 let mantissa = byte & 0x0F;
44
45 let magnitude = ((((mantissa as i32) << 3) + 0x84) << segment) - 0x84;
46 if sign != 0 {
47 -magnitude as i16
48 } else {
49 magnitude as i16
50 }
51}
52
53pub fn linear_to_alaw(sample: i16) -> u8 {
55 const CLIP: i32 = 32_635;
56
57 let sign: u8 = if sample >= 0 { 0x80 } else { 0 };
58 let magnitude = (sample as i32).abs().min(CLIP);
59
60 let compressed = if magnitude >= 256 {
61 let mut segment: u8 = 1;
62 let mut probe = magnitude >> 9;
63 while probe > 0 && segment < 7 {
64 segment += 1;
65 probe >>= 1;
66 }
67 let mantissa = ((magnitude >> (segment + 3)) & 0x0F) as u8;
68 (segment << 4) | mantissa
69 } else {
70 (magnitude >> 4) as u8
71 };
72
73 (sign | compressed) ^ 0x55
75}
76
77pub fn alaw_to_linear(byte: u8) -> i16 {
79 let byte = byte ^ 0x55;
80 let sign = byte & 0x80;
81 let segment = (byte >> 4) & 0x07;
82 let mantissa = (byte & 0x0F) as i32;
83
84 let magnitude = match segment {
85 0 => (mantissa << 4) + 8,
86 _ => ((mantissa << 4) + 0x108) << (segment - 1),
87 };
88 if sign != 0 {
89 magnitude as i16
90 } else {
91 -magnitude as i16
92 }
93}
94
95pub fn decode_ulaw(bytes: &[u8]) -> Vec<i16> {
97 bytes.iter().map(|&b| ulaw_to_linear(b)).collect()
98}
99
100pub fn encode_ulaw(samples: &[i16]) -> Vec<u8> {
102 samples.iter().map(|&s| linear_to_ulaw(s)).collect()
103}
104
105pub fn decode_alaw(bytes: &[u8]) -> Vec<i16> {
107 bytes.iter().map(|&b| alaw_to_linear(b)).collect()
108}
109
110pub fn encode_alaw(samples: &[i16]) -> Vec<u8> {
112 samples.iter().map(|&s| linear_to_alaw(s)).collect()
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn ulaw_silence_is_all_ones_on_the_wire() {
121 assert_eq!(linear_to_ulaw(0), 0xFF);
124 assert_eq!(ulaw_to_linear(0xFF), 0);
125 }
126
127 #[test]
128 fn ulaw_known_extremes() {
129 assert_eq!(ulaw_to_linear(linear_to_ulaw(32_767)), 32_124);
131 assert_eq!(ulaw_to_linear(linear_to_ulaw(-32_768)), -32_124);
132 }
133
134 #[test]
135 fn ulaw_round_trip_is_within_segment_quantisation() {
136 for &s in &[0i16, 1, -1, 100, -100, 1000, -1000, 8000, -8000, 30000] {
139 let rt = ulaw_to_linear(linear_to_ulaw(s));
140 let tolerance = (s.unsigned_abs() as i32 / 16).max(16);
141 assert!(
142 ((rt as i32) - (s as i32)).abs() <= tolerance,
143 "sample {s} round-tripped to {rt}"
144 );
145 }
146 }
147
148 #[test]
149 fn ulaw_is_monotone() {
150 let mut last = i16::MIN;
153 for s in (-32_768i32..=32_767).step_by(257) {
154 let rt = ulaw_to_linear(linear_to_ulaw(s as i16));
155 assert!(rt >= last, "non-monotone at input {s}");
156 last = rt;
157 }
158 }
159
160 #[test]
161 fn alaw_round_trip_is_within_segment_quantisation() {
162 for &s in &[0i16, 8, -8, 100, -100, 1000, -1000, 8000, -8000, 30000] {
163 let rt = alaw_to_linear(linear_to_alaw(s));
164 let tolerance = (s.unsigned_abs() as i32 / 16).max(24);
165 assert!(
166 ((rt as i32) - (s as i32)).abs() <= tolerance,
167 "sample {s} round-tripped to {rt}"
168 );
169 }
170 }
171
172 #[test]
173 fn alaw_is_monotone() {
174 let mut last = i16::MIN;
175 for s in (-32_768i32..=32_767).step_by(257) {
176 let rt = alaw_to_linear(linear_to_alaw(s as i16));
177 assert!(rt >= last, "non-monotone at input {s}");
178 last = rt;
179 }
180 }
181
182 #[test]
183 fn stream_helpers_round_trip_shape() {
184 let samples = vec![0i16, 500, -500, 12_000, -12_000];
185 assert_eq!(decode_ulaw(&encode_ulaw(&samples)).len(), samples.len());
186 assert_eq!(decode_alaw(&encode_alaw(&samples)).len(), samples.len());
187 }
188
189 #[test]
190 fn ulaw_segment_boundaries() {
191 for &boundary in &[256i16, 512, 1024, 2048, 4096, 8192, 16384] {
194 for offset in &[-1i32, 0, 1] {
195 let s = (boundary as i32 + offset) as i16;
196 let rt = ulaw_to_linear(linear_to_ulaw(s));
197 let tolerance = (s.unsigned_abs() as i32 / 16).max(16);
198 assert!(
199 ((rt as i32) - (s as i32)).abs() <= tolerance,
200 "sample {s} at boundary round-tripped to {rt}, error exceeds tolerance"
201 );
202 }
203 }
204 }
205
206 #[test]
207 fn alaw_segment_boundaries() {
208 for &boundary in &[256i16, 512, 1024, 2048, 4096, 8192, 16384] {
211 for offset in &[-1i32, 0, 1] {
212 let s = (boundary as i32 + offset) as i16;
213 let rt = alaw_to_linear(linear_to_alaw(s));
214 let tolerance = (s.unsigned_abs() as i32 / 16).max(24);
215 assert!(
216 ((rt as i32) - (s as i32)).abs() <= tolerance,
217 "alaw sample {s} at boundary round-tripped to {rt}, error exceeds tolerance"
218 );
219 }
220 }
221 }
222
223 #[test]
224 fn ulaw_all_mantissas_per_segment() {
225 for segment in 0u8..=7 {
227 for mantissa in 0u8..=15 {
228 let encoded = !((segment << 4) | mantissa);
230 let decoded = ulaw_to_linear(encoded);
231 let re_encoded = linear_to_ulaw(decoded);
233 assert_eq!(
234 re_encoded, encoded,
235 "mantissa {mantissa} in segment {segment}: round-trip mismatch"
236 );
237 }
238 }
239 }
240
241 #[test]
242 fn alaw_all_mantissas_per_segment() {
243 for segment in 0u8..=7 {
245 for mantissa in 0u8..=15 {
246 let compressed = (segment << 4) | mantissa;
248 let encoded = (0x80 | compressed) ^ 0x55; let decoded = alaw_to_linear(encoded);
250 let re_encoded = linear_to_alaw(decoded);
252 assert_eq!(
253 re_encoded, encoded,
254 "alaw mantissa {mantissa} in segment {segment}: round-trip mismatch"
255 );
256 }
257 }
258 }
259
260 #[test]
261 fn ulaw_negative_values_symmetric() {
262 for &abs_val in &[100i16, 256, 1000, 8000, 20000] {
264 let pos_rt = ulaw_to_linear(linear_to_ulaw(abs_val));
265 let neg_rt = ulaw_to_linear(linear_to_ulaw(-abs_val));
266 assert_eq!(
267 pos_rt as i32 + neg_rt as i32,
268 0,
269 "μ-law should preserve sign symmetry: {pos_rt} + {neg_rt} != 0"
270 );
271 }
272 }
273
274 #[test]
275 fn alaw_negative_values_symmetric() {
276 for &abs_val in &[100i16, 256, 1000, 8000, 20000] {
278 let pos_rt = alaw_to_linear(linear_to_alaw(abs_val));
279 let neg_rt = alaw_to_linear(linear_to_alaw(-abs_val));
280 assert_eq!(
281 pos_rt as i32 + neg_rt as i32,
282 0,
283 "A-law should preserve sign symmetry: {pos_rt} + {neg_rt} != 0"
284 );
285 }
286 }
287
288 #[test]
289 fn ulaw_clipping_at_extremes() {
290 let large_pos = ulaw_to_linear(linear_to_ulaw(32_767));
292 let large_pos2 = ulaw_to_linear(linear_to_ulaw(32_700));
293 assert_eq!(
294 large_pos, large_pos2,
295 "μ-law should clip large positive values to same result"
296 );
297
298 let large_neg = ulaw_to_linear(linear_to_ulaw(-32_768));
299 let large_neg2 = ulaw_to_linear(linear_to_ulaw(-32_700));
300 assert_eq!(
301 large_neg, large_neg2,
302 "μ-law should clip large negative values to same result"
303 );
304 }
305
306 #[test]
307 fn alaw_clipping_at_extremes() {
308 let large_pos = alaw_to_linear(linear_to_alaw(32_767));
310 let large_pos2 = alaw_to_linear(linear_to_alaw(32_700));
311 assert_eq!(
312 large_pos, large_pos2,
313 "A-law should clip large positive values to same result"
314 );
315
316 let large_neg = alaw_to_linear(linear_to_alaw(-32_768));
317 let large_neg2 = alaw_to_linear(linear_to_alaw(-32_700));
318 assert_eq!(
319 large_neg, large_neg2,
320 "A-law should clip large negative values to same result"
321 );
322 }
323}