Kaynağa Gözat

Enable rtx sources for streams (#346)

* re-enable setting for enabling/disabling rtx

* add tests for rtxmodifier.  (and a bugfix found by the tests)

* remove rtx handling from sdp-consistency (it will be handled separately)

* better error printing in JingleSessionPC around renegotiation

* add rtx modifier to handle inserting local rtx ssrcs, keeping local rtx ssrcs consistent, and imploding remote rtx ssrcs

* jsdoc, es6 cleanup, logging tweaks

* fix line that's too long

* add a 'parseGroupSsrcs' helper function

* fix a bug when passing group ssrcs to be parsed

* address pr feedback

* address pr feedback

* fix for loop

* address pr feedback

* better const in SampleSdpStrings
dev1
bbaldino 9 yıl önce
ebeveyn
işleme
6c17cb7e65

+ 5
- 2
modules/xmpp/JingleSessionPC.js Dosyayı Görüntüle

@@ -346,7 +346,7 @@ JingleSessionPC.prototype.setOfferCycle = function (jingleOfferIq,
346 346
             .then(() => {
347 347
                 finishedCallback();
348 348
             }, (error) => {
349
-                logger.info("Error renegotiating after setting new remote offer: " + error);
349
+                logger.error("Error renegotiating after setting new remote offer: " + error);
350 350
                 JingleSessionPC.onJingleFatalError(this, error);
351 351
                 finishedCallback(error);
352 352
             });
@@ -655,7 +655,7 @@ JingleSessionPC.prototype.addRemoteStream = function (elem) {
655 655
                 this.notifyMySSRCUpdate(mySdp, newSdp);
656 656
                 finishedCallback();
657 657
             }, (error) => {
658
-                logger.info("Error renegotiating after processing remote source-add: " + error);
658
+                logger.error("Error renegotiating after processing remote source-add: " + error);
659 659
                 finishedCallback(error);
660 660
             });
661 661
     };
@@ -807,12 +807,14 @@ JingleSessionPC.prototype._renegotiate = function(optionalRemoteSdp) {
807 807
                     XMPPEvents.REMOTE_UFRAG_CHANGED, remoteUfrag);
808 808
         }
809 809
 
