From 76004429c4594a2dca2633413ee197ea91b70429 Mon Sep 17 00:00:00 2001 From: Miriam Zimmerman Date: Tue, 22 Oct 2024 11:15:33 -0400 Subject: [PATCH] Fix stereo -> mono downmixing. Rather than summing the two channels, average them. This prevents integer overflow and clipping or needlessly increasing volume. --- Cargo.toml | 1 + src/backend/buffer_manager.rs | 31 ++++++++++++++++++++++++++++--- src/lib.rs | 2 ++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 95ebb14f..7c7ca582 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ cubeb-backend = "0.13" float-cmp = "0.6" libc = "0.2" mach = "0.3" +num = "0.4.3" audio-mixer = "0.2" ringbuf = "0.2.6" triple_buffer = "5.0.5" diff --git a/src/backend/buffer_manager.rs b/src/backend/buffer_manager.rs index 6f1c299b..4e76d4bd 100644 --- a/src/backend/buffer_manager.rs +++ b/src/backend/buffer_manager.rs @@ -4,6 +4,7 @@ use std::os::raw::c_void; use std::slice; use cubeb_backend::SampleFormat; +use num::cast::AsPrimitive; use super::ringbuf::RingBuffer; @@ -36,12 +37,25 @@ fn drop_first_n_channels_in_place( } } +trait DataType: AsPrimitive { + fn from_f32(v: f32) -> Self; +} + +impl> DataType for T +where + f32: AsPrimitive, +{ + fn from_f32(v: f32) -> T { + v.as_() + } +} + // It can be that the a stereo microphone is in use, but the user asked for mono input. In this // particular case, downmix the stereo pair into a mono channel. In all other cases, simply drop // the remaining channels before appending to the ringbuffer, becauses there is no right or wrong // way to do this, unlike with the output side, where proper channel matrixing can be done. // Return the number of valid samples in the buffer. -fn remix_or_drop_channels>( +fn remix_or_drop_channels( input_channels: usize, output_channels: usize, data: &mut [T], @@ -56,7 +70,8 @@ fn remix_or_drop_channels>( if input_channels == 2 && output_channels == 1 { let mut read_idx = 0; for (write_idx, _) in (0..frame_count).enumerate() { - data[write_idx] = data[read_idx] + data[read_idx + 1]; + let avg = (data[read_idx].as_() + data[read_idx + 1].as_()) / 2.0; + data[write_idx] = DataType::from_f32(avg); read_idx += 2; } return output_channels * frame_count; @@ -76,7 +91,7 @@ fn remix_or_drop_channels>( output_channels * frame_count } -fn process_data>( +fn process_data( data: *mut c_void, frame_count: usize, input_channel_count: usize, @@ -353,3 +368,13 @@ impl fmt::Debug for BufferManager { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn remix_stereo_ints() { + let mut data = [i16::MAX / 2 + 1, i16::MAX / 2 + 1]; + assert_eq!(remix_or_drop_channels(2, 1, &mut data, 1), 1); + } +} diff --git a/src/lib.rs b/src/lib.rs index bf1ae96f..8eda3dd2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,8 @@ extern crate cubeb_backend; extern crate float_cmp; extern crate mach; +extern crate num; + mod backend; mod capi;