Skip to content

Commit

Permalink
Match header extensions to remote media sections
Browse files Browse the repository at this point in the history
Firefox would send updated header extension
in renegotiation, e.g. publish a track without
simucalst then renegotiate second track with
simucalst, the two media secontions will have
different rtp header extensions in offer. Need
to match remote header extentions for each
media sections to avoid second track publish
failed.
  • Loading branch information
cnderrauber committed Jul 18, 2024
1 parent 166d82e commit c138a57
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 20 deletions.
54 changes: 42 additions & 12 deletions mediaengine.go
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,30 @@ func (m *MediaEngine) matchRemoteCodec(remoteCodec RTPCodecParameters, typ RTPCo
return matchType, nil
}

// Update header extensions from a remote media section
func (m *MediaEngine) updateHeaderExtensionFromMediaSection(media *sdp.MediaDescription) error {
var typ RTPCodecType
switch {
case strings.EqualFold(media.MediaName.Media, "audio"):
typ = RTPCodecTypeAudio
case strings.EqualFold(media.MediaName.Media, "video"):
typ = RTPCodecTypeVideo
default:
return nil
}
extensions, err := rtpExtensionsFromMediaDescription(media)
if err != nil {
return err
}

for extension, id := range extensions {
if err = m.updateHeaderExtension(id, extension, typ); err != nil {
return err
}
}
return nil
}

// Look up a header extension and enable if it exists
func (m *MediaEngine) updateHeaderExtension(id int, extension string, typ RTPCodecType) error {
if m.negotiatedHeaderExtensions == nil {
Expand Down Expand Up @@ -498,14 +522,27 @@ func (m *MediaEngine) updateFromRemoteDescription(desc sdp.SessionDescription) e

for _, media := range desc.MediaDescriptions {
var typ RTPCodecType

switch {
case !m.negotiatedAudio && strings.EqualFold(media.MediaName.Media, "audio"):
m.negotiatedAudio = true
case strings.EqualFold(media.MediaName.Media, "audio"):
typ = RTPCodecTypeAudio
case !m.negotiatedVideo && strings.EqualFold(media.MediaName.Media, "video"):
m.negotiatedVideo = true
case strings.EqualFold(media.MediaName.Media, "video"):
typ = RTPCodecTypeVideo
}

switch {
case !m.negotiatedAudio && typ == RTPCodecTypeAudio:
m.negotiatedAudio = true
case !m.negotiatedVideo && typ == RTPCodecTypeVideo:
m.negotiatedVideo = true
default:
// update header extesions from remote sdp if codec is negotiated, Firefox
// would send updated header extension in renegotiation.
// e.g. publish first track without simucalst ->negotiated-> publish second track with simucalst
// then the two media secontions have different rtp header extensions in offer
if err := m.updateHeaderExtensionFromMediaSection(media); err != nil {
return err
}
continue
}

Expand Down Expand Up @@ -541,16 +578,9 @@ func (m *MediaEngine) updateFromRemoteDescription(desc sdp.SessionDescription) e
continue
}

extensions, err := rtpExtensionsFromMediaDescription(media)
if err != nil {
if err := m.updateHeaderExtensionFromMediaSection(media); err != nil {
return err
}

for extension, id := range extensions {
if err = m.updateHeaderExtension(id, extension, typ); err != nil {
return err
}
}
}
return nil
}
Expand Down
33 changes: 33 additions & 0 deletions mediaengine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,39 @@ a=rtpmap:111 opus/48000/2
assert.False(t, midVideoEnabled)
})

t.Run("Different Header Extensions on same codec", func(t *testing.T) {
const headerExtensions = `v=0
o=- 4596489990601351948 2 IN IP4 127.0.0.1
s=-
t=0 0
m=audio 9 UDP/TLS/RTP/SAVPF 111
a=rtpmap:111 opus/48000/2
m=audio 9 UDP/TLS/RTP/SAVPF 111
a=extmap:7 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=rtpmap:111 opus/48000/2
`

m := MediaEngine{}
assert.NoError(t, m.RegisterDefaultCodecs())
assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{URI: "urn:ietf:params:rtp-hdrext:sdes:mid"}, RTPCodecTypeAudio))
assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{URI: "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id"}, RTPCodecTypeAudio))
assert.NoError(t, m.updateFromRemoteDescription(mustParse(headerExtensions)))

