From 7ecc9664c16b49a5126184ce04552886385a60ba Mon Sep 17 00:00:00 2001 From: Felix Date: Thu, 6 Aug 2026 14:21:02 +0300 Subject: [PATCH] fix(ios): enable voice-processing I/O for voiceChat/videoChat session modes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AVAudioSession mode voiceChat configures routing and EQ but does not enable echo cancellation — that requires setVoiceProcessingEnabled on the engine's input node. Full-duplex apps (VoIP, realtime voice agents) currently get raw speaker echo in the mic stream, so server-side VAD self-triggers and agents interrupt their own playback. Enable Apple's voice-processing I/O (AEC + noise suppression + AGC) on the system input node whenever the desired session mode is voiceChat or videoChat, before the input connection format is read (voice processing changes the hardware format) and only while the engine is stopped. Modes are synced both ways so leaving voice-chat mode disables it again. --- .../ios/audioapi/ios/system/AudioEngine.mm | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/react-native-audio-api/ios/audioapi/ios/system/AudioEngine.mm b/packages/react-native-audio-api/ios/audioapi/ios/system/AudioEngine.mm index d91ed60f9..b9c289984 100644 --- a/packages/react-native-audio-api/ios/audioapi/ios/system/AudioEngine.mm +++ b/packages/react-native-audio-api/ios/audioapi/ios/system/AudioEngine.mm @@ -38,6 +38,7 @@ - (BOOL)hasTrackedGraph; - (AVAudioFormat *)currentInputConnectionFormat; - (void)materializeSourceNodeWithId:(NSString *)sourceNodeId; - (BOOL)materializeInputNodeIfNeeded; +- (void)syncVoiceProcessingWithSessionMode; - (void)materializeTrackedNodesIfNeeded; - (AVAudioFormat *)liveInputFormat; @@ -186,6 +187,8 @@ - (BOOL)materializeInputNodeIfNeeded return YES; } + [self syncVoiceProcessingWithSessionMode]; + AVAudioFormat *inputFormat = [self currentInputConnectionFormat]; if (inputFormat == nil) { @@ -199,6 +202,33 @@ - (BOOL)materializeInputNodeIfNeeded return YES; } +// Voice-chat session modes imply Apple's voice-processing I/O (echo +// cancellation, noise suppression, AGC). Without it, speaker output loops back +// into the microphone — full-duplex apps (VoIP, voice agents) hear themselves +// and speech detection self-triggers. Must run before the input format is read +// (voice processing changes the hardware format) and while the engine is +// stopped. +- (void)syncVoiceProcessingWithSessionMode +{ + AVAudioSessionMode mode = self.sessionManager.desiredMode; + BOOL wantsVoiceProcessing = [mode isEqualToString:AVAudioSessionModeVoiceChat] || + [mode isEqualToString:AVAudioSessionModeVideoChat]; + + AVAudioInputNode *systemInputNode = self.audioEngine.inputNode; + if (systemInputNode.isVoiceProcessingEnabled == wantsVoiceProcessing) { + return; + } + + if (self.audioEngine.isRunning) { + [self.audioEngine stop]; + } + + NSError *vpError = nil; + if (![systemInputNode setVoiceProcessingEnabled:wantsVoiceProcessing error:&vpError]) { + NSLog(@"[AudioEngine] setVoiceProcessingEnabled:%d failed: %@", wantsVoiceProcessing, vpError); + } +} + - (void)materializeTrackedNodesIfNeeded { NSArray *sourceNodeIds =