-
Notifications
You must be signed in to change notification settings - Fork 196
Use AudioEngine ADM for PlatformAudio on Apple platforms #1215
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: hiroshi/adm-proxy-worker-thread
Are you sure you want to change the base?
Changes from all commits
8b3352d
f3c10e7
dddb8f2
fa5d8d4
1bcb5e1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| --- | ||
| 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. | ||
| - `prefer_hardware_processing` now defaults to `true` on macOS as well as iOS, so PlatformAudio uses Apple voice processing by default on both. Pass `prefer_hardware_processing: false` to keep WebRTC software processing. | ||
| - The ADM proxy forwards the platform voice processing interface (topology, path toggle, state) so WebRTC's audio processing resolution works through it when track audio options are applied. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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`. | ||
|
|
@@ -299,6 +299,9 @@ lazy_static! { | |
| /// When the last PlatformAudio is dropped, the Platform ADM is released. | ||
| struct PlatformAdmHandle { | ||
| runtime: Arc<LkRuntime>, | ||
| // Device level processing configuration last applied via | ||
| // configure_audio_processing, used by the active_*_type getters | ||
| processing_options: Mutex<AudioProcessingOptions>, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: A |
||
| } | ||
|
|
||
| impl Drop for PlatformAdmHandle { | ||
|
|
@@ -399,9 +402,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)] | ||
|
|
@@ -478,15 +481,18 @@ impl PlatformAudio { | |
| playout_count | ||
| ); | ||
|
|
||
| let handle = Arc::new(PlatformAdmHandle { runtime }); | ||
| let handle = Arc::new(PlatformAdmHandle { | ||
| runtime, | ||
| processing_options: Mutex::new(AudioProcessingOptions::default()), | ||
| }); | ||
| *handle_ref = Arc::downgrade(&handle); | ||
|
|
||
| let audio = Self { handle }; | ||
|
|
||
| // Configure audio processing with platform-appropriate defaults: | ||
| // - iOS: prefer_hardware_processing=true (VPIO is excellent) | ||
| // - iOS/macOS: 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) | ||
| // - Windows/Linux: prefer_hardware_processing=false (hardware not available) | ||
| if let Err(e) = audio.configure_audio_processing(AudioProcessingOptions::default()) { | ||
| log::warn!("PlatformAudio: failed to configure audio processing: {}", e); | ||
| } | ||
|
|
@@ -662,7 +668,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 +993,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 +1013,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 +1024,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 { | ||
|
|
@@ -1044,7 +1050,10 @@ impl PlatformAudio { | |
| /// } | ||
| /// ``` | ||
| pub fn active_aec_type(&self) -> AudioProcessingType { | ||
| if self.is_hardware_aec_available() { | ||
| let options = *self.handle.processing_options.lock(); | ||
| if !options.echo_cancellation { | ||
| AudioProcessingType::None | ||
| } else if options.prefer_hardware_processing && self.is_hardware_aec_available() { | ||
| AudioProcessingType::Hardware | ||
| } else { | ||
| AudioProcessingType::Software | ||
|
Comment on lines
+1053
to
1059
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Audio processing status queries return stale results after using individual toggle methods The processing status queries read from a cached options snapshot ( Impact: After toggling echo cancellation off via the convenience method, the status query still reports it as active (Software or Hardware), misleading callers. Stale cached state after convenience method callsThe PR adds a However, the three convenience methods — Example scenario:
(Refers to lines 1054-1061) Prompt for agentsWas this helpful? React with 👍 or 👎 to provide feedback. |
||
|
|
@@ -1053,7 +1062,10 @@ impl PlatformAudio { | |
|
|
||
| /// Gets the type of automatic gain control currently active. | ||
| pub fn active_agc_type(&self) -> AudioProcessingType { | ||
| if self.is_hardware_agc_available() { | ||
| let options = *self.handle.processing_options.lock(); | ||
| if !options.auto_gain_control { | ||
| AudioProcessingType::None | ||
| } else if options.prefer_hardware_processing && self.is_hardware_agc_available() { | ||
| AudioProcessingType::Hardware | ||
| } else { | ||
| AudioProcessingType::Software | ||
|
|
@@ -1062,7 +1074,10 @@ impl PlatformAudio { | |
|
|
||
| /// Gets the type of noise suppression currently active. | ||
| pub fn active_ns_type(&self) -> AudioProcessingType { | ||
| if self.is_hardware_ns_available() { | ||
| let options = *self.handle.processing_options.lock(); | ||
| if !options.noise_suppression { | ||
| AudioProcessingType::None | ||
| } else if options.prefer_hardware_processing && self.is_hardware_ns_available() { | ||
| AudioProcessingType::Hardware | ||
| } else { | ||
| AudioProcessingType::Software | ||
|
|
@@ -1076,10 +1091,11 @@ impl PlatformAudio { | |
| /// | ||
| /// # Platform Behavior | ||
| /// | ||
| /// - **iOS**: `prefer_hardware_processing` is ignored (always uses VPIO) | ||
| /// - **iOS/macOS**: `prefer_hardware_processing` uses Apple voice processing | ||
| /// when available (enabled by default) | ||
| /// - **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) | ||
| /// - **Windows/Linux**: `prefer_hardware_processing` is ignored (hardware not available) | ||
| /// | ||
| /// # Example | ||
| /// | ||
|
|
@@ -1130,6 +1146,8 @@ impl PlatformAudio { | |
| } | ||
| } | ||
|
|
||
| *self.handle.processing_options.lock() = options; | ||
|
|
||
| log::info!( | ||
| "Audio processing configured: AEC={}, AGC={}, NS={}, prefer_hw={}", | ||
| options.echo_cancellation, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 both. | ||
| /// | ||
| /// - **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: <https://github.com/react-native-webrtc/react-native-webrtc/issues/713> | ||
| /// | ||
| /// - **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,9 @@ 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/macOS**: `true` - Apple voice processing provides reliable, | ||
| /// low-latency AEC/AGC/NS that is tightly integrated with audio I/O, | ||
| /// and is especially effective for built-in speaker/microphone echo. | ||
| /// | ||
| /// - **Android**: `false` - Hardware AEC is unreliable on many devices. | ||
| /// Quality varies significantly across manufacturers (Samsung, Xiaomi, etc.) | ||
|
|
@@ -107,8 +107,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. | ||
| /// - **Windows/Linux**: `false` - Hardware processing is not available. | ||
| /// WebRTC software APM is used. | ||
| pub prefer_hardware_processing: bool, | ||
| } | ||
|
|
||
|
|
@@ -118,12 +118,12 @@ impl Default for AudioProcessingOptions { | |
| echo_cancellation: true, | ||
| noise_suppression: true, | ||
| auto_gain_control: true, | ||
| // iOS: VPIO hardware processing is excellent and tightly integrated. | ||
| // iOS/macOS: 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. | ||
| #[cfg(target_os = "ios")] | ||
| // Windows/Linux: Hardware processing not available. | ||
| #[cfg(any(target_os = "ios", target_os = "macos"))] | ||
| prefer_hardware_processing: true, | ||
| #[cfg(not(target_os = "ios"))] | ||
| #[cfg(not(any(target_os = "ios", target_os = "macos")))] | ||
| prefer_hardware_processing: false, | ||
|
Comment on lines
+124
to
127
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔍 macOS default changes from software to hardware audio processing This PR changes the default Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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); | ||
|
Comment on lines
+61
to
+65
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔍 ADM type switch from CoreAudio to AudioEngine on macOS The C++ constructor now selects Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| #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<bool>(false, [](webrtc::AudioDeviceModule& adm) { | ||
| return adm.PlatformVoiceProcessingPathIsAvailable(); | ||
| }); | ||
| } | ||
|
|
||
| int32_t AdmProxy::EnablePlatformVoiceProcessingPath(bool enable) { | ||
| return WithPlatformAdm<int32_t>(-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<int>(-1, [params](webrtc::AudioDeviceModule& adm) { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.