810
+        logger.debug("Renegotiate: setting remote description");
810 811
         this.peerconnection.setRemoteDescription(
811 812
             remoteDescription,
812 813
             () => {
813 814
                 if (this.signalingState === 'closed') {
814 815
                     reject("Attemped to setRemoteDescription in state closed");
815 816
                 }
817
+                logger.debug("Renegotiate: creating answer");
816 818
                 this.peerconnection.createAnswer(
817 819
                     (answer) => {
818 820
                         let localUfrag = getUfrag(answer.sdp);
@@ -821,6 +823,7 @@ JingleSessionPC.prototype._renegotiate = function(optionalRemoteSdp) {
821 823
                             this.room.eventEmitter.emit(
822 824
                                     XMPPEvents.LOCAL_UFRAG_CHANGED, localUfrag);
823 825
                         }
826
+                        logger.debug("Renegotiate: setting local description");
824 827
                         this.peerconnection.setLocalDescription(
825 828
                             answer,
826 829
                             () => { resolve(); },

+ 276
- 0
modules/xmpp/RtxModifier.js Dosyayı Görüntüle

@@ -0,0 +1,276 @@
1
+import { getLogger } from "jitsi-meet-logger";
2
+const logger = getLogger(__filename);
3
+import * as transform from 'sdp-transform';
4
+import * as SDPUtil from "./SDPUtil";
5
+
6
+/**
7
+ * Begin helper functions
8
+ */
9
+/**
10
+ * Given a videoMLine, returns a list of the video
11
+ *  ssrcs (those used to actually send video, not
12
+ *  any associated secondary streams)
13
+ * @param {object} videoMLine media line object from transform.parse
14
+ * @returns {list<string>} list of primary video ssrcs
15
+ */
16
+function getPrimaryVideoSsrcs (videoMLine) {
17
+    let videoSsrcs = videoMLine.ssrcs
18
+        .map(ssrcInfo => ssrcInfo.id)
19
+        .filter((ssrc, index, array) => array.indexOf(ssrc) === index);
20
+
21
+    if (videoMLine.ssrcGroups) {
22
+        videoMLine.ssrcGroups.forEach((ssrcGroupInfo) => {
23
+            // Right now, FID groups are the only ones we parse to 
24
+            //  disqualify streams.  If/when others arise we'll
25
+            //  need to add support for them here
26
+            if (ssrcGroupInfo.semantics === "FID") {
27
+                // secondary FID streams should be filtered out
28
+                let secondarySsrc = ssrcGroupInfo.ssrcs.split(" ")[1];
29
+                videoSsrcs.splice(
30
+                  videoSsrcs.indexOf(parseInt(secondarySsrc)), 1);
31
+            }
32
+        });
33
+    }
34
+    return videoSsrcs;
35
+}
36
+
37
+/**
38
+ * Given a video mline (as parsed from transform.parse),
39
+ *  and a primary ssrc, return the corresponding rtx ssrc
40
+ *  (if there is one) for that video ssrc
41
+ * @param {object} videoMLine the video MLine from which to extract the
42
+ *  rtx video ssrc
43
+ * @param {number} primarySsrc the video ssrc for which to find the
44
+ *  corresponding rtx ssrc
45
+ * @returns {number} the rtx ssrc (or undefined if there isn't one)
46
+ */
47
+function getRtxSsrc (videoMLine, primarySsrc) {
48
+    if (videoMLine.ssrcGroups) {
49
+        let fidGroup = videoMLine.ssrcGroups.find(group => {
50
+            if (group.semantics === "FID") {
51
+                let groupPrimarySsrc = SDPUtil.parseGroupSsrcs(group)[0];
52
+                return groupPrimarySsrc === primarySsrc;
53
+            }
54
+        });
55
+        if (fidGroup) {
56
+          return SDPUtil.parseGroupSsrcs(fidGroup)[1];
57
+        }
58
+    }
59
+}
60
+
61
+/**
62
+ * Updates or inserts the appropriate rtx information for primarySsrc with
63
+ *  the given rtxSsrc.  If no rtx ssrc for primarySsrc currently exists, it will
64
+ *  add the appropriate ssrc and ssrc group lines.  If primarySsrc already has
65
+ *  an rtx ssrc, the appropriate ssrc and group lines will be updated
66
+ * @param {object} videoMLine video mline object that will be updated (in place)
67
+ * @param {object} primarySsrcInfo the info (ssrc, msid & cname) for the 
68
+ *  primary ssrc
69
+ * @param {number} rtxSsrc the rtx ssrc to associate with the primary ssrc
70
+ */
71
+function updateAssociatedRtxStream (videoMLine, primarySsrcInfo, rtxSsrc) {
72
+    logger.info("Updating mline to associate " + rtxSsrc + 
73
+        " rtx ssrc with primary stream ", primarySsrcInfo.id);
74
+    let primarySsrc = primarySsrcInfo.id;
75
+    let primarySsrcMsid = primarySsrcInfo.msid;
76
+    let primarySsrcCname = primarySsrcInfo.cname;
77
+
78
+    let previousAssociatedRtxStream = 
79
+        getRtxSsrc (videoMLine, primarySsrc);
80
+    if (previousAssociatedRtxStream === rtxSsrc) {
81
+        logger.info(rtxSsrc + " was already associated with " +
82
+            primarySsrc);
83
+        return;
84
+    }
85
+    if (previousAssociatedRtxStream) {
86
+        logger.info(primarySsrc + " was previously assocaited with rtx " +
87
+            previousAssociatedRtxStream + ", removing all references to it");
88
+        // Stream already had an rtx ssrc that is different than the one given,
89
+        //  remove all trace of the old one
90
+        videoMLine.ssrcs = videoMLine.ssrcs
91
+            .filter(ssrcInfo => ssrcInfo.id !== previousAssociatedRtxStream);
92
+        logger.info("groups before filtering for " + 
93
+            previousAssociatedRtxStream);
94
+        logger.info(JSON.stringify(videoMLine.ssrcGroups));
95
+        videoMLine.ssrcGroups = videoMLine.ssrcGroups
96
+            .filter(groupInfo => {
97
+                return groupInfo
98
+                    .ssrcs
99
+                    .indexOf(previousAssociatedRtxStream + "") === -1;
100
+            });
101
+    }
102
+    videoMLine.ssrcs.push({
103
+        id: rtxSsrc,
104
+        attribute: "cname",
105
+        value: primarySsrcCname
106
+    });
107
+    videoMLine.ssrcs.push({
108
+        id: rtxSsrc,
109
+        attribute: "msid",
110
+        value: primarySsrcMsid
111
+    });
112
+    videoMLine.ssrcGroups = videoMLine.ssrcGroups || [];
113
+    videoMLine.ssrcGroups.push({
114
+        semantics: "FID",
115
+        ssrcs: primarySsrc + " " + rtxSsrc
116
+    });
117
+}
118
+/**
119
+ * End helper functions
120
+ */
121
+
122
+/**
123
+ * Adds any missing RTX streams for video streams
124
+ *  and makes sure that they remain consistent
125
+ */
126
+export default class RtxModifier {
127
+    /**
128
+     * Constructor
129
+     */
130
+    constructor () {
131
+        /**
132
+         * Map of video ssrc to corresponding RTX
133
+         *  ssrc
134
+         */
135
+        this.correspondingRtxSsrcs = new Map();
136
+    }
137
+
138
+    /**
139
+     * Clear the cached map of primary video ssrcs to
140
+     *  their corresponding rtx ssrcs so that they will
141
+     *  not be used for the next call to modifyRtxSsrcs
142
+     */
143
+    clearSsrcCache () {
144
+        this.correspondingRtxSsrcs.clear();
145
+    }
146
+
147
+    /**
148
+     * Explicitly set the primary video ssrc -> rtx ssrc
149
+     *  mapping to be used in modifyRtxSsrcs
150
+     * @param {Map} ssrcMapping a mapping of primary video
151
+     *  ssrcs to their corresponding rtx ssrcs
152
+     */
153
+    setSsrcCache (ssrcMapping) {
154
+        logger.info("Setting ssrc cache to ", ssrcMapping);
155
+        this.correspondingRtxSsrcs = ssrcMapping;
156
+    }
157
+
158
+    /**
159
+     * Adds RTX ssrcs for any video ssrcs that don't
160
+     *  already have them.  If the video ssrc has been
161
+     *  seen before, and already had an RTX ssrc generated,
162
+     *  the same RTX ssrc will be used again.
163
+     * @param {string} sdpStr sdp in raw string format
164
+     */
165
+    modifyRtxSsrcs (sdpStr) {
166
+        let parsedSdp = transform.parse(sdpStr);
167
+        let videoMLine = 
168
+            parsedSdp.media.find(mLine => mLine.type === "video");
169
+        if (videoMLine.direction === "inactive" ||
170
+                videoMLine.direction === "recvonly") {
171
+            logger.info("RtxModifier doing nothing, video " +
172
+                "m line is inactive or recvonly");
173
+            return sdpStr;
174
+        }
175
+        if (!videoMLine.ssrcs) {
176
+          logger.info("RtxModifier doing nothing, no video ssrcs present");
177
+          return sdpStr;
178
+        }
179
+        logger.info("Current ssrc mapping: ", this.correspondingRtxSsrcs);
180
+        let primaryVideoSsrcs = getPrimaryVideoSsrcs(videoMLine);
181
+        logger.info("Parsed primary video ssrcs ", primaryVideoSsrcs, " " +
182
+            "making sure all have rtx streams");
183
+        primaryVideoSsrcs.forEach(ssrc => {
184
+            let msid = SDPUtil.getSsrcAttribute(videoMLine, ssrc, "msid");
185
+            let cname = SDPUtil.getSsrcAttribute(videoMLine, ssrc, "cname");
186
+            let correspondingRtxSsrc = this.correspondingRtxSsrcs.get(ssrc);
187
+            if (correspondingRtxSsrc) {
188
+                logger.info("Already have an associated rtx ssrc for " +
189
+                    " video ssrc " + ssrc + ": " + 
190
+                    correspondingRtxSsrc);
191
+            } else {
192
+                logger.info("No previously associated rtx ssrc for " +
193
+                    " video ssrc " + ssrc);
194
+                // If there's one in the sdp already for it, we'll just set
195
+                //  that as the corresponding one
196
+                let previousAssociatedRtxStream = 
197
+                    getRtxSsrc (videoMLine, ssrc);
198
+                if (previousAssociatedRtxStream) {
199
+                    logger.info("Rtx stream " + previousAssociatedRtxStream + 
200
+                        " already existed in the sdp as an rtx stream for " +
201
+                        ssrc);
202
+                    correspondingRtxSsrc = previousAssociatedRtxStream;
203
+                } else {
204
+                    correspondingRtxSsrc = SDPUtil.generateSsrc();
205
+                    logger.info("Generated rtx ssrc " + correspondingRtxSsrc + 
206
+                        " for ssrc " + ssrc);
207
+                }
208
+                logger.info("Caching rtx ssrc " + correspondingRtxSsrc + 
209
+                    " for video ssrc " + ssrc);
210
+                this.correspondingRtxSsrcs.set(ssrc, correspondingRtxSsrc);
211
+            }
212
+            updateAssociatedRtxStream(
213
+                videoMLine, 
214
+                {
215
+                    id: ssrc,
216
+                    cname: cname,
217
+                    msid: msid
218
+                },
219
+                correspondingRtxSsrc);
220
+        });
221
+        return transform.write(parsedSdp);
222
+    }
223
+
224
+    /**
225
+     * Remove all reference to any rtx ssrcs that 
226
+     *  don't correspond to the primary stream.
227
+     * Must be called *after* any simulcast streams
228
+     *  have been imploded
229
+     * @param {string} sdpStr sdp in raw string format
230
+     */
231
+    implodeRemoteRtxSsrcs (sdpStr) {
232
+        let parsedSdp = transform.parse(sdpStr);
233
+        let videoMLine = 
234
+            parsedSdp.media.find(mLine => mLine.type === "video");
235
+        if (videoMLine.direction === "inactive" ||
236
+                videoMLine.direction === "recvonly") {
237
+            logger.info("RtxModifier doing nothing, video " +
238
+                "m line is inactive or recvonly");
239
+            return sdpStr;
240
+        }
241
+        if (!videoMLine.ssrcGroups) {
242
+            // Nothing to do
243
+            return sdpStr;
244
+        }
245
+
246
+        // Returns true if the given ssrc is present
247
+        //  in the mLine's ssrc list
248
+        let ssrcExists = (ssrcToFind) => {
249
+            return videoMLine.ssrcs.
250
+              find((ssrc) => ssrc.id + "" === ssrcToFind);
251
+        };
252
+        let ssrcsToRemove = [];
253
+        videoMLine.ssrcGroups.forEach(group => {
254
+            if (group.semantics === "FID") {
255
+                let primarySsrc = group.ssrcs.split(" ")[0];
256
+                let rtxSsrc = group.ssrcs.split(" ")[1];
257
+                if (!ssrcExists(primarySsrc)) {
258
+                    ssrcsToRemove.push(rtxSsrc);
259
+                }
260
+            }
261
+        });
262
+        videoMLine.ssrcs = videoMLine.ssrcs
263
+            .filter(ssrc => ssrcsToRemove.indexOf(ssrc.id + "") === -1);
264
+        videoMLine.ssrcGroups = videoMLine.ssrcGroups
265
+            .filter(group => {
266
+                let ssrcs = group.ssrcs.split(" ");
267
+                for (let i = 0; i < ssrcs.length; ++i) {
268
+                    if (ssrcsToRemove.indexOf(ssrcs[i]) !== -1) {
269
+                        return false;
270
+                    }
271
+                }
272
+                return true;
273
+            });
274
+        return transform.write(parsedSdp);
275
+    }
276
+}

+ 292
- 0
modules/xmpp/RtxModifier.spec.js Dosyayı Görüntüle

@@ -0,0 +1,292 @@
1
+/*eslint-disable max-len*/
2
+/*jshint maxlen:false*/
3
+import RtxModifier from "./RtxModifier.js";
4
+import * as SampleSdpStrings from "./SampleSdpStrings.js";
5
+import * as transform from 'sdp-transform';
6
+import * as SDPUtil from "./SDPUtil";
7
+
8
+/**
9
+ * Returns the number of video ssrcs in the given sdp
10
+ * @param {object} parsedSdp the sdp as parsed by transform.parse
11
+ * @returns {number} the number of video ssrcs in the given sdp
12
+ */
13
+function numVideoSsrcs (parsedSdp) {
14
+  let videoMLine = parsedSdp.media.find(m => m.type === "video");
15
+  return videoMLine.ssrcs
16
+    .map(ssrcInfo => ssrcInfo.id)
17
+    .filter((ssrc, index, array) => array.indexOf(ssrc) === index)
18
+    .length;
19
+}
20
+
21
+/**
22
+ * Return the (single) primary video ssrc in the given sdp
23
+ * @param {object} parsedSdp the sdp as parsed by transform.parse
24
+ * @returns {number} the primary video ssrc in the given sdp
25
+ */
26
+function getPrimaryVideoSsrc (parsedSdp) {
27
+  let videoMLine = parsedSdp.media.find(m => m.type === "video");
28
+  return parseInt(SDPUtil.parsePrimaryVideoSsrc(videoMLine));
29
+}
30
+
31
+/**
32
+ * Get the primary video ssrc(s) in the given sdp.
33
+ * Only handles parsing 2 scenarios right now:
34
+ * 1) Single video ssrc
35
+ * 2) Multiple video ssrcs in a single simulcast group
36
+ * @param {object} parsedSdp the sdp as parsed by transform.parse
37
+ * @returns {list<number>} the primary video ssrcs in the given sdp
38
+ */
39
+function getPrimaryVideoSsrcs (parsedSdp) {
40
+  let videoMLine = parsedSdp.media.find(m => m.type === "video");
41
+  if (numVideoSsrcs(parsedSdp) === 1) {
42
+    return [videoMLine.ssrcs[0].id];
43
+  } else {
44
+    let simGroups = getVideoGroups(parsedSdp, "SIM");
45
+    if (simGroups.length > 1) {
46
+      return;
47
+    }
48
+    let simGroup = simGroups[0];
49
+    return SDPUtil.parseGroupSsrcs(simGroup);
50
+  }
51
+}
52
+
53
+/**
54
+ * Get the video groups that match the passed semantics from the
55
+ *  given sdp
56
+ * @param {object} parsedSDp the sdp as parsed by transform.parse
57
+ * @param {string} groupSemantics the semantics string of the groups
58
+ *  the caller is interested in
59
+ * @returns {list<object>} a list of the groups from the given sdp
60
+ *  that matched the passed semantics
61
+ */
62
+function getVideoGroups (parsedSdp, groupSemantics) {
63
+  let videoMLine = parsedSdp.media.find(m => m.type === "video");
64
+  videoMLine.ssrcGroups = videoMLine.ssrcGroups || [];
65
+  return videoMLine.ssrcGroups
66
+    .filter(g => g.semantics === groupSemantics);
67
+}
68
+
69
+describe ("RtxModifier", function() {
70
+    beforeEach(function() {
71
+      this.rtxModifier = new RtxModifier();
72
+      this.transform = transform;
73
+      this.SDPUtil = SDPUtil;
74
+    });
75
+
76
+    describe ("modifyRtxSsrcs", function() {
77
+      describe ("when given an sdp with a single video ssrc", function() {
78
+        beforeEach(function() {
79
+          this.singleVideoSdp = SampleSdpStrings.plainVideoSdp;
80
+          this.primaryVideoSsrc = getPrimaryVideoSsrc(this.singleVideoSdp);
81
+        });
82
+        it ("should add a single rtx ssrc", function() {
83
+          // Call rtxModifier.modifyRtxSsrcs with an sdp that contains a single video
84
+          //  ssrc.  The returned sdp should have an rtx ssrc and an fid group.
85
+          let newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(this.singleVideoSdp));
86
+          let newSdp = transform.parse(newSdpStr);
87
+          let newPrimaryVideoSsrc = getPrimaryVideoSsrc(newSdp);
88
+          expect(newPrimaryVideoSsrc).toEqual(this.primaryVideoSsrc);
89
+          // Should now have an rtx ssrc as well
90
+          expect(numVideoSsrcs(newSdp)).toEqual(2);
91
+          // Should now have an FID group
92
+          let fidGroups = getVideoGroups(newSdp, "FID");
93
+          expect(fidGroups.length).toEqual(1);
94
+
95
+          let fidGroup = fidGroups[0];
96
+          let fidGroupPrimarySsrc = SDPUtil.parseGroupSsrcs(fidGroup)[0];
97
+          expect(fidGroupPrimarySsrc).toEqual(this.primaryVideoSsrc);
98
+        });
99
+
100
+        it ("should re-use the same rtx ssrc for a primary ssrc it's seen before", function() {
101
+          // Have rtxModifier generate an rtx ssrc via modifyRtxSsrcs.  Then call it again
102
+          //  with the same primary ssrc in the sdp (but no rtx ssrc).  It should use
103
+          //  the same rtx ssrc as before.
104
+          let newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(this.singleVideoSdp));
105
+          let newSdp = transform.parse(newSdpStr);
106
+
107
+          let fidGroup = getVideoGroups(newSdp, "FID")[0];
108
+          let fidGroupRtxSsrc = SDPUtil.parseGroupSsrcs(fidGroup)[1];
109
+
110
+          // Now pass the original sdp through again 
111
+          newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(this.singleVideoSdp));
112
+          newSdp = transform.parse(newSdpStr);
113
+          fidGroup = getVideoGroups(newSdp, "FID")[0];
114
+          let newFidGroupRtxSsrc = SDPUtil.parseGroupSsrcs(fidGroup)[1];
115
+          expect(newFidGroupRtxSsrc).toEqual(fidGroupRtxSsrc);
116
+        });
117
+
118
+        it ("should NOT re-use the same rtx ssrc for a primary ssrc it's seen before if the cache has been cleared", function() {
119
+          // Call modifyRtxSsrcs to generate an rtx ssrc
120
+          // Clear the rtxModifier cache
121
+          // Call modifyRtxSsrcs to generate an rtx ssrc again with the same primary ssrc
122
+          // --> We should get a different rtx ssrc
123
+          let newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(this.singleVideoSdp));
124
+          let newSdp = transform.parse(newSdpStr);
125
+
126
+          let fidGroup = getVideoGroups(newSdp, "FID")[0];
127
+          let fidGroupRtxSsrc = SDPUtil.parseGroupSsrcs(fidGroup)[1];
128
+          this.rtxModifier.clearSsrcCache();
129
+
130
+          // Now pass the original sdp through again
131
+          newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(this.singleVideoSdp));
132
+          newSdp = transform.parse(newSdpStr);
133
+          fidGroup = getVideoGroups(newSdp, "FID")[0];
134
+          let newFidGroupRtxSsrc = SDPUtil.parseGroupSsrcs(fidGroup)[1];
135
+          expect(newFidGroupRtxSsrc).not.toEqual(fidGroupRtxSsrc);
136
+        });
137
+
138
+        it ("should use the rtx ssrc from the cache when the cache has been manually set", function() {
139
+          // Manually set an rtx ssrc mapping in the cache
140
+          // Call modifyRtxSsrcs
141
+          // -->The rtx ssrc used should be the one we set
142
+          let forcedRtxSsrc = 123456;
143
+          let ssrcCache = new Map();
144
+          ssrcCache.set(this.primaryVideoSsrc, forcedRtxSsrc);
145
+          this.rtxModifier.setSsrcCache(ssrcCache);
146
+          let newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(this.singleVideoSdp));
147
+          let newSdp = transform.parse(newSdpStr);
148
+
149
+          let fidGroup = getVideoGroups(newSdp, "FID")[0];
150
+          let fidGroupRtxSsrc = SDPUtil.parseGroupSsrcs(fidGroup)[1];
151
+          expect(fidGroupRtxSsrc).toEqual(forcedRtxSsrc);
152
+        });
153
+      });
154
+
155
+      describe ("when given an sdp with multiple video ssrcs", function() {
156
+        beforeEach(function() {
157
+          this.multipleVideoSdp = SampleSdpStrings.simulcastSdp;
158
+          this.primaryVideoSsrcs = getPrimaryVideoSsrcs(this.multipleVideoSdp);
159
+        });
160
+
161
+        it ("should add rtx ssrcs for all of them", function() {
162
+          // Call rtxModifier.modifyRtxSsrcs with an sdp that contains multiple video
163
+          //  ssrcs.  The returned sdp should have an rtx ssrc and an fid group for all of them.
164
+          let newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(this.multipleVideoSdp));
165
+          let newSdp = transform.parse(newSdpStr);
166
+          let newPrimaryVideoSsrcs = getPrimaryVideoSsrcs(newSdp);
167
+          expect(newPrimaryVideoSsrcs).toEqual(this.primaryVideoSsrcs);
168
+          // Should now have rtx ssrcs as well
169
+          expect(numVideoSsrcs(newSdp)).toEqual(this.primaryVideoSsrcs.length * 2);
170
+          // Should now have FID groups
171
+          let fidGroups = getVideoGroups(newSdp, "FID");
172
+          expect(fidGroups.length).toEqual(this.primaryVideoSsrcs.length);
173
+          fidGroups.forEach(fidGroup => {
174
+            let fidGroupPrimarySsrc = SDPUtil.parseGroupSsrcs(fidGroup)[0];
175
+            expect(this.primaryVideoSsrcs.indexOf(fidGroupPrimarySsrc)).not.toEqual(-1);
176
+          });
177
+        });
178
+
179
+        it ("should re-use the same rtx ssrcs for any primary ssrc it's seen before", function() {
180
+          // Have rtxModifier generate an rtx ssrc via modifyRtxSsrcs.  Then call it again
181
+          //  with the same primary ssrc in the sdp (but no rtx ssrc).  It should use
182
+          //  the same rtx ssrc as before.
183
+          let newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(this.multipleVideoSdp));
184
+          let newSdp = transform.parse(newSdpStr);
185
+
186
+          let rtxMapping = new Map();
187
+          let fidGroups = getVideoGroups(newSdp, "FID");
188
+          // Save the first mapping that is made
189
+          fidGroups.forEach(fidGroup => {
190
+            let fidSsrcs = SDPUtil.parseGroupSsrcs(fidGroup);
191
+            let fidGroupPrimarySsrc = fidSsrcs[0];
192
+            let fidGroupRtxSsrc = fidSsrcs[1];
193
+            rtxMapping.set(fidGroupPrimarySsrc, fidGroupRtxSsrc);
194
+          });
195
+          // Now pass the original sdp through again and make sure we get the same mapping
196
+          newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(this.multipleVideoSdp));
197
+          newSdp = transform.parse(newSdpStr);
198
+          fidGroups = getVideoGroups(newSdp, "FID");
199
+          fidGroups.forEach(fidGroup => {
200
+            let fidSsrcs = SDPUtil.parseGroupSsrcs(fidGroup);
201
+            let fidGroupPrimarySsrc = fidSsrcs[0];
202
+            let fidGroupRtxSsrc = fidSsrcs[1];
203
+            expect(rtxMapping.has(fidGroupPrimarySsrc)).toBe(true);
204
+            expect(rtxMapping.get(fidGroupPrimarySsrc)).toEqual(fidGroupRtxSsrc);
205
+          });
206
+        });
207
+
208
+        it ("should NOT re-use the same rtx ssrcs for any primary ssrc it's seen before if the cache has been cleared", function() {
209
+          // Call modifyRtxSsrcs to generate an rtx ssrc
210
+          // Clear the rtxModifier cache
211
+          // Call modifyRtxSsrcs to generate rtx ssrcs again with the same primary ssrcs
212
+          // --> We should get different rtx ssrcs
213
+          let newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(this.multipleVideoSdp));
214
+          let newSdp = transform.parse(newSdpStr);
215
+
216
+          let rtxMapping = new Map();
217
+          let fidGroups = getVideoGroups(newSdp, "FID");
218
+          // Save the first mapping that is made
219
+          fidGroups.forEach(fidGroup => {
220
+            let fidSsrcs = SDPUtil.parseGroupSsrcs(fidGroup);
221
+            let fidGroupPrimarySsrc = fidSsrcs[0];
222
+            let fidGroupRtxSsrc = fidSsrcs[1];
223
+            rtxMapping.set(fidGroupPrimarySsrc, fidGroupRtxSsrc);
224
+          });
225
+
226
+          this.rtxModifier.clearSsrcCache();
227
+          // Now pass the original sdp through again and make sure we get the same mapping
228
+          newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(this.multipleVideoSdp));
229
+          newSdp = transform.parse(newSdpStr);
230
+          fidGroups = getVideoGroups(newSdp, "FID");
231
+          fidGroups.forEach(fidGroup => {
232
+            let fidSsrcs = SDPUtil.parseGroupSsrcs(fidGroup);
233
+            let fidGroupPrimarySsrc = fidSsrcs[0];
234
+            let fidGroupRtxSsrc = fidSsrcs[1];
235
+            expect(rtxMapping.has(fidGroupPrimarySsrc)).toBe(true);
236
+            expect(rtxMapping.get(fidGroupPrimarySsrc)).not.toEqual(fidGroupRtxSsrc);
237
+          });
238
+        });
239
+
240
+        it ("should use the rtx ssrcs from the cache when the cache has been manually set", function() {
241
+          // Manually set an rtx ssrc mapping in the cache
242
+          // Call modifyRtxSsrcs
243
+          // -->The rtx ssrc used should be the one we set
244
+          let rtxMapping = new Map();
245
+          this.primaryVideoSsrcs.forEach(ssrc => {
246
+            rtxMapping.set(ssrc, SDPUtil.generateSsrc());
247
+          });
248
+          this.rtxModifier.setSsrcCache(rtxMapping);
249
+
250
+          let newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(this.multipleVideoSdp));
251
+          let newSdp = transform.parse(newSdpStr);
252
+
253
+          let fidGroups = getVideoGroups(newSdp, "FID");
254
+          fidGroups.forEach(fidGroup => {
255
+            let fidSsrcs = SDPUtil.parseGroupSsrcs(fidGroup);
256
+            let fidGroupPrimarySsrc = fidSsrcs[0];
257
+            let fidGroupRtxSsrc = fidSsrcs[1];
258
+            expect(rtxMapping.has(fidGroupPrimarySsrc)).toBe(true);
259
+            expect(rtxMapping.get(fidGroupPrimarySsrc)).toEqual(fidGroupRtxSsrc);
260
+          });
261
+        });
262
+      });
263
+
264
+      describe ("(corner cases)", function() {
265
+        it ("should handle a recvonly video mline", function() {
266
+          let sdp = SampleSdpStrings.plainVideoSdp;
267
+          let videoMLine = sdp.media.find(m => m.type === "video");
268
+          videoMLine.direction = "recvonly";
269
+          let newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(sdp));
270
+          expect(newSdpStr).toEqual(this.transform.write(sdp));
271
+        });
272
+
273
+        it ("should handle an inactive video mline", function() {
274
+          let sdp = SampleSdpStrings.plainVideoSdp;
275
+          let videoMLine = sdp.media.find(m => m.type === "video");
276
+          videoMLine.direction = "inactive";
277
+          let newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(sdp));
278
+          expect(newSdpStr).toEqual(this.transform.write(sdp));
279
+        });
280
+
281
+        it ("should handle a video mline with no video ssrcs", function() {
282
+          let sdp = SampleSdpStrings.plainVideoSdp;
283
+          let videoMLine = sdp.media.find(m => m.type === "video");
284
+          videoMLine.ssrcs = [];
285
+          let newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(sdp));
286
+          expect(newSdpStr).toEqual(this.transform.write(sdp));
287
+        });
288
+      });
289
+    });
290
+});
291
+
292
+/*eslint-enable max-len*/

