use anyhow::{Result, bail};
use rubato::{FftFixedIn, Resampler as RubatoResampler};
use snapcast_proto::SampleFormat;
use crate::stream::SampleEncoding;
pub struct Resampler {
resampler: FftFixedIn<f64>,
in_format: SampleFormat,
in_encoding: SampleEncoding,
out_format: SampleFormat,
channels: usize,
}
impl Resampler {
pub fn new_if_needed(
in_format: SampleFormat,
out_format: SampleFormat,
in_encoding: SampleEncoding,
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,
in_encoding,
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;
if frame_size == 0 || sample_size == 0 {
bail!("cannot resample zero-sized frames");
}
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 if self.in_encoding == SampleEncoding::Float32 => f32::from_le_bytes([
sample_bytes[0],
sample_bytes[1],
sample_bytes[2],
sample_bytes[3],
])
as f64,
4 if self.in_format.bits() == 24 => {
i32::from_le_bytes([
sample_bytes[0],
sample_bytes[1],
sample_bytes[2],
sample_bytes[3],
]) as f64
/ snapcast_proto::PCM_24BIT_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 mut out = Vec::with_capacity(out_frames * self.channels * 4);
for frame_idx in 0..out_frames {
for ch_samples in &channels_out {
let s = ch_samples[frame_idx] as f32;
out.extend_from_slice(&s.to_le_bytes());
}
}
*data = out;
Ok(())
}
pub fn output_encoding(&self) -> SampleEncoding {
SampleEncoding::Float32
}
pub fn output_format(&self) -> SampleFormat {
SampleFormat::new(self.out_format.rate(), 32, self.in_format.channels())
}
}
#[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, SampleEncoding::PcmInt, 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, SampleEncoding::PcmInt, 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, SampleEncoding::PcmInt, 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, SampleEncoding::PcmInt, 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);
}
}