WIP resampling

This commit is contained in:
Daniel Wolf 2023-01-28 19:59:33 +01:00
parent 229ef2a25b
commit d29fb6d61a
6 changed files with 272 additions and 1 deletions

View File

@ -121,12 +121,38 @@ fn build_vorbis(
VorbisBuildResult { vorbis_utils_path }
}
fn build_libsamplerate(parent_dir: impl AsRef<Path>) {
let version = "0.2.2";
let repo_dir = checkout(
&parent_dir,
"https://github.com/libsndfile/libsamplerate.git",
version,
"libsamplerate",
);
let include_dir = repo_dir.join("include");
let src_dir = repo_dir.join("src");
cc::Build::new()
.define("PACKAGE", "\"libsamplerate\"")
.define("VERSION", format!("\"{version}\"").as_str())
.define("HAVE_STDBOOL_H", "1")
.define("ENABLE_SINC_MEDIUM_CONVERTER", "1")
.include(&include_dir)
.files(
["samplerate.c", "src_linear.c", "src_sinc.c", "src_zoh.c"]
.map(|name| src_dir.join(name)),
)
.compile("libsamplerate");
println!("cargo:rustc-link-lib=static=libsamplerate");
}
fn main() {
let out_dir = Path::new(&var_os("OUT_DIR").unwrap()).to_path_buf();
println!("cargo:rustc-link-search=native={}", out_dir.display());
let OggBuildResult { ogg_include_dir } = build_ogg(&out_dir);
let VorbisBuildResult { vorbis_utils_path } = build_vorbis(&out_dir, ogg_include_dir);
build_libsamplerate(out_dir);
println!("cargo:rerun-if-changed={}", vorbis_utils_path.display());
}

View File

@ -1,15 +1,22 @@
use crate::{AudioClip, Segment};
use crate::{resampled_audio_clip::ResampledAudioClip, AudioClip, Segment};
/// Blanket implementations of audio filters for audio clips.
pub trait AudioFilters {
/// Returns a new audio clip containing a segment of the specified clip.
fn segment(self, start: u64, end: u64) -> Box<dyn AudioClip>;
/// Returns a new audio clip resampled to the specified sampling rate.
fn resampled(self, sampling_rate: u32) -> Box<dyn AudioClip>;
}
impl AudioFilters for Box<dyn AudioClip> {
fn segment(self, start: u64, end: u64) -> Box<dyn AudioClip> {
Box::new(Segment::new(self, start, end))
}
fn resampled(self, sampling_rate: u32) -> Box<dyn AudioClip> {
Box::new(ResampledAudioClip::new(self, sampling_rate))
}
}
impl<TAudioClip> AudioFilters for TAudioClip
@ -20,4 +27,9 @@ where
let boxed_audio_clip: Box<dyn AudioClip> = Box::new(self);
boxed_audio_clip.segment(start, end)
}
fn resampled(self, sampling_rate: u32) -> Box<dyn AudioClip> {
let boxed_audio_clip: Box<dyn AudioClip> = Box::new(self);
boxed_audio_clip.resampled(sampling_rate)
}
}

View File

@ -19,6 +19,7 @@ mod memory_audio_clip;
mod ogg_audio_clip;
mod open_audio_file;
mod read_and_seek;
mod resampled_audio_clip;
mod sample_reader_assertions;
mod segment;
mod wave_audio_clip;

View File

@ -0,0 +1,53 @@
// Raw FFI for libsamplerate
use std::os::raw::{c_int, c_long, c_void};
use crate::Sample;
#[link(name = "libsamplerate")]
extern "C" {
pub fn src_callback_new(
read_func: unsafe extern "C" fn(
callback_data: *mut c_void,
buffer: *mut *const Sample,
) -> c_long,
converter_type: ConverterType,
channel_count: c_int,
error: *mut c_int,
callback_data: *mut c_void,
) -> *mut State;
pub fn src_callback_read(
state: *mut State,
ratio: f64,
frame_count: c_long,
buffer: *mut Sample,
) -> c_long;
pub fn src_error(state: *mut State) -> c_int;
pub fn src_reset(state: *mut State) -> c_int;
// Always returns null
pub fn src_delete(state: *mut State) -> *const c_void;
}
#[repr(C)]
#[allow(dead_code)]
pub enum ConverterType {
/// Band-limited sinc interpolation, best quality, 144 dB SNR, 96% BW.
SincBestQuality = 0,
/// Band-limited sinc interpolation, medium quality, 121 dB SNR, 90% BW.
SincMediumQuality = 1,
/// Band-limited sinc interpolation, low quality, 97 dB SNR, 80% BW.
SincFastest = 2,
/// Zero order hold interpolator, very fast, poor quality.
ZeroOrderHold = 3,
/// Linear interpolator, blindingly fast, poor quality.
Linear = 4,
}
#[repr(C)]
pub struct State {
private: [u8; 0],
}

View File

@ -0,0 +1,4 @@
mod libsamplerate_raw;
mod resampled_audio_clip;
pub use resampled_audio_clip::ResampledAudioClip;