+ 82
- 2
modules/xmpp/SDPUtil.js Dosyayı Görüntüle

@@ -1,8 +1,8 @@
1 1
 import {getLogger} from "jitsi-meet-logger";
2 2
 const logger = getLogger(__filename);
3
+import RandomUtil from "../util/RandomUtil";
3 4
 var RTCBrowserType = require("../RTC/RTCBrowserType");
4 5
 
5
-
6 6
 var SDPUtil = {
7 7
     filter_special_chars: function (text) {
8 8
         // XXX Neither one of the falsy values (e.g. null, undefined, false,
@@ -362,7 +362,87 @@ var SDPUtil = {
362 362
         line += ' ';
363 363
         line += cand.getAttribute('generation') || '0';
364 364
         return line + '\r\n';
365
-    }
365
+    },
366
+
367
+    /**
368
+     * Parse the 'most' primary video ssrc from the given m line
369
+     * @param {object} mLine object as parsed from transform.parse
370
+     * @return {number} the primary video ssrc from the given m line
371
+     */
372
+    parsePrimaryVideoSsrc: function(videoMLine) {
373
+        let numSsrcs = videoMLine.ssrcs
374
+            .map(ssrcInfo => ssrcInfo.id)
375
+            .filter((ssrc, index, array) => array.indexOf(ssrc) === index)
376
+            .length;
377
+        let numGroups = (videoMLine.ssrcGroups && videoMLine.ssrcGroups.length) || 0;
378
+        if (numSsrcs > 1 && numGroups === 0) {
379
+            // Ambiguous, can't figure out the primary
380
+            return;
381
+        }
382
+        let primarySsrc = null;
383
+        if (numSsrcs === 1) {
384
+            primarySsrc = videoMLine.ssrcs[0].id;
385
+        } else {
386
+            if (numSsrcs === 2) {
387
+                // Can figure it out if there's an FID group
388
+                let fidGroup = videoMLine.ssrcGroups
389
+                    .find(group => group.semantics === "FID");
390
+                if (fidGroup) {
391
+                    primarySsrc = fidGroup.ssrcs.split(" ")[0];
392
+                }
393
+            } else if (numSsrcs >= 3) {
394
+                // Can figure it out if there's a sim group
395
+                let simGroup = videoMLine.ssrcGroups
396
+                    .find(group => group.semantics === "SIM");
397
+                if (simGroup) {
398
+                    primarySsrc = simGroup.ssrcs.split(" ")[0];
399
+                }
400
+            }
401
+        }
402
+        return primarySsrc;
403
+    },
404
+
405
+    /**
406
+     * Generate an ssrc
407
+     * @returns {number} an ssrc
408
+     */
409
+    generateSsrc: function() {
410
+        return RandomUtil.randomInt(1, 0xffffffff);
411
+    },
412
+
413
+    /**
414
+     * Get an attribute for the given ssrc with the given attributeName
415
+     *  from the given mline
416
+     * @param {object} mLine an mLine object as parsed from transform.parse
417
+     * @param {number} ssrc the ssrc for which an attribtue is desired
418
+     * @param {string} attributeName the name of the desired attribute
419
+     * @returns {string} the value corresponding to the given ssrc
420
+     *  and attributeName
421
+     */
422
+    getSsrcAttribute: function (mLine, ssrc, attributeName) {
423
+        for (let i = 0; i < mLine.ssrcs.length; ++i) {
424
+            let ssrcLine = mLine.ssrcs[i];
425
+            if (ssrcLine.id === ssrc &&
426
+                ssrcLine.attribute === attributeName) {
427
+                return ssrcLine.value;
428
+            }
429
+        }
430
+    },
431
+
432
+    /**
433
+     * Parses the ssrcs from the group sdp line and
434
+     *  returns them as a list of numbers
435
+     * @param {object} the ssrcGroup object as parsed from
436
+     *  sdp-transform
437
+     * @returns {list<number>} a list of the ssrcs in the group
438
+     *  parsed as numbers
439
+     */
440
+    parseGroupSsrcs: function (ssrcGroup) {
441
+        return ssrcGroup
442
+            .ssrcs
443
+            .split(" ")
444
+            .map(ssrcStr => parseInt(ssrcStr));
445
+    },
366 446
 };
