use std::collections::VecDeque;
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct AudioBufferConfig {
pub sample_rate: u32,
pub chunk_duration_secs: f32,
pub overlap_secs: f32,
pub max_buffer_secs: f32,
}
impl Default for AudioBufferConfig {
fn default() -> Self {
Self {
sample_rate: 16000,
chunk_duration_secs: 30.0, overlap_secs: 1.0, max_buffer_secs: 120.0, }
}
}
impl AudioBufferConfig {
pub fn whisper() -> Self {
Self::default()
}
pub fn wav2vec2() -> Self {
Self {
sample_rate: 16000,
chunk_duration_secs: 10.0, overlap_secs: 0.5,
max_buffer_secs: 60.0,
}
}
pub fn chunk_samples(&self) -> usize {
(self.sample_rate as f32 * self.chunk_duration_secs) as usize
}
pub fn overlap_samples(&self) -> usize {
(self.sample_rate as f32 * self.overlap_secs) as usize
}
pub fn max_buffer_samples(&self) -> usize {
(self.sample_rate as f32 * self.max_buffer_secs) as usize
}
}
#[derive(Debug, Clone)]
pub struct AudioChunk {
pub samples: Vec<f32>,
pub start_time: Duration,
pub end_time: Duration,
pub sequence: u64,
pub is_final: bool,
}
impl AudioChunk {
pub fn duration(&self) -> Duration {
self.end_time - self.start_time
}
pub fn len(&self) -> usize {
self.samples.len()
}
pub fn is_empty(&self) -> bool {
self.samples.is_empty()
}
}
#[derive(Debug)]
pub struct AudioBuffer {
config: AudioBufferConfig,
samples: VecDeque<f32>,
total_samples_received: u64,
samples_processed: u64,
next_sequence: u64,
stream_start: Instant,
stream_ended: bool,
}
impl AudioBuffer {
pub fn new() -> Self {
Self::with_config(AudioBufferConfig::default())
}
pub fn with_config(config: AudioBufferConfig) -> Self {
Self {
samples: VecDeque::with_capacity(config.max_buffer_samples()),
config,
total_samples_received: 0,
samples_processed: 0,
next_sequence: 0,
stream_start: Instant::now(),
stream_ended: false,
}
}
pub fn push(&mut self, samples: &[f32]) {
for &sample in samples {
if self.samples.len() >= self.config.max_buffer_samples() {
self.samples.pop_front();
if self.samples_processed < self.total_samples_received {
self.samples_processed += 1;
}
}
self.samples.push_back(sample);
}
self.total_samples_received += samples.len() as u64;
}
pub fn end_stream(&mut self) {
self.stream_ended = true;
}
pub fn is_ended(&self) -> bool {
self.stream_ended
}
pub fn available_samples(&self) -> usize {
let processed_in_buffer = self
.samples_processed
.saturating_sub(self.total_samples_received - self.samples.len() as u64);
self.samples
.len()
.saturating_sub(processed_in_buffer as usize)
}
pub fn has_chunk_ready(&self) -> bool {
self.available_samples() >= self.config.chunk_samples()
}
pub fn has_audio(&self) -> bool {
self.available_samples() > 0
}
pub fn extract_chunk(&mut self, force: bool) -> Option<AudioChunk> {
let available = self.available_samples();
let chunk_size = self.config.chunk_samples();
if !force && available < chunk_size {
return None;
}
if available == 0 {
return None;
}
let extract_size = if force {
available.min(chunk_size)
} else {
chunk_size
};
let sample_rate = self.config.sample_rate as f64;
let start_sample = self.samples_processed;
let start_time = Duration::from_secs_f64(start_sample as f64 / sample_rate);
let end_time =
Duration::from_secs_f64((start_sample + extract_size as u64) as f64 / sample_rate);
let overlap = self.config.overlap_samples();
let samples: Vec<f32> = self
.samples
.iter()
.skip(self.get_buffer_offset())
.take(extract_size)
.copied()
.collect();
let advance = if force {
extract_size } else {
extract_size.saturating_sub(overlap)
};
self.samples_processed += advance as u64;
let chunk = AudioChunk {
samples,
start_time,
end_time,
sequence: self.next_sequence,
is_final: force && self.stream_ended,
};
self.next_sequence += 1;
Some(chunk)
}
pub fn flush(&mut self) -> Option<AudioChunk> {
if !self.has_audio() {
return None;
}
self.extract_chunk(true)
}
pub fn reset(&mut self) {
self.samples.clear();
self.total_samples_received = 0;
self.samples_processed = 0;
self.next_sequence = 0;
self.stream_start = Instant::now();
self.stream_ended = false;
}
pub fn stats(&self) -> AudioBufferStats {
AudioBufferStats {
total_received: self.total_samples_received,
total_processed: self.samples_processed,
buffer_size: self.samples.len(),
available: self.available_samples(),
chunks_extracted: self.next_sequence,
elapsed: self.stream_start.elapsed(),
}
}
fn get_buffer_offset(&self) -> usize {
let buffer_start_sample = self
.total_samples_received
.saturating_sub(self.samples.len() as u64);
self.samples_processed.saturating_sub(buffer_start_sample) as usize
}
pub fn config(&self) -> &AudioBufferConfig {
&self.config
}
}
impl Default for AudioBuffer {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct AudioBufferStats {
pub total_received: u64,
pub total_processed: u64,
pub buffer_size: usize,
pub available: usize,
pub chunks_extracted: u64,
pub elapsed: Duration,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_buffer_config_defaults() {
let config = AudioBufferConfig::default();
assert_eq!(config.sample_rate, 16000);
assert_eq!(config.chunk_duration_secs, 30.0);
assert_eq!(config.chunk_samples(), 480000); }
#[test]
fn test_buffer_push() {
let mut buffer = AudioBuffer::new();
let samples = vec![0.1, 0.2, 0.3];
buffer.push(&samples);
assert_eq!(buffer.available_samples(), 3);
assert!(!buffer.has_chunk_ready()); }
#[test]
fn test_buffer_extract_forced() {
let mut buffer = AudioBuffer::new();
let samples: Vec<f32> = (0..1000).map(|i| i as f32 / 1000.0).collect();
buffer.push(&samples);
buffer.end_stream();
let chunk = buffer.extract_chunk(true).unwrap();
assert_eq!(chunk.samples.len(), 1000);
assert!(chunk.is_final);
assert_eq!(chunk.sequence, 0);
}
#[test]
fn test_buffer_full_chunk() {
let config = AudioBufferConfig {
sample_rate: 16000,
chunk_duration_secs: 0.1, overlap_secs: 0.01, max_buffer_secs: 1.0,
};
let mut buffer = AudioBuffer::with_config(config);
let samples: Vec<f32> = (0..1600).map(|i| i as f32 / 1600.0).collect();
buffer.push(&samples);
assert!(buffer.has_chunk_ready());
let chunk = buffer.extract_chunk(false).unwrap();
assert_eq!(chunk.samples.len(), 1600);
assert_eq!(chunk.sequence, 0);
assert!(!chunk.is_final);
}
#[test]
fn test_buffer_overlap() {
let config = AudioBufferConfig {
sample_rate: 1000, chunk_duration_secs: 1.0,
overlap_secs: 0.2, max_buffer_secs: 10.0,
};
let mut buffer = AudioBuffer::with_config(config);
let samples: Vec<f32> = (0..2000).map(|i| i as f32).collect();
buffer.push(&samples);
let chunk1 = buffer.extract_chunk(false).unwrap();
assert_eq!(chunk1.samples.len(), 1000);
let chunk2 = buffer.extract_chunk(false).unwrap();
assert_eq!(chunk2.samples.len(), 1000);
assert_eq!(chunk2.samples[0], 800.0);
}
#[test]
fn test_buffer_reset() {
let mut buffer = AudioBuffer::new();
buffer.push(&[1.0, 2.0, 3.0]);
buffer.reset();
assert_eq!(buffer.available_samples(), 0);
assert!(!buffer.has_audio());
}
#[test]
fn test_chunk_duration() {
let chunk = AudioChunk {
samples: vec![0.0; 16000],
start_time: Duration::from_secs(0),
end_time: Duration::from_secs(1),
sequence: 0,
is_final: false,
};
assert_eq!(chunk.duration(), Duration::from_secs(1));
assert_eq!(chunk.len(), 16000);
}
}