View File

@ -0,0 +1,175 @@
use crate::{
resampled_audio_clip::libsamplerate_raw::src_error,
sample_reader_assertions::SampleReaderAssertions, AudioClip, AudioError, Sample, SampleReader,
};
use std::{
cmp::min,
mem::MaybeUninit,
os::raw::{c_long, c_void},
pin::Pin,
sync::Arc,
};
use super::libsamplerate_raw::{
src_callback_new, src_callback_read, src_delete, src_reset, ConverterType, State,
};
/// A resampled representation of another audio clip.
#[derive(Debug, Clone)]
pub struct ResampledAudioClip {
inner_clip: Arc<dyn AudioClip>,
sampling_rate: u32,
}
impl ResampledAudioClip {
pub fn new(inner_clip: Box<dyn AudioClip>, sampling_rate: u32) -> Self {
Self {
inner_clip: Arc::from(inner_clip),
sampling_rate,
}
}
fn factor(&self) -> f64 {
f64::from(self.sampling_rate) / f64::from(self.inner_clip.sampling_rate())
}
}
impl AudioClip for ResampledAudioClip {
fn len(&self) -> u64 {
(self.inner_clip.len() as f64 * self.factor()).round() as u64
}
fn sampling_rate(&self) -> u32 {
self.sampling_rate
}
fn create_sample_reader(&self) -> Result<Box<dyn SampleReader>, AudioError> {
Ok(Box::new(ResampledSampleReader::new(self)?))
}
}
#[derive(Debug)]
struct ResampledSampleReader {
callback_data: Pin<Box<CallbackData>>,
factor: f64,
len: u64,
position: u64,
position_changed: bool,
libsamplerate_state: *mut State,
}
const INPUT_BUFFER_SIZE: usize = 500;
#[derive(Debug)]
struct CallbackData {
inner_sample_reader: Box<dyn SampleReader>,
input_buffer: [Sample; INPUT_BUFFER_SIZE],
last_error: Option<AudioError>,
}
impl ResampledSampleReader {
fn new(clip: &ResampledAudioClip) -> Result<Self, AudioError> {
let callback_data = Box::pin(CallbackData {
inner_sample_reader: clip.inner_clip.create_sample_reader()?,
input_buffer: [0.0; INPUT_BUFFER_SIZE],
last_error: None,
});
let channel_count = 1;
let mut error = MaybeUninit::uninit();
let libsamplerate_state = unsafe {
src_callback_new(
read,
ConverterType::SincMediumQuality,
channel_count,
error.as_mut_ptr(),
&*callback_data as *const CallbackData as *mut c_void,
)
};
assert_eq!(unsafe { error.assume_init() }, 0);
Ok(Self {
callback_data,
factor: clip.factor(),
len: clip.len(),
position: 0,
position_changed: false,
libsamplerate_state,
})
}
}
impl Drop for ResampledSampleReader {
fn drop(&mut self) {
unsafe {
src_delete(self.libsamplerate_state);
}
}
}
impl SampleReader for ResampledSampleReader {
fn len(&self) -> u64 {
self.len
}
fn position(&self) -> u64 {
self.position
}
fn set_position(&mut self, position: u64) {
if position == self.position {
return;
}
self.assert_valid_seek_position(position);
self.position = position;
self.position_changed = true;
}
fn read(&mut self, buffer: &mut [Sample]) -> Result<(), AudioError> {
self.assert_valid_read_size(buffer);
if self.position_changed {
self.callback_data
.inner_sample_reader
.set_position((self.position as f64 / self.factor) as u64);
let error = unsafe { src_reset(self.libsamplerate_state) };
assert_eq!(error, 0);
self.position_changed = false;
}
let samples_read = unsafe {
src_callback_read(
self.libsamplerate_state,
self.factor,
buffer.len() as i32,
buffer.as_mut_ptr(),
) as usize
};
if let Some(error) = self.callback_data.last_error.take() {
return Err(error);
}
if samples_read < buffer.len() {
assert_eq!(unsafe { src_error(self.libsamplerate_state) }, 0);
panic!("Expected {} samples, got {}.", buffer.len(), samples_read);
}
self.position += buffer.len() as u64;
Ok(())
}
}
unsafe extern "C" fn read(callback_data: *mut c_void, buffer: *mut *const Sample) -> c_long {
let callback_data = callback_data.cast::<CallbackData>();
let inner_sample_reader = &mut (*callback_data).inner_sample_reader;
let sample_count = min(inner_sample_reader.remainder(), INPUT_BUFFER_SIZE as u64) as usize;
// Allow reading beyond the end of the audio clip by filling with zeros
let (sample_buffer, zero_buffer) = (*callback_data).input_buffer.split_at_mut(sample_count);
let read_result = inner_sample_reader.read(sample_buffer);
if let Err(error) = read_result {
(*callback_data).last_error = Some(error);
return 0;
}
zero_buffer.fill(0.0);
*buffer = sample_buffer.as_ptr();
sample_buffer.len() as c_long
}