mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
217 lines
6.7 KiB
Rust
217 lines
6.7 KiB
Rust
// 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<f32>,
|
|
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<CapturedFrame> {
|
|
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<f32>) -> Option<DirectReadMeta> {
|
|
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);
|
|
}
|
|
}
|