When a session uses PlatformAudioSource (AudioSourceType.AudioSourcePlatform, PreferHardware = true) and the user mutes then unmutes the microphone, the received audio contains echo/reverb after unmuting. The distortion sometimes disappears as AEC reconverges, but most of the time it remains for the remainder of the session.
Version: v1.3.8
Platform: iOS
Reproduction Steps
- Connect to a LiveKit room using PlatformAudioSource (hardware VPIO AEC, AudioProcessingOptions.Default).
- Publish the local audio track and begin receiving remote audio.
- Call micTrack.SetMute(true) to mute the local track.
- Wait several seconds (remote audio continues playing through the speaker).
- Call micTrack.SetMute(false) to unmute.
- Observe: echo in received audio.
Potential Cause (This is what Claude told me when trying to diagnose this issue)
ILocalTrack.SetMute(true) sends a LocalTrackMuteRequest to the native Rust/FFI layer, which pauses or stops ADM capture in the platform audio device. During the mute period the speaker is still active (remote audio plays), so the AEC reference signal keeps changing. Because the ADM capture is paused, the AEC engine receives no new capture frames and its room model becomes stale.
On unmute, LocalTrackMuteRequest(Mute=false) resumes ADM capture. The AEC model is now stale relative to the current acoustic state (speaker has been playing for the entire mute period). The engine needs to reconverge before echo cancellation is effective again. During that reconvergence window the mic picks up speaker audio that is not yet cancelled, causing audible echo in the outbound stream.
The same root cause exists in RtcAudioSource (the MicrophoneSource path) where the C# if (_muted) return; guard drops frames instead of sending silence, but on iOS with PlatformAudioSource the issue is in the Rust/ADM layer.
Expected Behavior
Muting should silence the outbound audio stream without interrupting the AEC processing pipeline. The AEC should continue receiving capture frames (zeroed, i.e. silence) so its room model stays current. On unmute, AEC is already converged and echo cancellation is immediately effective.
This is consistent with how hardware mute works at the OS level (e.g. iOS AVAudioSession mute via setVoiceProcessingEnabled): the captured signal is zeroed before reaching the encoder, but the AEC reference/capture loop continues unbroken.
Suggested Fix
For PlatformAudioSource (Rust/ADM layer): When LocalTrackMuteRequest(Mute=true) is received for a platform audio source, continue running the ADM capture and AEC pipeline but zero the PCM data before it reaches the encoder, rather than pausing or stopping capture. On unmute, stop zeroing.
For RtcAudioSource (MicrophoneSource path, C# layer): The same pattern applies. In OnAudioRead, instead of:
if (_muted) return;
zero the data and continue processing:
if (_muted)
Array.Clear(data, 0, data.Length);
// fall through — zeroed frame keeps AEC converged
API Request (Alternative)
If the "send silence when muted" behavior is intentionally not the default, expose a MuteMode enum on ILocalTrack or AudioProcessingOptions:
public enum MuteMode
{
SilenceFrames, // zero PCM, keep AEC running (preferred for voice sessions)
PauseCapture, // current behavior, disrupts AEC
}
This would let callers opt into AEC-preserving mute for conversational use cases.
When a session uses PlatformAudioSource (AudioSourceType.AudioSourcePlatform, PreferHardware = true) and the user mutes then unmutes the microphone, the received audio contains echo/reverb after unmuting. The distortion sometimes disappears as AEC reconverges, but most of the time it remains for the remainder of the session.
Version: v1.3.8
Platform: iOS
Reproduction Steps
Potential Cause (This is what Claude told me when trying to diagnose this issue)
ILocalTrack.SetMute(true) sends a LocalTrackMuteRequest to the native Rust/FFI layer, which pauses or stops ADM capture in the platform audio device. During the mute period the speaker is still active (remote audio plays), so the AEC reference signal keeps changing. Because the ADM capture is paused, the AEC engine receives no new capture frames and its room model becomes stale.
On unmute, LocalTrackMuteRequest(Mute=false) resumes ADM capture. The AEC model is now stale relative to the current acoustic state (speaker has been playing for the entire mute period). The engine needs to reconverge before echo cancellation is effective again. During that reconvergence window the mic picks up speaker audio that is not yet cancelled, causing audible echo in the outbound stream.
The same root cause exists in RtcAudioSource (the MicrophoneSource path) where the C# if (_muted) return; guard drops frames instead of sending silence, but on iOS with PlatformAudioSource the issue is in the Rust/ADM layer.
Expected Behavior
Muting should silence the outbound audio stream without interrupting the AEC processing pipeline. The AEC should continue receiving capture frames (zeroed, i.e. silence) so its room model stays current. On unmute, AEC is already converged and echo cancellation is immediately effective.
This is consistent with how hardware mute works at the OS level (e.g. iOS AVAudioSession mute via setVoiceProcessingEnabled): the captured signal is zeroed before reaching the encoder, but the AEC reference/capture loop continues unbroken.
Suggested Fix
For PlatformAudioSource (Rust/ADM layer): When LocalTrackMuteRequest(Mute=true) is received for a platform audio source, continue running the ADM capture and AEC pipeline but zero the PCM data before it reaches the encoder, rather than pausing or stopping capture. On unmute, stop zeroing.
For RtcAudioSource (MicrophoneSource path, C# layer): The same pattern applies. In OnAudioRead, instead of:
if (_muted) return;
zero the data and continue processing:
if (_muted)
Array.Clear(data, 0, data.Length);
// fall through — zeroed frame keeps AEC converged
API Request (Alternative)
If the "send silence when muted" behavior is intentionally not the default, expose a MuteMode enum on ILocalTrack or AudioProcessingOptions:
public enum MuteMode
{
SilenceFrames, // zero PCM, keep AEC running (preferred for voice sessions)
PauseCapture, // current behavior, disrupts AEC
}
This would let callers opt into AEC-preserving mute for conversational use cases.