From a5ab2c62258a95c51eacc641d640436feebf0bdf Mon Sep 17 00:00:00 2001 From: Hailey Somerville <hailey@hailey.lol> Date: Fri, 26 Jan 2024 09:06:48 +1100 Subject: [PATCH] use soxr for resampling --- Cargo.lock | 21 +++++ bark-core/Cargo.toml | 1 + bark-core/src/receive/resample.rs | 151 ++++-------------------------- 3 files changed, 39 insertions(+), 134 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d5dc06f..2d27134 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -154,6 +154,7 @@ dependencies = [ "heapless", "log", "opus", + "soxr", "thiserror", ] @@ -786,6 +787,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "libsoxr-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cffdc8b3b64759d2e95e3236fa6bf85fef226bf57023fa9273ec60edea8fbce" +dependencies = [ + "pkg-config", +] + [[package]] name = "linux-raw-sys" version = "0.4.7" @@ -1150,6 +1160,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "soxr" +version = "0.0.0" +source = "git+https://github.com/haileys/soxr-rs#5a69d664aa65f06f9fc124cbb817162a7b9be256" +dependencies = [ + "bitflags 2.4.0", + "bytemuck", + "libsoxr-sys", + "thiserror", +] + [[package]] name = "spin" version = "0.9.8" diff --git a/bark-core/Cargo.toml b/bark-core/Cargo.toml index b99c2c7..734f99d 100644 --- a/bark-core/Cargo.toml +++ b/bark-core/Cargo.toml @@ -14,3 +14,4 @@ heapless = { workspace = true } log = { workspace = true } opus = "0.3.0" thiserror = { workspace = true } +soxr = { git = "https://github.com/haileys/soxr-rs" } diff --git a/bark-core/src/receive/resample.rs b/bark-core/src/receive/resample.rs index 3387dc0..9dddb39 100644 --- a/bark-core/src/receive/resample.rs +++ b/bark-core/src/receive/resample.rs @@ -1,50 +1,12 @@ -use std::ffi::{c_void, c_int, CStr}; -use std::fmt::Debug; -use std::ptr; +use soxr::Soxr; +use soxr::format::Stereo; -use crate::audio::{Frame, FrameCount}; - -use self::ffi::speex_resampler_strerror; - -mod ffi { - use std::ffi::{c_void, c_int, c_char}; - - #[link(name = "speexdsp")] - extern "C" { - pub fn speex_resampler_init( - nb_channels: u32, - in_rate: u32, - out_rate: u32, - quality: c_int, - err: *mut c_int, - ) -> *mut c_void; - - pub fn speex_resampler_set_rate( - ptr: *mut c_void, - in_rate: u32, - out_rate: u32, - ) -> c_int; - - pub fn speex_resampler_process_interleaved_float( - ptr: *mut c_void, - input: *const f32, - input_len: *mut u32, - output: *mut f32, - output_len: *mut u32, - ) -> c_int; - - pub fn speex_resampler_destroy(ptr: *mut c_void); - - pub fn speex_resampler_strerror(err: c_int) -> *const c_char; - } -} +use crate::audio::{Frame, FrameCount, Sample}; pub struct Resampler { - ptr: ResamplerPtr, + soxr: Soxr<Stereo<Sample>>, } -unsafe impl Send for Resampler {} - pub struct ProcessResult { pub input_read: FrameCount, pub output_written: FrameCount, @@ -52,106 +14,27 @@ pub struct ProcessResult { impl Resampler { pub fn new() -> Self { - let mut err: c_int = 0; - - let ptr = unsafe { - ffi::speex_resampler_init( - bark_protocol::CHANNELS.into(), - bark_protocol::SAMPLE_RATE.into(), - bark_protocol::SAMPLE_RATE.into(), - 10, - &mut err - ) - }; - - if ptr == ptr::null_mut() { - // this should only really fail on allocation error, - // which rust already makes a panic, so shrug let's - // just panic so callers don't have to deal with it - let err = SpeexError::from_err(err); - panic!("speex_resampler_init failed: {err:?}"); - } - - Resampler { ptr: ResamplerPtr(ptr) } + let rate = bark_protocol::SAMPLE_RATE.0 as f64; + let soxr = Soxr::variable_rate(rate, rate).unwrap(); + Resampler { soxr } } - pub fn set_input_rate(&mut self, rate: u32) -> Result<(), SpeexError> { - let err = unsafe { - ffi::speex_resampler_set_rate( - self.ptr.0, - rate, - bark_protocol::SAMPLE_RATE.into(), - ) - }; - - if err != 0 { - return Err(SpeexError::from_err(err)); - } - - Ok(()) + pub fn set_input_rate(&mut self, rate: u32) -> Result<(), soxr::Error> { + let input = rate as f64; + let output = bark_protocol::SAMPLE_RATE.0 as f64; + self.soxr.set_rates(input, output, 0) } pub fn process(&mut self, input: &[Frame], output: &mut [Frame]) - -> Result<ProcessResult, SpeexError> + -> Result<ProcessResult, soxr::Error> { - // usize could technically be 64 bit, speex only takes u32 sizes, - // we don't want to panic or truncate, so let's just pick a reasonable - // length and cap input and output since the API allows us to. - // i'm going to say a reasonable length for a single call is 1<<20. - let max_reasonable_len = 1 << 20; - let input_len = std::cmp::min(input.len(), max_reasonable_len); - let output_len = std::cmp::min(output.len(), max_reasonable_len); - - let mut input_len = u32::try_from(input_len).unwrap(); - let mut output_len = u32::try_from(output_len).unwrap(); - - let err = unsafe { - ffi::speex_resampler_process_interleaved_float( - self.ptr.0, - input.as_ptr().cast(), - // speex API takes frame count already: - &mut input_len, - output.as_mut_ptr().cast(), - &mut output_len, - ) - }; - - if err != 0 { - return Err(SpeexError::from_err(err)); - } + let input = bytemuck::must_cast_slice(input); + let output = bytemuck::must_cast_slice_mut(output); + let result = self.soxr.process(input, output)?; Ok(ProcessResult { - input_read: FrameCount(usize::try_from(input_len).unwrap()), - output_written: FrameCount(usize::try_from(output_len).unwrap()), + input_read: FrameCount(result.input_frames), + output_written: FrameCount(result.output_frames), }) } } - -#[repr(transparent)] -struct ResamplerPtr(*mut c_void); - -impl Drop for ResamplerPtr { - fn drop(&mut self) { - unsafe { - ffi::speex_resampler_destroy(self.0); - } - } -} - -pub struct SpeexError(&'static CStr); - -impl SpeexError { - fn from_err(err: c_int) -> Self { - let cstr = unsafe { - CStr::from_ptr(speex_resampler_strerror(err)) - }; - - SpeexError(cstr) - } -} - -impl Debug for SpeexError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "SpeexError({:?})", self.0.to_string_lossy()) - } -}