Skip to content

Commit

Permalink
Generate answer to match group bundle in offer
Browse files Browse the repository at this point in the history
Generate answer to match group bundle in offer
  • Loading branch information
cnderrauber committed Sep 7, 2023
1 parent bbf5126 commit 7bac1c8
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 13 deletions.
9 changes: 7 additions & 2 deletions peerconnection.go
Original file line number Diff line number Diff line change
Expand Up @@ -2363,7 +2363,7 @@ func (pc *PeerConnection) generateUnmatchedSDP(transceivers []*RTPTransceiver, u
return nil, err
}

return populateSDP(d, isPlanB, dtlsFingerprints, pc.api.settingEngine.sdpMediaLevelFingerprints, pc.api.settingEngine.candidates.ICELite, true, pc.api.mediaEngine, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), candidates, iceParams, mediaSections, pc.ICEGatheringState())
return populateSDP(d, isPlanB, dtlsFingerprints, pc.api.settingEngine.sdpMediaLevelFingerprints, pc.api.settingEngine.candidates.ICELite, true, pc.api.mediaEngine, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), candidates, iceParams, mediaSections, pc.ICEGatheringState(), nil)
}

// generateMatchedSDP generates a SDP and takes the remote state into account
Expand Down Expand Up @@ -2461,6 +2461,7 @@ func (pc *PeerConnection) generateMatchedSDP(transceivers []*RTPTransceiver, use
}
}

var bundleGroup *string
// If we are offering also include unmatched local transceivers
if includeUnmatched {
if !detectedPlanB {
Expand All @@ -2479,6 +2480,10 @@ func (pc *PeerConnection) generateMatchedSDP(transceivers []*RTPTransceiver, use
mediaSections = append(mediaSections, mediaSection{id: strconv.Itoa(len(mediaSections)), data: true})
}
}
} else if remoteDescription != nil {
groupValue, _ := remoteDescription.parsed.Attribute(sdp.AttrKeyGroup)
groupValue = strings.TrimLeft(groupValue, "BUNDLE")
bundleGroup = &groupValue
}

if pc.configuration.SDPSemantics == SDPSemanticsUnifiedPlanWithFallback && detectedPlanB {
Expand All @@ -2490,7 +2495,7 @@ func (pc *PeerConnection) generateMatchedSDP(transceivers []*RTPTransceiver, use
return nil, err
}

return populateSDP(d, detectedPlanB, dtlsFingerprints, pc.api.settingEngine.sdpMediaLevelFingerprints, pc.api.settingEngine.candidates.ICELite, isExtmapAllowMixed, pc.api.mediaEngine, connectionRole, candidates, iceParams, mediaSections, pc.ICEGatheringState())
return populateSDP(d, detectedPlanB, dtlsFingerprints, pc.api.settingEngine.sdpMediaLevelFingerprints, pc.api.settingEngine.candidates.ICELite, isExtmapAllowMixed, pc.api.mediaEngine, connectionRole, candidates, iceParams, mediaSections, pc.ICEGatheringState(), bundleGroup)
}

