use anyhow::{Result, bail};
use rubato::{FftFixedIn, Resampler as RubatoResampler};
use snapcast_proto::SampleFormat;
pub struct Resampler {
resampler: FftFixedIn<f64>,
in_format: SampleFormat,
out_format: SampleFormat,
channels: usize,
}
impl Resampler {
pub fn new_if_needed(
in_format: SampleFormat,
out_format: SampleFormat,
chunk_frames: usize,
) -> Result<Option<Self>> {
if out_format.rate() == 0 || out_format.rate() == in_format.rate() {
return Ok(None);
}
let channels = in_format.channels() as usize;
if channels == 0 {
bail!("cannot resample 0 channels");
}
let resampler = FftFixedIn::new(
in_format.rate() as usize,
out_format.rate() as usize,
chunk_frames,
2, channels,
)?;
Ok(Some(Self {
resampler,
in_format,
out_format,
channels,
}))
}
pub fn process(&mut self, data: &mut Vec<u8>) -> Result<()> {
let sample_size = self.in_format.sample_size() as usize;
let frame_size = self.in_format.frame_size() as usize;
let in_frames = data.len() / frame_size;
let mut channels_in: Vec<Vec<f64>> = vec![vec![0.0; in_frames]; self.channels];
for (frame_idx, frame_bytes) in data.chunks_exact(frame_size).enumerate() {
for (ch, sample_bytes) in frame_bytes.chunks_exact(sample_size).enumerate() {
let sample = match sample_size {
2 => {
i16::from_le_bytes([sample_bytes[0], sample_bytes[1]]) as f64
/ i16::MAX as f64
}
4 => {
i32::from_le_bytes([
sample_bytes[0],
sample_bytes[1],
sample_bytes[2],
sample_bytes[3],
]) as f64
/ i32::MAX as f64
}
_ => 0.0,
};
channels_in[ch][frame_idx] = sample;
}
}
let channels_out = self.resampler.process(&channels_in, None)?;
let out_frames = channels_out[0].len();
let out_sample_size = self.out_format.sample_size() as usize;
let mut out = Vec::with_capacity(out_frames * self.channels * out_sample_size);
for frame_idx in 0..out_frames {
for ch_samples in &channels_out {
let sample = ch_samples[frame_idx];
match out_sample_size {
2 => {
let s = (sample * i16::MAX as f64).clamp(i16::MIN as f64, i16::MAX as f64)
as i16;
out.extend_from_slice(&s.to_le_bytes());
}
4 => {
let s = (sample * i32::MAX as f64).clamp(i32::MIN as f64, i32::MAX as f64)
as i32;
out.extend_from_slice(&s.to_le_bytes());
}
_ => out.extend_from_slice(&[0; 2]),
}
}
}
*data = out;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_resampler_when_same_rate() {
let fmt = SampleFormat::new(48000, 16, 2);
let r = Resampler::new_if_needed(fmt, fmt, 480).unwrap();
assert!(r.is_none());
}
#[test]
fn no_resampler_when_out_rate_zero() {
let in_fmt = SampleFormat::new(48000, 16, 2);
let out_fmt = SampleFormat::new(0, 16, 2);
let r = Resampler::new_if_needed(in_fmt, out_fmt, 480).unwrap();
assert!(r.is_none());
}
#[test]
fn creates_resampler_for_different_rates() {
let in_fmt = SampleFormat::new(44100, 16, 2);
let out_fmt = SampleFormat::new(48000, 16, 2);
let r = Resampler::new_if_needed(in_fmt, out_fmt, 441).unwrap();
assert!(r.is_some());
}
#[test]
fn resample_changes_length() {
let in_fmt = SampleFormat::new(44100, 16, 2);
let out_fmt = SampleFormat::new(48000, 16, 2);
let frames = 441; let mut r = Resampler::new_if_needed(in_fmt, out_fmt, frames)
.unwrap()
.unwrap();
let in_bytes = frames * in_fmt.frame_size() as usize;
let mut data = vec![0u8; in_bytes];
for (i, chunk) in data.chunks_exact_mut(2).enumerate() {
let sample = ((i as f64 * 0.1).sin() * 10000.0) as i16;
chunk.copy_from_slice(&sample.to_le_bytes());
}
r.process(&mut data).unwrap();
assert!(!data.is_empty());
assert_ne!(data.len(), in_bytes);
}
}