Match codec order of remote peer

Done when creating a transceiver from remote description to respect
codec order preference of remote peer.

There was a recent change to include partial matches which overwrote
same codecs and also rtx was getting magled.

Change it by removing codecs from search space as matches are found so
that a codec match is applied only once.

Also, move RTX matching to separate block to ensure proper RTXes ar
matched.
This commit is contained in:
boks1971
2025-08-28 01:01:45 +05:30
committed by Raja Subramanian
parent 42b3cfd2ca
commit c376d0edf9
5 changed files with 362 additions and 45 deletions

View File

@@ -630,6 +630,23 @@ func (m *MediaEngine) updateFromRemoteDescription(desc sdp.SessionDescription) e
return err return err
} }
addIfNew := func(existingCodecs []RTPCodecParameters, codec RTPCodecParameters) []RTPCodecParameters {
found := false
for _, existingCodec := range existingCodecs {
if existingCodec.PayloadType == codec.PayloadType {
found = true
break
}
}
if !found {
existingCodecs = append(existingCodecs, codec)
}
return existingCodecs
}
exactMatches := make([]RTPCodecParameters, 0, len(codecs)) exactMatches := make([]RTPCodecParameters, 0, len(codecs))
partialMatches := make([]RTPCodecParameters, 0, len(codecs)) partialMatches := make([]RTPCodecParameters, 0, len(codecs))
@@ -642,9 +659,24 @@ func (m *MediaEngine) updateFromRemoteDescription(desc sdp.SessionDescription) e
remoteCodec.RTCPFeedback = rtcpFeedbackIntersection(localCodec.RTCPFeedback, remoteCodec.RTCPFeedback) remoteCodec.RTCPFeedback = rtcpFeedbackIntersection(localCodec.RTCPFeedback, remoteCodec.RTCPFeedback)
if matchType == codecMatchExact { if matchType == codecMatchExact {
exactMatches = append(exactMatches, remoteCodec) exactMatches = addIfNew(exactMatches, remoteCodec)
} else if matchType == codecMatchPartial { } else if matchType == codecMatchPartial {
partialMatches = append(partialMatches, remoteCodec) partialMatches = addIfNew(partialMatches, remoteCodec)
}
}
// second pass in case there were missed RTX codecs
for _, remoteCodec := range codecs {
localCodec, matchType, mErr := m.matchRemoteCodec(remoteCodec, typ, exactMatches, partialMatches)
if mErr != nil {
return mErr
}
remoteCodec.RTCPFeedback = rtcpFeedbackIntersection(localCodec.RTCPFeedback, remoteCodec.RTCPFeedback)
if matchType == codecMatchExact {
exactMatches = addIfNew(exactMatches, remoteCodec)
} else if matchType == codecMatchPartial {
partialMatches = addIfNew(partialMatches, remoteCodec)
} }
} }

View File

