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 .chunks_exact(size_of::<i16>())
245 .map(|chunk| i16::from_le_bytes([chunk[0], chunk[1]]))
246 .collect(),
247 )
248}
249
250#[derive(Debug, Clone)]
260pub struct SmartAudioBuffer {
261 buffer: Vec<i16>,
262 sample_rate: u32,
263 target_duration_ms: u32,
264}
265
266impl SmartAudioBuffer {
267 pub fn new(sample_rate: u32, target_duration_ms: u32) -> Self {
269 Self { buffer: Vec::new(), sample_rate, target_duration_ms }
270 }
271
272 pub fn push(&mut self, samples: &[i16]) {
274 self.buffer.extend_from_slice(samples);
275 }
276
277 fn should_flush(&self) -> bool {
278 let duration_ms = (self.buffer.len() as f64 / self.sample_rate as f64) * 1000.0;
279
280 duration_ms >= self.target_duration_ms as f64
281 }
282
283 pub fn flush(&mut self) -> Option<Vec<i16>> {
285 if self.should_flush() { Some(std::mem::take(&mut self.buffer)) } else { None }
286 }
287
288 pub fn flush_remaining(&mut self) -> Option<Vec<i16>> {
290 if self.buffer.is_empty() { None } else { Some(std::mem::take(&mut self.buffer)) }
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297
298 #[test]
299 fn test_smart_audio_buffer_flush_threshold() {
300 let sample_rate = 1000;
301 let target_ms = 100;
302 let mut buffer = SmartAudioBuffer::new(sample_rate, target_ms);
306
307 buffer.push(&[0; 50]);
309 assert!(buffer.flush().is_none());
310
311 buffer.push(&[0; 49]);
313 assert!(buffer.flush().is_none());
314
315 buffer.push(&[0; 1]);
317 let flushed = buffer.flush();
318 assert!(flushed.is_some());
319 assert_eq!(flushed.unwrap().len(), 100);
320 assert!(buffer.buffer.is_empty());
321 }
322
323 #[test]
324 fn test_smart_audio_buffer_flush_remaining() {
325 let sample_rate = 1000;
326 let target_ms = 100;
327 let mut buffer = SmartAudioBuffer::new(sample_rate, target_ms);
328
329 buffer.push(&[0; 50]);
330 assert!(buffer.flush().is_none());
331
332 let remaining = buffer.flush_remaining();
333 assert!(remaining.is_some());
334 assert_eq!(remaining.unwrap().len(), 50);
335 assert!(buffer.buffer.is_empty());
336 }
337
338 #[test]
339 fn test_smart_audio_buffer_empty_flush() {
340 let mut buffer = SmartAudioBuffer::new(1000, 100);
341 assert!(buffer.flush().is_none());
342 assert!(buffer.flush_remaining().is_none());
343 }
344
345 #[test]
346 fn test_audio_format_bytes_per_second() {
347 let pcm16_24k = AudioFormat::pcm16_24khz();
348 assert_eq!(pcm16_24k.bytes_per_second(), 48000); let pcm16_16k = AudioFormat::pcm16_16khz();
351 assert_eq!(pcm16_16k.bytes_per_second(), 32000); }
353
354 #[test]
355 fn test_audio_format_duration() {
356 let format = AudioFormat::pcm16_24khz();
357 let duration = format.duration_ms(48000);
359 assert!((duration - 1000.0).abs() < 0.001);
360 }
361
362 #[test]
363 fn test_audio_chunk_base64() {
364 let original = AudioChunk::pcm16_24khz(vec![0, 1, 2, 3, 4, 5]);
365 let encoded = original.to_base64();
366 let decoded = AudioChunk::from_base64(&encoded, AudioFormat::pcm16_24khz()).unwrap();
367 assert_eq!(original.data, decoded.data);
368 }
369
370 #[test]
371 fn test_i16_samples_roundtrip() {
372 let samples: Vec<i16> = vec![0, 1, -1, 32767, -32768, 1000, -1000];
373 let chunk = AudioChunk::from_i16_samples(&samples, AudioFormat::pcm16_24khz());
374 let recovered = chunk.to_i16_samples().unwrap();
375 assert_eq!(samples.as_slice(), recovered.as_ref());
376 }
377
378 #[test]
379 fn test_from_i16_samples_emits_little_endian_bytes() {
380 let chunk = AudioChunk::from_i16_samples(&[1, -1, 256], AudioFormat::pcm16_24khz());
381 assert_eq!(chunk.data, vec![0x01, 0x00, 0xff, 0xff, 0x00, 0x01]);
382 }
383
384 #[test]
385 fn test_i16_samples_empty() {
386 let chunk = AudioChunk::from_i16_samples(&[], AudioFormat::pcm16_24khz());
387 assert!(chunk.data.is_empty());
388 assert!(chunk.to_i16_samples().unwrap().is_empty());
389 }
390
391 #[test]
392 fn test_i16_samples_odd_bytes_error() {
393 let chunk = AudioChunk::pcm16_24khz(vec![0, 1, 2]); assert_eq!(
395 chunk.to_i16_samples().unwrap_err(),
396 "Invalid data length for PCM16: 3 (must be even)"
397 );
398 }
399
400 #[test]
401 #[cfg(target_endian = "little")]
402 fn test_to_i16_samples_borrows_aligned_buffer() {
403 let chunk = AudioChunk::from_i16_samples(&[1, -1, 256], AudioFormat::pcm16_24khz());
406 let samples = chunk.to_i16_samples().unwrap();
407 assert!(matches!(samples, Cow::Borrowed(_)));
408 assert_eq!(samples.as_ref(), &[1, -1, 256]);
409 }
410
411 #[test]
412 fn test_decode_pcm16_le_misaligned_buffer_is_owned() {
413 let aligned_words = [
416 i16::from_ne_bytes([0x00, 0x01]),
417 i16::from_ne_bytes([0x02, 0x03]),
418 i16::from_ne_bytes([0x04, 0x00]),
419 ];
420 let aligned_bytes: &[u8] = bytemuck::cast_slice(&aligned_words);
421 let misaligned = &aligned_bytes[1..5];
422
423 let samples = decode_pcm16_le(misaligned);
424 assert!(matches!(samples, Cow::Owned(_)));
425 assert_eq!(samples.as_ref(), &[0x0201, 0x0403]);
426 }
427
428 #[test]
429 fn test_decode_pcm16_le_empty_input() {
430 let samples = decode_pcm16_le(&[]);
431 assert!(samples.is_empty());
432 }
433}