Repository: pion/webrtc
Pull request #2634 (2023-12-12 05:38:32)

Answer to paused simucalst stream correctly
#### Description
If the publisher sends an offer with paused simulcast stream like `a=simucalst:send 1;~2;~3`, the sfu needs to respond with paused stream too, otherwise, it will make the publisher's inactive encoding to become active unintentionally.

#### Reference issue
Fixes #...


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/pion/webrtc/pull/2634?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`21c5a71`)](https://app.codecov.io/gh/pion/webrtc/commit/21c5a711d84d35898be2fd1181970588d5f6ef0c?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion) 76.41% compared to head [(`cec0511`)](https://app.codecov.io/gh/pion/webrtc/pull/2634?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion) 64.54%.


<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff             @@
##           master    #2634       +/-   ##
===========================================
- Coverage   76.41%   64.54%   -11.88%     
===========================================
  Files          87       64       -23     
  Lines        9879     3689     -6190     
===========================================
- Hits         7549     2381     -5168     
+ Misses       1860     1180      -680     
+ Partials      470      128      -342     
```

| [Flag](https://app.codecov.io/gh/pion/webrtc/pull/2634/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion) | Coverage Δ | |
|---|---|---|
| [go](https://app.codecov.io/gh/pion/webrtc/pull/2634/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion) | `?` | |
| [wasm](https://app.codecov.io/gh/pion/webrtc/pull/2634/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion) | `64.54% <ø> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion#carryforward-flags-in-the-pull-request-comment) to find out more.


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/pion/webrtc/pull/2634?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion).


Patch:
diff --git a/constants.go b/constants.go
index 28029218..ea484431 100644
--- a/constants.go
+++ b/constants.go
@@ -22,6 +22,8 @@ const (
 
 	sdpAttributeRid = "rid"
 
+	sdpAttributeSimulcast = "simulcast"
+
 	rtpOutboundMTU = 1200
 
 	rtpPayloadTypeBitmask = 0x7F
diff --git a/sdp.go b/sdp.go
index 0cd61873..ce076415 100644
--- a/sdp.go
+++ b/sdp.go
@@ -232,12 +232,30 @@ func trackDetailsToRTPReceiveParameters(t *trackDetails) RTPReceiveParameters {
 	return RTPReceiveParameters{Encodings: encodings}
 }
 
-func getRids(media *sdp.MediaDescription) map[string]string {
-	rids := map[string]string{}
+func getRids(media *sdp.MediaDescription) map[string]*simulcastRid {
+	rids := map[string]*simulcastRid{}
+	var simulcastAttr string
 	for _, attr := range media.Attributes {
 		if attr.Key == sdpAttributeRid {
 			split := strings.Split(attr.Value, " ")
-			rids[split[0]] = attr.Value
+			rids[split[0]] = &simulcastRid{attrValue: attr.Value}
+		} else if attr.Key == sdpAttributeSimulcast {
+			simulcastAttr = attr.Value
+		}
+	}
+	// process paused stream like "a=simulcast:send 1;~2;~3"
+	if simulcastAttr != "" {
+		if space := strings.Index(simulcastAttr, " "); space > 0 {
+			simulcastAttr = simulcastAttr[space+1:]
+		}
+		ridStates := strings.Split(simulcastAttr, ";")
+		for _, ridState := range ridStates {
+			if ridState[:1] == "~" {
+				rid := ridState[1:]
+				if r, ok := rids[rid]; ok {
+					r.paused = true
+				}
+			}
 		}
 	}
 	return rids
@@ -379,7 +397,7 @@ func addSenderSDP(
 				sendRids = append(sendRids, encoding.RID)
 			}
 			// Simulcast
-			media.WithValueAttribute("simulcast", "send "+strings.Join(sendRids, ";"))
+			media.WithValueAttribute(sdpAttributeSimulcast, "send "+strings.Join(sendRids, ";"))
 		}
 
 		if !isPlanB {
@@ -475,10 +493,13 @@ func addTransceiverSDP(
 
 		for rid := range mediaSection.ridMap {
 			media.WithValueAttribute(sdpAttributeRid, rid+" recv")
+			if mediaSection.ridMap[rid].paused {
+				rid = "~" + rid
+			}
 			recvRids = append(recvRids, rid)
 		}
 		// Simulcast
-		media.WithValueAttribute("simulcast", "recv "+strings.Join(recvRids, ";"))
+		media.WithValueAttribute(sdpAttributeSimulcast, "recv "+strings.Join(recvRids, ";"))
 	}
 
 	addSenderSDP(mediaSection, isPlanB, media)
@@ -500,11 +521,16 @@ func addTransceiverSDP(
 	return true, nil
 }
 
+type simulcastRid struct {
+	attrValue string
+	paused    bool
+}
+
 type mediaSection struct {
 	id           string
 	transceivers []*RTPTransceiver
 	data         bool
-	ridMap       map[string]string
+	ridMap       map[string]*simulcastRid
 }
 
 func bundleMatchFromRemote(matchBundleGroup *string) func(mid string) bool {


Test patch:
diff --git a/sdp_test.go b/sdp_test.go
index a0d99b14..4696ce87 100644
--- a/sdp_test.go
+++ b/sdp_test.go
@@ -381,8 +381,14 @@ func TestPopulateSDP(t *testing.T) {
 
 		tr := &RTPTransceiver{kind: RTPCodecTypeVideo, api: api, codecs: me.videoCodecs}
 		tr.setDirection(RTPTransceiverDirectionRecvonly)
-		ridMap := map[string]string{
-			"ridkey": "some",
+		ridMap := map[string]*simulcastRid{
+			"ridkey": {
+				attrValue: "some",
+			},
+			"ridPaused": {
+				attrValue: "some2",
+				paused:    true,
+			},
 		}
 		mediaSections := []mediaSection{{id: "video", transceivers: []*RTPTransceiver{tr}, ridMap: ridMap}}
 
@@ -392,21 +398,20 @@ func TestPopulateSDP(t *testing.T) {
 		assert.Nil(t, err)
 
 		// Test contains rid map keys
-		var found bool
+		var ridFound int
 		for _, desc := range offerSdp.MediaDescriptions {
 			if desc.MediaName.Media != "video" {
 				continue
 			}
-			for _, a := range desc.Attributes {
-				if a.Key == sdpAttributeRid {
-					if strings.Contains(a.Value, "ridkey") {
-						found = true
-						break
-					}
-				}
+			ridInSDP := getRids(desc)
+			if ridKey, ok := ridInSDP["ridkey"]; ok && !ridKey.paused {
+				ridFound++
+			}
+			if ridPaused, ok := ridInSDP["ridPaused"]; ok && ridPaused.paused {
+				ridFound++
 			}
 		}
-		assert.Equal(t, true, found, "Rid key should be present")
+		assert.Equal(t, 2, ridFound, "All rid keys should be present")
 	})
 	t.Run("SetCodecPreferences", func(t *testing.T) {
 		se := SettingEngine{}