@@ -1157,35 +1157,11 @@ func (pc *PeerConnection) SetRemoteDescription(desc SessionDescription) error {
transceiver = newRTPTransceiver(receiver, nil, localDirection, kind, pc.api) transceiver = newRTPTransceiver(receiver, nil, localDirection, kind, pc.api)
transceiver.setCurrentRemoteDirection(direction) transceiver.setCurrentRemoteDirection(direction)
transceiver.setCodecPreferencesFromRemoteDescription(media)
pc.mu.Lock() pc.mu.Lock()
pc.addRTPTransceiver(transceiver) pc.addRTPTransceiver(transceiver)
pc.mu.Unlock() pc.mu.Unlock()
// if transceiver is create by remote sdp, set prefer codec same as remote peer
if codecs, err := codecsFromMediaDescription(media); err == nil {
filteredCodecs := []RTPCodecParameters{}
filteredCodecsPartial := []RTPCodecParameters{}
for _, codec := range codecs {
c, matchType := codecParametersFuzzySearch(
codec,
pc.api.mediaEngine.getCodecsByKind(kind),
)
switch matchType {
case codecMatchExact:
// if codec match exact, use payloadtype register to mediaengine
codec.PayloadType = c.PayloadType
filteredCodecs = append(filteredCodecs, codec)
case codecMatchPartial:
codec.PayloadType = c.PayloadType
filteredCodecsPartial = append(filteredCodecsPartial, codec)
default:
}
}
filteredCodecs = append(filteredCodecs, filteredCodecsPartial...)
_ = transceiver.SetCodecPreferences(filteredCodecs)
}
case direction == RTPTransceiverDirectionRecvonly: case direction == RTPTransceiverDirectionRecvonly:
if transceiver.Direction() == RTPTransceiverDirectionSendrecv { if transceiver.Direction() == RTPTransceiverDirectionSendrecv {
transceiver.setDirection(RTPTransceiverDirectionSendonly) transceiver.setDirection(RTPTransceiverDirectionSendonly)

View File

@@ -1502,13 +1502,13 @@ func TestPeerConnectionNilCallback(t *testing.T) {
} }
func TestTransceiverCreatedByRemoteSdpHasSameCodecOrderAsRemote(t *testing.T) { func TestTransceiverCreatedByRemoteSdpHasSameCodecOrderAsRemote(t *testing.T) {
t.Run("Codec MatchExact", func(t *testing.T) { //nolint:dupl t.Run("Codec MatchExact and MatchPartial", func(t *testing.T) { //nolint:dupl
const remoteSdp = `v=0 const remoteSdp = `v=0
o=- 4596489990601351948 2 IN IP4 127.0.0.1 o=- 4596489990601351948 2 IN IP4 127.0.0.1
s=- s=-
t=0 0 t=0 0
a=group:BUNDLE 0 1 a=group:BUNDLE 0 1
m=video 60323 UDP/TLS/RTP/SAVPF 98 94 106 m=video 60323 UDP/TLS/RTP/SAVPF 98 94 106 49
a=ice-ufrag:1/MvHwjAyVf27aLu a=ice-ufrag:1/MvHwjAyVf27aLu
a=ice-pwd:3dBU7cFOBl120v33cynDvN1E a=ice-pwd:3dBU7cFOBl120v33cynDvN1E
a=ice-options:google-ice a=ice-options:google-ice
@@ -1519,8 +1519,10 @@ a=fmtp:98 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:94 VP8/90000 a=rtpmap:94 VP8/90000
a=rtpmap:106 H264/90000 a=rtpmap:106 H264/90000
a=fmtp:106 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f a=fmtp:106 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
a=rtpmap:49 H265/90000
a=fmtp:49 level-id=186;profile-id=1;tier-flag=0;tx-mode=SRST
a=sendonly a=sendonly
m=video 60323 UDP/TLS/RTP/SAVPF 108 98 125 m=video 60323 UDP/TLS/RTP/SAVPF 49 108 98 125
a=ice-ufrag:1/MvHwjAyVf27aLu a=ice-ufrag:1/MvHwjAyVf27aLu
a=ice-pwd:3dBU7cFOBl120v33cynDvN1E a=ice-pwd:3dBU7cFOBl120v33cynDvN1E
a=ice-options:google-ice a=ice-options:google-ice
@@ -1532,6 +1534,8 @@ a=rtpmap:108 VP8/90000
a=sendonly a=sendonly
a=rtpmap:125 H264/90000 a=rtpmap:125 H264/90000
a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
a=rtpmap:49 H265/90000
a=fmtp:49 level-id=93;profile-id=1;tier-flag=0;tx-mode=SRST
` `
mediaEngine := MediaEngine{} mediaEngine := MediaEngine{}
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{ assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
@@ -1544,6 +1548,12 @@ a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01
}, },
PayloadType: 98, PayloadType: 98,
}, RTPCodecTypeVideo)) }, RTPCodecTypeVideo))
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{
MimeTypeH265, 90000, 0, "level-id=186;profile-id=1;tier-flag=0;tx-mode=SRST", nil,
},
PayloadType: 49,
}, RTPCodecTypeVideo))
api := NewAPI(WithMediaEngine(&mediaEngine)) api := NewAPI(WithMediaEngine(&mediaEngine))
pc, err := api.NewPeerConnection(Configuration{}) pc, err := api.NewPeerConnection(Configuration{})
@@ -1552,20 +1562,35 @@ a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01
Type: SDPTypeOffer, Type: SDPTypeOffer,
SDP: remoteSdp, SDP: remoteSdp,
})) }))
ans, _ := pc.CreateAnswer(nil) ans, _ := pc.CreateAnswer(nil)
assert.NoError(t, pc.SetLocalDescription(ans)) assert.NoError(t, pc.SetLocalDescription(ans))
codecOfTr1 := pc.GetTransceivers()[0].getCodecs()[0]
codecs := pc.api.mediaEngine.getCodecsByKind(RTPCodecTypeVideo) codecs := pc.api.mediaEngine.getCodecsByKind(RTPCodecTypeVideo)
_, matchType := codecParametersFuzzySearch(codecOfTr1, codecs)
codecsOfTr1 := pc.GetTransceivers()[0].getCodecs()
_, matchType := codecParametersFuzzySearch(codecsOfTr1[0], codecs)
assert.Equal(t, codecMatchExact, matchType) assert.Equal(t, codecMatchExact, matchType)
codecOfTr2 := pc.GetTransceivers()[1].getCodecs()[0] assert.EqualValues(t, 98, codecsOfTr1[0].PayloadType)
_, matchType = codecParametersFuzzySearch(codecOfTr2, codecs) _, matchType = codecParametersFuzzySearch(codecsOfTr1[1], codecs)
assert.Equal(t, codecMatchExact, matchType) assert.Equal(t, codecMatchExact, matchType)
assert.EqualValues(t, 94, codecOfTr2.PayloadType) assert.EqualValues(t, 94, codecsOfTr1[1].PayloadType)
codecPartialMatchOfTr2 := pc.GetTransceivers()[1].getCodecs()[2] _, matchType = codecParametersFuzzySearch(codecsOfTr1[2], codecs)
_, matchType = codecParametersFuzzySearch(codecPartialMatchOfTr2, codecs) assert.Equal(t, codecMatchExact, matchType)
assert.EqualValues(t, 49, codecsOfTr1[2].PayloadType)
codecsOfTr2 := pc.GetTransceivers()[1].getCodecs()
_, matchType = codecParametersFuzzySearch(codecsOfTr2[0], codecs)
assert.Equal(t, codecMatchExact, matchType)
assert.EqualValues(t, 94, codecsOfTr2[0].PayloadType)
_, matchType = codecParametersFuzzySearch(codecsOfTr2[1], codecs)
assert.Equal(t, codecMatchExact, matchType)
assert.EqualValues(t, 98, codecsOfTr2[1].PayloadType)
// as H.265 (49) is a partial match, it gets pushed to the end
_, matchType = codecParametersFuzzySearch(codecsOfTr2[2], codecs)
assert.Equal(t, codecMatchPartial, matchType) assert.Equal(t, codecMatchPartial, matchType)
assert.EqualValues(t, 98, codecPartialMatchOfTr2.PayloadType) assert.EqualValues(t, 49, codecsOfTr2[2].PayloadType)
assert.NoError(t, pc.Close()) assert.NoError(t, pc.Close())
}) })
@@ -1613,21 +1638,34 @@ a=sendonly
api := NewAPI(WithMediaEngine(&mediaEngine)) api := NewAPI(WithMediaEngine(&mediaEngine))
pc, err := api.NewPeerConnection(Configuration{}) pc, err := api.NewPeerConnection(Configuration{})
assert.NoError(t, err) assert.NoError(t, err)
assert.NoError(t, pc.SetRemoteDescription(SessionDescription{ assert.NoError(t, pc.SetRemoteDescription(SessionDescription{
Type: SDPTypeOffer, Type: SDPTypeOffer,
SDP: remoteSdp, SDP: remoteSdp,
})) }))
ans, _ := pc.CreateAnswer(nil) ans, _ := pc.CreateAnswer(nil)
assert.NoError(t, pc.SetLocalDescription(ans)) assert.NoError(t, pc.SetLocalDescription(ans))
codecOfTr1 := pc.GetTransceivers()[0].getCodecs()[0]
codecs := pc.api.mediaEngine.getCodecsByKind(RTPCodecTypeVideo) codecs := pc.api.mediaEngine.getCodecsByKind(RTPCodecTypeVideo)
_, matchType := codecParametersFuzzySearch(codecOfTr1, codecs)
codecsOfTr1 := pc.GetTransceivers()[0].getCodecs()
_, matchType := codecParametersFuzzySearch(codecsOfTr1[0], codecs)
assert.Equal(t, codecMatchExact, matchType) assert.Equal(t, codecMatchExact, matchType)
codecOfTr2 := pc.GetTransceivers()[1].getCodecs()[0] assert.EqualValues(t, 98, codecsOfTr1[0].PayloadType)
_, matchType = codecParametersFuzzySearch(codecOfTr2, codecs) _, matchType = codecParametersFuzzySearch(codecsOfTr1[1], codecs)
assert.Equal(t, codecMatchExact, matchType)
assert.EqualValues(t, 106, codecsOfTr1[1].PayloadType)
codecsOfTr2 := pc.GetTransceivers()[1].getCodecs()
_, matchType = codecParametersFuzzySearch(codecsOfTr2[0], codecs)
assert.Equal(t, codecMatchExact, matchType) assert.Equal(t, codecMatchExact, matchType)
// h.264/profile-id=640032 should be remap to 106 as same as transceiver 1 // h.264/profile-id=640032 should be remap to 106 as same as transceiver 1
assert.EqualValues(t, 106, codecOfTr2.PayloadType) assert.EqualValues(t, 106, codecsOfTr2[0].PayloadType)
_, matchType = codecParametersFuzzySearch(codecsOfTr2[1], codecs)
assert.Equal(t, codecMatchExact, matchType)
assert.EqualValues(t, 98, codecsOfTr2[1].PayloadType)
assert.NoError(t, pc.Close()) assert.NoError(t, pc.Close())
}) })
} }

