Add support for toggling AGC/AEC/NS in desktop
Co-authored-by: Adel Lahlou <adel@signal.org>
This commit is contained in:
parent
15125f8c01
commit
e36bee834d
@ -162,6 +162,8 @@ class NativeCallManager {
|
||||
(NativeCallManager.prototype as any).getAudioOutputs =
|
||||
Native.cm_getAudioOutputs;
|
||||
(NativeCallManager.prototype as any).setAudioOutput = Native.cm_setAudioOutput;
|
||||
(NativeCallManager.prototype as any).setVoiceProcessingEnabled =
|
||||
Native.cm_setVoiceProcessingEnabled;
|
||||
(NativeCallManager.prototype as any).processEvents = Native.cm_processEvents;
|
||||
(NativeCallManager.prototype as any).setRtcStatsInterval =
|
||||
Native.cm_setRtcStatsInterval;
|
||||
@ -1955,6 +1957,20 @@ export class RingRTCType {
|
||||
setAudioOutput(index: number): void {
|
||||
this.callManager.setAudioOutput(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables different voice processing techniques including:
|
||||
* - Acoustic Echo Cancellation (AEC)
|
||||
* - Noise Supression (NS)
|
||||
* - Automatic Gain Control (AGC)
|
||||
*
|
||||
* This request is idempotent
|
||||
*
|
||||
* @param enabled - whether to enable voice processing
|
||||
*/
|
||||
setVoiceProcessingEnabled(enabled: boolean): void {
|
||||
this.callManager.setVoiceProcessingEnabled(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
export interface CallSettings {
|
||||
@ -3176,6 +3192,7 @@ export interface CallManager {
|
||||
setAudioInput(index: number): void;
|
||||
getAudioOutputs(): Array<AudioDevice>;
|
||||
setAudioOutput(index: number): void;
|
||||
setVoiceProcessingEnabled(enabled: boolean): void;
|
||||
}
|
||||
|
||||
export interface CallManagerCallbacks {
|
||||
|
||||
@ -2309,6 +2309,23 @@ fn setAudioOutput(mut cx: FunctionContext) -> JsResult<JsValue> {
|
||||
Ok(cx.undefined().upcast())
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn setVoiceProcessingEnabled(mut cx: FunctionContext) -> JsResult<JsValue> {
|
||||
let enabled = cx.argument::<JsBoolean>(0)?.value(&mut cx);
|
||||
info!("setVoiceProcessingEnabled(): {:?}", enabled);
|
||||
|
||||
match with_call_endpoint(&mut cx, |endpoint| {
|
||||
endpoint
|
||||
.peer_connection_factory
|
||||
.set_input_voice_processing_enabled(enabled)
|
||||
}) {
|
||||
Ok(_) => (),
|
||||
Err(err) => error!("setVoiceProcessingEnabled failed: {}", err),
|
||||
};
|
||||
|
||||
Ok(cx.undefined().upcast())
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn setRtcStatsInterval(mut cx: FunctionContext) -> JsResult<JsValue> {
|
||||
let client_id = cx.argument::<JsNumber>(0)?.value(&mut cx) as group_call::ClientId;
|
||||
@ -3235,6 +3252,7 @@ fn register(mut cx: ModuleContext) -> NeonResult<()> {
|
||||
cx.export_function("cm_setAudioInput", setAudioInput)?;
|
||||
cx.export_function("cm_getAudioOutputs", getAudioOutputs)?;
|
||||
cx.export_function("cm_setAudioOutput", setAudioOutput)?;
|
||||
cx.export_function("cm_setVoiceProcessingEnabled", setVoiceProcessingEnabled)?;
|
||||
cx.export_function("cm_setRtcStatsInterval", setRtcStatsInterval)?;
|
||||
cx.export_function("cm_processEvents", processEvents)?;
|
||||
Ok(())
|
||||
|
||||
@ -19,7 +19,7 @@ use std::{
|
||||
|
||||
use anyhow::{Context as AnyhowContext, anyhow, bail};
|
||||
use cubeb::{Context, DeviceId, DeviceType, MonoFrame, StereoFrame, Stream, StreamPrefs};
|
||||
use cubeb_core::{LogLevel, log_enabled, set_logging};
|
||||
use cubeb_core::{InputProcessingParams, LogLevel, log_enabled, set_logging};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
#[cfg(target_os = "windows")]
|
||||
@ -83,6 +83,7 @@ enum Event {
|
||||
InitRecording,
|
||||
StartRecording,
|
||||
WarmupRecording,
|
||||
SetInputProcessing(bool),
|
||||
StopRecording,
|
||||
PlayoutDelay,
|
||||
Terminate,
|
||||
@ -107,6 +108,7 @@ struct Worker {
|
||||
// Note that the streams must not outlive the ctx.
|
||||
output_stream: Option<Stream<OutFrame>>,
|
||||
input_stream: Option<Stream<Frame>>,
|
||||
voice_processing_enabled: bool,
|
||||
// Note that the caches must not outlive the ctx.
|
||||
input_device_cache: DeviceCollectionWrapper,
|
||||
output_device_cache: DeviceCollectionWrapper,
|
||||
@ -440,9 +442,7 @@ impl Worker {
|
||||
.rate(SAMPLE_FREQUENCY)
|
||||
.channels(NUM_CHANNELS)
|
||||
.layout(cubeb::ChannelLayout::MONO);
|
||||
// On Mac, the AEC pipeline runs at 24kHz (FB15839727 tracks this). For now,
|
||||
// disable it.
|
||||
let params = if cfg!(not(target_os = "macos")) {
|
||||
let params = if cfg!(not(target_os = "macos")) || self.voice_processing_enabled {
|
||||
builder.prefs(StreamPrefs::VOICE)
|
||||
} else {
|
||||
builder
|
||||
@ -513,6 +513,41 @@ impl Worker {
|
||||
});
|
||||
match builder.init(&self.ctx) {
|
||||
Ok(stream) => {
|
||||
if cfg!(target_os = "macos") && self.voice_processing_enabled {
|
||||
// Note: On Mac, the AEC pipeline runs at 24kHz (FB15839727 tracks this).
|
||||
// This results in a slightly thin sound because of the Nyquist theorem.
|
||||
// See https://en.wikipedia.org/wiki/Nyquist_frequency
|
||||
match self.ctx.supported_input_processing_params() {
|
||||
Ok(params) => {
|
||||
// With cubeb-coreaudio-rs, the VPIO input is inaudible without these settings.
|
||||
// See https://github.com/mozilla/cubeb-coreaudio-rs/issues/239#issuecomment-2430361990
|
||||
info!("Available input processing params: {:?}", params);
|
||||
let mut desired_params = InputProcessingParams::empty();
|
||||
if params.contains(InputProcessingParams::AUTOMATIC_GAIN_CONTROL)
|
||||
&& self.voice_processing_enabled
|
||||
{
|
||||
desired_params |= InputProcessingParams::AUTOMATIC_GAIN_CONTROL;
|
||||
}
|
||||
// With the coreaudio-rust backend, these settings must be set together.
|
||||
if params.contains(
|
||||
InputProcessingParams::ECHO_CANCELLATION
|
||||
| InputProcessingParams::NOISE_SUPPRESSION,
|
||||
) && self.voice_processing_enabled
|
||||
{
|
||||
desired_params |= InputProcessingParams::ECHO_CANCELLATION
|
||||
| InputProcessingParams::NOISE_SUPPRESSION;
|
||||
}
|
||||
if let Err(e) = stream.set_input_processing_params(desired_params) {
|
||||
error!("couldn't set input params: {:?}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => warn!(
|
||||
"Failed to get supported input processing parameters; proceeding without: {}",
|
||||
e
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
self.input_stream = Some(stream);
|
||||
Ok(())
|
||||
}
|
||||
@ -574,6 +609,18 @@ impl Worker {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_input_processing_enabled(&mut self, enabled: bool) -> anyhow::Result<()> {
|
||||
if enabled == self.voice_processing_enabled {
|
||||
return Ok(());
|
||||
}
|
||||
self.voice_processing_enabled = enabled;
|
||||
if let Some(device) = self.recording_device {
|
||||
self.update_recording_device(device)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Get the playout delay, in ms.
|
||||
fn playout_delay(&mut self) -> anyhow::Result<u16> {
|
||||
match &self.output_stream {
|
||||
@ -654,6 +701,9 @@ impl Worker {
|
||||
Event::InitRecording => self.init_recording(),
|
||||
Event::StartRecording => self.start_recording(true),
|
||||
Event::WarmupRecording => self.start_recording(false),
|
||||
Event::SetInputProcessing(voice_processing_enabled) => {
|
||||
self.set_input_processing_enabled(voice_processing_enabled)
|
||||
}
|
||||
Event::StopRecording => self.stop_recording(),
|
||||
Event::PlayoutDelay => {
|
||||
if let Err(e) = playout_delay_sender.send(self.playout_delay()) {
|
||||
@ -808,6 +858,7 @@ impl Worker {
|
||||
recording_device: None,
|
||||
output_stream: None,
|
||||
input_stream: None,
|
||||
voice_processing_enabled: false,
|
||||
input_device_cache: Default::default(),
|
||||
output_device_cache: Default::default(),
|
||||
input_device_names,
|
||||
@ -1647,6 +1698,13 @@ impl AudioDeviceModule {
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn set_input_processing_enabled(&mut self, enabled: bool) -> anyhow::Result<()> {
|
||||
if let Err(e) = self.mpsc_sender.send(Event::SetInputProcessing(enabled)) {
|
||||
return Err(anyhow!("Failed to request SetInputProcessing: {}", e));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@ -528,6 +528,16 @@ impl PeerConnectionFactory {
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(all(not(feature = "sim"), feature = "native"))]
|
||||
pub fn set_input_voice_processing_enabled(&mut self, enabled: bool) -> Result<()> {
|
||||
self.adm
|
||||
.as_ref()
|
||||
.and_then(|adm| adm.lock().ok())
|
||||
.map_or(Err(anyhow!("couldn't access ADM")), |mut adm| {
|
||||
adm.set_input_processing_enabled(enabled)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "native")]
|
||||
pub fn get_audio_recording_devices(&mut self) -> Result<Vec<AudioDevice>> {
|
||||
let devices = self
|
||||
|
||||
Loading…
Reference in New Issue
Block a user