// SPDX-License-Identifier: AGPL-3.0-or-later #![allow(dead_code)] use std::collections::VecDeque; use crate::audio_contract::{ self, DIRECT_CAPTURE_CHANNELS, DIRECT_CAPTURE_MAX_SAMPLES, DIRECT_CAPTURE_SAMPLE_RATE, }; use crate::backend::CapturedFrame; pub struct DirectReadMeta { pub sample_rate: u32, pub channels: u32, pub timestamp_us: i64, } pub struct DirectAudioBuffer { samples: VecDeque, queue_start_us: i64, sample_rate: u32, channels: u32, } impl DirectAudioBuffer { pub fn new(sample_rate: u32, channels: u32) -> Self { Self { samples: VecDeque::with_capacity(DIRECT_CAPTURE_MAX_SAMPLES), queue_start_us: 0, sample_rate: sample_rate.max(1), channels: channels.max(1), } } pub fn default_format() -> Self { Self::new(DIRECT_CAPTURE_SAMPLE_RATE, DIRECT_CAPTURE_CHANNELS) } #[cfg(test)] pub fn len(&self) -> usize { self.samples.len() } #[cfg(test)] pub fn is_empty(&self) -> bool { self.samples.is_empty() } #[cfg(test)] pub fn queue_start_us(&self) -> i64 { self.queue_start_us } pub fn set_format(&mut self, sample_rate: u32, channels: u32) { let sample_rate = sample_rate.max(1); let channels = channels.max(1); if self.sample_rate != sample_rate || self.channels != channels { self.clear(); } self.sample_rate = sample_rate; self.channels = channels; } pub fn clear(&mut self) { self.samples.clear(); self.queue_start_us = 0; } pub fn push(&mut self, input: &[f32], end_timestamp_us: i64) { let whole = audio_contract::whole_frame_sample_count(input.len(), self.channels); if whole == 0 { return; } let mut frame = &input[..whole]; if frame.len() > DIRECT_CAPTURE_MAX_SAMPLES { let keep = audio_contract::whole_frame_sample_count(DIRECT_CAPTURE_MAX_SAMPLES, self.channels); frame = &frame[frame.len() - keep..]; self.clear(); } if self.samples.is_empty() { let duration_us = audio_contract::duration_us_for_sample_count( frame.len(), self.sample_rate, self.channels, ); self.queue_start_us = end_timestamp_us.saturating_sub(duration_us); } self.drop_for_incoming(frame.len()); self.samples.extend(frame.iter().copied()); } pub fn read(&mut self) -> Option { let mut out = Vec::with_capacity(audio_contract::DIRECT_CAPTURE_MAX_READ_SAMPLES); let meta = self.read_into(&mut out)?; Some(CapturedFrame { samples: out, sample_rate: meta.sample_rate, channels: meta.channels, timestamp_us: meta.timestamp_us, }) } pub fn read_into(&mut self, out: &mut Vec) -> Option { assert!(self.sample_rate >= 1); assert!(self.channels >= 1); out.clear(); if self.samples.is_empty() { return None; } let take = audio_contract::bounded_direct_read_sample_count_for_format( self.samples.len(), self.sample_rate, self.channels, ); if take == 0 { return None; } assert!(take <= self.samples.len()); assert!(take.is_multiple_of(self.channels as usize)); let timestamp_us = self.queue_start_us.max(0); let (front, back) = self.samples.as_slices(); let front_take = take.min(front.len()); out.extend_from_slice(&front[..front_take]); out.extend_from_slice(&back[..take - front_take]); self.samples.drain(..take); self.queue_start_us = self.queue_start_us .saturating_add(audio_contract::duration_us_for_sample_count( take, self.sample_rate, self.channels, )); if self.samples.is_empty() { self.queue_start_us = 0; } Some(DirectReadMeta { sample_rate: self.sample_rate, channels: self.channels, timestamp_us, }) } fn drop_for_incoming(&mut self, incoming: usize) { assert!(incoming >= 1); assert!(incoming <= DIRECT_CAPTURE_MAX_SAMPLES); assert!(self.channels >= 1); let total = self.samples.len() + incoming; if total <= DIRECT_CAPTURE_MAX_SAMPLES { return; } let overflow = total - DIRECT_CAPTURE_MAX_SAMPLES; let channels = self.channels as usize; let remainder = overflow % channels; let drop = if remainder == 0 { overflow } else { overflow + (channels - remainder) } .min(self.samples.len()); self.samples.drain(..drop); self.queue_start_us = self.queue_start_us .saturating_add(audio_contract::duration_us_for_sample_count( drop, self.sample_rate, self.channels, )); } } #[cfg(test)] mod tests { use super::*; #[test] fn read_emits_stable_twenty_ms_chunks_with_continuous_timestamps() { let mut buffer = DirectAudioBuffer::default_format(); buffer.push(&vec![0.5; 4_800], 1_000_000); let first = buffer.read().expect("first chunk"); assert_eq!(1_920, first.samples.len()); assert_eq!(950_000, first.timestamp_us); let second = buffer.read().expect("second chunk"); assert_eq!(1_920, second.samples.len()); assert_eq!(970_000, second.timestamp_us); } #[test] fn format_changes_clear_queued_samples() { let mut buffer = DirectAudioBuffer::default_format(); buffer.push(&vec![0.5; 1_920], 100_000); assert!(!buffer.is_empty()); buffer.set_format(44_100, 2); assert!(buffer.is_empty()); assert_eq!(0, buffer.queue_start_us()); buffer.push(&vec![0.25; 1_764], 200_000); let frame = buffer.read().expect("chunk"); assert_eq!(1_764, frame.samples.len()); assert_eq!(180_000, frame.timestamp_us); assert_eq!(44_100, frame.sample_rate); } #[test] fn overflow_drops_from_front_and_advances_timestamp() { let mut buffer = DirectAudioBuffer::default_format(); buffer.push(&vec![0.5; DIRECT_CAPTURE_MAX_SAMPLES + 1_920], 3_000_000); assert_eq!(DIRECT_CAPTURE_MAX_SAMPLES, buffer.len()); let frame = buffer.read().expect("chunk"); assert_eq!(1_000_000, frame.timestamp_us); } }