Files
chanora/crates/chanora_audio/src/capture_resampler.rs
T

106 lines
3.4 KiB
Rust

pub(crate) struct CaptureResampleResult {
pub(crate) output_len: usize,
pub(crate) dropped: bool,
}
pub(crate) fn resample_capture_to_48k(
samples: &[i16],
input_sample_rate_hz: u32,
resample_pos: &mut f64,
resample_last: &mut i16,
scratch: &mut Vec<i16>,
) -> CaptureResampleResult {
scratch.clear();
if samples.is_empty() {
return CaptureResampleResult {
output_len: 0,
dropped: false,
};
}
let ratio = input_sample_rate_hz.max(1) as f64 / crate::frame::SAMPLE_RATE_HZ as f64;
let mut pos = *resample_pos;
let mut dropped = false;
while pos < samples.len() as f64 {
let i = pos.floor() as isize;
let frac = pos - i as f64;
let a = if i <= 0 {
*resample_last as f64
} else {
samples[(i - 1) as usize] as f64
};
let b = if i < samples.len() as isize {
samples[i as usize] as f64
} else {
a
};
let value = (a + frac * (b - a))
.round()
.clamp(i16::MIN as f64, i16::MAX as f64) as i16;
if scratch.len() < scratch.capacity() {
scratch.push(value);
} else {
dropped = true;
}
pos += ratio;
}
*resample_pos = pos - samples.len() as f64;
*resample_last = *samples.last().unwrap_or(resample_last);
CaptureResampleResult {
output_len: scratch.len(),
dropped,
}
}
#[cfg(test)]
mod android_voice_unit_resampler_tests {
use super::resample_capture_to_48k;
#[test]
fn android_voice_unit_resampler_reuses_scratch_without_capacity_growth() {
let samples: Vec<i16> = (0..882).map(|i| i as i16).collect();
let mut pos = 0.0;
let mut last = 0_i16;
let mut scratch = Vec::with_capacity(960);
let first = resample_capture_to_48k(&samples, 44_100, &mut pos, &mut last, &mut scratch);
let first_len = first.output_len;
assert_eq!(first_len, 960);
assert!(!first.dropped);
assert_eq!(scratch.len(), first_len);
let warmed_capacity = scratch.capacity();
let warmed_ptr = scratch.as_ptr();
for _ in 0..8 {
let result =
resample_capture_to_48k(&samples, 44_100, &mut pos, &mut last, &mut scratch);
let len = result.output_len;
assert_eq!(len, scratch.len());
assert!(len >= 959 && len <= 960);
assert!(!result.dropped);
assert_eq!(scratch.capacity(), warmed_capacity);
assert_eq!(scratch.as_ptr(), warmed_ptr);
}
}
#[test]
fn android_voice_unit_resampler_truncates_oversized_burst_without_capacity_growth() {
let samples: Vec<i16> = (0..4_800).map(|i| i as i16).collect();
let mut pos = 0.0;
let mut last = 0_i16;
let mut scratch = Vec::with_capacity(960);
let warmed_capacity = scratch.capacity();
let warmed_ptr = scratch.as_ptr();
let result = resample_capture_to_48k(&samples, 48_000, &mut pos, &mut last, &mut scratch);
assert_eq!(result.output_len, warmed_capacity);
assert!(result.dropped);
assert_eq!(scratch.len(), warmed_capacity);
assert_eq!(scratch.capacity(), warmed_capacity);
assert_eq!(scratch.as_ptr(), warmed_ptr);
assert_eq!(pos, 0.0);
assert_eq!(last, *samples.last().unwrap());
}
}