Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/audioengine-platformaudio.md
Original file line number Diff line number Diff line change
@@ -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.
36 changes: 36 additions & 0 deletions libwebrtc/src/native/peer_connection_factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
59 changes: 20 additions & 39 deletions livekit/src/platform_audio/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
///
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
Expand All @@ -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!(
Expand Down
21 changes: 10 additions & 11 deletions livekit/src/platform_audio/processing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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: <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.
///
Expand Down Expand Up @@ -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.)
Expand All @@ -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,
}

Expand All @@ -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"))]
Expand Down
41 changes: 41 additions & 0 deletions livekit/src/rtc_engine/lk_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 8 additions & 0 deletions webrtc-sys/include/livekit/adm_proxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 8 additions & 0 deletions webrtc-sys/include/livekit/audio_device_controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
16 changes: 8 additions & 8 deletions webrtc-sys/libwebrtc/patches/external_audio_source.patch
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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;
}
Expand All @@ -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());
Expand All @@ -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,
Expand Down
Loading