mirror of
https://github.com/pion/webrtc.git
synced 2025-09-27 03:25:58 +08:00
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:

committed by
Raja Subramanian

parent
42b3cfd2ca
commit
c376d0edf9
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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)
|
||||||
|
@@ -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())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
|
@@ -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 {
|
||||||
|
Reference in New Issue
Block a user