367 447
 
368 448
 module.exports = SDPUtil;

+ 174
- 0
modules/xmpp/SampleSdpStrings.js Dosyayı Görüntüle

@@ -0,0 +1,174 @@
1
+/*eslint-disable max-len*/
2
+/*jshint maxlen:false*/
3
+import * as transform from 'sdp-transform';
4
+
5
+// A generic sdp session block
6
+const baseSessionSdp = "" +
7
+"v=0\r\n" +
8
+"o=- 814997227879783433 5 IN IP4 127.0.0.1\r\n" +
9
+"s=-\r\n" +
10
+"t=0 0\r\n" +
11
+"a=msid-semantic: WMS 0836cc8e-a7bb-47e9-affb-0599414bc56d\r\n" +
12
+"a=group:BUNDLE audio video data\r\n";
13
+
14
+// A basic sdp audio mline with a single stream
15
+const baseAudioMLineSdp = "" +
16
+"m=audio 54405 RTP/SAVPF 111 103 104 126\r\n" +
17
+"c=IN IP4 172.29.32.39\r\n" +
18
+"a=rtpmap:111 opus/48000/2\r\n" +
19
+"a=rtpmap:103 ISAC/16000\r\n" +
20
+"a=rtpmap:104 ISAC/32000\r\n" +
21
+"a=rtpmap:126 telephone-event/8000\r\n" +
22
+"a=fmtp:111 minptime=10;useinbandfec=1\r\n" +
23
+"a=rtcp:9 IN IP4 0.0.0.0\r\n" +
24
+"a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n" +
25
+"a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n" +
26
+"a=setup:passive\r\n" +
27
+"a=mid:audio\r\n" +
28
+"a=sendrecv\r\n" +
29
+"a=ice-ufrag:adPg\r\n" +
30
+"a=ice-pwd:Xsr05Mq8S7CR44DAnusZE26F\r\n" +
31
+"a=fingerprint:sha-256 6A:39:DE:11:24:AD:2E:4E:63:D6:69:D3:85:05:53:C7:3C:38:A4:B7:91:74:C0:91:44:FC:94:63:7F:01:AB:A9\r\n" +
32
+"a=candidate:1581043602 1 udp 2122260223 172.29.32.39 54405 typ host generation 0\r\n" +
33
+"a=ssrc:124723944 cname:peDGrDD6WsxUOki/\r\n" +
34
+"a=ssrc:124723944 msid:dcbb0236-cea5-402e-9e9a-595c65ffcc2a 40abf2d3-a415-4c68-8c17-2a038e8bebcf\r\n" +
35
+"a=ssrc:124723944 mslabel:dcbb0236-cea5-402e-9e9a-595c65ffcc2a\r\n" +
36
+"a=ssrc:124723944 label:40abf2d3-a415-4c68-8c17-2a038e8bebcf\r\n" +
37
+"a=rtcp-mux\r\n";
38
+
39
+// A basic sdp application mline
40
+const baseDataMLineSdp = "" +
41
+"m=application 9 DTLS/SCTP 5000\r\n" +
42
+"c=IN IP4 0.0.0.0\r\n" +
43
+"b=AS:30\r\n" +
44
+"a=setup:passive\r\n" +
45
+"a=mid:data\r\n" +
46
+"a=ice-ufrag:adPg\r\n" +
47
+"a=ice-pwd:Xsr05Mq8S7CR44DAnusZE26F\r\n" +
48
+"a=fingerprint:sha-256 6A:39:DE:11:24:AD:2E:4E:63:D6:69:D3:85:05:53:C7:3C:38:A4:B7:91:74:C0:91:44:FC:94:63:7F:01:AB:A9\r\n" +
49
+"a=sctpmap:5000 webrtc-datachannel 1024\r\n";
50
+
51
+// A basic sdp video mline with a single stream
52
+const plainVideoMLineSdp = "" +
53
+"m=video 9 RTP/SAVPF 100\r\n" +
54
+"c=IN IP4 0.0.0.0\r\n" +
55
+"a=rtpmap:100 VP8/90000\r\n" +
56
+"a=rtcp:9 IN IP4 0.0.0.0\r\n" +
57
+"a=rtcp-fb:100 ccm fir\r\n" +
58
+"a=rtcp-fb:100 nack\r\n" +
59
+"a=rtcp-fb:100 nack pli\r\n" +
60
+"a=rtcp-fb:100 goog-remb\r\n" +
61
+"a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n" +
62
+"a=setup:passive\r\n" +
63
+"a=mid:video\r\n" +
64
+"a=sendrecv\r\n" +
65
+"a=ice-ufrag:adPg\r\n" +
66
+"a=ice-pwd:Xsr05Mq8S7CR44DAnusZE26F\r\n" +
67
+"a=fingerprint:sha-256 6A:39:DE:11:24:AD:2E:4E:63:D6:69:D3:85:05:53:C7:3C:38:A4:B7:91:74:C0:91:44:FC:94:63:7F:01:AB:A9\r\n" +
68
+"a=ssrc:1757014965 msid:0836cc8e-a7bb-47e9-affb-0599414bc56d bdbd2c0a-7959-4578-8db5-9a6a1aec4ecf\r\n" +
69
+"a=ssrc:1757014965 cname:peDGrDD6WsxUOki/\r\n" +
70
+"a=rtcp-mux\r\n";
71
+
72
+// An sdp video mline with 3 simulcast streams
73
+const simulcastVideoMLineSdp = "" +
74
+"m=video 9 RTP/SAVPF 100\r\n" +
75
+"c=IN IP4 0.0.0.0\r\n" +
76
+"a=rtpmap:100 VP8/90000\r\n" +
77
+"a=rtcp:9 IN IP4 0.0.0.0\r\n" +
78
+"a=rtcp-fb:100 ccm fir\r\n" +
79
+"a=rtcp-fb:100 nack\r\n" +
80
+"a=rtcp-fb:100 nack pli\r\n" +
81
+"a=rtcp-fb:100 goog-remb\r\n" +
82
+"a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n" +
83
+"a=setup:passive\r\n" +
84
+"a=mid:video\r\n" +
85
+"a=sendrecv\r\n" +
86
+"a=ice-ufrag:adPg\r\n" +
87
+"a=ice-pwd:Xsr05Mq8S7CR44DAnusZE26F\r\n" +
88
+"a=fingerprint:sha-256 6A:39:DE:11:24:AD:2E:4E:63:D6:69:D3:85:05:53:C7:3C:38:A4:B7:91:74:C0:91:44:FC:94:63:7F:01:AB:A9\r\n" +
89
+"a=ssrc:1757014965 msid:0836cc8e-a7bb-47e9-affb-0599414bc56d bdbd2c0a-7959-4578-8db5-9a6a1aec4ecf\r\n" +
90
+"a=ssrc:1757014965 cname:peDGrDD6WsxUOki/\r\n" +
91
+"a=ssrc:1479742055 msid:0836cc8e-a7bb-47e9-affb-0599414bc56d bdbd2c0a-7959-4578-8db5-9a6a1aec4ecf\r\n" +
92
+"a=ssrc:1479742055 cname:peDGrDD6WsxUOki/\r\n" +
93
+"a=ssrc:1089111804 msid:0836cc8e-a7bb-47e9-affb-0599414bc56d bdbd2c0a-7959-4578-8db5-9a6a1aec4ecf\r\n" +
94
+"a=ssrc:1089111804 cname:peDGrDD6WsxUOki/\r\n" +
95
+"a=ssrc-group:SIM 1757014965 1479742055 1089111804\r\n" +
96
+"a=rtcp-mux\r\n";
97
+
98
+// An sdp video mline with a single video stream and a
99
+//  corresponding rtx stream
100
+const rtxVideoMLineSdp = "" +
101
+"m=video 9 RTP/SAVPF 100 96\r\n" +
102
+"c=IN IP4 0.0.0.0\r\n" +
103
+"a=rtpmap:100 VP8/90000\r\n" +
104
+"a=fmtp:96 apt=100\r\n" +
105
+"a=rtcp:9 IN IP4 0.0.0.0\r\n" +
106
+"a=rtcp-fb:100 ccm fir\r\n" +
107
+"a=rtcp-fb:100 nack\r\n" +
108
+"a=rtcp-fb:100 nack pli\r\n" +
109
+"a=rtcp-fb:100 goog-remb\r\n" +
110
+"a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n" +
111
+"a=setup:passive\r\n" +
112
+"a=mid:video\r\n" +
113
+"a=sendrecv\r\n" +
114
+"a=ice-ufrag:adPg\r\n" +
115
+"a=ice-pwd:Xsr05Mq8S7CR44DAnusZE26F\r\n" +
116
+"a=fingerprint:sha-256 6A:39:DE:11:24:AD:2E:4E:63:D6:69:D3:85:05:53:C7:3C:38:A4:B7:91:74:C0:91:44:FC:94:63:7F:01:AB:A9\r\n" +
117
+"a=ssrc:1757014965 msid:0836cc8e-a7bb-47e9-affb-0599414bc56d bdbd2c0a-7959-4578-8db5-9a6a1aec4ecf\r\n" +
118
+"a=ssrc:1757014965 cname:peDGrDD6WsxUOki/\r\n" +
119
+"a=ssrc:984899560 msid:0836cc8e-a7bb-47e9-affb-0599414bc56d bdbd2c0a-7959-4578-8db5-9a6a1aec4ecf\r\n" +
120
+"a=ssrc:984899560 cname:peDGrDD6WsxUOki/\r\n" +
121
+"a=ssrc-group:FID 1757014965 984899560\r\n" +
122
+"a=rtcp-mux\r\n";
123
+
124
+// An sdp video mline with 3 simulcast streams and 3 rtx streams
125
+const simulcastRtxVideoMLineSdp = "" +
126
+"m=video 9 RTP/SAVPF 100 96\r\n" +
127
+"c=IN IP4 0.0.0.0\r\n" +
128
+"a=rtpmap:100 VP8/90000\r\n" +
129
+"a=fmtp:96 apt=100\r\n" +
130
+"a=rtcp:9 IN IP4 0.0.0.0\r\n" +
131
+"a=rtcp-fb:100 ccm fir\r\n" +
132
+"a=rtcp-fb:100 nack\r\n" +
133
+"a=rtcp-fb:100 nack pli\r\n" +
134
+"a=rtcp-fb:100 goog-remb\r\n" +
135
+"a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n" +
136
+"a=setup:passive\r\n" +
137
+"a=mid:video\r\n" +
138
+"a=sendrecv\r\n" +
139
+"a=ice-ufrag:adPg\r\n" +
140
+"a=ice-pwd:Xsr05Mq8S7CR44DAnusZE26F\r\n" +
141
+"a=fingerprint:sha-256 6A:39:DE:11:24:AD:2E:4E:63:D6:69:D3:85:05:53:C7:3C:38:A4:B7:91:74:C0:91:44:FC:94:63:7F:01:AB:A9\r\n" +
142
+"a=ssrc:1757014965 msid:0836cc8e-a7bb-47e9-affb-0599414bc56d bdbd2c0a-7959-4578-8db5-9a6a1aec4ecf\r\n" +
143
+"a=ssrc:1757014965 cname:peDGrDD6WsxUOki/\r\n" +
144
+"a=ssrc:1479742055 msid:0836cc8e-a7bb-47e9-affb-0599414bc56d bdbd2c0a-7959-4578-8db5-9a6a1aec4ecf\r\n" +
145
+"a=ssrc:1479742055 cname:peDGrDD6WsxUOki/\r\n" +
146
+"a=ssrc:1089111804 msid:0836cc8e-a7bb-47e9-affb-0599414bc56d bdbd2c0a-7959-4578-8db5-9a6a1aec4ecf\r\n" +
147
+"a=ssrc:1089111804 cname:peDGrDD6WsxUOki/\r\n" +
148
+"a=ssrc:855213044 msid:0836cc8e-a7bb-47e9-affb-0599414bc56d bdbd2c0a-7959-4578-8db5-9a6a1aec4ecf\r\n" +
149
+"a=ssrc:855213044 cname:peDGrDD6WsxUOki/\r\n" +
150
+"a=ssrc:984899560 msid:0836cc8e-a7bb-47e9-affb-0599414bc56d bdbd2c0a-7959-4578-8db5-9a6a1aec4ecf\r\n" +
151
+"a=ssrc:984899560 cname:peDGrDD6WsxUOki/\r\n" +
152
+"a=ssrc:2963867077 msid:0836cc8e-a7bb-47e9-affb-0599414bc56d bdbd2c0a-7959-4578-8db5-9a6a1aec4ecf\r\n" +
153
+"a=ssrc:2963867077 cname:peDGrDD6WsxUOki/\r\n" +
154
+"a=ssrc-group:FID 1757014965 984899560\r\n" +
155
+"a=ssrc-group:FID 1479742055 855213044\r\n" +
156
+"a=ssrc-group:FID 1089111804 2963867077\r\n" +
157
+"a=ssrc-group:SIM 1757014965 1479742055 1089111804\r\n" +
158
+"a=rtcp-mux\r\n";
159
+
160
+// A full sdp string representing a client doing simulcast
161
+const simulcastSdpStr = baseSessionSdp + baseAudioMLineSdp + simulcastVideoMLineSdp + baseDataMLineSdp;
162
+// A full sdp string representing a client doing simulcast and rtx
163
+const simulcastRtxSdpStr = baseSessionSdp + baseAudioMLineSdp + simulcastRtxVideoMLineSdp + baseDataMLineSdp;
164
+// A full sdp string representing a client doing a single video stream
165
+const plainVideoSdpStr = baseSessionSdp + baseAudioMLineSdp + plainVideoMLineSdp + baseDataMLineSdp;
166
+// A full sdp string representing a client doing a single video stream with rtx
167
+const rtxVideoSdpStr = baseSessionSdp + baseAudioMLineSdp + rtxVideoMLineSdp + baseDataMLineSdp;
168
+
169
+export const simulcastSdp = transform.parse(simulcastSdpStr);
170
+export const simulcastRtxSdp = transform.parse(simulcastRtxSdpStr);
171
+export const plainVideoSdp = transform.parse(plainVideoSdpStr);
172
+export const rtxVideoSdp = transform.parse(rtxVideoSdpStr);
173
+
174
+/*eslint-enable max-len*/

