106 lines
3.4 KiB
Rust
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());
|
|
}
|
|
}
|