From f6012e5c3f329e21a1686d24ae43918596fdd5bb Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 13 Nov 2025 10:58:47 +0200 Subject: [PATCH] Fix: prevent concurrent Opus decoder access during session transfers During session transfers, multiple handleInputTrackForSession goroutines could call WriteMessage() concurrently on the shared inputSource, causing Opus SILK decoder state corruption and assertion failures. Added inputSourceMutex to serialize WriteMessage() calls and prevent race conditions in both input and output audio paths. --- audio.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/audio.go b/audio.go index df6e3763..c5265fd4 100644 --- a/audio.go +++ b/audio.go @@ -13,6 +13,8 @@ import ( var ( audioMutex sync.Mutex + setAudioTrackMutex sync.Mutex // Prevents concurrent setAudioTrack() calls + inputSourceMutex sync.Mutex // Serializes WriteMessage() calls to input source outputSource audio.AudioSource inputSource atomic.Pointer[audio.AudioSource] outputRelay *audio.OutputRelay @@ -130,6 +132,10 @@ func onWebRTCDisconnect() { } func setAudioTrack(audioTrack *webrtc.TrackLocalStaticSample) { + // Prevent concurrent calls to avoid creating multiple relays + setAudioTrackMutex.Lock() + defer setAudioTrackMutex.Unlock() + audioMutex.Lock() currentAudioTrack = audioTrack oldRelay := outputRelay @@ -146,7 +152,6 @@ func setAudioTrack(audioTrack *webrtc.TrackLocalStaticSample) { oldSource.Disconnect() } - // Create new source and relay for the new track audioMutex.Lock() if currentAudioTrack != nil && audioOutputEnabled.Load() { alsaDevice := "hw:1,0" @@ -265,9 +270,12 @@ func handleInputTrackForSession(track *webrtc.TrackRemote) { } } + // Serialize WriteMessage() to prevent concurrent session handlers from corrupting Opus decoder + inputSourceMutex.Lock() if err := (*source).WriteMessage(0, opusData); err != nil { audioLogger.Warn().Err(err).Msg("failed to write audio message") (*source).Disconnect() } + inputSourceMutex.Unlock() } }