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
|
# 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
|
# 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.
|
# 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:
|
environment:
|
||||||
sdk: ^3.11.5
|
sdk: ^3.11.5
|
||||||
|
|||||||
@@ -387,6 +387,65 @@ impl IosVoiceUnit {
|
|||||||
)
|
)
|
||||||
.map_err(|e| AudioError::Backend(format!("vpio enable input I/O: {e}")))?;
|
.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
|
// Stream format. Apple's iOS canonical format for
|
||||||
// AudioUnits is Linear PCM, 16-bit signed integer samples
|
// AudioUnits is Linear PCM, 16-bit signed integer samples
|
||||||
// (see "Canonical formats" in the Audio Unit Hosting Guide
|
// (see "Canonical formats" in the Audio Unit Hosting Guide
|
||||||
|
|||||||
Reference in New Issue
Block a user