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.
This commit is contained in:
boks1971
2025-08-22 09:58:54 +05:30
committed by Raja Subramanian
parent 8efd17e592
commit c82d96cb75
3 changed files with 240 additions and 0 deletions

View File

@@ -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 {

View File

@@ -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

View File

@@ -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