feat(audio): prefer native voice backends

This commit is contained in:
Edison Jwa
2026-05-20 14:52:33 +09:00
parent 7d6d56e330
commit c87b47f064
6 changed files with 127 additions and 119 deletions
+15 -16
View File
@@ -1,9 +1,11 @@
//! iOS output + capture stream via the **VoiceProcessingIO**
//! Apple output + capture stream via the **VoiceProcessingIO**
//! AudioUnit (`kAudioUnitSubType_VoiceProcessingIO`, a.k.a. VPIO).
//!
//! ## Why not cpal on iOS
//! ## Why not cpal on Apple voice paths
//!
//! cpal's iOS backend opens `kAudioUnitSubType_RemoteIO` with no
//! cpal's Apple backend does not expose the voice-processing unit controls
//! Chanora needs for a VoIP client. On iOS, cpal opens
//! `kAudioUnitSubType_RemoteIO` with no
//! control over the stream format, buffer size, or channel count;
//! on iPhone 16 Pro running iOS 18 it reports the output element as
//! **mono 48 kHz** even when the session category is `.playAndRecord`
@@ -17,20 +19,14 @@
//! in logs but produces no audible difference — the audio is still
//! coming out the earpiece.
//!
//! Every production iOS VoIP client (Mumble iOS, Linphone /
//! mediastreamer2, Signal-iOS, Jitsi, the WebRTC reference impl)
//! avoids RemoteIO and drives VPIO directly instead. VPIO is
//! Apple's recommended voice unit: it ships hardware AEC + AGC +
//! NS, it accepts arbitrary stream-format requests on bus 0
//! (output) and bus 1 (input), and it re-binds its underlying HAL
//! transducer correctly when the AVAudioSession route changes,
//! because it IS the canonical voice unit on iOS — Apple's own
//! FaceTime audio path runs through it.
//! Production VoIP clients on Apple platforms drive VPIO directly instead
//! of treating CoreAudio as a generic music-playback device. VPIO is Apple's
//! native voice unit: it ships hardware AEC + AGC + NS and accepts explicit
//! stream-format requests on bus 0 (output) and bus 1 (input).
//!
//! This file replaces the cpal capture + playback streams on iOS
//! only. macOS continues to use cpal's CoreAudio HAL backend (which
//! works correctly for desktop audio). Linux uses SDL2 (see
//! `sdl_output.rs`). Windows uses cpal's WASAPI backend.
//! This file replaces the cpal capture + playback streams on iOS and macOS.
//! Linux uses SDL2 for output (see `sdl_output.rs`) and cpal for capture;
//! Windows uses cpal's WASAPI backend.
//!
//! ## What VPIO gives us
//!
@@ -663,6 +659,7 @@ impl IosVoiceUnit {
/// Route rebinding on iOS is most reliable when we bounce the
/// VoiceProcessingIO unit through an uninitialize/reinitialize
/// cycle, then start again.
#[cfg(target_os = "ios")]
pub fn restart(&mut self) -> Result<(), AudioError> {
self.unit
.stop()
@@ -681,6 +678,7 @@ impl IosVoiceUnit {
}
/// Pause the audio unit during an interruption.
#[cfg(target_os = "ios")]
pub fn pause(&mut self) -> Result<(), AudioError> {
self.unit
.stop()
@@ -688,6 +686,7 @@ impl IosVoiceUnit {
}
/// Resume the audio unit after an interruption.
#[cfg(target_os = "ios")]
pub fn resume(&mut self) -> Result<(), AudioError> {
self.unit
.start()