func (pc *PeerConnection) setGatherCompleteHandler(handler func()) {
Expand Down
2 changes: 1 addition & 1 deletion rtpcodec.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const (
func (t RTPCodecType) String() string {
switch t {
case RTPCodecTypeAudio:
return "audio"
return "audio" //nolint: goconst

Check warning on line 27 in rtpcodec.go

View check run for this annotation

Codecov / codecov/patch

rtpcodec.go#L27

Added line #L27 was not covered by tests
case RTPCodecTypeVideo:
return "video" //nolint: goconst
default:
Expand Down
46 changes: 43 additions & 3 deletions sdp.go
Original file line number Diff line number Diff line change
Expand Up @@ -507,8 +507,39 @@ type mediaSection struct {
ridMap map[string]string
}

func bundleMatchFromRemote(matchBundleGroup *string) func(mid string) bool {
if matchBundleGroup == nil {
return func(midValue string) bool {
return true
}
}
bundleTags := strings.Split(*matchBundleGroup, " ")
return func(midValue string) bool {
for _, tag := range bundleTags {
if tag == midValue {
return true
}
}
return false
}
}

// populateSDP serializes a PeerConnections state into an SDP
func populateSDP(d *sdp.SessionDescription, isPlanB bool, dtlsFingerprints []DTLSFingerprint, mediaDescriptionFingerprint bool, isICELite bool, isExtmapAllowMixed bool, mediaEngine *MediaEngine, connectionRole sdp.ConnectionRole, candidates []ICECandidate, iceParams ICEParameters, mediaSections []mediaSection, iceGatheringState ICEGatheringState) (*sdp.SessionDescription, error) {
func populateSDP(
d *sdp.SessionDescription,
isPlanB bool,
dtlsFingerprints []DTLSFingerprint,
mediaDescriptionFingerprint bool,
isICELite bool,
isExtmapAllowMixed bool,
mediaEngine *MediaEngine,
connectionRole sdp.ConnectionRole,
candidates []ICECandidate,
iceParams ICEParameters,
mediaSections []mediaSection,
iceGatheringState ICEGatheringState,
matchBundleGroup *string,
) (*sdp.SessionDescription, error) {
var err error
mediaDtlsFingerprints := []DTLSFingerprint{}

Expand All @@ -518,6 +549,8 @@ func populateSDP(d *sdp.SessionDescription, isPlanB bool, dtlsFingerprints []DTL

bundleValue := "BUNDLE"
bundleCount := 0

bundleMatch := bundleMatchFromRemote(matchBundleGroup)
appendBundle := func(midValue string) {
bundleValue += " " + midValue
bundleCount++
Expand All @@ -544,7 +577,11 @@ func populateSDP(d *sdp.SessionDescription, isPlanB bool, dtlsFingerprints []DTL
}

if shouldAddID {
appendBundle(m.id)
if bundleMatch(m.id) {
appendBundle(m.id)
} else {
d.MediaDescriptions[len(d.MediaDescriptions)-1].MediaName.Port = sdp.RangedPort{Value: 0}
}
}
}

Expand All @@ -563,7 +600,10 @@ func populateSDP(d *sdp.SessionDescription, isPlanB bool, dtlsFingerprints []DTL
d = d.WithPropertyAttribute(sdp.AttrKeyExtMapAllowMixed)
}

return d.WithValueAttribute(sdp.AttrKeyGroup, bundleValue), nil
if bundleCount > 0 {
d = d.WithValueAttribute(sdp.AttrKeyGroup, bundleValue)
}
return d, nil
}

func getMidValue(media *sdp.MediaDescription) string {
Expand Down
85 changes: 78 additions & 7 deletions sdp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ func TestMediaDescriptionFingerprints(t *testing.T) {
s, err = populateSDP(s, false,
dtlsFingerprints,
SDPMediaDescriptionFingerprints,
false, true, engine, sdp.ConnectionRoleActive, []ICECandidate{}, ICEParameters{}, media, ICEGatheringStateNew)
false, true, engine, sdp.ConnectionRoleActive, []ICECandidate{}, ICEParameters{}, media, ICEGatheringStateNew, nil)
assert.NoError(t, err)

sdparray, err := s.Marshal()
Expand Down Expand Up @@ -388,7 +388,7 @@ func TestPopulateSDP(t *testing.T) {

d := &sdp.SessionDescription{}

offerSdp, err := populateSDP(d, false, []DTLSFingerprint{}, se.sdpMediaLevelFingerprints, se.candidates.ICELite, true, me, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), []ICECandidate{}, ICEParameters{}, mediaSections, ICEGatheringStateComplete)
offerSdp, err := populateSDP(d, false, []DTLSFingerprint{}, se.sdpMediaLevelFingerprints, se.candidates.ICELite, true, me, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), []ICECandidate{}, ICEParameters{}, mediaSections, ICEGatheringStateComplete, nil)
assert.Nil(t, err)

// Test contains rid map keys
Expand Down Expand Up @@ -431,7 +431,7 @@ func TestPopulateSDP(t *testing.T) {

d := &sdp.SessionDescription{}

offerSdp, err := populateSDP(d, false, []DTLSFingerprint{}, se.sdpMediaLevelFingerprints, se.candidates.ICELite, true, me, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), []ICECandidate{}, ICEParameters{}, mediaSections, ICEGatheringStateComplete)
offerSdp, err := populateSDP(d, false, []DTLSFingerprint{}, se.sdpMediaLevelFingerprints, se.candidates.ICELite, true, me, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), []ICECandidate{}, ICEParameters{}, mediaSections, ICEGatheringStateComplete, nil)
assert.Nil(t, err)

// Test codecs
Expand All @@ -456,7 +456,7 @@ func TestPopulateSDP(t *testing.T) {
se := SettingEngine{}
se.SetLite(true)

offerSdp, err := populateSDP(&sdp.SessionDescription{}, false, []DTLSFingerprint{}, se.sdpMediaLevelFingerprints, se.candidates.ICELite, true, &MediaEngine{}, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), []ICECandidate{}, ICEParameters{}, []mediaSection{}, ICEGatheringStateComplete)
offerSdp, err := populateSDP(&sdp.SessionDescription{}, false, []DTLSFingerprint{}, se.sdpMediaLevelFingerprints, se.candidates.ICELite, true, &MediaEngine{}, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), []ICECandidate{}, ICEParameters{}, []mediaSection{}, ICEGatheringStateComplete, nil)
assert.Nil(t, err)

var found bool
Expand Down Expand Up @@ -489,7 +489,7 @@ func TestPopulateSDP(t *testing.T) {

d := &sdp.SessionDescription{}

offerSdp, err := populateSDP(d, false, []DTLSFingerprint{}, se.sdpMediaLevelFingerprints, se.candidates.ICELite, true, me, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), []ICECandidate{}, ICEParameters{}, mediaSections, ICEGatheringStateComplete)
offerSdp, err := populateSDP(d, false, []DTLSFingerprint{}, se.sdpMediaLevelFingerprints, se.candidates.ICELite, true, me, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), []ICECandidate{}, ICEParameters{}, mediaSections, ICEGatheringStateComplete, nil)
assert.NoError(t, err)

// Test codecs
Expand All @@ -507,7 +507,7 @@ func TestPopulateSDP(t *testing.T) {
})
t.Run("allow mixed extmap", func(t *testing.T) {
se := SettingEngine{}
offerSdp, err := populateSDP(&sdp.SessionDescription{}, false, []DTLSFingerprint{}, se.sdpMediaLevelFingerprints, se.candidates.ICELite, true, &MediaEngine{}, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), []ICECandidate{}, ICEParameters{}, []mediaSection{}, ICEGatheringStateComplete)
offerSdp, err := populateSDP(&sdp.SessionDescription{}, false, []DTLSFingerprint{}, se.sdpMediaLevelFingerprints, se.candidates.ICELite, true, &MediaEngine{}, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), []ICECandidate{}, ICEParameters{}, []mediaSection{}, ICEGatheringStateComplete, nil)
assert.Nil(t, err)

var found bool
Expand All @@ -522,7 +522,7 @@ func TestPopulateSDP(t *testing.T) {
}
assert.Equal(t, true, found, "AllowMixedExtMap key should be present")

offerSdp, err = populateSDP(&sdp.SessionDescription{}, false, []DTLSFingerprint{}, se.sdpMediaLevelFingerprints, se.candidates.ICELite, false, &MediaEngine{}, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), []ICECandidate{}, ICEParameters{}, []mediaSection{}, ICEGatheringStateComplete)
offerSdp, err = populateSDP(&sdp.SessionDescription{}, false, []DTLSFingerprint{}, se.sdpMediaLevelFingerprints, se.candidates.ICELite, false, &MediaEngine{}, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), []ICECandidate{}, ICEParameters{}, []mediaSection{}, ICEGatheringStateComplete, nil)
assert.Nil(t, err)

