From 67f5a389eb55bb5b266eaee6eb0dbbeab4057c9c Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:09:34 +0900 Subject: [PATCH 1/4] Use AudioEngine ADM on Apple platforms Select the new kAppleAudioEngine audio layer when creating the platform ADM on iOS and macOS. The AVAudioEngine based ADM supports runtime switchable voice processing and device change handling. Construction happens in the proxy constructor on the worker thread, which the AudioEngine device binds its sequence checker to. Also forward the platform voice processing interface (topology, path availability and toggle, processing state) to the platform ADM so the coupled Apple AEC+NS path is reachable through the proxy. Requires a libwebrtc build that includes CreateAudioEngineDeviceModule. --- webrtc-sys/include/livekit/adm_proxy.h | 8 ++++++ webrtc-sys/src/adm_proxy.cpp | 39 ++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/webrtc-sys/include/livekit/adm_proxy.h b/webrtc-sys/include/livekit/adm_proxy.h index 31555103d..7a6149940 100644 --- a/webrtc-sys/include/livekit/adm_proxy.h +++ b/webrtc-sys/include/livekit/adm_proxy.h @@ -216,6 +216,14 @@ class AdmProxy : public webrtc::AudioDeviceModule { int32_t EnableBuiltInAGC(bool enable) override; int32_t EnableBuiltInNS(bool enable) override; + // Platform voice processing (Apple's coupled AEC+NS path) + webrtc::AudioDeviceModule::PlatformAudioProcessingTopology + GetPlatformAudioProcessingTopology() const override; + bool PlatformVoiceProcessingPathIsAvailable() const override; + int32_t EnablePlatformVoiceProcessingPath(bool enable) override; + webrtc::AudioDeviceModule::PlatformAudioProcessingState + GetPlatformAudioProcessingState() const override; + #if defined(WEBRTC_IOS) int GetPlayoutAudioParameters(webrtc::AudioParameters* params) const override; int GetRecordAudioParameters(webrtc::AudioParameters* params) const override; diff --git a/webrtc-sys/src/adm_proxy.cpp b/webrtc-sys/src/adm_proxy.cpp index 16b8ff5cf..f27945357 100644 --- a/webrtc-sys/src/adm_proxy.cpp +++ b/webrtc-sys/src/adm_proxy.cpp @@ -57,9 +57,16 @@ AdmProxy::AdmProxy(const webrtc::Environment& env, webrtc::Thread* worker_thread // 3. Deferring creation ensures JNI is ready when we actually need the ADM #if defined(__ANDROID__) // platform_adm_ stays nullptr, will be created in EnsurePlatformAdmCreated() +#else +#if defined(WEBRTC_IOS) || defined(WEBRTC_MAC) + // Use the AVAudioEngine based ADM on Apple platforms. It supports runtime + // switchable voice processing and device change handling. + platform_adm_ = webrtc::CreateAudioDeviceModule( + env_, webrtc::AudioDeviceModule::kAppleAudioEngine); #else platform_adm_ = webrtc::CreateAudioDeviceModule( env_, webrtc::AudioDeviceModule::kPlatformDefaultAudio); +#endif if (!platform_adm_) { RTC_LOG(LS_ERROR) << "AdmProxy: CreateAudioDeviceModule returned nullptr"; @@ -836,6 +843,38 @@ int32_t AdmProxy::EnableBuiltInNS(bool enable) { }); } +webrtc::AudioDeviceModule::PlatformAudioProcessingTopology +AdmProxy::GetPlatformAudioProcessingTopology() const { + return WithPlatformAdm< + webrtc::AudioDeviceModule::PlatformAudioProcessingTopology>( + webrtc::AudioDeviceModule::PlatformAudioProcessingTopology::kIndependent, + [](webrtc::AudioDeviceModule& adm) { + return adm.GetPlatformAudioProcessingTopology(); + }); +} + +bool AdmProxy::PlatformVoiceProcessingPathIsAvailable() const { + return WithPlatformAdm(false, [](webrtc::AudioDeviceModule& adm) { + return adm.PlatformVoiceProcessingPathIsAvailable(); + }); +} + +int32_t AdmProxy::EnablePlatformVoiceProcessingPath(bool enable) { + return WithPlatformAdm(-1, [enable](webrtc::AudioDeviceModule& adm) { + return adm.EnablePlatformVoiceProcessingPath(enable); + }); +} + +webrtc::AudioDeviceModule::PlatformAudioProcessingState +AdmProxy::GetPlatformAudioProcessingState() const { + return WithPlatformAdm< + webrtc::AudioDeviceModule::PlatformAudioProcessingState>( + webrtc::AudioDeviceModule::PlatformAudioProcessingState(), + [](webrtc::AudioDeviceModule& adm) { + return adm.GetPlatformAudioProcessingState(); + }); +} + #if defined(WEBRTC_IOS) int AdmProxy::GetPlayoutAudioParameters(webrtc::AudioParameters* params) const { return WithPlatformAdm(-1, [params](webrtc::AudioDeviceModule& adm) { From f8cfda4cdd7a3b61cc0f169e83af3ce3c8d8c490 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:09:44 +0900 Subject: [PATCH 2/4] Plumb platform voice processing through to PlatformAudio Apple's AudioEngine ADM exposes AEC and NS through one coupled voice processing path rather than independent toggles. Expose the platform audio processing topology and voice processing path controls through AudioDeviceController, PeerConnectionFactory and LkRuntime, and add configure_platform_audio_processing which picks the right strategy from the reported topology: - Coupled topology (Apple): enable the platform voice processing path only when both AEC and NS are requested with hardware preferred, otherwise disable it and fall back to WebRTC software processing. - Independent topology: toggle builtin AEC/AGC/NS individually as before. PlatformAudio::configure_audio_processing now makes a single native call instead of toggling builtin effects from Rust, and the docs are updated from VPIO wording to the AudioEngine ADM. --- .../src/native/peer_connection_factory.rs | 36 +++++++++ livekit/src/platform_audio/mod.rs | 59 +++++--------- livekit/src/platform_audio/processing.rs | 21 +++-- livekit/src/rtc_engine/lk_runtime.rs | 41 ++++++++++ .../include/livekit/audio_device_controller.h | 8 ++ webrtc-sys/src/audio_device_controller.cpp | 81 +++++++++++++++++++ webrtc-sys/src/audio_device_controller.rs | 14 ++++ 7 files changed, 210 insertions(+), 50 deletions(-) diff --git a/libwebrtc/src/native/peer_connection_factory.rs b/libwebrtc/src/native/peer_connection_factory.rs index b48f2bd83..36a927cee 100644 --- a/libwebrtc/src/native/peer_connection_factory.rs +++ b/libwebrtc/src/native/peer_connection_factory.rs @@ -259,6 +259,42 @@ impl PeerConnectionFactory { self.sys_handle.audio_device().enable_builtin_ns(enable) } + /// Returns the platform audio processing topology reported by the ADM. + pub fn platform_audio_processing_topology(&self) -> i32 { + self.sys_handle.audio_device().platform_audio_processing_topology() + } + + /// Returns whether the ADM can enable a platform voice-processing path. + pub fn platform_voice_processing_path_is_available(&self) -> bool { + self.sys_handle.audio_device().platform_voice_processing_path_is_available() + } + + /// Enables or disables the platform voice-processing path when supported. + pub fn enable_platform_voice_processing_path(&self, enable: bool) -> bool { + self.sys_handle.audio_device().enable_platform_voice_processing_path(enable) + } + + /// Returns whether the platform voice-processing path is enabled. + pub fn platform_voice_processing_path_is_enabled(&self) -> bool { + self.sys_handle.audio_device().platform_voice_processing_path_is_enabled() + } + + /// Configures platform audio processing using the ADM's native topology. + pub fn configure_platform_audio_processing( + &self, + echo_cancellation: bool, + auto_gain_control: bool, + noise_suppression: bool, + prefer_hardware: bool, + ) -> bool { + self.sys_handle.audio_device().configure_platform_audio_processing( + echo_cancellation, + auto_gain_control, + noise_suppression, + prefer_hardware, + ) + } + /// Control whether ADM recording (microphone) is enabled. /// /// When disabled, WebRTC's calls to InitRecording/StartRecording will be no-ops. diff --git a/livekit/src/platform_audio/mod.rs b/livekit/src/platform_audio/mod.rs index 7af17dfc2..7cf537ec4 100644 --- a/livekit/src/platform_audio/mod.rs +++ b/livekit/src/platform_audio/mod.rs @@ -95,9 +95,9 @@ //! //! # Platform-Specific Notes //! -//! - **iOS**: Creates a VPIO (Voice Processing IO) AudioUnit. Only one VPIO -//! can exist per process. Drop all `PlatformAudio` instances to release it. -//! - **macOS**: Uses CoreAudio. Full device enumeration and selection supported. +//! - **iOS**: Uses WebRTC's Apple AudioEngine ADM with platform voice processing. +//! Drop all `PlatformAudio` instances to release active audio I/O. +//! - **macOS**: Uses WebRTC's Apple AudioEngine ADM. Full device enumeration and selection supported. //! - **Windows**: Uses WASAPI. Full device enumeration and selection supported. //! - **Linux**: Uses PulseAudio or ALSA. Full device enumeration and selection supported. //! - **Android**: Uses Java AudioRecord/AudioTrack via WebRTC's `JavaAudioDeviceModule`. @@ -399,9 +399,9 @@ impl Drop for PlatformAdmHandle { /// /// # Platform-Specific Notes /// -/// - **iOS**: Creates a VPIO AudioUnit (exclusive microphone access). +/// - **iOS**: Uses WebRTC's Apple AudioEngine ADM with platform voice processing. /// Drop all instances to allow other audio frameworks to use the mic. -/// - **macOS**: Uses CoreAudio for device management. +/// - **macOS**: Uses WebRTC's Apple AudioEngine ADM for device management. /// - **Windows**: Uses WASAPI for device management. /// - **Linux**: Uses PulseAudio or ALSA. #[derive(Clone)] @@ -484,7 +484,7 @@ impl PlatformAudio { let audio = Self { handle }; // Configure audio processing with platform-appropriate defaults: - // - iOS: prefer_hardware_processing=true (VPIO is excellent) + // - iOS: prefer_hardware_processing=true (Apple voice processing is preferred) // - Android: prefer_hardware_processing=false (hardware AEC unreliable across devices) // - Desktop: prefer_hardware_processing=false (hardware not available anyway) if let Err(e) = audio.configure_audio_processing(AudioProcessingOptions::default()) { @@ -662,7 +662,7 @@ impl PlatformAudio { /// /// **Mobile (iOS/Android):** Device selection is a no-op. Both platforms handle /// microphone selection at the system level. This method will succeed but has no effect. - /// - iOS: VPIO AudioUnit handles input selection + /// - iOS: Apple AudioEngine handles input selection /// - Android: System selects best input source based on audio mode /// /// # Arguments @@ -987,7 +987,7 @@ impl PlatformAudio { /// /// # Platform Behavior /// - /// - **iOS**: Returns `true` (VPIO provides hardware AEC) + /// - **iOS**: Returns `true` when Apple voice processing can provide AEC /// - **Android**: Returns `true` on devices with hardware AEC support /// - **Desktop**: Returns `false` (hardware AEC not available) /// @@ -1007,7 +1007,7 @@ impl PlatformAudio { /// /// # Platform Behavior /// - /// - **iOS**: Returns `true` (VPIO provides hardware AGC) + /// - **iOS**: Returns `true` when Apple voice processing can provide AGC /// - **Android**: Returns `true` on devices with hardware AGC support /// - **Desktop**: Returns `false` (hardware AGC not available) pub fn is_hardware_agc_available(&self) -> bool { @@ -1018,7 +1018,7 @@ impl PlatformAudio { /// /// # Platform Behavior /// - /// - **iOS**: Returns `true` (VPIO provides hardware NS) + /// - **iOS**: Returns `true` when Apple voice processing can provide NS /// - **Android**: Returns `true` on devices with hardware NS support /// - **Desktop**: Returns `false` (hardware NS not available) pub fn is_hardware_ns_available(&self) -> bool { @@ -1076,7 +1076,7 @@ impl PlatformAudio { /// /// # Platform Behavior /// - /// - **iOS**: `prefer_hardware_processing` is ignored (always uses VPIO) + /// - **iOS/macOS**: `prefer_hardware_processing` uses Apple voice processing when available /// - **Android**: When `prefer_hardware_processing` is `false`, hardware /// effects are disabled and WebRTC's software APM is used instead /// - **Desktop**: `prefer_hardware_processing` is ignored (hardware not available) @@ -1100,34 +1100,15 @@ impl PlatformAudio { pub fn configure_audio_processing(&self, options: AudioProcessingOptions) -> AudioResult<()> { let runtime = &self.handle.runtime; - // Configure hardware vs software processing preference - // When prefer_hardware_processing is false, we disable hardware effects - // to force WebRTC to use its software APM instead - let use_hardware = options.prefer_hardware_processing; - - // Enable/disable hardware AEC - // Note: When hardware is disabled, WebRTC automatically falls back to software - if runtime.builtin_aec_is_available() { - let enable_hw = use_hardware && options.echo_cancellation; - if !runtime.enable_builtin_aec(enable_hw) { - log::warn!("enable_builtin_aec({}) failed", enable_hw); - } - } - - // Enable/disable hardware AGC - if runtime.builtin_agc_is_available() { - let enable_hw = use_hardware && options.auto_gain_control; - if !runtime.enable_builtin_agc(enable_hw) { - log::warn!("enable_builtin_agc({}) failed", enable_hw); - } - } - - // Enable/disable hardware NS - if runtime.builtin_ns_is_available() { - let enable_hw = use_hardware && options.noise_suppression; - if !runtime.enable_builtin_ns(enable_hw) { - log::warn!("enable_builtin_ns({}) failed", enable_hw); - } + if !runtime.configure_platform_audio_processing( + options.echo_cancellation, + options.auto_gain_control, + options.noise_suppression, + options.prefer_hardware_processing, + ) { + log::warn!( + "PlatformAudio: native audio processing configuration was partially applied" + ); } log::info!( diff --git a/livekit/src/platform_audio/processing.rs b/livekit/src/platform_audio/processing.rs index 8b99ef90d..3160434a5 100644 --- a/livekit/src/platform_audio/processing.rs +++ b/livekit/src/platform_audio/processing.rs @@ -17,7 +17,7 @@ /// The type of audio processing being used. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum AudioProcessingType { - /// Hardware audio processing (iOS VPIO, Android hardware effects). + /// Platform audio processing (Apple voice processing, Android hardware effects). Hardware, /// Software audio processing (WebRTC's built-in APM). Software, @@ -35,15 +35,15 @@ impl Default for AudioProcessingType { /// /// # Platform Behavior /// -/// - **iOS**: Hardware processing via VPIO is always used and provides excellent -/// AEC/AGC/NS. The `prefer_hardware_processing` default is `true` on iOS. +/// - **iOS/macOS**: Apple voice processing can provide platform AEC/AGC/NS. +/// The `prefer_hardware_processing` default is `true` on iOS. /// /// - **Android**: Hardware AEC quality varies significantly across manufacturers /// and device models. Many devices have broken or poorly-tuned hardware AEC. /// The default is `false` to use WebRTC's reliable software processing. /// See: /// -/// - **Desktop** (macOS, Windows, Linux): Hardware processing is not available. +/// - **Desktop** (Windows, Linux): Hardware processing is not available. /// WebRTC's software Audio Processing Module (APM) is always used. /// The `prefer_hardware_processing` setting is ignored. /// @@ -96,9 +96,8 @@ pub struct AudioProcessingOptions { /// /// # Platform Defaults /// - /// - **iOS**: `true` - VPIO hardware processing is excellent and always used. - /// Apple's Voice Processing IO unit provides reliable, low-latency AEC/AGC/NS - /// that is tightly integrated with the audio hardware. + /// - **iOS**: `true` - Apple voice processing provides reliable, + /// low-latency AEC/AGC/NS that is tightly integrated with audio I/O. /// /// - **Android**: `false` - Hardware AEC is unreliable on many devices. /// Quality varies significantly across manufacturers (Samsung, Xiaomi, etc.) @@ -107,8 +106,8 @@ pub struct AudioProcessingOptions { /// Reference: Meta found hardware AEC "broken on many combinations of HW + OS" /// when supporting billions of users across thousands of device models. /// - /// - **Desktop**: `false` - Hardware processing is not available. - /// This setting is ignored; WebRTC software APM is always used. + /// - **Desktop**: `false` - Hardware processing is not available on + /// non-Apple desktop targets. WebRTC software APM is used. pub prefer_hardware_processing: bool, } @@ -118,9 +117,9 @@ impl Default for AudioProcessingOptions { echo_cancellation: true, noise_suppression: true, auto_gain_control: true, - // iOS: VPIO hardware processing is excellent and tightly integrated. + // iOS: Apple voice processing is preferred when available. // Android: Hardware AEC is unreliable across the fragmented device ecosystem. - // Desktop: Hardware processing not available, setting is ignored. + // Desktop: Hardware processing not available on non-Apple desktop targets. #[cfg(target_os = "ios")] prefer_hardware_processing: true, #[cfg(not(target_os = "ios"))] diff --git a/livekit/src/rtc_engine/lk_runtime.rs b/livekit/src/rtc_engine/lk_runtime.rs index 81ad95e70..6479b97b5 100644 --- a/livekit/src/rtc_engine/lk_runtime.rs +++ b/livekit/src/rtc_engine/lk_runtime.rs @@ -209,6 +209,47 @@ impl LkRuntime { self.pc_factory.enable_builtin_ns(enable) } + /// Returns the platform audio processing topology reported by the ADM. + #[cfg(not(target_arch = "wasm32"))] + pub(crate) fn platform_audio_processing_topology(&self) -> i32 { + self.pc_factory.platform_audio_processing_topology() + } + + /// Returns whether the ADM can enable a platform voice-processing path. + #[cfg(not(target_arch = "wasm32"))] + pub(crate) fn platform_voice_processing_path_is_available(&self) -> bool { + self.pc_factory.platform_voice_processing_path_is_available() + } + + /// Enables or disables the platform voice-processing path when supported. + #[cfg(not(target_arch = "wasm32"))] + pub(crate) fn enable_platform_voice_processing_path(&self, enable: bool) -> bool { + self.pc_factory.enable_platform_voice_processing_path(enable) + } + + /// Returns whether the platform voice-processing path is enabled. + #[cfg(not(target_arch = "wasm32"))] + pub(crate) fn platform_voice_processing_path_is_enabled(&self) -> bool { + self.pc_factory.platform_voice_processing_path_is_enabled() + } + + /// Configures platform audio processing using the ADM's native topology. + #[cfg(not(target_arch = "wasm32"))] + pub(crate) fn configure_platform_audio_processing( + &self, + echo_cancellation: bool, + auto_gain_control: bool, + noise_suppression: bool, + prefer_hardware: bool, + ) -> bool { + self.pc_factory.configure_platform_audio_processing( + echo_cancellation, + auto_gain_control, + noise_suppression, + prefer_hardware, + ) + } + /// Control whether ADM recording (microphone) is enabled. /// /// When disabled, WebRTC's calls to InitRecording/StartRecording will be no-ops. diff --git a/webrtc-sys/include/livekit/audio_device_controller.h b/webrtc-sys/include/livekit/audio_device_controller.h index db68e9151..68ce005f7 100644 --- a/webrtc-sys/include/livekit/audio_device_controller.h +++ b/webrtc-sys/include/livekit/audio_device_controller.h @@ -64,6 +64,14 @@ class AudioDeviceController { bool enable_builtin_aec(bool enable) const; bool enable_builtin_agc(bool enable) const; bool enable_builtin_ns(bool enable) const; + int platform_audio_processing_topology() const; + bool platform_voice_processing_path_is_available() const; + bool enable_platform_voice_processing_path(bool enable) const; + bool platform_voice_processing_path_is_enabled() const; + bool configure_platform_audio_processing(bool echo_cancellation, + bool auto_gain_control, + bool noise_suppression, + bool prefer_hardware) const; // ADM recording control void set_adm_recording_enabled(bool enabled) const; diff --git a/webrtc-sys/src/audio_device_controller.cpp b/webrtc-sys/src/audio_device_controller.cpp index 500b6efe9..85d65569a 100644 --- a/webrtc-sys/src/audio_device_controller.cpp +++ b/webrtc-sys/src/audio_device_controller.cpp @@ -174,6 +174,87 @@ bool AudioDeviceController::enable_builtin_ns(bool enable) const { return adm_proxy_->EnableBuiltInNS(enable) == 0; } +int AudioDeviceController::platform_audio_processing_topology() const { + return static_cast(adm_proxy_->GetPlatformAudioProcessingTopology()); +} + +bool AudioDeviceController::platform_voice_processing_path_is_available() const { + return adm_proxy_->PlatformVoiceProcessingPathIsAvailable(); +} + +bool AudioDeviceController::enable_platform_voice_processing_path(bool enable) const { + return adm_proxy_->EnablePlatformVoiceProcessingPath(enable) == 0; +} + +bool AudioDeviceController::platform_voice_processing_path_is_enabled() const { + auto state = adm_proxy_->GetPlatformAudioProcessingState(); + return state.is_voice_processing_enabled_active.value_or( + state.is_voice_processing_enabled_requested.value_or(false)); +} + +bool AudioDeviceController::configure_platform_audio_processing( + bool echo_cancellation, + bool auto_gain_control, + bool noise_suppression, + bool prefer_hardware) const { + const bool use_hardware = prefer_hardware; + bool ok = true; + auto update_ok = [&ok](bool result) { + ok = result && ok; + }; + + const auto topology = adm_proxy_->GetPlatformAudioProcessingTopology(); + if (topology == webrtc::AudioDeviceModule::PlatformAudioProcessingTopology:: + kEchoCancellationAndNoiseSuppressionCoupled) { + // Apple exposes AEC and NS through one shared voice-processing path. Only + // use the platform path when both requested components can be represented. + const bool use_echo_noise_platform_path = + use_hardware && echo_cancellation && noise_suppression; + + if (!use_echo_noise_platform_path) { + if (adm_proxy_->PlatformVoiceProcessingPathIsAvailable()) { + update_ok(adm_proxy_->EnablePlatformVoiceProcessingPath(false) == 0); + } + if (adm_proxy_->BuiltInAGCIsAvailable()) { + update_ok(adm_proxy_->EnableBuiltInAGC(false) == 0); + } + return ok; + } + + if (!adm_proxy_->PlatformVoiceProcessingPathIsAvailable()) { + return false; + } + if (adm_proxy_->EnablePlatformVoiceProcessingPath(true) != 0) { + return false; + } + + const bool echo_noise_available = adm_proxy_->BuiltInAECIsAvailable() && + adm_proxy_->BuiltInNSIsAvailable(); + if (!echo_noise_available) { + adm_proxy_->EnablePlatformVoiceProcessingPath(false); + return false; + } + + update_ok(adm_proxy_->EnableBuiltInAEC(true) == 0); + update_ok(adm_proxy_->EnableBuiltInNS(true) == 0); + if (adm_proxy_->BuiltInAGCIsAvailable()) { + update_ok(adm_proxy_->EnableBuiltInAGC(auto_gain_control) == 0); + } + return ok; + } + + if (adm_proxy_->BuiltInAECIsAvailable()) { + update_ok(adm_proxy_->EnableBuiltInAEC(use_hardware && echo_cancellation) == 0); + } + if (adm_proxy_->BuiltInAGCIsAvailable()) { + update_ok(adm_proxy_->EnableBuiltInAGC(use_hardware && auto_gain_control) == 0); + } + if (adm_proxy_->BuiltInNSIsAvailable()) { + update_ok(adm_proxy_->EnableBuiltInNS(use_hardware && noise_suppression) == 0); + } + return ok; +} + void AudioDeviceController::set_adm_recording_enabled(bool enabled) const { adm_proxy_->set_recording_enabled(enabled); } diff --git a/webrtc-sys/src/audio_device_controller.rs b/webrtc-sys/src/audio_device_controller.rs index 00ce73943..ca3da1d13 100644 --- a/webrtc-sys/src/audio_device_controller.rs +++ b/webrtc-sys/src/audio_device_controller.rs @@ -55,6 +55,20 @@ pub mod ffi { fn enable_builtin_aec(self: &AudioDeviceController, enable: bool) -> bool; fn enable_builtin_agc(self: &AudioDeviceController, enable: bool) -> bool; fn enable_builtin_ns(self: &AudioDeviceController, enable: bool) -> bool; + fn platform_audio_processing_topology(self: &AudioDeviceController) -> i32; + fn platform_voice_processing_path_is_available(self: &AudioDeviceController) -> bool; + fn enable_platform_voice_processing_path( + self: &AudioDeviceController, + enable: bool, + ) -> bool; + fn platform_voice_processing_path_is_enabled(self: &AudioDeviceController) -> bool; + fn configure_platform_audio_processing( + self: &AudioDeviceController, + echo_cancellation: bool, + auto_gain_control: bool, + noise_suppression: bool, + prefer_hardware: bool, + ) -> bool; fn set_adm_recording_enabled(self: &AudioDeviceController, enabled: bool); fn adm_recording_enabled(self: &AudioDeviceController) -> bool; From 55c2625957c10591f52d6fb63de66f1bc636b0a8 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Fri, 3 Jul 2026 00:24:35 +0900 Subject: [PATCH 3/4] Add changeset for AudioEngine PlatformAudio --- .changeset/audioengine-platformaudio.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changeset/audioengine-platformaudio.md diff --git a/.changeset/audioengine-platformaudio.md b/.changeset/audioengine-platformaudio.md new file mode 100644 index 000000000..571ad9a21 --- /dev/null +++ b/.changeset/audioengine-platformaudio.md @@ -0,0 +1,9 @@ +--- +livekit: minor +webrtc-sys: minor +--- + +Use the Apple AudioEngine ADM for PlatformAudio on iOS and macOS. + +- The platform ADM on Apple platforms is now the AVAudioEngine based device with runtime switchable voice processing and device change handling. +- `PlatformAudio::configure_audio_processing` now drives the ADM's native audio processing topology: on Apple the coupled AEC+NS voice processing path is enabled when both are requested with hardware preferred, otherwise WebRTC software processing is used. From b3ea04c6f603ba50914e91ad82880bb2fd68389f Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Fri, 3 Jul 2026 09:45:50 +0900 Subject: [PATCH 4/4] Refresh external_audio_source patch context for webrtc m144 The AudioSourceInterface hunk stopped applying after the fork added SetOptions right where the patch expected the class to end. The build script tolerates patch failures (git apply || true), so building a prebuilt from current m144 would have silently produced a libwebrtc without external audio source support. Content is unchanged, only the surrounding context is regenerated against m144. --- .../patches/external_audio_source.patch | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/webrtc-sys/libwebrtc/patches/external_audio_source.patch b/webrtc-sys/libwebrtc/patches/external_audio_source.patch index 088be4044..970fad6d8 100644 --- a/webrtc-sys/libwebrtc/patches/external_audio_source.patch +++ b/webrtc-sys/libwebrtc/patches/external_audio_source.patch @@ -1,11 +1,11 @@ diff --git a/api/media_stream_interface.h b/api/media_stream_interface.h -index fb1cc4e58e..85062ba60e 100644 +index 643b5f273b..7b5b568692 100644 --- a/api/media_stream_interface.h +++ b/api/media_stream_interface.h -@@ -267,6 +267,11 @@ class RTC_EXPORT AudioSourceInterface : public MediaSourceInterface { - // (for some of the settings this approach is broken, e.g. setting +@@ -270,6 +270,11 @@ class RTC_EXPORT AudioSourceInterface : public MediaSourceInterface { // audio network adaptation on the source is the wrong layer of abstraction). virtual const AudioOptions options() const; + virtual void SetOptions(const AudioOptions & /* options */) {} + + // Returns true if this source delivers audio externally (via AddSink), + // bypassing the ADM/AudioState audio distribution path. @@ -79,10 +79,10 @@ index 04a7d19dfa..9f513f3c75 100644 virtual ~AudioSource() {} }; diff --git a/media/engine/webrtc_voice_engine.cc b/media/engine/webrtc_voice_engine.cc -index 762f9d584c..4ce07ddc9d 100644 +index 44ff51e439..65b32e2d74 100644 --- a/media/engine/webrtc_voice_engine.cc +++ b/media/engine/webrtc_voice_engine.cc -@@ -1017,6 +1017,14 @@ class WebRtcVoiceSendChannel::WebRtcAudioSendStream : public AudioSource::Sink { +@@ -942,6 +942,14 @@ class WebRtcVoiceSendChannel::WebRtcAudioSendStream : public AudioSource::Sink { RTC_DCHECK(source_ == source); return; } @@ -98,10 +98,10 @@ index 762f9d584c..4ce07ddc9d 100644 source_ = source; UpdateSendState(); diff --git a/pc/rtp_sender.cc b/pc/rtp_sender.cc -index d5edbbf0ed..c14ddfe868 100644 +index bd3e4a34d8..6498a4eef8 100644 --- a/pc/rtp_sender.cc +++ b/pc/rtp_sender.cc -@@ -786,6 +786,13 @@ void AudioRtpSender::SetSend() { +@@ -792,6 +792,13 @@ void AudioRtpSender::SetSend() { RTC_DCHECK_RUN_ON(signaling_thread_); RTC_DCHECK(!stopped_); RTC_DCHECK(can_send_track()); @@ -116,7 +116,7 @@ index d5edbbf0ed..c14ddfe868 100644 RTC_LOG(LS_ERROR) << "SetAudioSend: No audio channel exists."; return; diff --git a/pc/rtp_sender.h b/pc/rtp_sender.h -index eaffd4ef0f..d0489df9e7 100644 +index 516ce7de05..c6216b6b7e 100644 --- a/pc/rtp_sender.h +++ b/pc/rtp_sender.h @@ -307,6 +307,12 @@ class LocalAudioSinkAdapter : public AudioTrackSinkInterface,