fix(audio,ios): bypass VPIO voice processing for clean playback (rc.8+68)
User report at +67 (.voiceChat mode): playback still 'broken'. Even with VPIO's Apple-documented session-mode pairing, its output-side gating chain (echo subtraction + adaptive noise suppression) chops quiet inter-phoneme content of human voice. Musicbot signal (loud, ~continuous) survives because it stays above the gating threshold; speech does not. Fix: set kAUVoiceIOProperty_BypassVoiceProcessing = 1 on the unit immediately after EnableIO (before stream format / callbacks / initialize). This disables ALL VPIO voice processing \u2014 the unit becomes effectively a vanilla RemoteIO with mic + speaker buses. Raw samples pass through both directions. Trade-off: * Lost: Apple's hardware AEC + AGC + NS on the mic path. User reports current capture is clean already, suggesting their test environment (headset? non-speakerphone?) doesn't need AEC. If echo loops back when speakerphone is engaged, we'll re-evaluate \u2014 either re-enable VPIO selectively for echo-prone routes or ship software AEC (DEC-007). * Gained: playback is no longer gated. Quiet inter-phoneme speech content reaches the speaker. Property setter: * Constant: kAUVoiceIOProperty_BypassVoiceProcessing = 2100 * Scope: Global, Element: Input (1) per WebRTC's reference iOS ADM (voice_processing_audio_unit.mm). * Value: u32 = 1 (= bypass). * Soft-fail with warn log if the property is rejected on an exotic iOS version (the unit still works, just with VPIO defaults). Build counter 67 -> 68.
This commit is contained in:
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.0.0-rc.8+67
|
||||
version: 1.0.0-rc.8+68
|
||||
|
||||
environment:
|
||||
sdk: ^3.11.5
|
||||
|
||||
@@ -387,6 +387,65 @@ impl IosVoiceUnit {
|
||||
)
|
||||
.map_err(|e| AudioError::Backend(format!("vpio enable input I/O: {e}")))?;
|
||||
|
||||
// Bypass VPIO's built-in voice processing (AEC + AGC + NS).
|
||||
//
|
||||
// Rationale: with the AVAudioSession in mode .voiceChat
|
||||
// (the documented VPIO pair), VPIO's output-side gating
|
||||
// chain still aggressively gates quiet inter-phoneme
|
||||
// content as "noise". User-reported symptom: musicbot
|
||||
// audio plays cleanly (loud, near-continuous signal),
|
||||
// but human voice with normal 20 dB peak-to-average
|
||||
// ratio sounds broken and unintelligible because the
|
||||
// average-level content gets chopped out between
|
||||
// phonemes.
|
||||
//
|
||||
// Bypassing voice processing turns VPIO into effectively
|
||||
// a vanilla I/O unit \u2014 raw mic samples in, raw
|
||||
// playback out. The trade-offs:
|
||||
//
|
||||
// * Capture lost: Apple's AEC + AGC + NS on the mic
|
||||
// input. The user's current setup reports clean
|
||||
// capture without these (likely using a headset
|
||||
// where AEC is unnecessary, or not generating
|
||||
// echo loud enough to matter). If echo loops back
|
||||
// when on speakerphone, we'll need to either
|
||||
// re-enable VPIO selectively or ship software AEC
|
||||
// (DEC-007).
|
||||
// * Playback gained: signal passes through verbatim.
|
||||
// Quiet inter-phoneme samples are no longer gated.
|
||||
//
|
||||
// Property constants from Apple's AudioUnitProperties.h
|
||||
// (also exposed via objc2-audio-toolbox):
|
||||
// kAUVoiceIOProperty_BypassVoiceProcessing = 2100
|
||||
//
|
||||
// Set on (Scope::Global, Element::Input = 1) per WebRTC's
|
||||
// reference iOS audio device manager
|
||||
// (voice_processing_audio_unit.mm). The bypass flag is a
|
||||
// UInt32 with 1 = bypass.
|
||||
const K_AU_VOICE_IO_PROPERTY_BYPASS_VOICE_PROCESSING: u32 = 2100;
|
||||
let bypass: u32 = 1;
|
||||
if let Err(e) = unit.set_property(
|
||||
K_AU_VOICE_IO_PROPERTY_BYPASS_VOICE_PROCESSING,
|
||||
Scope::Global,
|
||||
Element::Input,
|
||||
Some(&bypass),
|
||||
) {
|
||||
// Soft-fail: if the bypass property is rejected on
|
||||
// an exotic iOS version, log and continue. The unit
|
||||
// is still usable with default voice processing
|
||||
// (with whatever quality issues that brings).
|
||||
warn!(
|
||||
target: "chanora_audio",
|
||||
error = %e,
|
||||
"vpio: BypassVoiceProcessing set failed; continuing with VPIO defaults"
|
||||
);
|
||||
} else {
|
||||
info!(
|
||||
target: "chanora_audio",
|
||||
"vpio: voice processing bypassed (no AEC/AGC/NS) for cleaner playback"
|
||||
);
|
||||
}
|
||||
|
||||
// Stream format. Apple's iOS canonical format for
|
||||
// AudioUnits is Linear PCM, 16-bit signed integer samples
|
||||
// (see "Canonical formats" in the Audio Unit Hosting Guide
|
||||
|
||||
Reference in New Issue
Block a user