use base64::Engine;
pub const INPUT_AUDIO_MIME: &str = "audio/pcm;rate=16000";
pub const OUTPUT_AUDIO_MIME: &str = "audio/pcm;rate=24000";
pub const INPUT_SAMPLE_RATE: u32 = 16_000;
pub const OUTPUT_SAMPLE_RATE: u32 = 24_000;
pub struct AudioEncoder {
pcm_buf: Vec<u8>,
b64_buf: String,
}
impl AudioEncoder {
pub fn new() -> Self {
Self {
pcm_buf: Vec::with_capacity(8_000),
b64_buf: String::with_capacity(11_000),
}
}
pub fn encode_f32(&mut self, samples: &[f32]) -> &str {
self.pcm_buf.clear();
for &s in samples {
let clamped = s.clamp(-1.0, 1.0);
let i16_val = (clamped * 32767.0) as i16;
self.pcm_buf.extend_from_slice(&i16_val.to_le_bytes());
}
self.b64_buf.clear();
base64::engine::general_purpose::STANDARD.encode_string(&self.pcm_buf, &mut self.b64_buf);
&self.b64_buf
}
pub fn encode_i16_le(&mut self, pcm: &[u8]) -> &str {
self.b64_buf.clear();
base64::engine::general_purpose::STANDARD.encode_string(pcm, &mut self.b64_buf);
&self.b64_buf
}
}
impl Default for AudioEncoder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encode_f32_silence() {
let mut enc = AudioEncoder::new();
let samples = vec![0.0f32; 100];
let b64 = enc.encode_f32(&samples);
let decoded = base64::engine::general_purpose::STANDARD
.decode(b64)
.unwrap();
assert_eq!(decoded.len(), 200);
assert!(decoded.iter().all(|&b| b == 0));
}
#[test]
fn encode_f32_boundary_values() {
let mut enc = AudioEncoder::new();
let samples = vec![1.0f32, -1.0, 0.5, -0.5];
let b64 = enc.encode_f32(&samples);
let decoded = base64::engine::general_purpose::STANDARD
.decode(b64)
.unwrap();
assert_eq!(decoded.len(), 8);
let s0 = i16::from_le_bytes([decoded[0], decoded[1]]);
let s1 = i16::from_le_bytes([decoded[2], decoded[3]]);
let s2 = i16::from_le_bytes([decoded[4], decoded[5]]);
let s3 = i16::from_le_bytes([decoded[6], decoded[7]]);
assert_eq!(s0, 32767); assert_eq!(s1, -32767); assert_eq!(s2, 16383); assert_eq!(s3, -16383); }
#[test]
fn encode_f32_clamps_overflow() {
let mut enc = AudioEncoder::new();
let samples = vec![2.0f32, -2.0];
let b64 = enc.encode_f32(&samples);
let decoded = base64::engine::general_purpose::STANDARD
.decode(b64)
.unwrap();
let s0 = i16::from_le_bytes([decoded[0], decoded[1]]);
let s1 = i16::from_le_bytes([decoded[2], decoded[3]]);
assert_eq!(s0, 32767);
assert_eq!(s1, -32767);
}
#[test]
fn encode_i16_le_roundtrip() {
let mut enc = AudioEncoder::new();
let raw_pcm: Vec<u8> = vec![0x01, 0x02, 0x03, 0x04];
let b64 = enc.encode_i16_le(&raw_pcm);
let decoded = base64::engine::general_purpose::STANDARD
.decode(b64)
.unwrap();
assert_eq!(decoded, raw_pcm);
}
#[test]
fn encoder_reuses_buffer() {
let mut enc = AudioEncoder::new();
let _ = enc.encode_f32(&[0.0; 1000]);
let cap_after_first = enc.b64_buf.capacity();
let _ = enc.encode_f32(&[0.0; 100]);
assert!(enc.b64_buf.capacity() >= cap_after_first);
}
}