+ 22
- 51
modules/xmpp/SdpConsistency.js Dosyayı Görüntüle

@@ -1,4 +1,7 @@
1
+import { getLogger } from "jitsi-meet-logger";
2
+const logger = getLogger(__filename);
1 3
 import * as transform from 'sdp-transform';
4
+import * as SDPUtil from "./SDPUtil";
2 5
 
3 6
 /**
4 7
  * Begin helper functions
@@ -30,39 +33,16 @@ function getPrimarySsrc (videoMLine) {
30 33
         if (videoMLine.ssrcGroups) {
31 34
             let simGroup = findGroup(videoMLine, "SIM");
32 35
             if (simGroup) {
33
-                return parseInt(simGroup.ssrcs.split(" ")[0]);
36
+                return SDPUtil.parseGroupSsrcs(simGroup)[0];
34 37
             }
35 38
             let fidGroup = findGroup(videoMLine, "FID");
36 39
             if (fidGroup) {
37
-                return parseInt(fidGroup.ssrcs.split(" ")[0]);
40
+                return SDPUtil.parseGroupSsrcs(fidGroup)[0];
38 41
             }
39 42
         }
40 43
     }
41 44
 }
42 45
 
43
-/**
44
- * Given a video mline (as parsed from transform.parse),
45
- *  and a primary ssrc, return the corresponding rtx ssrc
46
- *  (if there is one) for that video ssrc
47
- * @param {object} videoMLine the video MLine from which to extract the
48
- *  rtx video ssrc
49
- * @param {number} primarySsrc the video ssrc for which to find the
50
- *  corresponding rtx ssrc
51
- * @returns {number} the rtx ssrc (or undefined if there isn't one)
52
- */
53
-function getRtxSsrc (videoMLine, primarySsrc) {
54
-    if (videoMLine.ssrcGroups) {
55
-        let fidGroup = videoMLine.ssrcGroups.find(group => {
56
-            if (group.semantics === "FID") {
57
-                let groupPrimarySsrc = parseInt(group.ssrcs.split(" ")[0]);
58
-                return groupPrimarySsrc === primarySsrc;
59
-            }
60
-        });
61
-        if (fidGroup) {
62
-          return parseInt(fidGroup.ssrcs.split(" ")[1]);
63
-        }
64
-    }
65
-}
66 46
 /**
67 47
  * End helper functions
68 48
  */