View File

@@ -27,6 +27,7 @@ import (
"github.com/pion/sdp/v3" "github.com/pion/sdp/v3"
"github.com/pion/transport/v3/test" "github.com/pion/transport/v3/test"
"github.com/pion/transport/v3/vnet" "github.com/pion/transport/v3/vnet"
"github.com/pion/webrtc/v4/internal/fmtp"
"github.com/pion/webrtc/v4/internal/util" "github.com/pion/webrtc/v4/internal/util"
"github.com/pion/webrtc/v4/pkg/media" "github.com/pion/webrtc/v4/pkg/media"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -766,7 +767,6 @@ func TestAddTransceiverAddTrack_Reuse(t *testing.T) {
for _, testCase := range testCases { for _, testCase := range testCases {
t.Run(testCase.remoteDirection.String(), func(t *testing.T) { t.Run(testCase.remoteDirection.String(), func(t *testing.T) {
pcOffer, pcAnswer, err := newPair() pcOffer, pcAnswer, err := newPair()
require.NoError(t, err)
assert.NoError(t, err) assert.NoError(t, err)
remoteTrack, err := NewTrackLocalStaticSample( remoteTrack, err := NewTrackLocalStaticSample(
@@ -883,6 +883,184 @@ func TestAddTransceiverAddTrack_Reuse(t *testing.T) {
}) })
} }
func TestAddTransceiverFromRemoteDescription(t *testing.T) {
lim := test.TimeOut(time.Second * 30)
defer lim.Stop()
report := test.CheckRoutines(t)
defer report()
// offer side
se := SettingEngine{}
se.DisableMediaEngineCopy(true)
mediaEngineOffer := &MediaEngine{}
// offer side has fewer codecs than answer side
for _, codec := range []RTPCodecParameters{
{
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=50", nil},
PayloadType: 51,
},
{
RTPCodecCapability: RTPCodecCapability{"video/vp9", 90000, 0, "", nil},
PayloadType: 50,
},
{
RTPCodecCapability: RTPCodecCapability{"video/vp8", 90000, 0, "", nil},
PayloadType: 52,
},
{
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=52", nil},
PayloadType: 53,
},
} {
assert.NoError(t, mediaEngineOffer.RegisterCodec(codec, RTPCodecTypeVideo))
}
pcOffer, err := NewAPI(WithSettingEngine(se), WithMediaEngine(mediaEngineOffer)).NewPeerConnection(Configuration{})
assert.NoError(t, err)
// answer side
mediaEngineAnswer := &MediaEngine{}
// answer has more codecs than offer side and in different order
for _, codec := range []RTPCodecParameters{
{
RTPCodecCapability: RTPCodecCapability{"video/vp8", 90000, 0, "", nil},
PayloadType: 82,
},
{
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=82", nil},
PayloadType: 83,
},
{
RTPCodecCapability: RTPCodecCapability{"video/vp9", 90000, 0, "", nil},
PayloadType: 80,
},
{
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=80", nil},
PayloadType: 81,
},
{
RTPCodecCapability: RTPCodecCapability{"video/av1", 90000, 0, "", nil},
PayloadType: 84,
},
{
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=84", nil},
PayloadType: 85,
},
{
RTPCodecCapability: RTPCodecCapability{"video/h265", 90000, 0, "", nil},
PayloadType: 86,
},
{
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=86", nil},
PayloadType: 87,
},
} {
assert.NoError(t, mediaEngineAnswer.RegisterCodec(codec, RTPCodecTypeVideo))
}
pcAnswer, err := NewAPI(WithMediaEngine(mediaEngineAnswer)).NewPeerConnection(Configuration{})
assert.NoError(t, err)
track1, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion1")
require.NoError(t, err)
_, err = pcOffer.AddTransceiverFromTrack(track1)
assert.NoError(t, err)
offer, err := pcOffer.CreateOffer(nil)
assert.NoError(t, err)
assert.NoError(t, pcOffer.SetLocalDescription(offer))
// this should create a transceiver on answer side from remote description and
// set codec prefereces with order of codecs in offer using the corresponding
// payload types from the media engine codecs
assert.NoError(t, pcAnswer.SetRemoteDescription(offer))
answerSideTransceivers := pcAnswer.GetTransceivers()
assert.Equal(t, 1, len(answerSideTransceivers))
// media engine updates negotiated codecs from remote description,
// so payload type will be what is in the offer
// all rtx are placed later and could be in any order
checkPreferredCodecs := func(
actualPreferredCodecs []RTPCodecParameters,
expectedPreferredCodecsPrimary []RTPCodecParameters,
expectedPreferredCodecsRTX []RTPCodecParameters,
) {
assert.Equal(
t,
len(expectedPreferredCodecsPrimary)+len(expectedPreferredCodecsRTX),
len(actualPreferredCodecs),
)
for i, expectedPreferredCodec := range expectedPreferredCodecsPrimary {
expectedFmtp := fmtp.Parse(
expectedPreferredCodec.RTPCodecCapability.MimeType,
expectedPreferredCodec.RTPCodecCapability.ClockRate,
expectedPreferredCodec.RTPCodecCapability.Channels,
expectedPreferredCodec.RTPCodecCapability.SDPFmtpLine,
)
actualFmtp := fmtp.Parse(
actualPreferredCodecs[i].RTPCodecCapability.MimeType,
actualPreferredCodecs[i].RTPCodecCapability.ClockRate,
actualPreferredCodecs[i].RTPCodecCapability.Channels,
actualPreferredCodecs[i].RTPCodecCapability.SDPFmtpLine,
)
assert.True(t, expectedFmtp.Match(actualFmtp))
}
for _, expectedPreferredCodec := range expectedPreferredCodecsRTX {
expectedFmtp := fmtp.Parse(
expectedPreferredCodec.RTPCodecCapability.MimeType,
expectedPreferredCodec.RTPCodecCapability.ClockRate,
expectedPreferredCodec.RTPCodecCapability.Channels,
expectedPreferredCodec.RTPCodecCapability.SDPFmtpLine,
)
found := false
for j := len(expectedPreferredCodecsPrimary); j < len(actualPreferredCodecs); j++ {
actualFmtp := fmtp.Parse(
actualPreferredCodecs[j].RTPCodecCapability.MimeType,
actualPreferredCodecs[j].RTPCodecCapability.ClockRate,
actualPreferredCodecs[j].RTPCodecCapability.Channels,
actualPreferredCodecs[j].RTPCodecCapability.SDPFmtpLine,
)
if expectedFmtp.Match(actualFmtp) {
found = true
break
}
}
assert.True(t, found)
}
}
preferredCodecsPrimary := []RTPCodecParameters{
{
RTPCodecCapability: RTPCodecCapability{"video/vp9", 90000, 0, "", nil},
PayloadType: 50,
},
{
RTPCodecCapability: RTPCodecCapability{"video/vp8", 90000, 0, "", nil},
PayloadType: 52,
},
}
preferredCodecsRTX := []RTPCodecParameters{
{
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=50", nil},
PayloadType: 51,
},
{
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=52", nil},
PayloadType: 53,
},
}
checkPreferredCodecs(answerSideTransceivers[0].getCodecs(), preferredCodecsPrimary, preferredCodecsRTX)
assert.NoError(t, pcOffer.Close())
assert.NoError(t, pcAnswer.Close())
}
func TestAddTransceiverAddTrack_NewRTPSender_Error(t *testing.T) { func TestAddTransceiverAddTrack_NewRTPSender_Error(t *testing.T) {
pc, err := NewPeerConnection(Configuration{}) pc, err := NewPeerConnection(Configuration{})
assert.NoError(t, err) assert.NoError(t, err)

View File

@@ -8,10 +8,13 @@ package webrtc
import ( import (
"fmt" "fmt"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/pion/sdp/v3"
"github.com/pion/webrtc/v4/internal/fmtp"
) )
// RTPTransceiver represents a combination of an RTPSender and an RTPReceiver that share a common mid. // RTPTransceiver represents a combination of an RTPSender and an RTPReceiver that share a common mid.
@@ -73,7 +76,7 @@ func (t *RTPTransceiver) getCodecs() []RTPCodecParameters {
mediaEngineCodecs := t.api.mediaEngine.getCodecsByKind(t.kind) mediaEngineCodecs := t.api.mediaEngine.getCodecsByKind(t.kind)
if len(t.codecs) == 0 { if len(t.codecs) == 0 {
return mediaEngineCodecs return filterUnattachedRTX(mediaEngineCodecs)
} }
filteredCodecs := []RTPCodecParameters{} filteredCodecs := []RTPCodecParameters{}
@@ -90,6 +93,96 @@ func (t *RTPTransceiver) getCodecs() []RTPCodecParameters {
return filterUnattachedRTX(filteredCodecs) return filterUnattachedRTX(filteredCodecs)
} }
// match codecs from remote description, used when remote is offerer and creating a transceiver
// from remote description with the aim of keeping order of codecs in remote description.
func (t *RTPTransceiver) setCodecPreferencesFromRemoteDescription(media *sdp.MediaDescription) { //nolint:cyclop
remoteCodecs, err := codecsFromMediaDescription(media)
if err != nil {
return
}
// make a copy as this slice is modified
leftCodecs := append([]RTPCodecParameters{}, t.api.mediaEngine.getCodecsByKind(t.kind)...)
// find codec matches between what is in remote description and
// the transceivers codecs and use payload type registered to
// media engine.
payloadMapping := make(map[PayloadType]PayloadType) // for RTX re-mapping later
filterByMatchType := func(matchFilter codecMatchType) []RTPCodecParameters {
filteredCodecs := []RTPCodecParameters{}
for remoteCodecIdx := len(remoteCodecs) - 1; remoteCodecIdx >= 0; remoteCodecIdx-- {
remoteCodec := remoteCodecs[remoteCodecIdx]
if strings.EqualFold(remoteCodec.RTPCodecCapability.MimeType, MimeTypeRTX) {
continue
}
matchCodec, matchType := codecParametersFuzzySearch(
remoteCodec,
leftCodecs,
)
if matchType == matchFilter {
payloadMapping[remoteCodec.PayloadType] = matchCodec.PayloadType
remoteCodec.PayloadType = matchCodec.PayloadType
filteredCodecs = append([]RTPCodecParameters{remoteCodec}, filteredCodecs...)
// removed matched codec for next round
remoteCodecs = append(remoteCodecs[:remoteCodecIdx], remoteCodecs[remoteCodecIdx+1:]...)
needleFmtp := fmtp.Parse(
matchCodec.RTPCodecCapability.MimeType,
matchCodec.RTPCodecCapability.ClockRate,
matchCodec.RTPCodecCapability.Channels,
matchCodec.RTPCodecCapability.SDPFmtpLine,
)
for leftCodecIdx := len(leftCodecs) - 1; leftCodecIdx >= 0; leftCodecIdx-- {
leftCodec := leftCodecs[leftCodecIdx]
leftCodecFmtp := fmtp.Parse(
leftCodec.RTPCodecCapability.MimeType,
leftCodec.RTPCodecCapability.ClockRate,
leftCodec.RTPCodecCapability.Channels,
leftCodec.RTPCodecCapability.SDPFmtpLine,
)
if needleFmtp.Match(leftCodecFmtp) {
leftCodecs = append(leftCodecs[:leftCodecIdx], leftCodecs[leftCodecIdx+1:]...)
break
}
}
}
}
return filteredCodecs
}
filteredCodecs := filterByMatchType(codecMatchExact)
filteredCodecs = append(filteredCodecs, filterByMatchType(codecMatchPartial)...)
// find RTX associations and add those
for remotePayloadType, mediaEnginePayloadType := range payloadMapping {
remoteRTX := findRTXPayloadType(remotePayloadType, remoteCodecs)
if remoteRTX == PayloadType(0) {
continue
}
mediaEngineRTX := findRTXPayloadType(mediaEnginePayloadType, leftCodecs)
if mediaEngineRTX == PayloadType(0) {
continue
}
for _, rtxCodec := range leftCodecs {
if rtxCodec.PayloadType == mediaEngineRTX {
filteredCodecs = append(filteredCodecs, rtxCodec)
break
}
}
}
_ = t.SetCodecPreferences(filteredCodecs)
}
// Sender returns the RTPTransceiver's RTPSender if it has one. // Sender returns the RTPTransceiver's RTPSender if it has one.
func (t *RTPTransceiver) Sender() *RTPSender { func (t *RTPTransceiver) Sender() *RTPSender {
if v, ok := t.sender.Load().(*RTPSender); ok { if v, ok := t.sender.Load().(*RTPSender); ok {