From 164584aa079744e54e6417bfcad1532880ac6a86 Mon Sep 17 00:00:00 2001 From: Andreas Pehrson Date: Wed, 17 Jan 2024 11:28:01 +0100 Subject: [PATCH] Implement input mute and processing params APIs This enables input muting when VPIO is used. It also enables input processing of AEC+NS (only in combination) when VPIO is used. --- src/backend/mod.rs | 151 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 146 insertions(+), 5 deletions(-) diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 2b2da33..0e3ac26 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -284,6 +284,83 @@ fn get_volume(unit: AudioUnit) -> Result { } } +fn set_input_mute(unit: AudioUnit, mute: bool) -> Result<()> { + assert!(!unit.is_null()); + let mute: UInt32 = mute.into(); + let r = audio_unit_set_property( + unit, + kAUVoiceIOProperty_MuteOutput, + kAudioUnitScope_Global, + AU_IN_BUS, + &mute, + mem::size_of::(), + ); + if r == NO_ERR { + Ok(()) + } else { + cubeb_log!( + "AudioUnitSetProperty/kAUVoiceIOProperty_MuteOutput rv={}", + r + ); + Err(Error::error()) + } +} + +fn set_input_processing_params(unit: AudioUnit, params: InputProcessingParams) -> Result<()> { + assert!(!unit.is_null()); + let aec = params.contains(InputProcessingParams::ECHO_CANCELLATION); + let ns = params.contains(InputProcessingParams::NOISE_SUPPRESSION); + + // We don't use AGC, but keep it here for reference. + // See the comment in supported_input_processing_params. + let agc = params.contains(InputProcessingParams::AUTOMATIC_GAIN_CONTROL); + assert!(!agc); + + // AEC and NS are active as soon as VPIO is not bypassed. + // Therefore the only modes we can explicitly support are {} and {aec, ns}. + + if aec != ns { + // No control to turn on AEC without NS or vice versa. + return Err(Error::error()); + } + + let agc = u32::from(agc); + let r = audio_unit_set_property( + unit, + kAUVoiceIOProperty_VoiceProcessingEnableAGC, + kAudioUnitScope_Global, + AU_IN_BUS, + &agc, + mem::size_of::(), + ); + if r != NO_ERR { + cubeb_log!( + "AudioUnitSetProperty/kAUVoiceIOProperty_VoiceProcessingEnableAGC rv={}", + r + ); + return Err(Error::error()); + } + + let bypass = u32::from(!aec); + let r = audio_unit_set_property( + unit, + kAUVoiceIOProperty_BypassVoiceProcessing, + kAudioUnitScope_Global, + AU_IN_BUS, + &bypass, + mem::size_of::(), + ); + if r != NO_ERR { + cubeb_log!( + "AudioUnitSetProperty/kAUVoiceIOProperty_BypassVoiceProcessing rv={}", + r + ); + return Err(Error::error()); + } + + Ok(()) +} + fn minimum_resampling_input_frames( input_rate: f64, output_rate: f64, @@ -2171,7 +2248,11 @@ impl ContextOps for AudioUnitContext { Ok(rate as u32) } fn supported_input_processing_params(&mut self) -> Result { - Ok(InputProcessingParams::NONE) + // The VoiceProcessingIO AudioUnit has the + // kAUVoiceIOProperty_VoiceProcessingEnableAGC property to enable AGC on + // the input signal, but some simple manual tests on MacOS 14.0 suggest + // it doesn't have any effect. + Ok(InputProcessingParams::ECHO_CANCELLATION | InputProcessingParams::NOISE_SUPPRESSION) } fn enumerate_devices( &mut self, @@ -4111,11 +4192,71 @@ impl<'ctx> StreamOps for AudioUnitStream<'ctx> { fn current_device(&mut self) -> Result<&DeviceRef> { Err(Error::not_supported()) } - fn set_input_mute(&mut self, _mute: bool) -> Result<()> { - Err(Error::not_supported()) + fn set_input_mute(&mut self, mute: bool) -> Result<()> { + if self.core_stream_data.input_unit.is_null() { + return Err(Error::invalid_parameter()); + } + + if !self.core_stream_data.using_voice_processing_unit() { + return Err(Error::error()); + } + + // Execute set_input_mute in serial queue to avoid racing with destroy or reinit. + let mut result = Err(Error::error()); + let set = &mut result; + let stream = &self; + self.queue.run_sync(move || { + *set = set_input_mute(stream.core_stream_data.input_unit, mute); + }); + + result?; + + cubeb_log!( + "Cubeb stream ({:p}) set input mute to {}.", + self as *const AudioUnitStream, + mute + ); + Ok(()) } - fn set_input_processing_params(&mut self, _params: InputProcessingParams) -> Result<()> { - Err(Error::not_supported()) + fn set_input_processing_params(&mut self, params: InputProcessingParams) -> Result<()> { + // CUBEB_ERROR_INVALID_PARAMETER if a given param is not supported by + // this backend, or if this stream does not have an input device + if self.core_stream_data.input_unit.is_null() { + return Err(Error::invalid_parameter()); + } + + if self + .context + .supported_input_processing_params() + .unwrap() + .intersection(params) + != params + { + return Err(Error::invalid_parameter()); + } + + // CUBEB_ERROR if params could not be applied + // note: only works with VoiceProcessingIO + if !self.core_stream_data.using_voice_processing_unit() { + return Err(Error::error()); + } + + // Execute set_input_processing_params in serial queue to avoid racing with destroy or reinit. + let mut result = Err(Error::error()); + let set = &mut result; + let stream = &self; + self.queue.run_sync(move || { + *set = set_input_processing_params(stream.core_stream_data.input_unit, params); + }); + + result?; + + cubeb_log!( + "Cubeb stream ({:p}) set input processing params to {:?}.", + self as *const AudioUnitStream, + params + ); + Ok(()) } #[cfg(target_os = "ios")] fn device_destroy(&mut self, device: &DeviceRef) -> Result<()> {