@@ -71,8 +51,8 @@ function getRtxSsrc (videoMLine, primarySsrc) {
71 51
  * Handles the work of keeping video ssrcs consistent across multiple
72 52
  * o/a cycles, making it such that all stream operations can be
73 53
  * kept local and do not need to be signaled.
74
- * NOTE: This only keeps the 'primary' video ssrcs consistent: meaning
75
- * the primary video stream and an associated RTX stream, if it exists
54
+ * NOTE: This only keeps the 'primary' video ssrc consistent: meaning
55
+ * the primary video stream
76 56
  */
77 57
 export default class SdpConsistency {
78 58
     /**
@@ -89,7 +69,6 @@ export default class SdpConsistency {
89 69
      */
90 70
     clearSsrcCache () {
91 71
         this.cachedPrimarySsrc = null;
92
-        this.cachedPrimaryRtxSsrc = null;
93 72
     }
94 73
 
95 74
     /**
@@ -118,7 +97,7 @@ export default class SdpConsistency {
118 97
         let videoMLine =
119 98
             parsedSdp.media.find(mLine => mLine.type === "video");
120 99
         if (videoMLine.direction === "inactive") {
121
-            console.log("Sdp-consistency doing nothing, " +
100
+            logger.info("Sdp-consistency doing nothing, " +
122 101
                 "video mline is inactive");
123 102
             return sdpStr;
124 103
         }
@@ -133,44 +112,36 @@ export default class SdpConsistency {
133 112
                     value: "recvonly-" + this.cachedPrimarySsrc
134 113
                 });
135 114
             } else {
136
-                console.error("No SSRC found for the recvonly video stream!");
115
+                logger.error("No SSRC found for the recvonly video stream!");
137 116
             }
138 117
         } else {
139 118
             let newPrimarySsrc = getPrimarySsrc(videoMLine);
140 119
             if (!newPrimarySsrc) {
141
-                console.log("Sdp-consistency couldn't parse new primary ssrc");
120
+                logger.info("Sdp-consistency couldn't parse new primary ssrc");
142 121
                 return sdpStr;
143 122
             }
144
-            let newPrimaryRtxSsrc =
145
-                getRtxSsrc(videoMLine, newPrimarySsrc);
146 123
             if (!this.cachedPrimarySsrc) {
147 124
                 this.cachedPrimarySsrc = newPrimarySsrc;
148
-                this.cachedPrimaryRtxSsrc = newPrimaryRtxSsrc;
149
-                console.log("Sdp-consistency caching primary ssrc " +
150
-                    this.cachedPrimarySsrc + " and rtx " +
151
-                    this.cachedPrimaryRtxSsrc);
125
+                logger.info("Sdp-consistency caching primary ssrc " + 
126
+                    this.cachedPrimarySsrc);
152 127
             } else {
153
-                console.log("Sdp-consistency replacing new ssrc " +
154
-                    newPrimarySsrc + " with cached " + this.cachedPrimarySsrc +
155
-                    " and new rtx " + newPrimaryRtxSsrc + " with cached " +
156
-                    this.cachedPrimaryRtxSsrc);
157
-                let self = this;
128
+                logger.info("Sdp-consistency replacing new ssrc " + 
129
+                    newPrimarySsrc + " with cached " + this.cachedPrimarySsrc);
158 130
                 videoMLine.ssrcs.forEach(ssrcInfo => {
159 131
                     if (ssrcInfo.id === newPrimarySsrc) {
160
-                        ssrcInfo.id = self.cachedPrimarySsrc;
161
-                    } else if (ssrcInfo.id === newPrimaryRtxSsrc) {
162
-                        ssrcInfo.id = self.cachedPrimaryRtxSsrc;
132
+                        ssrcInfo.id = this.cachedPrimarySsrc;
163 133
                     }
164 134
                 });
165 135
                 if (videoMLine.ssrcGroups) {
166 136
                     videoMLine.ssrcGroups.forEach(group => {
167 137
                         if (group.semantics === "FID") {
168
-                            let primarySsrc =
169
-                                parseInt(group.ssrcs.split(" ")[0]);
170
-                            if (primarySsrc == self.cachedPrimarySsrc) {
171
-                                group.ssrcs =
172
-                                    self.cachedPrimarySsrc + " " +
173
-                                        self.cachedPrimaryRtxSsrc;
138
+                            let fidGroupSsrcs = SDPUtil.parseGroupSsrcs(group);
139
+                            let primarySsrc = fidGroupSsrcs[0];
140
+                            let rtxSsrc = fidGroupSsrcs[1];
141
+                            if (primarySsrc === newPrimarySsrc) {
142
+                                group.ssrcs = 
143
+                                    this.cachedPrimarySsrc + " " + 
144
+                                        rtxSsrc;
174 145
                             }
175 146
                         }
176 147
                     });

+ 61
- 7
modules/xmpp/TraceablePeerConnection.js Dosyayı Görüntüle

@@ -3,10 +3,10 @@
3 3
 import { getLogger } from "jitsi-meet-logger";
4 4
 const logger = getLogger(__filename);
5 5
 import SdpConsistency from "./SdpConsistency.js";
6
+import RtxModifier from "./RtxModifier.js";
6 7
 var RTCBrowserType = require("../RTC/RTCBrowserType.js");
7 8
 var XMPPEvents = require("../../service/xmpp/XMPPEvents");
8 9
 var transform = require('sdp-transform');
9
-var RandomUtil = require('../util/RandomUtil');
10 10
 var SDP = require("./SDP");
11 11
 var SDPUtil = require("./SDPUtil");
12 12
 
@@ -34,6 +34,7 @@ function TraceablePeerConnection(ice_config, constraints, session) {
34 34
     this.simulcast = new Simulcast({numOfLayers: SIMULCAST_LAYERS,
35 35
         explodeRemoteSimulcast: false});
36 36
     this.sdpConsistency = new SdpConsistency();
37
+    this.rtxModifier = new RtxModifier();
37 38
     this.eventEmitter = this.session.room.eventEmitter;
38 39
 
39 40
     // override as desired
@@ -317,7 +318,29 @@ TraceablePeerConnection.prototype.addStream = function (stream, ssrcInfo) {
317 318
         this.peerconnection.addStream(stream);
318 319
     if (ssrcInfo && ssrcInfo.type === "addMuted") {
319 320
         this.sdpConsistency.setPrimarySsrc(ssrcInfo.ssrc.ssrcs[0]);
320
-        this.simulcast.setSsrcCache(ssrcInfo.ssrc.ssrcs);
321
+        const simGroup = 
322
+            ssrcInfo.ssrc.groups.find(groupInfo => {
323
+                return groupInfo.group.semantics === "SIM";
324
+            });
325
+        if (simGroup) {
326
+            const simSsrcs = SDPUtil.parseGroupSsrcs(simGroup.group);
327
+            this.simulcast.setSsrcCache(simSsrcs);
328
+        }
329
+        const fidGroups =
330
+            ssrcInfo.ssrc.groups.filter(groupInfo => {
331
+                return groupInfo.group.semantics === "FID";
332
+            });
333
+        if (fidGroups) {
334
+            const rtxSsrcMapping = new Map();
335
+            fidGroups.forEach(fidGroup => {
336
+                const fidGroupSsrcs = 
337
+                    SDPUtil.parseGroupSsrcs(fidGroup.group);
338
+                const primarySsrc = fidGroupSsrcs[0];
339
+                const rtxSsrc = fidGroupSsrcs[1];
340
+                rtxSsrcMapping.set(primarySsrc, rtxSsrc);
341
+            });
342
+            this.rtxModifier.setSsrcCache(rtxSsrcMapping);
343
+        }
321 344
     }
322 345
 };
323 346
 
@@ -366,6 +389,9 @@ TraceablePeerConnection.prototype.setRemoteDescription
366 389
     description = this.simulcast.mungeRemoteDescription(description);
367 390
     this.trace('setRemoteDescription::postTransform (simulcast)', dumpSDP(description));
368 391
 
392
+    description.sdp = this.rtxModifier.implodeRemoteRtxSsrcs(description.sdp);
393
+    this.trace('setRemoteDescription::postTransform (implodeRemoteRtxSsrcs)', dumpSDP(description));
394
+
369 395
     // if we're running on FF, transform to Plan A first.
370 396
     if (RTCBrowserType.usesUnifiedPlan()) {
371 397
         description = this.interop.toUnifiedPlan(description);
@@ -515,6 +541,13 @@ TraceablePeerConnection.prototype.createAnswer
515 541
                         dumpSDP(answer));
516 542
                 }
517 543
 
544
+                if (!this.session.room.options.disableRtx) {
545
+                    answer.sdp = this.rtxModifier.modifyRtxSsrcs(answer.sdp);
546
+                    this.trace(
547
+                        'createAnswerOnSuccess::postTransform (rtx modifier)',
548
+                        dumpSDP(answer));
549
+                }
550
+
518 551
                 // Fix the setup attribute (see _fixAnswerRFC4145Setup for
519 552
                 //  details)
520 553
                 let remoteDescription = new SDP(this.remoteDescription.sdp);
@@ -583,18 +616,39 @@ TraceablePeerConnection.prototype.getStats = function(callback, errback) {
583 616
  * - groups - Array of the groups associated with the stream.
584 617
  */
585 618
 TraceablePeerConnection.prototype.generateNewStreamSSRCInfo = function () {
619
+    let ssrcInfo = {ssrcs: [], groups: []};
586 620
     if (!this.session.room.options.disableSimulcast
587 621
         && this.simulcast.isSupported()) {
588
-        var ssrcInfo = {ssrcs: [], groups: []};
589
-        for(var i = 0; i < SIMULCAST_LAYERS; i++)
590
-            ssrcInfo.ssrcs.push(RandomUtil.randomInt(1, 0xffffffff));
622
+        for (let i = 0; i < SIMULCAST_LAYERS; i++) {
623
+            ssrcInfo.ssrcs.push(SDPUtil.generateSsrc());
624
+        }
591 625
         ssrcInfo.groups.push({
592 626
             primarySSRC: ssrcInfo.ssrcs[0],
593 627
             group: {ssrcs: ssrcInfo.ssrcs.join(" "), semantics: "SIM"}});
594
-        return ssrcInfo;
628
+        ssrcInfo;
595 629
     } else {
596
-        return {ssrcs: [RandomUtil.randomInt(1, 0xffffffff)], groups: []};
630
+        ssrcInfo = {ssrcs: [SDPUtil.generateSsrc()], groups: []};
631
+    }
632
+    if (!this.session.room.options.disableRtx) {
633
+        // Specifically use a for loop here because we'll
634
+        //  be adding to the list we're iterating over, so we
635
+        //  only want to iterate through the items originally
636
+        //  on the list
637
+        const currNumSsrcs = ssrcInfo.ssrcs.length;
638
+        for (let i = 0; i < currNumSsrcs; ++i) {
639
+            const primarySsrc = ssrcInfo.ssrcs[i];
640
+            const rtxSsrc = SDPUtil.generateSsrc();
641
+            ssrcInfo.ssrcs.push(rtxSsrc);
642
+            ssrcInfo.groups.push({
643
+                primarySSRC: primarySsrc,
644
+                group: { 
645
+                    ssrcs: primarySsrc + " " + rtxSsrc,
646
+                    semantics: "FID"
647
+                }
648
+            });
649
+        }
597 650
     }
651
+    return ssrcInfo;
598 652
 };
599 653
 
600 654
 module.exports = TraceablePeerConnection;

+ 3
- 5
modules/xmpp/moderator.js Dosyayı Görüntüle

@@ -165,15 +165,13 @@ Moderator.prototype.createConferenceIq =  function () {
165 165
                 value: value
166 166
             }).up();
167 167
     }
168
-    // TODO: re-enable once rtx is stable
169
-    //if (this.options.conference.disableRtx !== undefined) {
168
+    if (this.options.conference.disableRtx !== undefined) {
170 169
         elem.c(
171 170
             'property', {
172 171
                 name: 'disableRtx',
173
-                //value: this.options.conference.disableRtx
174
-                value: true
172
+                value: this.options.conference.disableRtx
175 173
             }).up();
176
-    //}
174
+    }
177 175
     elem.c(
178 176
         'property', {
179 177
             name: 'enableLipSync',

Loading…
İptal
Kaydet