From c7f625d626f12eb485afef22322e626896559383 Mon Sep 17 00:00:00 2001 From: Raja Subramanian Date: Sat, 4 Oct 2025 00:17:17 +0530 Subject: [PATCH] Do not force codec regression between opus and red. (#3980) The following scenario produced silence audio in egress 1. JS SDK publishing audio/red 2. Egress joins 3. Egress picks the RED primary receiver -> this converts RED to Opus in SFU 4. At the same time, codec regression is triggered back to the publisher. Publisher switches to publishing audio/opus and stops audio/red. 5. But, egress is still attached to audio/red and pulls down only silence. Fix by checking if the negotiated codec needs publish before sending subscribed codec udpate. --- pkg/rtc/subscribedtrack.go | 2 +- pkg/rtc/wrappedreceiver.go | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/pkg/rtc/subscribedtrack.go b/pkg/rtc/subscribedtrack.go index 0aeda7199..2c39bda3a 100644 --- a/pkg/rtc/subscribedtrack.go +++ b/pkg/rtc/subscribedtrack.go @@ -433,7 +433,7 @@ func (t *SubscribedTrack) OnRttUpdate(rtt uint32) { } func (t *SubscribedTrack) OnCodecNegotiated(codec webrtc.RTPCodecCapability) { - if !t.params.WrappedReceiver.DetermineReceiver(codec) { + if isAvailable, needsPublish := t.params.WrappedReceiver.DetermineReceiver(codec); !isAvailable || !needsPublish { return } diff --git a/pkg/rtc/wrappedreceiver.go b/pkg/rtc/wrappedreceiver.go index fc8edbb45..e29dfe799 100644 --- a/pkg/rtc/wrappedreceiver.go +++ b/pkg/rtc/wrappedreceiver.go @@ -88,8 +88,13 @@ func (r *WrappedReceiver) StreamID() string { return r.params.StreamId } -// DetermineReceiver determines the receiver of negotiated codec and return if there is a match -func (r *WrappedReceiver) DetermineReceiver(codec webrtc.RTPCodecCapability) bool { +// DetermineReceiver determines the receiver of negotiated codec and returns +// +// isAvailable: returns true if given codec is a potential codec from publisher or if an existing published codec can be translated +// needsPublish: indicates if the codec is needed from publisher, some combinations can be achieved via codec translation internally, +// +// example: unecrypted opus -> RED translation and vice-versa can be done without the need for publisher to send the other codec. +func (r *WrappedReceiver) DetermineReceiver(codec webrtc.RTPCodecCapability) (isAvailable bool, needsPublish bool) { r.lock.Lock() codecMimeType := mime.NormalizeMimeType(codec.MimeType) @@ -98,6 +103,8 @@ func (r *WrappedReceiver) DetermineReceiver(codec webrtc.RTPCodecCapability) boo receiverMimeType := receiver.Mime() if receiverMimeType == codecMimeType { trackReceiver = receiver + isAvailable = true + needsPublish = true break } @@ -105,9 +112,11 @@ func (r *WrappedReceiver) DetermineReceiver(codec webrtc.RTPCodecCapability) boo if receiverMimeType == mime.MimeTypeRED && codecMimeType == mime.MimeTypeOpus { // audio opus/red can match opus only trackReceiver = receiver.GetPrimaryReceiverForRed() + isAvailable = true break } else if receiverMimeType == mime.MimeTypeOpus && codecMimeType == mime.MimeTypeRED { trackReceiver = receiver.GetRedReceiver() + isAvailable = true break } } @@ -115,7 +124,7 @@ func (r *WrappedReceiver) DetermineReceiver(codec webrtc.RTPCodecCapability) boo if trackReceiver == nil { r.lock.Unlock() r.params.Logger.Warnw("can't determine receiver for codec", nil, "codec", codec.MimeType) - return false + return } r.TrackReceiver = trackReceiver @@ -127,7 +136,7 @@ func (r *WrappedReceiver) DetermineReceiver(codec webrtc.RTPCodecCapability) boo trackReceiver.AddOnReady(f) } - return true + return } func (r *WrappedReceiver) Codecs() []webrtc.RTPCodecParameters {