From c82d96cb75b1910c2cf74b73dc19a20908152d69 Mon Sep 17 00:00:00 2001 From: boks1971 Date: Fri, 22 Aug 2025 09:58:54 +0530 Subject: [PATCH] Remove RTX codec if no primary While adding transceivers from SetRemoteDescription, the filtered codecs could filter out the primart codec and leave the RTX codec in. Generating an answer with that fails `SetRemoteDescription` on remote peer due to an unrecognisable codec. Fix it by filtering out RTX is primary is not there. --- rtpcodec.go | 33 ++++++++ rtpcodec_test.go | 193 ++++++++++++++++++++++++++++++++++++++++++++++ rtptransceiver.go | 14 ++++ 3 files changed, 240 insertions(+) diff --git a/rtpcodec.go b/rtpcodec.go index d4733dbd..d03dba0b 100644 --- a/rtpcodec.go +++ b/rtpcodec.go @@ -5,6 +5,7 @@ package webrtc import ( "fmt" + "strconv" "strings" "github.com/pion/webrtc/v4/internal/fmtp" @@ -155,6 +156,38 @@ func findRTXPayloadType(needle PayloadType, haystack []RTPCodecParameters) Paylo return PayloadType(0) } +// Given needle CodecParameters, returns if needle is RTX and +// if primary codec corresponding to that needle is in the haystack of codecs. +func primaryPayloadTypeForRTXExists(needle RTPCodecParameters, haystack []RTPCodecParameters) ( + isRTX bool, primaryExists bool, +) { + if !strings.EqualFold(needle.MimeType, MimeTypeRTX) { + return + } + + isRTX = true + parsed := fmtp.Parse(needle.MimeType, needle.ClockRate, needle.Channels, needle.SDPFmtpLine) + aptPayload, ok := parsed.Parameter("apt") + if !ok { + return + } + + primaryPayloadType, err := strconv.Atoi(aptPayload) + if err != nil || primaryPayloadType < 0 || primaryPayloadType > 255 { + return + } + + for _, c := range haystack { + if c.PayloadType == PayloadType(primaryPayloadType) { + primaryExists = true + + return + } + } + + return +} + // For now, only FlexFEC is supported. func findFECPayloadType(haystack []RTPCodecParameters) PayloadType { for _, c := range haystack { diff --git a/rtpcodec_test.go b/rtpcodec_test.go index 0328f36f..8b5923de 100644 --- a/rtpcodec_test.go +++ b/rtpcodec_test.go @@ -9,6 +9,199 @@ import ( "github.com/stretchr/testify/assert" ) +func TestFindPrimaryPayloadTypeForRTX(t *testing.T) { + for _, test := range []struct { + Name string + Needle RTPCodecParameters + Haystack []RTPCodecParameters + ResultIsRTX bool + ResultPrimaryExists bool + }{ + { + Name: "not RTX", + Needle: RTPCodecParameters{ + PayloadType: 2, + RTPCodecCapability: RTPCodecCapability{ + MimeType: MimeTypeH264, + ClockRate: 90000, + SDPFmtpLine: "apt=2", + }, + }, + Haystack: []RTPCodecParameters{ + { + PayloadType: 1, + RTPCodecCapability: RTPCodecCapability{ + MimeType: MimeTypeH264, + ClockRate: 90000, + }, + }, + }, + ResultIsRTX: false, + ResultPrimaryExists: false, + }, + { + Name: "incorrect fmtp", + Needle: RTPCodecParameters{ + PayloadType: 2, + RTPCodecCapability: RTPCodecCapability{ + MimeType: MimeTypeRTX, + ClockRate: 90000, + SDPFmtpLine: "incorrect-fmtp", + }, + }, + Haystack: []RTPCodecParameters{ + { + PayloadType: 1, + RTPCodecCapability: RTPCodecCapability{ + MimeType: MimeTypeH264, + ClockRate: 90000, + }, + }, + }, + ResultIsRTX: true, + ResultPrimaryExists: false, + }, + { + Name: "incomplete fmtp", + Needle: RTPCodecParameters{ + PayloadType: 2, + RTPCodecCapability: RTPCodecCapability{ + MimeType: MimeTypeRTX, + ClockRate: 90000, + SDPFmtpLine: "apt=", + }, + }, + Haystack: []RTPCodecParameters{ + { + PayloadType: 1, + RTPCodecCapability: RTPCodecCapability{ + MimeType: MimeTypeH264, + ClockRate: 90000, + }, + }, + }, + ResultIsRTX: true, + ResultPrimaryExists: false, + }, + { + Name: "primary payload type outside range (negative)", + Needle: RTPCodecParameters{ + PayloadType: 2, + RTPCodecCapability: RTPCodecCapability{ + MimeType: MimeTypeRTX, + ClockRate: 90000, + SDPFmtpLine: "apt=-10", + }, + }, + Haystack: []RTPCodecParameters{ + { + PayloadType: 1, + RTPCodecCapability: RTPCodecCapability{ + MimeType: MimeTypeH264, + ClockRate: 90000, + }, + }, + }, + ResultIsRTX: true, + ResultPrimaryExists: false, + }, + { + Name: "primary payload type outside range (high positive)", + Needle: RTPCodecParameters{ + PayloadType: 2, + RTPCodecCapability: RTPCodecCapability{ + MimeType: MimeTypeRTX, + ClockRate: 90000, + SDPFmtpLine: "apt=1000", + }, + }, + Haystack: []RTPCodecParameters{ + { + PayloadType: 1, + RTPCodecCapability: RTPCodecCapability{ + MimeType: MimeTypeH264, + ClockRate: 90000, + }, + }, + }, + ResultIsRTX: true, + ResultPrimaryExists: false, + }, + { + Name: "non-matching needle", + Needle: RTPCodecParameters{ + PayloadType: 2, + RTPCodecCapability: RTPCodecCapability{ + MimeType: MimeTypeRTX, + ClockRate: 90000, + SDPFmtpLine: "apt=23", + }, + }, + Haystack: []RTPCodecParameters{ + { + PayloadType: 1, + RTPCodecCapability: RTPCodecCapability{ + MimeType: MimeTypeH264, + ClockRate: 90000, + }, + }, + }, + ResultIsRTX: true, + ResultPrimaryExists: false, + }, + { + Name: "matching needle", + Needle: RTPCodecParameters{ + PayloadType: 2, + RTPCodecCapability: RTPCodecCapability{ + MimeType: MimeTypeRTX, + ClockRate: 90000, + SDPFmtpLine: "apt=1", + }, + }, + Haystack: []RTPCodecParameters{ + { + PayloadType: 1, + RTPCodecCapability: RTPCodecCapability{ + MimeType: MimeTypeH264, + ClockRate: 90000, + }, + }, + }, + ResultIsRTX: true, + ResultPrimaryExists: true, + }, + { + Name: "matching fmtp is a substring", + Needle: RTPCodecParameters{ + PayloadType: 2, + RTPCodecCapability: RTPCodecCapability{ + MimeType: MimeTypeRTX, + ClockRate: 90000, + SDPFmtpLine: "apt=1;rtx-time:2000", + }, + }, + Haystack: []RTPCodecParameters{ + { + PayloadType: 1, + RTPCodecCapability: RTPCodecCapability{ + MimeType: MimeTypeH264, + ClockRate: 90000, + }, + }, + }, + ResultIsRTX: true, + ResultPrimaryExists: true, + }, + } { + t.Run(test.Name, func(t *testing.T) { + isRTX, primaryExists := primaryPayloadTypeForRTXExists(test.Needle, test.Haystack) + assert.Equal(t, test.ResultIsRTX, isRTX) + assert.Equal(t, test.ResultPrimaryExists, primaryExists) + }) + } +} + func TestFindFECPayloadType(t *testing.T) { for _, test := range []struct { Haystack []RTPCodecParameters diff --git a/rtptransceiver.go b/rtptransceiver.go index 5ce0f2d4..3741e932 100644 --- a/rtptransceiver.go +++ b/rtptransceiver.go @@ -8,6 +8,7 @@ package webrtc import ( "fmt" + "strings" "sync" "sync/atomic" @@ -60,6 +61,19 @@ func (t *RTPTransceiver) SetCodecPreferences(codecs []RTPCodecParameters) error } } + // remove RTX codecs if there is no corresponding primary codec + for i := len(codecs) - 1; i >= 0; i-- { + c := codecs[i] + if !strings.EqualFold(c.MimeType, MimeTypeRTX) { + continue + } + + if isRTX, primaryExists := primaryPayloadTypeForRTXExists(c, codecs); isRTX && !primaryExists { + // no primary for RTX, remove the RTX + codecs = append(codecs[:i], codecs[i+1:]...) + } + } + t.codecs = codecs return nil