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.
This commit is contained in:
Alex P 2025-11-13 10:58:47 +02:00
parent abe61c8959
commit 33ee474865
1 changed files with 9 additions and 1 deletions

View File

@ -13,6 +13,8 @@ import (
var ( var (
audioMutex sync.Mutex audioMutex sync.Mutex
setAudioTrackMutex sync.Mutex // Prevents concurrent setAudioTrack() calls
inputSourceMutex sync.Mutex // Serializes WriteMessage() calls to input source
outputSource audio.AudioSource outputSource audio.AudioSource
inputSource atomic.Pointer[audio.AudioSource] inputSource atomic.Pointer[audio.AudioSource]
outputRelay *audio.OutputRelay outputRelay *audio.OutputRelay
@ -130,6 +132,10 @@ func onWebRTCDisconnect() {
} }
func setAudioTrack(audioTrack *webrtc.TrackLocalStaticSample) { func setAudioTrack(audioTrack *webrtc.TrackLocalStaticSample) {
// Prevent concurrent calls to avoid creating multiple relays
setAudioTrackMutex.Lock()
defer setAudioTrackMutex.Unlock()
audioMutex.Lock() audioMutex.Lock()
currentAudioTrack = audioTrack currentAudioTrack = audioTrack
oldRelay := outputRelay oldRelay := outputRelay
@ -146,7 +152,6 @@ func setAudioTrack(audioTrack *webrtc.TrackLocalStaticSample) {
oldSource.Disconnect() oldSource.Disconnect()
} }
// Create new source and relay for the new track
audioMutex.Lock() audioMutex.Lock()
if currentAudioTrack != nil && audioOutputEnabled.Load() { if currentAudioTrack != nil && audioOutputEnabled.Load() {
alsaDevice := "hw:1,0" 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 { if err := (*source).WriteMessage(0, opusData); err != nil {
audioLogger.Warn().Err(err).Msg("failed to write audio message") audioLogger.Warn().Err(err).Msg("failed to write audio message")
(*source).Disconnect() (*source).Disconnect()
} }
inputSourceMutex.Unlock()
} }
} }