From 960bba358e912e5e27fed2e4325f7958ab31cdc0 Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Mon, 26 Jul 2021 00:29:24 +0200 Subject: [PATCH] Add basic implementation of audio buffer and sample --- Cargo.toml | 17 ++ benches/audio_buffer.rs | 39 ++++ src/audio.rs | 4 + src/audio/buffer.rs | 447 +++++++++++++++++++++++++++++++++++++ src/audio/buffer/info.rs | 122 ++++++++++ src/audio/buffer/layout.rs | 178 +++++++++++++++ src/audio/sample.rs | 198 ++++++++++++++++ src/lib.rs | 10 +- 8 files changed, 1009 insertions(+), 6 deletions(-) create mode 100644 benches/audio_buffer.rs create mode 100644 src/audio.rs create mode 100644 src/audio/buffer.rs create mode 100644 src/audio/buffer/info.rs create mode 100644 src/audio/buffer/layout.rs create mode 100644 src/audio/sample.rs diff --git a/Cargo.toml b/Cargo.toml index a015bf0..398a14b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,20 @@ repository = "https://github.com/webrtc-rs/media" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +util = { package = "webrtc-util", version = "0.3.0", default-features = false, features = [ + "marshal", +] } + +anyhow = "1.0.41" +byteorder = "1.4.3" +bytes = "1.0.1" +displaydoc = "0.2.1" +thiserror = "1.0.25" + +[dev-dependencies] +criterion = { version = "0.3.4", features = ["html_reports"] } +nearly_eq = "0.2.4" + +[[bench]] +name = "audio_buffer" +harness = false diff --git a/benches/audio_buffer.rs b/benches/audio_buffer.rs new file mode 100644 index 0000000..7a0d4b2 --- /dev/null +++ b/benches/audio_buffer.rs @@ -0,0 +1,39 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +use webrtc_media::audio::buffer::{ + layout::{Deinterleaved, Interleaved}, + Buffer, +}; + +fn benchmark_from(c: &mut Criterion) { + type Sample = i32; + let channels = 4; + let frames = 100_000; + let deinterleaved_buffer: Buffer = { + let samples = (0..(channels * frames)).map(|i| i as i32).collect(); + Buffer::new(samples, channels) + }; + let interleaved_buffer: Buffer = { + let samples = (0..(channels * frames)).map(|i| i as i32).collect(); + Buffer::new(samples, channels) + }; + + c.bench_function("Buffer => Buffer", |b| { + b.iter(|| { + black_box(Buffer::::from( + deinterleaved_buffer.as_ref(), + )); + }) + }); + + c.bench_function("Buffer => Buffer", |b| { + b.iter(|| { + black_box(Buffer::::from( + interleaved_buffer.as_ref(), + )); + }) + }); +} + +criterion_group!(benches, benchmark_from); +criterion_main!(benches); diff --git a/src/audio.rs b/src/audio.rs new file mode 100644 index 0000000..cfb799a --- /dev/null +++ b/src/audio.rs @@ -0,0 +1,4 @@ +pub mod buffer; +mod sample; + +pub use sample::Sample; diff --git a/src/audio/buffer.rs b/src/audio/buffer.rs new file mode 100644 index 0000000..f606ff9 --- /dev/null +++ b/src/audio/buffer.rs @@ -0,0 +1,447 @@ +pub mod info; +pub mod layout; + +use std::{ + mem::{ManuallyDrop, MaybeUninit}, + ops::Range, +}; + +use byteorder::ByteOrder; +use thiserror::Error; + +pub use info::BufferInfo; +pub use layout::BufferLayout; + +use layout::{Deinterleaved, Interleaved}; + +pub trait FromBytes: Sized { + type Error; + + fn from_bytes(bytes: &[u8], channels: usize) -> Result; +} + +pub trait ToByteBufferRef: Sized { + type Error; + + fn bytes_len(&self); + fn to_bytes( + &self, + bytes: &mut [u8], + channels: usize, + ) -> Result; +} + +#[derive(Debug, Error, PartialEq)] +pub enum Error { + #[error("Unexpected end of buffer: (expected: {expected}, actual: {actual})")] + UnexpectedEndOfBuffer { expected: usize, actual: usize }, +} + +#[derive(Eq, PartialEq, Clone, Debug)] +pub struct BufferRef<'a, T, L> { + samples: &'a [T], + info: BufferInfo, +} + +impl<'a, T, L> BufferRef<'a, T, L> { + pub fn new(samples: &'a [T], channels: usize) -> Self { + debug_assert_eq!(samples.len() % channels, 0); + let info = { + let frames = samples.len() / channels; + BufferInfo::new(channels, frames) + }; + Self { samples, info } + } +} + +/// Buffer multi-channel interlaced Audio. +#[derive(Eq, PartialEq, Clone, Debug)] +pub struct Buffer { + samples: Vec, + info: BufferInfo, +} + +impl Buffer { + pub fn new(samples: Vec, channels: usize) -> Self { + debug_assert_eq!(samples.len() % channels, 0); + let info = { + let frames = samples.len() / channels; + BufferInfo::new(channels, frames) + }; + Self { samples, info } + } + + pub fn as_ref(&'_ self) -> BufferRef<'_, T, L> { + BufferRef { + samples: &self.samples[..], + info: self.info, + } + } + + pub fn sub_range(&'_ self, range: Range) -> BufferRef<'_, T, L> { + let samples_len = range.len(); + let samples = &self.samples[range]; + let info = { + let channels = self.info.channels(); + assert_eq!(samples_len % channels, 0); + let frames = samples_len / channels; + BufferInfo::new(channels, frames) + }; + BufferRef { samples, info } + } +} + +impl From> for Buffer +where + T: Default + Copy, +{ + fn from(buffer: Buffer) -> Self { + Self::from(buffer.as_ref()) + } +} + +impl<'a, T> From> for Buffer +where + T: Default + Copy, +{ + fn from(buffer: BufferRef<'a, T, Deinterleaved>) -> Self { + // Writing into a vec of uninitialized `samples` is about 10% faster than + // cloning it or creating a default-initialized one and over-writing it. + // + // # Safety + // + // The performance boost comes with a cost though: + // At the end of the block each and every single item in + // `samples` needs to have been initialized, or else you get UB! + let samples = { + // Create a vec of uninitialized samples. + let mut samples: Vec> = + vec![MaybeUninit::uninit(); buffer.samples.len()]; + + // Initialize all of its values: + layout::interleaved_by( + buffer.samples, + &mut samples[..], + buffer.info.channels(), + |sample| MaybeUninit::new(*sample), + ); + + // Transmute the vec to the initialized type. + unsafe { std::mem::transmute::<_, Vec>(samples) } + }; + + let info = buffer.info.into(); + Self { samples, info } + } +} + +impl From> for Buffer +where + T: Default + Copy, +{ + fn from(buffer: Buffer) -> Self { + Self::from(buffer.as_ref()) + } +} + +impl<'a, T> From> for Buffer +where + T: Default + Copy, +{ + fn from(buffer: BufferRef<'a, T, Interleaved>) -> Self { + // Writing into a vec of uninitialized `samples` is about 10% faster than + // cloning it or creating a default-initialized one and over-writing it. + // + // # Safety + // + // The performance boost comes with a cost though: + // At the end of the block each and every single item in + // `samples` needs to have been initialized, or else you get UB! + let samples = { + // Create a vec of uninitialized samples. + let mut samples: Vec> = + vec![MaybeUninit::uninit(); buffer.samples.len()]; + + // Initialize the vec's values: + layout::deinterleaved_by( + buffer.samples, + &mut samples[..], + buffer.info.channels(), + |sample| MaybeUninit::new(*sample), + ); + + // Everything is initialized. Transmute the vec to the initialized type. + unsafe { std::mem::transmute::<_, Vec>(samples) } + }; + + let info = buffer.info.into(); + Self { samples, info } + } +} + +impl FromBytes for Buffer { + type Error = (); + + fn from_bytes(bytes: &[u8], channels: usize) -> Result { + const STRIDE: usize = std::mem::size_of::(); + assert_eq!(bytes.len() % STRIDE, 0); + + let chunks = { + let chunks_ptr = bytes.as_ptr() as *const [u8; STRIDE]; + let chunks_len = bytes.len() / STRIDE; + unsafe { std::slice::from_raw_parts(chunks_ptr, chunks_len) } + }; + + let samples: Vec<_> = chunks.iter().map(|chunk| B::read_i16(&chunk[..])).collect(); + + let info = { + let frames = samples.len() / channels; + BufferInfo::new(channels, frames) + }; + Ok(Self { samples, info }) + } +} + +impl FromBytes for Buffer { + type Error = (); + + fn from_bytes(bytes: &[u8], channels: usize) -> Result { + const STRIDE: usize = std::mem::size_of::(); + assert_eq!(bytes.len() % STRIDE, 0); + + let chunks = { + let chunks_ptr = bytes.as_ptr() as *const [u8; STRIDE]; + let chunks_len = bytes.len() / STRIDE; + unsafe { std::slice::from_raw_parts(chunks_ptr, chunks_len) } + }; + + // Writing into a vec of uninitialized `samples` is about 10% faster than + // cloning it or creating a default-initialized one and over-writing it. + // + // # Safety + // + // The performance boost comes with a cost though: + // At the end of the block each and every single item in + // `samples` needs to have been initialized, or else you get UB! + let samples = unsafe { + init_vec(chunks.len(), |samples| { + layout::interleaved_by(chunks, samples, channels, |chunk| { + MaybeUninit::new(B::read_i16(&chunk[..])) + }); + }) + }; + + let info = { + let frames = samples.len() / channels; + BufferInfo::new(channels, frames) + }; + Ok(Self { samples, info }) + } +} + +impl FromBytes for Buffer { + type Error = (); + + fn from_bytes(bytes: &[u8], channels: usize) -> Result { + const STRIDE: usize = std::mem::size_of::(); + assert_eq!(bytes.len() % STRIDE, 0); + + let chunks = { + let chunks_ptr = bytes.as_ptr() as *const [u8; STRIDE]; + let chunks_len = bytes.len() / STRIDE; + unsafe { std::slice::from_raw_parts(chunks_ptr, chunks_len) } + }; + + let samples: Vec<_> = chunks.iter().map(|chunk| B::read_i16(&chunk[..])).collect(); + + let info = { + let frames = samples.len() / channels; + BufferInfo::new(channels, frames) + }; + Ok(Self { samples, info }) + } +} + +impl FromBytes for Buffer { + type Error = (); + + fn from_bytes(bytes: &[u8], channels: usize) -> Result { + const STRIDE: usize = std::mem::size_of::(); + assert_eq!(bytes.len() % STRIDE, 0); + + let chunks = { + let chunks_ptr = bytes.as_ptr() as *const [u8; STRIDE]; + let chunks_len = bytes.len() / STRIDE; + unsafe { std::slice::from_raw_parts(chunks_ptr, chunks_len) } + }; + + // Writing into a vec of uninitialized `samples` is about 10% faster than + // cloning it or creating a default-initialized one and over-writing it. + // + // # Safety + // + // The performance boost comes with a cost though: + // At the end of the block each and every single item in + // `samples` needs to have been initialized, or else you get UB! + let samples = unsafe { + init_vec(chunks.len(), |samples| { + layout::deinterleaved_by(chunks, samples, channels, |chunk| { + MaybeUninit::new(B::read_i16(&chunk[..])) + }); + }) + }; + + let info = { + let frames = samples.len() / channels; + BufferInfo::new(channels, frames) + }; + Ok(Self { samples, info }) + } +} + +/// Creates a vec with deferred initialization. +/// +/// # Safety +/// +/// The closure `f` MUST initialize every single item in the provided slice. +unsafe fn init_vec(len: usize, f: F) -> Vec +where + MaybeUninit: Clone, + F: FnOnce(&mut [MaybeUninit]), +{ + // Create a vec of uninitialized values. + let mut vec: Vec> = vec![MaybeUninit::uninit(); len]; + + // Initialize values: + f(&mut vec[..]); + + // Take owner-ship away from `vec`: + let mut manually_drop: ManuallyDrop<_> = ManuallyDrop::new(vec); + + // Create vec of proper type from `vec`'s raw parts. + let ptr = manually_drop.as_mut_ptr() as *mut T; + let len = manually_drop.len(); + let cap = manually_drop.capacity(); + Vec::from_raw_parts(ptr, len, cap) +} + +#[cfg(test)] +mod tests { + use byteorder::NativeEndian; + + use super::*; + + #[test] + fn deinterleaved_from_interleaved() { + let channels = 3; + + let input_samples: Vec = vec![0, 5, 10, 1, 6, 11, 2, 7, 12, 3, 8, 13, 4, 9, 14]; + let input: Buffer = Buffer::new(input_samples, channels); + + let output = Buffer::::from(input); + + let actual = output.samples; + let expected = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]; + + assert_eq!(actual, expected); + } + + #[test] + fn interleaved_from_deinterleaved() { + let channels = 3; + + let input_samples: Vec = vec![0, 3, 6, 9, 12, 1, 4, 7, 10, 13, 2, 5, 8, 11, 14]; + let input: Buffer = Buffer::new(input_samples, channels); + + let output = Buffer::::from(input); + + let actual = output.samples; + let expected = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]; + + assert_eq!(actual, expected); + } + + #[test] + fn deinterleaved_from_deinterleaved_bytes() { + let channels = 3; + let stride = 2; + + let input_samples: Vec = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]; + let input_bytes: &[u8] = { + let bytes_ptr = input_samples.as_ptr() as *const u8; + let bytes_len = input_samples.len() * stride; + unsafe { std::slice::from_raw_parts(bytes_ptr, bytes_len) } + }; + + let output: Buffer = + FromBytes::::from_bytes::(input_bytes, channels).unwrap(); + + let actual = output.samples; + let expected = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]; + + assert_eq!(actual, expected); + } + + #[test] + fn deinterleaved_from_interleaved_bytes() { + let channels = 3; + let stride = 2; + + let input_samples: Vec = vec![0, 5, 10, 1, 6, 11, 2, 7, 12, 3, 8, 13, 4, 9, 14]; + let input_bytes: &[u8] = { + let bytes_ptr = input_samples.as_ptr() as *const u8; + let bytes_len = input_samples.len() * stride; + unsafe { std::slice::from_raw_parts(bytes_ptr, bytes_len) } + }; + + let output: Buffer = + FromBytes::::from_bytes::(input_bytes, channels).unwrap(); + + let actual = output.samples; + let expected = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]; + + assert_eq!(actual, expected); + } + + #[test] + fn interleaved_from_interleaved_bytes() { + let channels = 3; + let stride = 2; + + let input_samples: Vec = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]; + let input_bytes: &[u8] = { + let bytes_ptr = input_samples.as_ptr() as *const u8; + let bytes_len = input_samples.len() * stride; + unsafe { std::slice::from_raw_parts(bytes_ptr, bytes_len) } + }; + + let output: Buffer = + FromBytes::::from_bytes::(input_bytes, channels).unwrap(); + + let actual = output.samples; + let expected = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]; + + assert_eq!(actual, expected); + } + + #[test] + fn interleaved_from_deinterleaved_bytes() { + let channels = 3; + let stride = 2; + + let input_samples: Vec = vec![0, 3, 6, 9, 12, 1, 4, 7, 10, 13, 2, 5, 8, 11, 14]; + let input_bytes: &[u8] = { + let bytes_ptr = input_samples.as_ptr() as *const u8; + let bytes_len = input_samples.len() * stride; + unsafe { std::slice::from_raw_parts(bytes_ptr, bytes_len) } + }; + + let output: Buffer = + FromBytes::::from_bytes::(input_bytes, channels).unwrap(); + + let actual = output.samples; + let expected = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]; + + assert_eq!(actual, expected); + } +} diff --git a/src/audio/buffer/info.rs b/src/audio/buffer/info.rs new file mode 100644 index 0000000..fbb57fa --- /dev/null +++ b/src/audio/buffer/info.rs @@ -0,0 +1,122 @@ +use std::marker::PhantomData; + +use crate::audio::buffer::layout::{Deinterleaved, Interleaved}; + +#[derive(Eq, PartialEq, Debug)] +pub struct BufferInfo { + channels: usize, + frames: usize, + _phantom: PhantomData, +} + +impl BufferInfo { + pub fn new(channels: usize, frames: usize) -> Self { + Self { + channels, + frames, + _phantom: PhantomData, + } + } + + /// Get a reference to the buffer info's channels. + pub fn channels(&self) -> usize { + self.channels + } + + /// Set the buffer info's channels. + pub fn set_channels(&mut self, channels: usize) { + self.channels = channels; + } + + /// Get a reference to the buffer info's frames. + pub fn frames(&self) -> usize { + self.frames + } + + /// Set the buffer info's frames. + pub fn set_frames(&mut self, frames: usize) { + self.frames = frames; + } + + pub fn samples(&self) -> usize { + self.channels * self.frames + } +} + +impl Copy for BufferInfo {} + +impl Clone for BufferInfo { + fn clone(&self) -> Self { + Self { + channels: self.channels, + frames: self.frames, + _phantom: PhantomData, + } + } +} + +macro_rules! impl_from_buffer_info { + ($in_layout:ty => $out_layout:ty) => { + impl From> for BufferInfo<$out_layout> { + fn from(info: BufferInfo<$in_layout>) -> Self { + Self { + channels: info.channels, + frames: info.frames, + _phantom: PhantomData, + } + } + } + }; +} + +impl_from_buffer_info!(Interleaved => Deinterleaved); +impl_from_buffer_info!(Deinterleaved => Interleaved); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn new() { + let channels = 3; + let frames = 100; + + let interleaved = BufferInfo::::new(channels, frames); + + assert_eq!(interleaved.channels, channels); + assert_eq!(interleaved.frames, frames); + + let deinterleaved = BufferInfo::::new(channels, frames); + + assert_eq!(deinterleaved.channels, channels); + assert_eq!(deinterleaved.frames, frames); + } + + #[test] + fn clone() { + let channels = 3; + let frames = 100; + + let interleaved = BufferInfo::::new(channels, frames); + + assert_eq!(interleaved.clone(), interleaved); + + let deinterleaved = BufferInfo::::new(channels, frames); + + assert_eq!(deinterleaved.clone(), deinterleaved); + } + + #[test] + fn samples() { + let channels = 3; + let frames = 100; + + let interleaved = BufferInfo::::new(channels, frames); + + assert_eq!(interleaved.samples(), channels * frames); + + let deinterleaved = BufferInfo::::new(channels, frames); + + assert_eq!(deinterleaved.samples(), channels * frames); + } +} diff --git a/src/audio/buffer/layout.rs b/src/audio/buffer/layout.rs new file mode 100644 index 0000000..df51e8f --- /dev/null +++ b/src/audio/buffer/layout.rs @@ -0,0 +1,178 @@ +use crate::{audio::buffer::BufferInfo, sealed::Sealed}; + +pub trait BufferLayout: Sized + Sealed { + fn index_of(info: &BufferInfo, channel: usize, frame: usize) -> usize; +} + +#[derive(Eq, PartialEq, Copy, Clone, Debug)] +pub enum Deinterleaved {} + +impl Sealed for Deinterleaved {} + +impl BufferLayout for Deinterleaved { + #[inline] + fn index_of(info: &BufferInfo, channel: usize, frame: usize) -> usize { + (channel * info.frames()) + frame + } +} + +#[derive(Eq, PartialEq, Copy, Clone, Debug)] +pub enum Interleaved {} + +impl Sealed for Interleaved {} + +impl BufferLayout for Interleaved { + #[inline] + fn index_of(info: &BufferInfo, channel: usize, frame: usize) -> usize { + (frame * info.channels()) + channel + } +} + +#[cfg(test)] +#[inline(always)] +pub(crate) fn deinterleaved(input: &[T], output: &mut [T], channels: usize) +where + T: Copy, +{ + deinterleaved_by(input, output, channels, |sample| *sample) +} + +/// De-interleaves an interleaved slice using a memory access pattern +/// that's optimized for efficient cached (i.e. sequential) reads. +pub(crate) fn deinterleaved_by(input: &[T], output: &mut [U], channels: usize, f: F) +where + F: Fn(&T) -> U, +{ + assert_eq!(input.len(), output.len()); + assert_eq!(input.len() % channels, 0); + + let frames = input.len() / channels; + let mut interleaved_index = 0; + for frame in 0..frames { + let mut deinterleaved_index = frame; + for _channel in 0..channels { + output[deinterleaved_index] = f(&input[interleaved_index]); + interleaved_index += 1; + deinterleaved_index += frames; + } + } +} + +#[cfg(test)] +#[inline(always)] +pub(crate) fn interleaved(input: &[T], output: &mut [T], channels: usize) +where + T: Copy, +{ + interleaved_by(input, output, channels, |sample| *sample) +} + +/// Interleaves an de-interleaved slice using a memory access pattern +/// that's optimized for efficient cached (i.e. sequential) reads. +pub(crate) fn interleaved_by(input: &[T], output: &mut [U], channels: usize, f: F) +where + F: Fn(&T) -> U, +{ + assert_eq!(input.len(), output.len()); + assert_eq!(input.len() % channels, 0); + + let frames = input.len() / channels; + let mut deinterleaved_index = 0; + for channel in 0..channels { + let mut interleaved_index = channel; + for _frame in 0..frames { + output[interleaved_index] = f(&input[deinterleaved_index]); + deinterleaved_index += 1; + interleaved_index += channels; + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn interleaved_1_channel() { + let input: Vec<_> = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + let mut output = vec![0; input.len()]; + let channels = 1; + + interleaved(&input[..], &mut output[..], channels); + + let actual = output; + let expected = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + + assert_eq!(actual, expected); + } + + #[test] + fn deinterleaved_1_channel() { + let input: Vec<_> = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + let mut output = vec![0; input.len()]; + let channels = 1; + + deinterleaved(&input[..], &mut output[..], channels); + + let actual = output; + let expected = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + + assert_eq!(actual, expected); + } + + #[test] + fn interleaved_2_channel() { + let input: Vec<_> = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + let mut output = vec![0; input.len()]; + let channels = 2; + + interleaved(&input[..], &mut output[..], channels); + + let actual = output; + let expected = vec![0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15]; + + assert_eq!(actual, expected); + } + + #[test] + fn deinterleaved_2_channel() { + let input: Vec<_> = vec![0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15]; + let mut output = vec![0; input.len()]; + let channels = 2; + + deinterleaved(&input[..], &mut output[..], channels); + + let actual = output; + let expected = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + + assert_eq!(actual, expected); + } + + #[test] + fn interleaved_3_channel() { + let input: Vec<_> = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]; + let mut output = vec![0; input.len()]; + let channels = 3; + + interleaved(&input[..], &mut output[..], channels); + + let actual = output; + let expected = vec![0, 5, 10, 1, 6, 11, 2, 7, 12, 3, 8, 13, 4, 9, 14]; + + assert_eq!(actual, expected); + } + + #[test] + fn deinterleaved_3_channel() { + let input: Vec<_> = vec![0, 5, 10, 1, 6, 11, 2, 7, 12, 3, 8, 13, 4, 9, 14]; + let mut output = vec![0; input.len()]; + let channels = 3; + + deinterleaved(&input[..], &mut output[..], channels); + + let actual = output; + let expected = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]; + + assert_eq!(actual, expected); + } +} diff --git a/src/audio/sample.rs b/src/audio/sample.rs new file mode 100644 index 0000000..dbc58b2 --- /dev/null +++ b/src/audio/sample.rs @@ -0,0 +1,198 @@ +use std::io::{Cursor, Read}; + +use byteorder::{ByteOrder, ReadBytesExt}; + +#[cfg(test)] +use nearly_eq::NearlyEq; + +#[derive(Eq, PartialEq, Copy, Clone, Default, Debug)] +#[repr(transparent)] +pub struct Sample(Raw); + +impl From for Sample { + #[inline] + fn from(raw: i16) -> Self { + Self(raw) + } +} + +impl From for Sample { + #[inline] + fn from(raw: f32) -> Self { + Self(raw.clamp(-1.0, 1.0)) + } +} + +macro_rules! impl_from_sample_for_raw { + ($raw:ty) => { + impl From> for $raw { + #[inline] + fn from(sample: Sample<$raw>) -> $raw { + sample.0 + } + } + }; +} + +impl_from_sample_for_raw!(i16); +impl_from_sample_for_raw!(f32); + +// impl From> for Sample { +// #[inline] +// fn from(sample: Sample) -> Self { +// // Fast but imprecise approach: +// // Perform crude but fast upsample by bit-shifting the raw value: +// Self::from((sample.0 as i64) << 16) + +// // Slow but precise approach: +// // Perform a proper but expensive lerp from +// // i16::MIN..i16::MAX to i32::MIN..i32::MAX: + +// // let value = sample.0 as i64; + +// // let from = if value <= 0 { i16::MIN } else { i16::MAX } as i64; +// // let to = if value <= 0 { i32::MIN } else { i32::MAX } as i64; + +// // Self::from((value * to + from / 2) / from) +// } +// } + +impl From> for Sample { + #[inline] + fn from(sample: Sample) -> Self { + let divisor = if sample.0 < 0 { + i16::MIN as f32 + } else { + i16::MAX as f32 + } + .abs(); + Self::from((sample.0 as f32) / divisor) + } +} + +impl From> for Sample { + #[inline] + fn from(sample: Sample) -> Self { + let multiplier = if sample.0 < 0.0 { + i16::MIN as f32 + } else { + i16::MAX as f32 + } + .abs(); + Self::from((sample.0 * multiplier) as i16) + } +} + +trait FromBytes: Sized { + fn from_reader(reader: &mut R) -> Result; + + fn from_bytes(bytes: &[u8]) -> Result { + let mut cursor = Cursor::new(bytes); + Self::from_reader::(&mut cursor) + } +} + +impl FromBytes for Sample { + fn from_reader(reader: &mut R) -> Result { + reader.read_i16::().map(Self::from) + } +} + +impl FromBytes for Sample { + fn from_reader(reader: &mut R) -> Result { + reader.read_f32::().map(Self::from) + } +} + +#[cfg(test)] +impl NearlyEq for Sample +where + Raw: NearlyEq, +{ + fn eps() -> Raw { + Raw::eps() + } + + fn eq(&self, other: &Self, eps: &Raw) -> bool { + NearlyEq::eq(&self.0, &other.0, eps) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use nearly_eq::assert_nearly_eq; + + #[test] + fn sample_i16_from_i16() { + // i16: + assert_eq!(Sample::::from(i16::MIN).0, i16::MIN); + assert_eq!(Sample::::from(i16::MIN / 2).0, i16::MIN / 2); + assert_eq!(Sample::::from(0).0, 0); + assert_eq!(Sample::::from(i16::MAX / 2).0, i16::MAX / 2); + assert_eq!(Sample::::from(i16::MAX).0, i16::MAX); + } + + #[test] + fn sample_f32_from_f32() { + assert_eq!(Sample::::from(-1.0).0, -1.0); + assert_eq!(Sample::::from(-0.5).0, -0.5); + assert_eq!(Sample::::from(0.0).0, 0.0); + assert_eq!(Sample::::from(0.5).0, 0.5); + assert_eq!(Sample::::from(1.0).0, 1.0); + + // For any values outside of -1.0..=1.0 we expect clamping: + assert_eq!(Sample::::from(f32::MIN).0, -1.0); + assert_eq!(Sample::::from(f32::MAX).0, 1.0); + } + + #[test] + fn sample_i16_from_sample_f32() { + assert_nearly_eq!( + Sample::::from(Sample::::from(-1.0)), + Sample::from(i16::MIN) + ); + assert_nearly_eq!( + Sample::::from(Sample::::from(-0.5)), + Sample::from(i16::MIN / 2) + ); + assert_nearly_eq!( + Sample::::from(Sample::::from(0.0)), + Sample::from(0) + ); + assert_nearly_eq!( + Sample::::from(Sample::::from(0.5)), + Sample::from(i16::MAX / 2) + ); + assert_nearly_eq!( + Sample::::from(Sample::::from(1.0)), + Sample::from(i16::MAX) + ); + } + + #[test] + fn sample_f32_from_sample_i16() { + assert_nearly_eq!( + Sample::::from(Sample::::from(i16::MIN)), + Sample::from(-1.0) + ); + assert_nearly_eq!( + Sample::::from(Sample::::from(i16::MIN / 2)), + Sample::from(-0.5) + ); + assert_nearly_eq!( + Sample::::from(Sample::::from(0)), + Sample::from(0.0) + ); + assert_nearly_eq!( + Sample::::from(Sample::::from(i16::MAX / 2)), + Sample::from(0.5), + 0.0001 // rounding error due to i16::MAX being odd + ); + assert_nearly_eq!( + Sample::::from(Sample::::from(i16::MAX)), + Sample::from(1.0) + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index 31e1bb2..90cee1d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,5 @@ -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } +pub mod audio; + +mod sealed { + pub trait Sealed {} }