assert.False(t, m.negotiatedVideo)
assert.True(t, m.negotiatedAudio)

absID, absAudioEnabled, absVideoEnabled := m.getHeaderExtensionID(RTPHeaderExtensionCapability{sdp.ABSSendTimeURI})
assert.Equal(t, absID, 0)
assert.False(t, absAudioEnabled)
assert.False(t, absVideoEnabled)

midID, midAudioEnabled, midVideoEnabled := m.getHeaderExtensionID(RTPHeaderExtensionCapability{sdp.SDESMidURI})
assert.Equal(t, midID, 7)
assert.True(t, midAudioEnabled)
assert.False(t, midVideoEnabled)
})

t.Run("Prefers exact codec matches", func(t *testing.T) {
const profileLevels = `v=0
o=- 4596489990601351948 2 IN IP4 127.0.0.1
Expand Down
4 changes: 3 additions & 1 deletion peerconnection.go
Original file line number Diff line number Diff line change
Expand Up @@ -2465,7 +2465,9 @@ func (pc *PeerConnection) generateMatchedSDP(transceivers []*RTPTransceiver, use
sender.setNegotiated()
}
mediaTransceivers := []*RTPTransceiver{t}
mediaSections = append(mediaSections, mediaSection{id: midValue, transceivers: mediaTransceivers, ridMap: getRids(media)})

extensions, _ := rtpExtensionsFromMediaDescription(media)
mediaSections = append(mediaSections, mediaSection{id: midValue, transceivers: mediaTransceivers, matchExtensions: extensions, ridMap: getRids(media)})
}
}

Expand Down
9 changes: 6 additions & 3 deletions peerconnection_media_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ func (u *undeclaredSsrcLogger) Infof(string, ...interface{}) {}
func (u *undeclaredSsrcLogger) Warn(string) {}
func (u *undeclaredSsrcLogger) Warnf(string, ...interface{}) {}
func (u *undeclaredSsrcLogger) Error(string) {}
func (u *undeclaredSsrcLogger) Errorf(format string, _ ...interface{}) {
func (u *undeclaredSsrcLogger) Errorf(format string, args ...interface{}) {

Check warning on line 397 in peerconnection_media_test.go

View workflow job for this annotation

GitHub Actions / lint / Go

unused-parameter: parameter 'args' seems to be unused, consider removing or renaming it as _ (revive)
if format == incomingUnhandledRTPSsrc {
close(u.unhandledSimulcastError)
}
Expand Down Expand Up @@ -1080,6 +1080,9 @@ func TestPeerConnection_Simulcast_Probe(t *testing.T) {
return
}))

peerConnectionConnected := untilConnectionState(PeerConnectionStateConnected, pcOffer, pcAnswer)
peerConnectionConnected.Wait()

sequenceNumber := uint16(0)
sendRTPPacket := func() {
sequenceNumber++
Expand All @@ -1097,13 +1100,13 @@ func TestPeerConnection_Simulcast_Probe(t *testing.T) {
sendRTPPacket()
}

assert.NoError(t, signalPair(pcOffer, pcAnswer))

trackRemoteChan := make(chan *TrackRemote, 1)
pcAnswer.OnTrack(func(trackRemote *TrackRemote, _ *RTPReceiver) {
trackRemoteChan <- trackRemote
})

assert.NoError(t, signalPair(pcOffer, pcAnswer))

trackRemote := func() *TrackRemote {
for {
select {
Expand Down
14 changes: 10 additions & 4 deletions sdp.go
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,11 @@ func addTransceiverSDP(

parameters := mediaEngine.getRTPParametersByKind(t.kind, directions)
for _, rtpExtension := range parameters.HeaderExtensions {
if mediaSection.matchExtensions != nil {
if _, enabled := mediaSection.matchExtensions[rtpExtension.URI]; !enabled {
continue
}
}
extURL, err := url.Parse(rtpExtension.URI)
if err != nil {
return false, err
Expand Down Expand Up @@ -533,10 +538,11 @@ type simulcastRid struct {
}

type mediaSection struct {
id string
transceivers []*RTPTransceiver
data bool
ridMap map[string]*simulcastRid
id string
transceivers []*RTPTransceiver
data bool
matchExtensions map[string]int
ridMap map[string]*simulcastRid
}

func bundleMatchFromRemote(matchBundleGroup *string) func(mid string) bool {
Expand Down

0 comments on commit c138a57

Please sign in to comment.