use std::error;
use std::fmt;
use crate::audio_processing_impl::AudioProcessingImpl;
use crate::config::{Config, PlayoutAudioDeviceInfo, RuntimeSetting};
use crate::stats::AudioProcessingStats;
use crate::stream_config::StreamConfig;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error {
InvalidSampleRate {
rate: u32,
},
InvalidChannelCount {
count: u16,
},
ChannelMismatch {
input: u16,
output: u16,
},
StreamParameterClamped,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidSampleRate { rate } => {
write!(
f,
"invalid sample rate {rate} (must be {MIN_SAMPLE_RATE}..={MAX_SAMPLE_RATE})"
)
}
Self::InvalidChannelCount { count } => {
write!(f, "invalid channel count {count}")
}
Self::ChannelMismatch { input, output } => {
write!(
f,
"channel mismatch: output ({output}) must be 1 or match input ({input})"
)
}
Self::StreamParameterClamped => write!(f, "stream parameter was clamped"),
}
}
}
impl error::Error for Error {}
const MAX_SAMPLE_RATE: u32 = 384_000;
const MIN_SAMPLE_RATE: u32 = 8_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum FormatValidity {
ValidAndSupported,
ValidButUnsupportedRate,
InvalidChannels,
}
impl FormatValidity {
fn is_interpretable(self) -> bool {
!matches!(self, Self::InvalidChannels)
}
fn to_error(self, config: &StreamConfig) -> Option<Error> {
match self {
Self::ValidAndSupported => None,
Self::ValidButUnsupportedRate => Some(Error::InvalidSampleRate {
rate: config.sample_rate_hz(),
}),
Self::InvalidChannels => Some(Error::InvalidChannelCount {
count: config.num_channels(),
}),
}
}
}
fn validate_audio_format(config: &StreamConfig) -> FormatValidity {
if config.num_channels() == 0 {
return FormatValidity::InvalidChannels;
}
let rate = config.sample_rate_hz();
if !(MIN_SAMPLE_RATE..=MAX_SAMPLE_RATE).contains(&rate) {
return FormatValidity::ValidButUnsupportedRate;
}
FormatValidity::ValidAndSupported
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ErrorOutputOption {
DoNothing,
Silence,
CopyOfFirstChannel,
ExactCopy,
}
fn choose_error_output_option(
input_config: &StreamConfig,
output_config: &StreamConfig,
) -> Result<(), (Error, ErrorOutputOption)> {
let input_validity = validate_audio_format(input_config);
let output_validity = validate_audio_format(output_config);
if input_validity == FormatValidity::ValidAndSupported
&& output_validity == FormatValidity::ValidAndSupported
{
let out_ch = output_config.num_channels();
let in_ch = input_config.num_channels();
if out_ch == 1 || out_ch == in_ch {
return Ok(());
}
}
let error = if let Some(e) = input_validity.to_error(input_config) {
e
} else if let Some(e) = output_validity.to_error(output_config) {
e
} else {
Error::ChannelMismatch {
input: input_config.num_channels(),
output: output_config.num_channels(),
}
};
let option = if !output_validity.is_interpretable() {
ErrorOutputOption::DoNothing
} else if !input_validity.is_interpretable() {
ErrorOutputOption::Silence
} else if input_config.sample_rate_hz() != output_config.sample_rate_hz() {
ErrorOutputOption::Silence
} else if input_config.num_channels() != output_config.num_channels() {
ErrorOutputOption::CopyOfFirstChannel
} else {
ErrorOutputOption::ExactCopy
};
Err((error, option))
}
fn handle_unsupported_formats_f32(
src: &[&[f32]],
input_config: &StreamConfig,
output_config: &StreamConfig,
dest: &mut [&mut [f32]],
) -> Result<(), Error> {
let (error, option) = match choose_error_output_option(input_config, output_config) {
Ok(()) => return Ok(()),
Err(pair) => pair,
};
let out_frames = output_config.num_frames();
let out_ch = dest.len();
match option {
ErrorOutputOption::DoNothing => {}
ErrorOutputOption::Silence => {
for ch_buf in dest.iter_mut().take(out_ch) {
let len = ch_buf.len().min(out_frames);
ch_buf[..len].fill(0.0);
}
}
ErrorOutputOption::CopyOfFirstChannel => {
if let Some(first_in) = src.first() {
for ch_buf in dest.iter_mut().take(out_ch) {
let len = ch_buf.len().min(out_frames).min(first_in.len());
ch_buf[..len].copy_from_slice(&first_in[..len]);
}
}
}
ErrorOutputOption::ExactCopy => {
for (out_ch_buf, in_ch_buf) in dest.iter_mut().zip(src.iter()) {
let len = out_ch_buf.len().min(out_frames).min(in_ch_buf.len());
out_ch_buf[..len].copy_from_slice(&in_ch_buf[..len]);
}
}
}
Err(error)
}
fn handle_unsupported_formats_i16(
src: &[i16],
input_config: &StreamConfig,
output_config: &StreamConfig,
dest: &mut [i16],
) -> Result<(), Error> {
let (error, option) = match choose_error_output_option(input_config, output_config) {
Ok(()) => return Ok(()),
Err(pair) => pair,
};
let out_frames = output_config.num_frames();
let out_channels = output_config.num_channels() as usize;
let in_channels = input_config.num_channels() as usize;
let out_samples = out_frames * out_channels;
match option {
ErrorOutputOption::DoNothing => {}
ErrorOutputOption::Silence => {
let len = dest.len().min(out_samples);
dest[..len].fill(0);
}
ErrorOutputOption::CopyOfFirstChannel => {
for i in 0..out_frames {
let src_idx = i * in_channels;
let sample = if src_idx < src.len() { src[src_idx] } else { 0 };
for ch in 0..out_channels {
let dest_idx = i * out_channels + ch;
if dest_idx < dest.len() {
dest[dest_idx] = sample;
}
}
}
}
ErrorOutputOption::ExactCopy => {
let len = dest.len().min(out_samples).min(src.len());
dest[..len].copy_from_slice(&src[..len]);
}
}
Err(error)
}
#[derive(Debug)]
pub struct AudioProcessingBuilder {
config: Config,
capture_config: StreamConfig,
render_config: StreamConfig,
echo_detector: bool,
}
impl Default for AudioProcessingBuilder {
fn default() -> Self {
Self {
config: Config::default(),
capture_config: DEFAULT_STREAM_CONFIG,
render_config: DEFAULT_STREAM_CONFIG,
echo_detector: false,
}
}
}
const DEFAULT_STREAM_CONFIG: StreamConfig = StreamConfig::new(16000, 1);
impl AudioProcessingBuilder {
pub fn config(mut self, config: Config) -> Self {
self.config = config;
self
}
pub fn capture_config(mut self, config: StreamConfig) -> Self {
self.capture_config = config;
self
}
pub fn render_config(mut self, config: StreamConfig) -> Self {
self.render_config = config;
self
}
pub fn echo_detector(mut self, enabled: bool) -> Self {
self.echo_detector = enabled;
self
}
pub fn build(self) -> AudioProcessing {
let mut inner = AudioProcessingImpl::with_config(self.config);
if self.echo_detector {
inner.set_echo_detector();
}
use crate::audio_processing_impl::ProcessingConfig;
let processing_config = ProcessingConfig {
input_stream: self.capture_config,
output_stream: self.capture_config,
reverse_input_stream: self.render_config,
reverse_output_stream: self.render_config,
};
inner.initialize_with_config(processing_config);
AudioProcessing {
inner,
capture_config: self.capture_config,
render_config: self.render_config,
stream_delay_ms: 0,
was_stream_delay_set: false,
}
}
}
#[derive(Debug)]
pub struct AudioProcessing {
pub(crate) inner: AudioProcessingImpl,
pub(crate) capture_config: StreamConfig,
pub(crate) render_config: StreamConfig,
stream_delay_ms: i32,
was_stream_delay_set: bool,
}
impl AudioProcessing {
pub fn new() -> Self {
Self::builder().build()
}
pub fn builder() -> AudioProcessingBuilder {
AudioProcessingBuilder::default()
}
pub fn apply_config(&mut self, config: Config) {
self.inner.apply_config(config);
}
pub fn config(&self) -> &Config {
self.inner.config()
}
pub fn initialize(
&mut self,
input: StreamConfig,
output: StreamConfig,
reverse_input: StreamConfig,
reverse_output: StreamConfig,
) {
use crate::audio_processing_impl::ProcessingConfig;
self.capture_config = input;
self.render_config = reverse_input;
self.inner.initialize_with_config(ProcessingConfig {
input_stream: input,
output_stream: output,
reverse_input_stream: reverse_input,
reverse_output_stream: reverse_output,
});
}
pub fn set_capture_pre_gain(&mut self, gain: f32) {
self.inner
.set_runtime_setting(RuntimeSetting::CapturePreGain(gain));
}
pub fn set_capture_post_gain(&mut self, gain: f32) {
self.inner
.set_runtime_setting(RuntimeSetting::CapturePostGain(gain));
}
pub fn set_capture_fixed_post_gain(&mut self, gain_db: f32) {
self.inner
.set_runtime_setting(RuntimeSetting::CaptureFixedPostGain(gain_db));
}
pub fn set_playout_volume(&mut self, volume: i32) {
self.inner
.set_runtime_setting(RuntimeSetting::PlayoutVolumeChange(volume));
}
pub fn set_playout_audio_device(&mut self, id: i32, max_volume: i32) {
self.inner
.set_runtime_setting(RuntimeSetting::PlayoutAudioDeviceChange(
PlayoutAudioDeviceInfo { id, max_volume },
));
}
pub fn set_capture_output_used(&mut self, used: bool) {
self.inner
.set_runtime_setting(RuntimeSetting::CaptureOutputUsed(used));
}
pub fn statistics(&self) -> &AudioProcessingStats {
self.inner.get_statistics()
}
pub fn set_stream_analog_level(&mut self, level: i32) {
self.inner.set_applied_input_volume(level);
}
pub fn recommended_stream_analog_level(&self) -> i32 {
const FALLBACK_INPUT_VOLUME: i32 = 255;
self.inner
.recommended_input_volume()
.or_else(|| self.inner.applied_input_volume())
.unwrap_or(FALLBACK_INPUT_VOLUME)
}
pub fn set_stream_delay_ms(&mut self, delay: i32) -> Result<(), Error> {
self.was_stream_delay_set = true;
let mut clamped = delay;
let mut warning = false;
if clamped < 0 {
clamped = 0;
warning = true;
}
if clamped > 500 {
clamped = 500;
warning = true;
}
self.stream_delay_ms = clamped;
self.inner.set_stream_delay_ms(clamped);
if warning {
Err(Error::StreamParameterClamped)
} else {
Ok(())
}
}
pub fn stream_delay_ms(&self) -> i32 {
self.stream_delay_ms
}
pub fn proc_sample_rate_hz(&self) -> usize {
self.inner.proc_sample_rate_hz()
}
pub fn process_capture_f32(
&mut self,
src: &[&[f32]],
dest: &mut [&mut [f32]],
) -> Result<(), Error> {
let cfg = self.capture_config;
self.process_capture_f32_with_config(src, &cfg, &cfg, dest)
}
pub fn process_capture_f32_with_config(
&mut self,
src: &[&[f32]],
input_config: &StreamConfig,
output_config: &StreamConfig,
dest: &mut [&mut [f32]],
) -> Result<(), Error> {
handle_unsupported_formats_f32(src, input_config, output_config, dest)?;
self.inner
.process_stream(src, input_config, output_config, dest);
Ok(())
}
pub fn process_render_f32(
&mut self,
src: &[&[f32]],
dest: &mut [&mut [f32]],
) -> Result<(), Error> {
let cfg = self.render_config;
self.process_render_f32_with_config(src, &cfg, &cfg, dest)
}
pub fn process_render_f32_with_config(
&mut self,
src: &[&[f32]],
input_config: &StreamConfig,
output_config: &StreamConfig,
dest: &mut [&mut [f32]],
) -> Result<(), Error> {
handle_unsupported_formats_f32(src, input_config, output_config, dest)?;
self.inner
.process_reverse_stream(src, input_config, output_config, dest);
Ok(())
}
pub fn process_capture_i16(&mut self, src: &[i16], dest: &mut [i16]) -> Result<(), Error> {
let cfg = self.capture_config;
self.process_capture_i16_with_config(src, &cfg, &cfg, dest)
}
pub fn process_capture_i16_with_config(
&mut self,
src: &[i16],
input_config: &StreamConfig,
output_config: &StreamConfig,
dest: &mut [i16],
) -> Result<(), Error> {
handle_unsupported_formats_i16(src, input_config, output_config, dest)?;
self.inner
.process_stream_i16(src, input_config, output_config, dest);
Ok(())
}
pub fn process_render_i16(&mut self, src: &[i16], dest: &mut [i16]) -> Result<(), Error> {
let cfg = self.render_config;
self.process_render_i16_with_config(src, &cfg, &cfg, dest)
}
pub fn process_render_i16_with_config(
&mut self,
src: &[i16],
input_config: &StreamConfig,
output_config: &StreamConfig,
dest: &mut [i16],
) -> Result<(), Error> {
handle_unsupported_formats_i16(src, input_config, output_config, dest)?;
self.inner
.process_reverse_stream_i16(src, input_config, output_config, dest);
Ok(())
}
}
impl Default for AudioProcessing {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::{
AdaptiveDigital, EchoCanceller, GainController2, HighPassFilter, NoiseSuppression,
NoiseSuppressionLevel, Pipeline, PreAmplifier,
};
const fn send<T: Send>() {}
const _: () = send::<AudioProcessing>();
const fn sync<T: Sync>() {}
const _: () = sync::<AudioProcessing>();
#[test]
fn builder_creates_default_instance() {
let apm = AudioProcessing::builder().build();
assert_eq!(apm.stream_delay_ms(), 0);
assert_eq!(apm.recommended_stream_analog_level(), 255);
}
#[test]
fn builder_with_config() {
let config = Config {
echo_canceller: Some(EchoCanceller::default()),
noise_suppression: Some(NoiseSuppression::default()),
..Default::default()
};
let _apm = AudioProcessing::builder().config(config).build();
}
#[test]
fn set_stream_delay_clamps_negative() {
let mut apm = AudioProcessing::new();
let result = apm.set_stream_delay_ms(-10);
assert_eq!(result, Err(Error::StreamParameterClamped));
assert_eq!(apm.stream_delay_ms(), 0);
}
#[test]
fn set_stream_delay_clamps_high() {
let mut apm = AudioProcessing::new();
let result = apm.set_stream_delay_ms(600);
assert_eq!(result, Err(Error::StreamParameterClamped));
assert_eq!(apm.stream_delay_ms(), 500);
}
#[test]
fn set_stream_delay_valid_range() {
let mut apm = AudioProcessing::new();
assert!(apm.set_stream_delay_ms(50).is_ok());
assert_eq!(apm.stream_delay_ms(), 50);
}
#[test]
fn process_capture_f32_with_config_validates_bad_rate() {
let mut apm = AudioProcessing::new();
let input_config = StreamConfig::new(100, 1); let output_config = StreamConfig::new(16000, 1);
let src_data = [0.0f32; 1];
let src: &[&[f32]] = &[&src_data];
let mut dest_data = [0.0f32; 160];
let dest: &mut [&mut [f32]] = &mut [&mut dest_data];
let result = apm.process_capture_f32_with_config(src, &input_config, &output_config, dest);
assert_eq!(result, Err(Error::InvalidSampleRate { rate: 100 }));
}
#[test]
fn process_capture_f32_with_config_validates_bad_channels() {
let mut apm = AudioProcessing::new();
let input_config = StreamConfig::new(16000, 0); let output_config = StreamConfig::new(16000, 1);
let src: &[&[f32]] = &[];
let mut dest_data = [0.0f32; 160];
let dest: &mut [&mut [f32]] = &mut [&mut dest_data];
let result = apm.process_capture_f32_with_config(src, &input_config, &output_config, dest);
assert_eq!(result, Err(Error::InvalidChannelCount { count: 0 }));
}
#[test]
fn process_capture_f32_with_config_validates_channel_mismatch() {
let mut apm = AudioProcessing::new();
let input_config = StreamConfig::new(16000, 2);
let output_config = StreamConfig::new(16000, 3); let src_data = [0.0f32; 160];
let src: &[&[f32]] = &[&src_data, &src_data];
let mut dest0 = [0.0f32; 160];
let mut dest1 = [0.0f32; 160];
let mut dest2 = [0.0f32; 160];
let dest: &mut [&mut [f32]] = &mut [&mut dest0, &mut dest1, &mut dest2];
let result = apm.process_capture_f32_with_config(src, &input_config, &output_config, dest);
assert_eq!(
result,
Err(Error::ChannelMismatch {
input: 2,
output: 3
})
);
}
#[test]
fn process_capture_i16_with_config_accepts_non_native_rate() {
let mut apm = AudioProcessing::new();
let input_config = StreamConfig::new(44100, 1);
let output_config = StreamConfig::new(44100, 1);
let src = [0i16; 441];
let mut dest = [0i16; 441];
let result =
apm.process_capture_i16_with_config(&src, &input_config, &output_config, &mut dest);
assert!(result.is_ok());
}
#[test]
fn process_capture_f32_with_config_silence_passthrough() {
let mut apm = AudioProcessing::new();
let config = StreamConfig::new(16000, 1);
let src_data = [0.0f32; 160];
let src: &[&[f32]] = &[&src_data];
let mut dest_data = [1.0f32; 160]; let dest: &mut [&mut [f32]] = &mut [&mut dest_data];
let result = apm.process_capture_f32_with_config(src, &config, &config, dest);
assert!(result.is_ok());
for &sample in dest[0].iter() {
assert!(sample.abs() < 1e-6, "expected silence, got {sample}",);
}
}
#[test]
fn process_capture_i16_with_config_silence_passthrough() {
let mut apm = AudioProcessing::new();
let config = StreamConfig::new(16000, 1);
let src = [0i16; 160];
let mut dest = [100i16; 160]; let result = apm.process_capture_i16_with_config(&src, &config, &config, &mut dest);
assert!(result.is_ok());
for &sample in dest.iter() {
assert_eq!(sample, 0, "expected silence");
}
}
#[test]
fn process_render_f32_with_config_passthrough() {
let mut apm = AudioProcessing::new();
let config = StreamConfig::new(16000, 1);
let src_data = [0.5f32; 160];
let src: &[&[f32]] = &[&src_data];
let mut dest_data = [0.0f32; 160];
let dest: &mut [&mut [f32]] = &mut [&mut dest_data];
let result = apm.process_render_f32_with_config(src, &config, &config, dest);
assert!(result.is_ok());
}
#[test]
fn error_display() {
assert_eq!(
format!("{}", Error::InvalidSampleRate { rate: 100 }),
"invalid sample rate 100 (must be 8000..=384000)"
);
assert_eq!(
format!("{}", Error::InvalidChannelCount { count: 0 }),
"invalid channel count 0"
);
assert_eq!(
format!(
"{}",
Error::ChannelMismatch {
input: 2,
output: 3
}
),
"channel mismatch: output (3) must be 1 or match input (2)"
);
assert_eq!(
format!("{}", Error::StreamParameterClamped),
"stream parameter was clamped"
);
}
#[test]
fn format_handling_f32_error_and_silence_rate_mismatch() {
let mut apm = AudioProcessing::new();
let input_config = StreamConfig::new(7900, 1);
let output_config = StreamConfig::new(16000, 1);
let src_data = [1.0f32; 79]; let src: &[&[f32]] = &[&src_data];
let mut dest_data = [42.0f32; 160];
let dest: &mut [&mut [f32]] = &mut [&mut dest_data];
let result = apm.process_capture_f32_with_config(src, &input_config, &output_config, dest);
assert_eq!(result, Err(Error::InvalidSampleRate { rate: 7900 }));
for &sample in dest[0].iter() {
assert_eq!(sample, 0.0, "expected silence");
}
}
#[test]
fn format_handling_f32_error_and_copy_of_first_channel() {
let mut apm = AudioProcessing::new();
let input_config = StreamConfig::new(16000, 3);
let output_config = StreamConfig::new(16000, 2);
let ch0 = [0.5f32; 160];
let ch1 = [0.3f32; 160];
let ch2 = [0.1f32; 160];
let src: &[&[f32]] = &[&ch0, &ch1, &ch2];
let mut dest0 = [42.0f32; 160];
let mut dest1 = [42.0f32; 160];
let dest: &mut [&mut [f32]] = &mut [&mut dest0, &mut dest1];
let result = apm.process_capture_f32_with_config(src, &input_config, &output_config, dest);
assert_eq!(
result,
Err(Error::ChannelMismatch {
input: 3,
output: 2
})
);
for (i, (&d0, &d1)) in dest[0].iter().zip(dest[1].iter()).enumerate().take(160) {
assert_eq!(d0, 0.5, "dest[0][{i}] should be ch0 value");
assert_eq!(d1, 0.5, "dest[1][{i}] should be ch0 value");
}
}
#[test]
fn format_handling_f32_error_and_exact_copy() {
let mut apm = AudioProcessing::new();
let input_config = StreamConfig::new(7900, 1);
let output_config = StreamConfig::new(7900, 1);
let src_data: Vec<f32> = (0..79).map(|i| i as f32 * 0.01).collect();
let src: &[&[f32]] = &[&src_data];
let mut dest_data = [42.0f32; 79];
let dest: &mut [&mut [f32]] = &mut [&mut dest_data];
let result = apm.process_capture_f32_with_config(src, &input_config, &output_config, dest);
assert_eq!(result, Err(Error::InvalidSampleRate { rate: 7900 }));
for i in 0..79 {
assert_eq!(dest[0][i], src_data[i], "sample {i} should be exact copy");
}
}
#[test]
fn format_handling_i16_error_and_silence() {
let mut apm = AudioProcessing::new();
let input_config = StreamConfig::new(7900, 1);
let output_config = StreamConfig::new(16000, 1);
let src = [100i16; 79];
let mut dest = [42i16; 160];
let result =
apm.process_capture_i16_with_config(&src, &input_config, &output_config, &mut dest);
assert_eq!(result, Err(Error::InvalidSampleRate { rate: 7900 }));
for &sample in dest.iter() {
assert_eq!(sample, 0, "expected silence");
}
}
#[test]
fn format_handling_i16_error_and_broadcast_first_channel() {
let mut apm = AudioProcessing::new();
let input_config = StreamConfig::new(16000, 3);
let output_config = StreamConfig::new(16000, 2);
let mut src = vec![0i16; 160 * 3];
for i in 0..160 {
src[i * 3] = (i * 3) as i16; src[i * 3 + 1] = 1000; src[i * 3 + 2] = 2000; }
let mut dest = vec![42i16; 160 * 2];
let result =
apm.process_capture_i16_with_config(&src, &input_config, &output_config, &mut dest);
assert_eq!(
result,
Err(Error::ChannelMismatch {
input: 3,
output: 2
})
);
for i in 0..160 {
let expected = (i * 3) as i16;
assert_eq!(
dest[i * 2],
expected,
"dest[{}] should be ch0 frame {i}",
i * 2
);
assert_eq!(
dest[i * 2 + 1],
expected,
"dest[{}] should be ch0 frame {i}",
i * 2 + 1
);
}
}
#[test]
fn format_handling_i16_error_and_exact_copy() {
let mut apm = AudioProcessing::new();
let input_config = StreamConfig::new(7900, 1);
let output_config = StreamConfig::new(7900, 1);
let src: Vec<i16> = (0..79).map(|i| i as i16).collect();
let mut dest = vec![42i16; 79];
let result =
apm.process_capture_i16_with_config(&src, &input_config, &output_config, &mut dest);
assert_eq!(result, Err(Error::InvalidSampleRate { rate: 7900 }));
for i in 0..79 {
assert_eq!(dest[i], src[i], "sample {i} should be exact copy");
}
}
#[test]
fn format_handling_valid_formats_proceed() {
let input_config = StreamConfig::new(16000, 2);
let output_config = StreamConfig::new(16000, 1);
let result = choose_error_output_option(&input_config, &output_config);
assert!(result.is_ok());
}
#[test]
fn format_handling_zero_channels_is_invalid() {
let config = StreamConfig::new(16000, 0);
assert_eq!(
validate_audio_format(&config),
FormatValidity::InvalidChannels
);
}
#[test]
fn format_handling_unsupported_rate() {
let config = StreamConfig::new(7900, 1);
assert_eq!(
validate_audio_format(&config),
FormatValidity::ValidButUnsupportedRate
);
}
fn sawtooth_frame(sample_rate_hz: usize, num_channels: usize) -> Vec<f32> {
let num_frames = sample_rate_hz / 100;
let mut data = vec![0.0f32; num_frames * num_channels];
for i in 0..num_frames {
let sample = ((i % 100) as f32 / 100.0) * 2.0 - 1.0;
for ch in 0..num_channels {
data[i * num_channels + ch] = sample * 0.5;
}
}
data
}
fn deinterleave(data: &[f32], num_channels: usize) -> Vec<Vec<f32>> {
let num_frames = data.len() / num_channels;
let mut channels = vec![vec![0.0f32; num_frames]; num_channels];
for i in 0..num_frames {
for ch in 0..num_channels {
channels[ch][i] = data[i * num_channels + ch];
}
}
channels
}
#[test]
fn no_processing_when_all_disabled_float() {
let mut apm = AudioProcessing::new();
let config = StreamConfig::new(16000, 1);
let num_frames = 160;
let src_data: Vec<f32> = (0..num_frames)
.map(|i| (i as f32 / num_frames as f32) * 2.0 - 1.0)
.collect();
let src: &[&[f32]] = &[&src_data];
let mut dest_data = vec![0.0f32; num_frames];
let dest: &mut [&mut [f32]] = &mut [&mut dest_data];
apm.process_capture_f32_with_config(src, &config, &config, dest)
.unwrap();
for i in 0..num_frames {
assert_eq!(
src_data[i], dest[0][i],
"sample {i} mismatch: src={}, dest={}",
src_data[i], dest[0][i]
);
}
}
#[test]
fn no_processing_when_all_disabled_float_reverse() {
let mut apm = AudioProcessing::new();
let config = StreamConfig::new(16000, 1);
let num_frames = 160;
let src_data: Vec<f32> = (0..num_frames)
.map(|i| (i as f32 / num_frames as f32) * 2.0 - 1.0)
.collect();
let src: &[&[f32]] = &[&src_data];
let mut dest_data = vec![0.0f32; num_frames];
let dest: &mut [&mut [f32]] = &mut [&mut dest_data];
apm.process_render_f32_with_config(src, &config, &config, dest)
.unwrap();
for i in 0..num_frames {
assert_eq!(src_data[i], dest[0][i], "sample {i} mismatch");
}
}
#[test]
fn no_processing_when_all_disabled_i16() {
let mut apm = AudioProcessing::new();
for &rate in &[8000u32, 16000, 32000, 48000] {
let config = StreamConfig::new(rate, 1);
let num_frames = config.num_frames();
let src: Vec<i16> = (0..num_frames)
.map(|i| ((i as i32 * 200) % 30000 - 15000) as i16)
.collect();
let mut dest = vec![0i16; num_frames];
apm.process_capture_i16_with_config(&src, &config, &config, &mut dest)
.unwrap();
for i in 0..num_frames {
assert_eq!(src[i], dest[i], "rate={rate} sample {i} mismatch");
}
}
}
#[test]
fn all_processing_disabled_by_default() {
let apm = AudioProcessing::new();
let config = apm.config();
assert!(config.echo_canceller.is_none());
assert!(config.high_pass_filter.is_none());
assert!(config.noise_suppression.is_none());
assert!(config.gain_controller2.is_none());
}
#[test]
fn echo_canceller_processes_multiple_frames() {
let config = Config {
echo_canceller: Some(EchoCanceller::default()),
..Default::default()
};
let mut apm = AudioProcessing::builder().config(config).build();
let stream = StreamConfig::new(16000, 1);
let num_frames = 160;
for _ in 0..50 {
let render = vec![0.1f32; num_frames];
let render_src: &[&[f32]] = &[&render];
let mut render_dest = vec![0.0f32; num_frames];
let render_dest: &mut [&mut [f32]] = &mut [&mut render_dest];
apm.process_render_f32_with_config(render_src, &stream, &stream, render_dest)
.unwrap();
let capture = vec![0.05f32; num_frames];
let capture_src: &[&[f32]] = &[&capture];
let mut capture_dest = vec![0.0f32; num_frames];
let capture_dest: &mut [&mut [f32]] = &mut [&mut capture_dest];
apm.process_capture_f32_with_config(capture_src, &stream, &stream, capture_dest)
.unwrap();
}
}
#[test]
fn noise_suppression_processes_multiple_frames() {
let config = Config {
noise_suppression: Some(NoiseSuppression {
level: NoiseSuppressionLevel::High,
..Default::default()
}),
..Default::default()
};
let mut apm = AudioProcessing::builder().config(config).build();
let stream = StreamConfig::new(16000, 1);
let num_frames = 160;
for _ in 0..50 {
let capture = sawtooth_frame(16000, 1);
let channels = deinterleave(&capture, 1);
let src: Vec<&[f32]> = channels.iter().map(|c| c.as_slice()).collect();
let mut dest_data = vec![0.0f32; num_frames];
let dest: &mut [&mut [f32]] = &mut [&mut dest_data];
apm.process_capture_f32_with_config(&src, &stream, &stream, dest)
.unwrap();
}
}
#[test]
fn gain_controller2_processes_multiple_frames() {
let config = Config {
gain_controller2: Some(GainController2 {
adaptive_digital: Some(AdaptiveDigital::default()),
..Default::default()
}),
..Default::default()
};
let mut apm = AudioProcessing::builder().config(config).build();
let stream = StreamConfig::new(16000, 1);
let num_frames = 160;
for _ in 0..50 {
let capture = sawtooth_frame(16000, 1);
let channels = deinterleave(&capture, 1);
let src: Vec<&[f32]> = channels.iter().map(|c| c.as_slice()).collect();
let mut dest_data = vec![0.0f32; num_frames];
let dest: &mut [&mut [f32]] = &mut [&mut dest_data];
apm.process_capture_f32_with_config(&src, &stream, &stream, dest)
.unwrap();
}
}
#[test]
fn high_pass_filter_processes_multiple_frames() {
let config = Config {
high_pass_filter: Some(HighPassFilter::default()),
..Default::default()
};
let mut apm = AudioProcessing::builder().config(config).build();
let stream = StreamConfig::new(16000, 1);
let num_frames = 160;
for _ in 0..50 {
let capture = sawtooth_frame(16000, 1);
let channels = deinterleave(&capture, 1);
let src: Vec<&[f32]> = channels.iter().map(|c| c.as_slice()).collect();
let mut dest_data = vec![0.0f32; num_frames];
let dest: &mut [&mut [f32]] = &mut [&mut dest_data];
apm.process_capture_f32_with_config(&src, &stream, &stream, dest)
.unwrap();
}
}
#[test]
fn all_components_enabled_processes_multiple_frames() {
let config = Config {
echo_canceller: Some(EchoCanceller::default()),
noise_suppression: Some(NoiseSuppression::default()),
high_pass_filter: Some(HighPassFilter::default()),
gain_controller2: Some(GainController2 {
adaptive_digital: Some(AdaptiveDigital::default()),
..Default::default()
}),
..Default::default()
};
let mut apm = AudioProcessing::builder().config(config).build();
let stream = StreamConfig::new(16000, 1);
let num_frames = 160;
for _ in 0..100 {
let render = sawtooth_frame(16000, 1);
let render_ch = deinterleave(&render, 1);
let render_src: Vec<&[f32]> = render_ch.iter().map(|c| c.as_slice()).collect();
let mut render_dest = vec![0.0f32; num_frames];
let render_dest: &mut [&mut [f32]] = &mut [&mut render_dest];
apm.process_render_f32_with_config(&render_src, &stream, &stream, render_dest)
.unwrap();
let capture = sawtooth_frame(16000, 1);
let capture_ch = deinterleave(&capture, 1);
let capture_src: Vec<&[f32]> = capture_ch.iter().map(|c| c.as_slice()).collect();
let mut capture_dest = vec![0.0f32; num_frames];
let capture_dest: &mut [&mut [f32]] = &mut [&mut capture_dest];
apm.process_capture_f32_with_config(&capture_src, &stream, &stream, capture_dest)
.unwrap();
}
}
#[test]
fn config_change_mid_stream() {
let mut apm = AudioProcessing::new();
let stream = StreamConfig::new(16000, 1);
let num_frames = 160;
let silence = vec![0.0f32; num_frames];
let src: &[&[f32]] = &[&silence];
let mut dest_data = vec![0.0f32; num_frames];
let dest: &mut [&mut [f32]] = &mut [&mut dest_data];
for _ in 0..10 {
apm.process_capture_f32_with_config(src, &stream, &stream, dest)
.unwrap();
}
let config = Config {
echo_canceller: Some(EchoCanceller::default()),
..Default::default()
};
apm.apply_config(config);
for _ in 0..10 {
apm.process_capture_f32_with_config(src, &stream, &stream, dest)
.unwrap();
}
let mut config = apm.config().clone();
config.noise_suppression = Some(NoiseSuppression::default());
apm.apply_config(config);
for _ in 0..10 {
apm.process_capture_f32_with_config(src, &stream, &stream, dest)
.unwrap();
}
}
#[test]
fn sample_rate_change_mid_stream() {
let mut apm = AudioProcessing::new();
let stream_16k = StreamConfig::new(16000, 1);
let src_16k = vec![0.0f32; 160];
let src: &[&[f32]] = &[&src_16k];
let mut dest_16k = vec![0.0f32; 160];
let dest: &mut [&mut [f32]] = &mut [&mut dest_16k];
for _ in 0..5 {
apm.process_capture_f32_with_config(src, &stream_16k, &stream_16k, dest)
.unwrap();
}
let stream_48k = StreamConfig::new(48000, 1);
let src_48k = vec![0.0f32; 480];
let src: &[&[f32]] = &[&src_48k];
let mut dest_48k = vec![0.0f32; 480];
let dest: &mut [&mut [f32]] = &mut [&mut dest_48k];
for _ in 0..5 {
apm.process_capture_f32_with_config(src, &stream_48k, &stream_48k, dest)
.unwrap();
}
}
#[test]
fn pre_amplifier_applies_gain() {
let config = Config {
pre_amplifier: Some(PreAmplifier {
fixed_gain_factor: 2.0,
}),
..Default::default()
};
let mut apm = AudioProcessing::builder().config(config).build();
let stream = StreamConfig::new(16000, 1);
let num_frames = 160;
for _ in 0..20 {
let src_data: Vec<f32> = (0..num_frames)
.map(|i| ((i % 3) as f32 - 1.0) * 0.3)
.collect();
let src: &[&[f32]] = &[&src_data];
let mut dest_data = vec![0.0f32; num_frames];
let dest: &mut [&mut [f32]] = &mut [&mut dest_data];
apm.process_capture_f32_with_config(src, &stream, &stream, dest)
.unwrap();
}
let src_data: Vec<f32> = (0..num_frames)
.map(|i| ((i % 3) as f32 - 1.0) * 0.3)
.collect();
let input_power: f32 = src_data.iter().map(|&s| s * s).sum::<f32>() / num_frames as f32;
let src: &[&[f32]] = &[&src_data];
let mut dest_data = vec![0.0f32; num_frames];
let dest: &mut [&mut [f32]] = &mut [&mut dest_data];
apm.process_capture_f32_with_config(src, &stream, &stream, dest)
.unwrap();
let output_power: f32 = dest[0].iter().map(|&s| s * s).sum::<f32>() / num_frames as f32;
assert!(
output_power > input_power * 3.0,
"expected ~4x power, got input={input_power}, output={output_power}",
);
}
#[test]
fn runtime_setting_capture_pre_gain() {
let config = Config {
pre_amplifier: Some(PreAmplifier {
fixed_gain_factor: 1.0,
}),
..Default::default()
};
let mut apm = AudioProcessing::builder().config(config).build();
apm.set_capture_pre_gain(2.0);
let stream = StreamConfig::new(16000, 1);
let num_frames = 160;
for _ in 0..20 {
let src_data = vec![0.1f32; num_frames];
let src: &[&[f32]] = &[&src_data];
let mut dest_data = vec![0.0f32; num_frames];
let dest: &mut [&mut [f32]] = &mut [&mut dest_data];
apm.process_capture_f32_with_config(src, &stream, &stream, dest)
.unwrap();
}
let config = apm.config();
assert_eq!(
config.pre_amplifier.as_ref().unwrap().fixed_gain_factor,
2.0
);
}
#[test]
fn stereo_processing_does_not_crash() {
let config = Config {
echo_canceller: Some(EchoCanceller::default()),
pipeline: Pipeline {
multi_channel_render: true,
multi_channel_capture: true,
..Default::default()
},
..Default::default()
};
let mut apm = AudioProcessing::builder().config(config).build();
let stream = StreamConfig::new(16000, 2);
let num_frames = 160;
for _ in 0..20 {
let render_l = vec![0.1f32; num_frames];
let render_r = vec![0.05f32; num_frames];
let render_src: &[&[f32]] = &[&render_l, &render_r];
let mut render_dest_l = vec![0.0f32; num_frames];
let mut render_dest_r = vec![0.0f32; num_frames];
let render_dest: &mut [&mut [f32]] = &mut [&mut render_dest_l, &mut render_dest_r];
apm.process_render_f32_with_config(render_src, &stream, &stream, render_dest)
.unwrap();
let capture_l = vec![0.05f32; num_frames];
let capture_r = vec![0.02f32; num_frames];
let capture_src: &[&[f32]] = &[&capture_l, &capture_r];
let mut capture_dest_l = vec![0.0f32; num_frames];
let mut capture_dest_r = vec![0.0f32; num_frames];
let capture_dest: &mut [&mut [f32]] = &mut [&mut capture_dest_l, &mut capture_dest_r];
apm.process_capture_f32_with_config(capture_src, &stream, &stream, capture_dest)
.unwrap();
}
}
}