Environment
LiveKitWebRTC version: 125.6422.11 (M125)
iOS version: 18.x
Device: iPhone (reproducible on all iOS devices)
Usage pattern: Direct LKRTCPeerConnection usage (not via LiveKit Room API)
Bug Description
When using LKRTCPeerConnection directly for P2P calls on iOS, the first call works perfectly, but after hanging up and making a second call, audio playback is silent despite ICE connection succeeding and remote tracks being received.
The error in console:
AURemoteIO.cpp:1666 AUIOClient_StartIO failed (561145187)
Error code 561145187 (0x21706D63) = kAudioUnitErr_CannotDoInCurrentContext.
Root Cause Analysis
The Problem
WebRTC iOS's AudioDeviceIOS manages a AURemoteIO audio unit via VoiceProcessingAudioUnit. When a PeerConnection is closed, the audio unit is stopped and uninitialized, but NOT disposed:
// voice_processing_audio_unit.mm - Stop()
bool VoiceProcessingAudioUnit::Stop() {
OSStatus result = AudioOutputUnitStop(vpio_unit_); // Stop, but don't release
state_ = kInitialized;
return true;
}
// voice_processing_audio_unit.mm - Uninitialize()
bool VoiceProcessingAudioUnit::Uninitialize() {
OSStatus result = AudioUnitUninitialize(vpio_unit_); // Uninitialize, but don't release
state_ = kUninitialized;
return true;
}
The DisposeAudioUnit() method (called from destructor and error paths) should call AudioComponentInstanceDispose(vpio_unit_) to fully release the CoreAudio resource, but based on source code analysis, the AURemoteIO handle persists after PeerConnection close.
When a new PeerConnection is created for the second call, AudioDeviceIOS tries to start a new AURemoteIO, but detects the old instance is still running → kAudioUnitErr_CannotDoInCurrentContext.
Why This Didn't Happen with Older WebRTC Versions
This project previously used OWT SDK (Intel Open WebRTC Toolkit) with WebRTC ~M73 (2019), which did NOT have this bug. In M73, ShutdownPlayOrRecord() called AudioComponentInstanceDispose to fully release the AURemoteIO handle. Between M73 and M125, Google WebRTC changed the AudioDeviceIOS cleanup behavior.
Comparison
| |
OWT WebRTC (~M73) |
LiveKitWebRTC (M125) |
| ShutdownPlayOrRecord() |
Calls AudioComponentInstanceDispose |
Only AudioOutputUnitStop + AudioUnitUninitialize |
| Second P2P call |
Works |
No audio (AUIOClient_StartIO failed) |
The only thing that works is exit(0) to kill the process, which is not acceptable for App Store.
Suggested Fix
Ensure DisposeAudioUnit() calls AudioComponentInstanceDispose to fully release the AURemoteIO handle:
void VoiceProcessingAudioUnit::DisposeAudioUnit() {
if (vpio_unit_) {
AudioOutputUnitStop(vpio_unit_);
AudioUnitUninitialize(vpio_unit_);
AudioComponentInstanceDispose(vpio_unit_); // ← Add this line
vpio_unit_ = nullptr;
}
state_ = kInitRequired;
}
Note: I was unable to locate the actual implementation of DisposeAudioUnit() in the livekit-prefixed-m125 branch — it's declared in voice_processing_audio_unit.h and called from voice_processing_audio_unit.mm, but the definition appears to be in a separate compilation unit (possibly moved by the "Audio Device Optimization" patch group). The exact patch location would need to be verified by cloning the full webrtc-sdk/webrtc repository.
Impact
This bug affects any iOS app that:
Uses LKRTCPeerConnection directly (not via LiveKit Room API)
Supports multiple sequential P2P calls within the same app session
Apps using LiveKit's Room API are not affected because LiveKit's AudioManager uses a different audio backend (AVAudioEngine + Voice Processing I/O) with proper lifecycle management via setEngineAvailability.
Related
## Environment
- LiveKitWebRTC version: 125.6422.11 (M125)
- iOS version: 18.x
- Device: iPhone (reproducible on all iOS devices)
- Usage pattern: Direct
LKRTCPeerConnection usage (not via LiveKit Room API)
Bug Description
When using LKRTCPeerConnection directly for P2P calls on iOS, the first call works perfectly, but after hanging up and making a second call, audio playback is silent despite ICE connection succeeding and remote tracks being received.
The error in console:
AURemoteIO.cpp:1666 AUIOClient_StartIO failed (561145187)
Error code 561145187 (0x21706D63) = kAudioUnitErr_CannotDoInCurrentContext.
Root Cause Analysis
The Problem
WebRTC iOS's AudioDeviceIOS manages a AURemoteIO audio unit via VoiceProcessingAudioUnit. When a PeerConnection is closed, the audio unit is stopped and uninitialized, but NOT disposed:
// voice_processing_audio_unit.mm - Stop()
bool VoiceProcessingAudioUnit::Stop() {
OSStatus result = AudioOutputUnitStop(vpio_unit_); // Stop, but don't release
state_ = kInitialized;
return true;
}
// voice_processing_audio_unit.mm - Uninitialize()
bool VoiceProcessingAudioUnit::Uninitialize() {
OSStatus result = AudioUnitUninitialize(vpio_unit_); // Uninitialize, but don't release
state_ = kUninitialized;
return true;
}
The DisposeAudioUnit() method (called from destructor and error paths) should call AudioComponentInstanceDispose(vpio_unit_) to fully release the CoreAudio resource, but based on source code analysis, the AURemoteIO handle persists after PeerConnection close.
When a new PeerConnection is created for the second call, AudioDeviceIOS tries to start a new AURemoteIO, but detects the old instance is still running → kAudioUnitErr_CannotDoInCurrentContext.
Why This Didn't Happen with Older WebRTC Versions
This project previously used OWT SDK (Intel Open WebRTC Toolkit) with WebRTC ~M73 (2019), which did NOT have this bug. In M73, ShutdownPlayOrRecord() called AudioComponentInstanceDispose to fully release the AURemoteIO handle. Between M73 and M125, Google WebRTC changed the AudioDeviceIOS cleanup behavior.
Comparison
|
OWT WebRTC (~M73) |
LiveKitWebRTC (M125) |
ShutdownPlayOrRecord() |
Calls AudioComponentInstanceDispose |
Only AudioOutputUnitStop + AudioUnitUninitialize |
| Second P2P call |
Works |
No audio (AUIOClient_StartIO failed) |
Log Comparison
First call (works):
addTrack audioTrack=EC51D715..., sender=OK, isEnabled=1
setRemoteSDP called, type=answer
didChangeIceConnectionState 2 ← ICE connected
[Audio works]
Second call (no audio):
addTrack audioTrack=1F5D4B1B..., sender=OK, isEnabled=1
setRemoteSDP called, type=answer
AUIOClient_StartIO failed (561145187) ← Audio unit start failed
didChangeIceConnectionState 2 ← ICE connected
didAddReceiver track=..., kind=audio ← Remote audio track received
[No audio]
Workarounds Attempted (All Failed)
| # |
Approach |
Result |
| 1 |
useManualAudio = YES/NO + isAudioEnabled toggle |
Interferes with signaling, remote SDP becomes recvonly |
| 2 |
AVAudioSession.setActive(NO/YES) |
Kills first call's audio unit too |
| 3 |
Audio track isEnabled = NO/YES toggle |
No effect (tracks are newly created each call) |
| 4 |
Re-create RTCPeerConnectionFactory |
No effect (AudioDeviceModule still detects conflict) |
| 5 |
Keep factory as singleton (don't release on disconnect) |
No effect (AURemoteIO still not disposed) |
| 6 |
LKRTCAudioSession.setActive:NO after disconnect |
No effect (WebRTC C++ auto-deactivates after close) |
| 7 |
Force release previousClient + delay before reconnect |
No effect |
| 8 |
Double deactive (LKRTCAudioSession + AVAudioSession) + @autoreleasepool force release factory |
Worse - breaks WebRTC C++ state machine |
| 9 |
audioDeviceModule.stopPlayout/stopRecording |
No effect (already stopped at disconnect time) |
The only thing that works is exit(0) to kill the process, which is not acceptable for App Store.
Suggested Fix
Ensure DisposeAudioUnit() calls AudioComponentInstanceDispose to fully release the AURemoteIO handle:
void VoiceProcessingAudioUnit::DisposeAudioUnit() {
if (vpio_unit_) {
AudioOutputUnitStop(vpio_unit_);
AudioUnitUninitialize(vpio_unit_);
AudioComponentInstanceDispose(vpio_unit_); // ← Add this line
vpio_unit_ = nullptr;
}
state_ = kInitRequired;
}
Note: I was unable to locate the actual implementation of DisposeAudioUnit() in the livekit-prefixed-m125 branch — it's declared in voice_processing_audio_unit.h and called from voice_processing_audio_unit.mm, but the definition appears to be in a separate compilation unit (possibly moved by the "Audio Device Optimization" patch group). The exact patch location would need to be verified by cloning the full webrtc-sdk/webrtc repository.
Impact
This bug affects any iOS app that:
- Uses
LKRTCPeerConnection directly (not via LiveKit Room API)
- Supports multiple sequential P2P calls within the same app session
Apps using LiveKit's Room API are not affected because LiveKit's AudioManager uses a different audio backend (AVAudioEngine + Voice Processing I/O) with proper lifecycle management via setEngineAvailability.
Related
Environment
LiveKitWebRTC version: 125.6422.11 (M125)
iOS version: 18.x
Device: iPhone (reproducible on all iOS devices)
Usage pattern: Direct
LKRTCPeerConnectionusage (not via LiveKit Room API)Bug Description
When using
LKRTCPeerConnectiondirectly for P2P calls on iOS, the first call works perfectly, but after hanging up and making a second call, audio playback is silent despite ICE connection succeeding and remote tracks being received.The error in console:
Error code
561145187(0x21706D63) =kAudioUnitErr_CannotDoInCurrentContext.Root Cause Analysis
The Problem
WebRTC iOS's
AudioDeviceIOSmanages aAURemoteIOaudio unit viaVoiceProcessingAudioUnit. When aPeerConnectionis closed, the audio unit is stopped and uninitialized, but NOT disposed:The
DisposeAudioUnit()method (called from destructor and error paths) should callAudioComponentInstanceDispose(vpio_unit_)to fully release the CoreAudio resource, but based on source code analysis, the AURemoteIO handle persists after PeerConnection close.When a new
PeerConnectionis created for the second call,AudioDeviceIOStries to start a new AURemoteIO, but detects the old instance is still running →kAudioUnitErr_CannotDoInCurrentContext.Why This Didn't Happen with Older WebRTC Versions
This project previously used OWT SDK (Intel Open WebRTC Toolkit) with WebRTC ~M73 (2019), which did NOT have this bug. In M73,
ShutdownPlayOrRecord()calledAudioComponentInstanceDisposeto fully release the AURemoteIO handle. Between M73 and M125, Google WebRTC changed theAudioDeviceIOScleanup behavior.Comparison
The only thing that works is
exit(0)to kill the process, which is not acceptable for App Store.Suggested Fix
Ensure
DisposeAudioUnit()callsAudioComponentInstanceDisposeto fully release the AURemoteIO handle:Note: I was unable to locate the actual implementation of
DisposeAudioUnit()in thelivekit-prefixed-m125branch — it's declared invoice_processing_audio_unit.hand called fromvoice_processing_audio_unit.mm, but the definition appears to be in a separate compilation unit (possibly moved by the "Audio Device Optimization" patch group). The exact patch location would need to be verified by cloning the fullwebrtc-sdk/webrtcrepository.Impact
This bug affects any iOS app that:
Uses
LKRTCPeerConnectiondirectly (not via LiveKit Room API)Supports multiple sequential P2P calls within the same app session
Apps using LiveKit's
RoomAPI are not affected because LiveKit'sAudioManageruses a different audio backend (AVAudioEngine+ Voice Processing I/O) with proper lifecycle management viasetEngineAvailability.Related
## EnvironmentLiveKit "Audio Device Optimization" patches (PRs allow listen-only mode in AudioUnit, adjust when category changes #2, Release mic when category changes #5, Change AVAudioSession defaults to iOS defaults #7, Sync audio session config #8, feat: support bypass voice processing for iOS. #15, [Windows] Fix can't open mic alone when built-in AEC is enabled. #29, AudioUnit: Don't rely on category switch for mic indicator to turn off #52, Stop recording on mute (turn off mic indicator) #55) that modified
AudioDeviceIOSbehaviorCommunity report: iOS SDK degraded audio after reconnect
LKRTCPeerConnectionusage (not via LiveKit Room API)Bug Description
When using
LKRTCPeerConnectiondirectly for P2P calls on iOS, the first call works perfectly, but after hanging up and making a second call, audio playback is silent despite ICE connection succeeding and remote tracks being received.The error in console:
Error code
561145187(0x21706D63) =kAudioUnitErr_CannotDoInCurrentContext.Root Cause Analysis
The Problem
WebRTC iOS's
AudioDeviceIOSmanages aAURemoteIOaudio unit viaVoiceProcessingAudioUnit. When aPeerConnectionis closed, the audio unit is stopped and uninitialized, but NOT disposed:The
DisposeAudioUnit()method (called from destructor and error paths) should callAudioComponentInstanceDispose(vpio_unit_)to fully release the CoreAudio resource, but based on source code analysis, the AURemoteIO handle persists after PeerConnection close.When a new
PeerConnectionis created for the second call,AudioDeviceIOStries to start a new AURemoteIO, but detects the old instance is still running →kAudioUnitErr_CannotDoInCurrentContext.Why This Didn't Happen with Older WebRTC Versions
This project previously used OWT SDK (Intel Open WebRTC Toolkit) with WebRTC ~M73 (2019), which did NOT have this bug. In M73,
ShutdownPlayOrRecord()calledAudioComponentInstanceDisposeto fully release the AURemoteIO handle. Between M73 and M125, Google WebRTC changed theAudioDeviceIOScleanup behavior.Comparison
ShutdownPlayOrRecord()AudioComponentInstanceDisposeAudioOutputUnitStop+AudioUnitUninitializeAUIOClient_StartIO failed)Log Comparison
First call (works):
Second call (no audio):
Workarounds Attempted (All Failed)
useManualAudio = YES/NO+isAudioEnabledtogglerecvonlyAVAudioSession.setActive(NO/YES)isEnabled = NO/YEStoggleRTCPeerConnectionFactoryLKRTCAudioSession.setActive:NOafter disconnectpreviousClient+ delay before reconnect@autoreleasepoolforce release factoryaudioDeviceModule.stopPlayout/stopRecordingThe only thing that works is
exit(0)to kill the process, which is not acceptable for App Store.Suggested Fix
Ensure
DisposeAudioUnit()callsAudioComponentInstanceDisposeto fully release the AURemoteIO handle:Note: I was unable to locate the actual implementation of
DisposeAudioUnit()in thelivekit-prefixed-m125branch — it's declared invoice_processing_audio_unit.hand called fromvoice_processing_audio_unit.mm, but the definition appears to be in a separate compilation unit (possibly moved by the "Audio Device Optimization" patch group). The exact patch location would need to be verified by cloning the fullwebrtc-sdk/webrtcrepository.Impact
This bug affects any iOS app that:
LKRTCPeerConnectiondirectly (not via LiveKit Room API)Apps using LiveKit's
RoomAPI are not affected because LiveKit'sAudioManageruses a different audio backend (AVAudioEngine+ Voice Processing I/O) with proper lifecycle management viasetEngineAvailability.Related
AudioDeviceIOSbehavior