Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions rtpcodec.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package webrtc

import (
"fmt"
"strconv"
"strings"

"github.com/pion/webrtc/v4/internal/fmtp"
Expand Down Expand Up @@ -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 {
Expand Down
193 changes: 193 additions & 0 deletions rtpcodec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions rtptransceiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package webrtc

import (
"fmt"
"strings"
"sync"
"sync/atomic"

Expand Down Expand Up @@ -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
Expand Down
Loading