diff --git a/src/config.rs b/src/config.rs index f006210..bcc35e9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -101,6 +101,28 @@ impl From for ffi::AudioProcessing_Config_HighPassFilter { } } +/// [Highly Experimental] Configurations of internal AEC3 implementation. +/// TODO(skywhale): Add more parameters from: +/// https://gitlab.freedesktop.org/pulseaudio/webrtc-audio-processing/-/blob/master/webrtc/api/audio/echo_canceller3_config.h +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "derive_serde", derive(Serialize, Deserialize))] +pub struct EchoCanceller3Config { + /// Number of linear filters to apply. + pub num_filters: usize, +} + +impl Default for EchoCanceller3Config { + fn default() -> Self { + Self { num_filters: 5 } + } +} + +impl From for ffi::EchoCanceller3ConfigOverride { + fn from(other: EchoCanceller3Config) -> Self { + Self { num_filters: other.num_filters as i32 } + } +} + /// AEC (acoustic echo cancellation) configuration. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "derive_serde", derive(Serialize, Deserialize))] diff --git a/src/lib.rs b/src/lib.rs index 11376b9..c018084 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ mod config; mod stats; -use std::{error, fmt, sync::Arc}; +use std::{error, fmt, ptr::null, sync::Arc}; use webrtc_audio_processing_sys as ffi; pub use config::*; @@ -49,7 +49,15 @@ impl Processor { /// instantiation, however new configs can be be passed to `set_config()` /// at any time during processing. pub fn new(config: &InitializationConfig) -> Result { - let inner = Arc::new(AudioProcessing::new(config)?); + Self::with_aec3_config(config, None) + } + + /// Creates a new `Processor` with custom AEC3 configuration. + pub fn with_aec3_config( + config: &InitializationConfig, + aec3_config: Option, + ) -> Result { + let inner = Arc::new(AudioProcessing::new(config, aec3_config)?); let num_samples = inner.num_samples_per_frame(); Ok(Self { inner, @@ -64,6 +72,13 @@ impl Processor { }) } + /// Initializes internal states, while retaining all user settings. This should be called before + /// beginning to process a new audio stream. However, it is not necessary to call before processing + /// the first stream after creation. + pub fn initialize(&mut self) { + self.inner.initialize() + } + /// Processes and modifies the audio frame from a capture device by applying /// signal processing as specified in the config. `frame` should hold an /// interleaved f32 audio frame, with NUM_SAMPLES_PER_FRAME samples. @@ -182,13 +197,23 @@ struct AudioProcessing { } impl AudioProcessing { - fn new(config: &InitializationConfig) -> Result { + fn new( + config: &InitializationConfig, + aec3_config: Option, + ) -> Result { + let aec3_config = if let Some(aec3_config) = aec3_config { + &aec3_config.into() as *const ffi::EchoCanceller3ConfigOverride + } else { + null() + }; + let mut code = 0; let inner = unsafe { ffi::audio_processing_create( config.num_capture_channels as i32, config.num_render_channels as i32, config.sample_rate_hz as i32, + aec3_config, &mut code, ) }; @@ -199,6 +224,10 @@ impl AudioProcessing { } } + fn initialize(&self) { + unsafe { ffi::initialize(self.inner) } + } + fn process_capture_frame(&self, frame: &mut Vec>) -> Result<(), Error> { let mut frame_ptr = frame.iter_mut().map(|v| v.as_mut_ptr()).collect::>(); unsafe { @@ -412,8 +441,8 @@ mod tests { ..InitializationConfig::default() }; let mut ap = Processor::new(&config).unwrap(); - - // tweak params outside of config + + // tweak params outside of config ap.set_output_will_be_muted(true); ap.set_stream_key_pressed(true); @@ -427,5 +456,4 @@ mod tests { // it shouldn't crash } - } diff --git a/webrtc-audio-processing-sys/build.rs b/webrtc-audio-processing-sys/build.rs index faf6681..9f414b3 100644 --- a/webrtc-audio-processing-sys/build.rs +++ b/webrtc-audio-processing-sys/build.rs @@ -7,6 +7,10 @@ fn out_dir() -> PathBuf { std::env::var("OUT_DIR").expect("OUT_DIR environment var not set.").into() } +fn src_dir() -> PathBuf { + std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR environment var not set.").into() +} + #[cfg(not(feature = "bundled"))] mod webrtc { use super::*; @@ -62,6 +66,7 @@ mod webrtc { let mut include_paths = vec![ out_dir().join("include"), out_dir().join("include").join("webrtc-audio-processing-1"), + src_dir().join("webrtc-audio-processing").join("webrtc"), ]; let mut lib_paths = vec![out_dir().join("lib")]; diff --git a/webrtc-audio-processing-sys/src/wrapper.cpp b/webrtc-audio-processing-sys/src/wrapper.cpp index 46d25a8..1e7915f 100644 --- a/webrtc-audio-processing-sys/src/wrapper.cpp +++ b/webrtc-audio-processing-sys/src/wrapper.cpp @@ -1,5 +1,12 @@ #include "wrapper.hpp" +// These definitions shouldn't affect the implementation of AEC3. +// We are defining them to work around some build-time assertions +// when including the internal header file of echo_canceller3.h +#define WEBRTC_APM_DEBUG_DUMP 0 +#define WEBRTC_POSIX +#include + #include #include @@ -29,6 +36,32 @@ OptionalBool from_absl_optional(const absl::optional& optional) { return rv; } +webrtc::EchoCanceller3Config build_aec3_config(const EchoCanceller3ConfigOverride& override) { + webrtc::EchoCanceller3Config config; + config.delay.num_filters = override.num_filters; + return config; +} + +class EchoCanceller3Factory : public webrtc::EchoControlFactory { + public: + explicit EchoCanceller3Factory(const webrtc::EchoCanceller3Config& config) + : config_(config) {} + + std::unique_ptr Create( + int sample_rate_hz, + int num_render_channels, + int num_capture_channels) override { + return std::unique_ptr(new webrtc::EchoCanceller3( + config_, + sample_rate_hz, + num_render_channels, + num_capture_channels)); + } + + private: + webrtc::EchoCanceller3Config config_; +}; + } // namespace struct AudioProcessing { @@ -43,9 +76,16 @@ AudioProcessing* audio_processing_create( int num_capture_channels, int num_render_channels, int sample_rate_hz, + const EchoCanceller3ConfigOverride* aec3_config_override, int* error) { AudioProcessing* ap = new AudioProcessing; - ap->processor.reset(webrtc::AudioProcessingBuilder().Create()); + + webrtc::AudioProcessingBuilder builder; + if (aec3_config_override != nullptr) { + auto* factory = new EchoCanceller3Factory(build_aec3_config(*aec3_config_override)); + builder.SetEchoControlFactory(std::unique_ptr(factory)); + } + ap->processor.reset(builder.Create()); const bool has_keyboard = false; ap->capture_stream_config = webrtc::StreamConfig( diff --git a/webrtc-audio-processing-sys/src/wrapper.hpp b/webrtc-audio-processing-sys/src/wrapper.hpp index 9521a6b..8dce12e 100644 --- a/webrtc-audio-processing-sys/src/wrapper.hpp +++ b/webrtc-audio-processing-sys/src/wrapper.hpp @@ -41,11 +41,18 @@ struct Stats { OptionalInt delay_ms; }; +// A slimmed-down version of webrtc::EchoCanceller3Config. +// We can not just expose the webrtc variant as the binding loses all the default values. +struct EchoCanceller3ConfigOverride { + int num_filters; +}; + // Creates a new instance of AudioProcessing. AudioProcessing* audio_processing_create( int num_capture_channels, int num_render_channels, int sample_rate_hz, + const EchoCanceller3ConfigOverride* aec3_config_override, int* error); // Processes and modifies the audio frame from a capture device.