found = false
Expand All @@ -537,6 +537,77 @@ func TestPopulateSDP(t *testing.T) {
}
assert.Equal(t, false, found, "AllowMixedExtMap key should not be present")
})
t.Run("bundle all", func(t *testing.T) {
se := SettingEngine{}

me := &MediaEngine{}
assert.NoError(t, me.RegisterDefaultCodecs())
api := NewAPI(WithMediaEngine(me))

tr := &RTPTransceiver{kind: RTPCodecTypeVideo, api: api, codecs: me.videoCodecs}
tr.setDirection(RTPTransceiverDirectionRecvonly)
mediaSections := []mediaSection{{id: "video", transceivers: []*RTPTransceiver{tr}}}

d := &sdp.SessionDescription{}

offerSdp, err := populateSDP(d, false, []DTLSFingerprint{}, se.sdpMediaLevelFingerprints, se.candidates.ICELite, true, me, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), []ICECandidate{}, ICEParameters{}, mediaSections, ICEGatheringStateComplete, nil)
assert.Nil(t, err)

bundle, ok := offerSdp.Attribute(sdp.AttrKeyGroup)
assert.True(t, ok)
assert.Equal(t, "BUNDLE video", bundle)
})
t.Run("bundle matched", func(t *testing.T) {
se := SettingEngine{}

me := &MediaEngine{}
assert.NoError(t, me.RegisterDefaultCodecs())
api := NewAPI(WithMediaEngine(me))

tra := &RTPTransceiver{kind: RTPCodecTypeVideo, api: api, codecs: me.videoCodecs}
tra.setDirection(RTPTransceiverDirectionRecvonly)
mediaSections := []mediaSection{{id: "video", transceivers: []*RTPTransceiver{tra}}}

trv := &RTPTransceiver{kind: RTPCodecTypeAudio, api: api, codecs: me.audioCodecs}
trv.setDirection(RTPTransceiverDirectionRecvonly)
mediaSections = append(mediaSections, mediaSection{id: "audio", transceivers: []*RTPTransceiver{trv}})

d := &sdp.SessionDescription{}

matchedBundle := "audio"
offerSdp, err := populateSDP(d, false, []DTLSFingerprint{}, se.sdpMediaLevelFingerprints, se.candidates.ICELite, true, me, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), []ICECandidate{}, ICEParameters{}, mediaSections, ICEGatheringStateComplete, &matchedBundle)
assert.Nil(t, err)

bundle, ok := offerSdp.Attribute(sdp.AttrKeyGroup)
assert.True(t, ok)
assert.Equal(t, "BUNDLE audio", bundle)

mediaVideo := offerSdp.MediaDescriptions[0]
mid, ok := mediaVideo.Attribute(sdp.AttrKeyMID)
assert.True(t, ok)
assert.Equal(t, "video", mid)
assert.True(t, mediaVideo.MediaName.Port.Value == 0)
})
t.Run("empty bundle group", func(t *testing.T) {
se := SettingEngine{}

me := &MediaEngine{}
assert.NoError(t, me.RegisterDefaultCodecs())
api := NewAPI(WithMediaEngine(me))

tra := &RTPTransceiver{kind: RTPCodecTypeVideo, api: api, codecs: me.videoCodecs}
tra.setDirection(RTPTransceiverDirectionRecvonly)
mediaSections := []mediaSection{{id: "video", transceivers: []*RTPTransceiver{tra}}}

d := &sdp.SessionDescription{}

matchedBundle := ""
offerSdp, err := populateSDP(d, false, []DTLSFingerprint{}, se.sdpMediaLevelFingerprints, se.candidates.ICELite, true, me, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), []ICECandidate{}, ICEParameters{}, mediaSections, ICEGatheringStateComplete, &matchedBundle)
assert.Nil(t, err)

_, ok := offerSdp.Attribute(sdp.AttrKeyGroup)
assert.False(t, ok)
})
}

func TestGetRIDs(t *testing.T) {
Expand Down

0 comments on commit 7bac1c8

Please sign in to comment.