1use std::borrow::Cow;
4
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
9#[serde(rename_all = "lowercase")]
10pub enum AudioEncoding {
11 #[serde(rename = "pcm16")]
13 #[default]
14 Pcm16,
15 #[serde(rename = "g711_ulaw")]
17 G711Ulaw,
18 #[serde(rename = "g711_alaw")]
20 G711Alaw,
21}
22
23impl std::fmt::Display for AudioEncoding {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 match self {
26 Self::Pcm16 => write!(f, "pcm16"),
27 Self::G711Ulaw => write!(f, "g711_ulaw"),
28 Self::G711Alaw => write!(f, "g711_alaw"),
29 }
30 }
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
35pub struct AudioFormat {
36 pub sample_rate: u32,
38 pub channels: u8,
40 pub bits_per_sample: u8,
42 pub encoding: AudioEncoding,
44}
45
46impl Default for AudioFormat {
47 fn default() -> Self {
48 Self::pcm16_24khz()
49 }
50}
51
52impl AudioFormat {
53 pub fn new(
55 sample_rate: u32,
56 channels: u8,
57 bits_per_sample: u8,
58 encoding: AudioEncoding,
59 ) -> Self {
60 Self { sample_rate, channels, bits_per_sample, encoding }
61 }
62
63 pub fn pcm16_24khz() -> Self {
65 Self {
66 sample_rate: 24000,
67 channels: 1,
68 bits_per_sample: 16,
69 encoding: AudioEncoding::Pcm16,
70 }
71 }
72
73 pub fn pcm16_16khz() -> Self {
75 Self {
76 sample_rate: 16000,
77 channels: 1,
78 bits_per_sample: 16,
79 encoding: AudioEncoding::Pcm16,
80 }
81 }
82
83 pub fn g711_ulaw() -> Self {
85 Self {
86 sample_rate: 8000,
87 channels: 1,
88 bits_per_sample: 8,
89 encoding: AudioEncoding::G711Ulaw,
90 }
91 }
92
93 pub fn g711_alaw() -> Self {
95 Self {
96 sample_rate: 8000,
97 channels: 1,
98 bits_per_sample: 8,
99 encoding: AudioEncoding::G711Alaw,
100 }
101 }
102
103 pub fn bytes_per_second(&self) -> u32 {
105 self.sample_rate * self.channels as u32 * (self.bits_per_sample / 8) as u32
106 }
107
108 pub fn duration_ms(&self, bytes: usize) -> f64 {
110 let bytes_per_ms = self.bytes_per_second() as f64 / 1000.0;
111 bytes as f64 / bytes_per_ms
112 }
113}
114
115#[derive(Debug, Clone)]
117pub struct AudioChunk {
118 pub data: Vec<u8>,
120 pub format: AudioFormat,
122}
123
124impl AudioChunk {
125 pub fn new(data: Vec<u8>, format: AudioFormat) -> Self {
127 Self { data, format }
128 }
129
130 pub fn pcm16_24khz(data: Vec<u8>) -> Self {
132 Self::new(data, AudioFormat::pcm16_24khz())
133 }
134
135 pub fn pcm16_16khz(data: Vec<u8>) -> Self {
137 Self::new(data, AudioFormat::pcm16_16khz())
138 }
139
140 pub fn duration_ms(&self) -> f64 {
142 self.format.duration_ms(self.data.len())
143 }
144
145 pub fn to_base64(&self) -> String {
147 use base64::Engine;
148 base64::engine::general_purpose::STANDARD.encode(&self.data)
149 }
150
151 pub fn from_base64(encoded: &str, format: AudioFormat) -> Result<Self, base64::DecodeError> {
153 use base64::Engine;
154 let data = base64::engine::general_purpose::STANDARD.decode(encoded)?;
155 Ok(Self::new(data, format))
156 }
157
158 pub fn from_i16_samples(samples: &[i16], format: AudioFormat) -> Self {
177 #[cfg(target_endian = "little")]
180 let data = bytemuck::cast_slice::<i16, u8>(samples).to_vec();
181
182 #[cfg(target_endian = "big")]
183 let data = {
184 let mut data = Vec::with_capacity(samples.len() * size_of::<i16>());
185 for sample in samples {
186 data.extend_from_slice(&sample.to_le_bytes());
187 }
188 data
189 };
190
191 Self::new(data, format)
192 }
193
194 pub fn to_i16_samples(&self) -> Result<Cow<'_, [i16]>, String> {
218 if !self.data.len().is_multiple_of(size_of::<i16>()) {
219 return Err(format!(
220 "Invalid data length for PCM16: {} (must be even)",
221 self.data.len()
222 ));
223 }
224 Ok(decode_pcm16_le(&self.data))
225 }
226}
227
228fn decode_pcm16_le(audio: &[u8]) -> Cow<'_, [i16]> {
235 debug_assert!(audio.len().is_multiple_of(size_of::<i16>()));
236
237 #[cfg(target_endian = "little")]
238 if let Ok(aligned_slice) = bytemuck::try_cast_slice::<u8, i16>(audio) {
239 return Cow::Borrowed(aligned_slice);
240 }
241
242 Cow::Owned(
243 audio
244 .as_chunks::<{ size_of::<i16>() }>()
245 .0
246 .iter()
247 .map(|chunk| i16::from_le_bytes([chunk[0], chunk[1]]))
248 .collect(),
249 )
250}
251
252#[derive(Debug, Clone)]
262pub struct SmartAudioBuffer {
263 buffer: Vec<i16>,
264 sample_rate: u32,
265 target_duration_ms: u32,
266}
267
268impl SmartAudioBuffer {
269 pub fn new(sample_rate: u32, target_duration_ms: u32) -> Self {
271 Self { buffer: Vec::new(), sample_rate, target_duration_ms }
272 }
273
274 pub fn push(&mut self, samples: &[i16]) {
276 self.buffer.extend_from_slice(samples);
277 }
278
279 fn should_flush(&self) -> bool {
280 let duration_ms = (self.buffer.len() as f64 / self.sample_rate as f64) * 1000.0;
281
282 duration_ms >= self.target_duration_ms as f64
283 }
284
285 pub fn flush(&mut self) -> Option<Vec<i16>> {
287 if self.should_flush() { Some(std::mem::take(&mut self.buffer)) } else { None }
288 }
289
290 pub fn flush_remaining(&mut self) -> Option<Vec<i16>> {
292 if self.buffer.is_empty() { None } else { Some(std::mem::take(&mut self.buffer)) }
293 }
294}
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299
300 #[test]
301 fn test_smart_audio_buffer_flush_threshold() {
302 let sample_rate = 1000;
303 let target_ms = 100;
304 let mut buffer = SmartAudioBuffer::new(sample_rate, target_ms);
308
309 buffer.push(&[0; 50]);
311 assert!(buffer.flush().is_none());
312
313 buffer.push(&[0; 49]);
315 assert!(buffer.flush().is_none());
316
317 buffer.push(&[0; 1]);
319 let flushed = buffer.flush();
320 assert!(flushed.is_some());
321 assert_eq!(flushed.unwrap().len(), 100);
322 assert!(buffer.buffer.is_empty());
323 }
324
325 #[test]
326 fn test_smart_audio_buffer_flush_remaining() {
327 let sample_rate = 1000;
328 let target_ms = 100;
329 let mut buffer = SmartAudioBuffer::new(sample_rate, target_ms);
330
331 buffer.push(&[0; 50]);
332 assert!(buffer.flush().is_none());
333
334 let remaining = buffer.flush_remaining();
335 assert!(remaining.is_some());
336 assert_eq!(remaining.unwrap().len(), 50);
337 assert!(buffer.buffer.is_empty());
338 }
339
340 #[test]
341 fn test_smart_audio_buffer_empty_flush() {
342 let mut buffer = SmartAudioBuffer::new(1000, 100);
343 assert!(buffer.flush().is_none());
344 assert!(buffer.flush_remaining().is_none());
345 }
346
347 #[test]
348 fn test_audio_format_bytes_per_second() {
349 let pcm16_24k = AudioFormat::pcm16_24khz();
350 assert_eq!(pcm16_24k.bytes_per_second(), 48000); let pcm16_16k = AudioFormat::pcm16_16khz();
353 assert_eq!(pcm16_16k.bytes_per_second(), 32000); }
355
356 #[test]
357 fn test_audio_format_duration() {
358 let format = AudioFormat::pcm16_24khz();
359 let duration = format.duration_ms(48000);
361 assert!((duration - 1000.0).abs() < 0.001);
362 }
363
364 #[test]
365 fn test_audio_chunk_base64() {
366 let original = AudioChunk::pcm16_24khz(vec![0, 1, 2, 3, 4, 5]);
367 let encoded = original.to_base64();
368 let decoded = AudioChunk::from_base64(&encoded, AudioFormat::pcm16_24khz()).unwrap();
369 assert_eq!(original.data, decoded.data);
370 }
371
372 #[test]
373 fn test_i16_samples_roundtrip() {
374 let samples: Vec<i16> = vec![0, 1, -1, 32767, -32768, 1000, -1000];
375 let chunk = AudioChunk::from_i16_samples(&samples, AudioFormat::pcm16_24khz());
376 let recovered = chunk.to_i16_samples().unwrap();
377 assert_eq!(samples.as_slice(), recovered.as_ref());
378 }
379
380 #[test]
381 fn test_from_i16_samples_emits_little_endian_bytes() {
382 let chunk = AudioChunk::from_i16_samples(&[1, -1, 256], AudioFormat::pcm16_24khz());
383 assert_eq!(chunk.data, vec![0x01, 0x00, 0xff, 0xff, 0x00, 0x01]);
384 }
385
386 #[test]
387 fn test_i16_samples_empty() {
388 let chunk = AudioChunk::from_i16_samples(&[], AudioFormat::pcm16_24khz());
389 assert!(chunk.data.is_empty());
390 assert!(chunk.to_i16_samples().unwrap().is_empty());
391 }
392
393 #[test]
394 fn test_i16_samples_odd_bytes_error() {
395 let chunk = AudioChunk::pcm16_24khz(vec![0, 1, 2]); assert_eq!(
397 chunk.to_i16_samples().unwrap_err(),
398 "Invalid data length for PCM16: 3 (must be even)"
399 );
400 }
401
402 #[test]
403 #[cfg(target_endian = "little")]
404 fn test_to_i16_samples_borrows_aligned_buffer() {
405 let chunk = AudioChunk::from_i16_samples(&[1, -1, 256], AudioFormat::pcm16_24khz());
408 let samples = chunk.to_i16_samples().unwrap();
409 assert!(matches!(samples, Cow::Borrowed(_)));
410 assert_eq!(samples.as_ref(), &[1, -1, 256]);
411 }
412
413 #[test]
414 fn test_decode_pcm16_le_misaligned_buffer_is_owned() {
415 let aligned_words = [
418 i16::from_ne_bytes([0x00, 0x01]),
419 i16::from_ne_bytes([0x02, 0x03]),
420 i16::from_ne_bytes([0x04, 0x00]),
421 ];
422 let aligned_bytes: &[u8] = bytemuck::cast_slice(&aligned_words);
423 let misaligned = &aligned_bytes[1..5];
424
425 let samples = decode_pcm16_le(misaligned);
426 assert!(matches!(samples, Cow::Owned(_)));
427 assert_eq!(samples.as_ref(), &[0x0201, 0x0403]);
428 }
429
430 #[test]
431 fn test_decode_pcm16_le_empty_input() {
432 let samples = decode_pcm16_le(&[]);
433 assert!(samples.is_empty());
434 }
435}