Files
webrtc/rtptransceiver_test.go
boks1971 2af60a491d Filter unattached RTX when getting codecs
Recently added filtering out unattached RTX in SetCodecPrefrences.
Turns out it is needed in `getCodecs()` also in the following
scenario
- AddTrack from answer side adds a tracks and has media engine codecs
- An offer is received and negotiated codecs get updated in
  media engine. And this does not have one of the codecs added
  AddTrack above (default media engine codecs)
- Generate answer will do `getCodecs` which will filter out
  the codec missing from the `offer`, but would have let the
  corresponding RTX pass through and get added to the answer.
2025-08-27 01:34:35 +05:30

277 lines
8.2 KiB
Go

// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !js
// +build !js
package webrtc
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_RTPTransceiver_SetCodecPreferences(t *testing.T) {
mediaEngine := &MediaEngine{}
api := NewAPI(WithMediaEngine(mediaEngine))
assert.NoError(t, mediaEngine.RegisterDefaultCodecs())
assert.NoError(t, mediaEngine.pushCodecs(mediaEngine.videoCodecs, RTPCodecTypeVideo))
assert.NoError(t, mediaEngine.pushCodecs(mediaEngine.audioCodecs, RTPCodecTypeAudio))
tr := RTPTransceiver{kind: RTPCodecTypeVideo, api: api, codecs: mediaEngine.videoCodecs}
assert.EqualValues(t, mediaEngine.videoCodecs, tr.getCodecs())
failTestCases := [][]RTPCodecParameters{
{
{
RTPCodecCapability: RTPCodecCapability{MimeTypeOpus, 48000, 2, "minptime=10;useinbandfec=1", nil},
PayloadType: 111,
},
},
{
{
RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", nil},
PayloadType: 96,
},
{
RTPCodecCapability: RTPCodecCapability{MimeTypeOpus, 48000, 2, "minptime=10;useinbandfec=1", nil},
PayloadType: 111,
},
},
}
for _, testCase := range failTestCases {
assert.ErrorIs(t, tr.SetCodecPreferences(testCase), errRTPTransceiverCodecUnsupported)
}
successTestCases := [][]RTPCodecParameters{
{
{
RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", nil},
PayloadType: 96,
},
},
{
{
RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", nil},
PayloadType: 96,
},
{
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=96", nil},
PayloadType: 97,
},
{
RTPCodecCapability: RTPCodecCapability{MimeTypeVP9, 90000, 0, "profile-id=0", nil},
PayloadType: 98,
},
{
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=98", nil},
PayloadType: 99,
},
},
}
for _, testCase := range successTestCases {
assert.NoError(t, tr.SetCodecPreferences(testCase))
}
assert.NoError(t, tr.SetCodecPreferences(nil))
assert.NotEqual(t, 0, len(tr.getCodecs()))
assert.NoError(t, tr.SetCodecPreferences([]RTPCodecParameters{}))
assert.NotEqual(t, 0, len(tr.getCodecs()))
}
// Assert that SetCodecPreferences properly filters codecs and PayloadTypes are respected.
func Test_RTPTransceiver_SetCodecPreferences_PayloadType(t *testing.T) {
notOfferedCodec := RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{"video/notOfferedCodec", 90000, 0, "", nil},
PayloadType: 50,
}
offeredCodec := RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{"video/offeredCodec", 90000, 0, "", nil},
PayloadType: 52,
}
offeredCodecRTX := RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=52", nil},
PayloadType: 53,
}
mediaEngine := &MediaEngine{}
assert.NoError(t, mediaEngine.RegisterDefaultCodecs())
assert.NoError(t, mediaEngine.RegisterCodec(offeredCodec, RTPCodecTypeVideo))
assert.NoError(t, mediaEngine.RegisterCodec(offeredCodecRTX, RTPCodecTypeVideo))
offerPC, err := NewAPI(WithMediaEngine(mediaEngine)).NewPeerConnection(Configuration{})
assert.NoError(t, err)
assert.NoError(t, mediaEngine.RegisterCodec(notOfferedCodec, RTPCodecTypeVideo))
answerPC, err := NewAPI(WithMediaEngine(mediaEngine)).NewPeerConnection(Configuration{})
assert.NoError(t, err)
_, err = offerPC.AddTransceiverFromKind(RTPCodecTypeVideo)
assert.NoError(t, err)
track, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion")
assert.NoError(t, err)
answerTransceiver, err := answerPC.AddTransceiverFromTrack(
track,
RTPTransceiverInit{Direction: RTPTransceiverDirectionSendonly},
)
assert.NoError(t, err)
assert.NoError(t, answerTransceiver.SetCodecPreferences([]RTPCodecParameters{
notOfferedCodec,
offeredCodec,
offeredCodecRTX,
{
RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", nil},
PayloadType: 54,
},
}))
offer, err := offerPC.CreateOffer(nil)
assert.NoError(t, err)
assert.NoError(t, offerPC.SetLocalDescription(offer))
assert.NoError(t, answerPC.SetRemoteDescription(offer))
answer, err := answerPC.CreateAnswer(nil)
assert.NoError(t, err)
// VP8 with proper PayloadType
assert.NotEqual(t, -1, strings.Index(answer.SDP, "a=rtpmap:54 VP8/90000"))
// testCodec1 and testCodec1RTX should be included as they are in the offer
assert.NotEqual(t, -1, strings.Index(answer.SDP, "a=rtpmap:52 offeredCodec/90000"))
assert.NotEqual(t, -1, strings.Index(answer.SDP, "a=rtpmap:53 rtx/90000"))
assert.NotEqual(t, -1, strings.Index(answer.SDP, "a=fmtp:53 apt=52"))
// testCodec is ignored since offerer doesn't support
assert.Equal(t, -1, strings.Index(answer.SDP, "notOfferedCodec"))
closePairNow(t, offerPC, answerPC)
}
// Assert that SetCodecPreferences and getCodecs properly filters unattached RTX.
func Test_RTPTransceiver_UnattachedRTX(t *testing.T) {
testCodec := RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{"video/testCodec", 90000, 0, "", nil},
PayloadType: 50,
}
testCodecRTX := RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=50", nil},
PayloadType: 51,
}
mediaEngine := &MediaEngine{}
assert.NoError(t, mediaEngine.RegisterDefaultCodecs())
offerPC, err := NewAPI(WithMediaEngine(mediaEngine)).NewPeerConnection(Configuration{})
assert.NoError(t, err)
assert.NoError(t, mediaEngine.RegisterCodec(testCodec, RTPCodecTypeVideo))
assert.NoError(t, mediaEngine.RegisterCodec(testCodecRTX, RTPCodecTypeVideo))
answerPC, err := NewAPI(WithMediaEngine(mediaEngine)).NewPeerConnection(Configuration{})
assert.NoError(t, err)
_, err = offerPC.AddTransceiverFromKind(RTPCodecTypeVideo)
assert.NoError(t, err)
answerTransceiver, err := answerPC.AddTransceiverFromKind(RTPCodecTypeVideo)
assert.NoError(t, err)
assert.NoError(t, answerTransceiver.SetCodecPreferences([]RTPCodecParameters{
testCodecRTX,
{
RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", nil},
PayloadType: 52,
},
}))
// rtx should not be in the list of transceiver codecs as testCodec (primary) is
// not given to SetCodecPreferences
answerTransceiver.mu.RLock()
foundRTX := false
for _, codec := range answerTransceiver.codecs {
if strings.EqualFold(codec.RTPCodecCapability.MimeType, MimeTypeRTX) {
foundRTX = true
break
}
}
assert.False(t, foundRTX)
answerTransceiver.mu.RUnlock()
assert.NoError(t, answerTransceiver.SetCodecPreferences([]RTPCodecParameters{
testCodec,
testCodecRTX,
{
RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", nil},
PayloadType: 52,
},
}))
// rtx should be in the list of transceiver codecs as testCodec (primary) is
// given to SetCodecPreferences
answerTransceiver.mu.RLock()
foundRTX = false
for _, codec := range answerTransceiver.codecs {
if strings.EqualFold(codec.RTPCodecCapability.MimeType, MimeTypeRTX) {
foundRTX = true
break
}
}
assert.True(t, foundRTX)
answerTransceiver.mu.RUnlock()
// getCodecs() should have RTX as remote offer has not been processed
codecs := answerTransceiver.getCodecs()
foundRTX = false
for _, codec := range codecs {
if strings.EqualFold(codec.RTPCodecCapability.MimeType, MimeTypeRTX) {
foundRTX = true
break
}
}
assert.True(t, foundRTX)
offer, err := offerPC.CreateOffer(nil)
assert.NoError(t, err)
assert.NoError(t, offerPC.SetLocalDescription(offer))
assert.NoError(t, answerPC.SetRemoteDescription(offer))
// getCodecs() should filter out RTX as remote does not offer testCodec (primary)
codecs = answerTransceiver.getCodecs()
foundRTX = false
for _, codec := range codecs {
if strings.EqualFold(codec.RTPCodecCapability.MimeType, MimeTypeRTX) {
foundRTX = true
break
}
}
assert.False(t, foundRTX)
answer, err := answerPC.CreateAnswer(nil)
assert.NoError(t, err)
// VP8 with proper PayloadType
assert.NotEqual(t, -1, strings.Index(answer.SDP, "a=rtpmap:52 VP8/90000"))
// testCodec is ignored since offerer doesn't support
assert.Equal(t, -1, strings.Index(answer.SDP, "testCodec"))
assert.Equal(t, -1, strings.Index(answer.SDP, "rtx"))
closePairNow(t, offerPC, answerPC)
}