Pārlūkot izejas kodu

Apply max bitrate of 500Kbps on desktop streams and set the contentHint attribute on the track to 'detail'. (#992)

Enable/disable this feature using 'capScreenshareBitrate' testing flag in config.js and send analytics
events to Amplitude to indicate which type of screensharing is enabled.
dev1
Jaya Allamsetty 6 gadus atpakaļ
vecāks
revīzija
a6fd643e06

+ 27
- 0
JitsiMeetJS.js Parādīt failu

@@ -386,6 +386,15 @@ export default _mergeNamespaceAndModule({
386 386
                     }
387 387
                 }
388 388
 
389
+                // set the contentHint to "detail" for desktop tracks
390
+                // eslint-disable-next-line prefer-const
391
+                for (const track of tracks) {
392
+                    if (track.type === MediaType.VIDEO
393
+                        && track.videoType === 'desktop') {
394
+                        this.setVideoTrackContentHints(track.track, 'detail');
395
+                    }
396
+                }
397
+
389 398
                 return tracks;
390 399
             })
391 400
             .catch(error => {
@@ -565,6 +574,24 @@ export default _mergeNamespaceAndModule({
565 574
         Statistics.reportGlobalError(error);
566 575
     },
567 576
 
577
+    /**
578
+     * Set the contentHint on the transmitted stream track to indicate
579
+     * charaterstics in the video stream, which informs PeerConnection
580
+     * on how to encode the track (to prefer motion or individual frame detail)
581
+     * @param {MediaStreamTrack} track - the track that is transmitted
582
+     * @param {String} hint - contentHint value that needs to be set on the track
583
+     */
584
+    setVideoTrackContentHints(track, hint) {
585
+        if ('contentHint' in track) {
586
+            track.contentHint = hint;
587
+            if (track.contentHint !== hint) {
588
+                logger.debug('Invalid video track contentHint');
589
+            }
590
+        } else {
591
+            logger.debug('MediaStreamTrack contentHint attribute not supported');
592
+        }
593
+    },
594
+
568 595
     /* eslint-enable max-params */
569 596
 
570 597
     /**

+ 78
- 3
modules/RTC/TraceablePeerConnection.js Parādīt failu

@@ -26,6 +26,8 @@ const SIM_LAYER_1_RID = '1';
26 26
 const SIM_LAYER_2_RID = '2';
27 27
 const SIM_LAYER_3_RID = '3';
28 28
 const SIM_LAYER_RIDS = [ SIM_LAYER_1_RID, SIM_LAYER_2_RID, SIM_LAYER_3_RID ];
29
+const SIM_LAYER_BITRATES_BPS = [ 200000, 700000, 2500000 ];
30
+const DESKSTOP_SHARE_RATE = 500000;
29 31
 
30 32
 /* eslint-disable max-params */
31 33
 
@@ -45,6 +47,9 @@ const SIM_LAYER_RIDS = [ SIM_LAYER_1_RID, SIM_LAYER_2_RID, SIM_LAYER_3_RID ];
45 47
  * @param {boolean} options.disableRtx if set to 'true' will disable the RTX
46 48
  * @param {boolean} options.enableFirefoxSimulcast if set to 'true' will enable
47 49
  * experimental simulcast support on Firefox.
50
+ * @param {boolean} options.capScreenshareBitrate if set to 'true' simulcast will
51
+ * be disabled for screenshare and a max bitrate of 500Kbps will applied on the
52
+ * stream.
48 53
  * @param {boolean} options.disableH264 If set to 'true' H264 will be
49 54
  *      disabled by removing it from the SDP.
50 55
  * @param {boolean} options.preferH264 if set to 'true' H264 will be preferred
@@ -1883,6 +1888,55 @@ TraceablePeerConnection.prototype._insertUnifiedPlanSimulcastReceive
1883 1888
         });
1884 1889
     };
1885 1890
 
1891
+/**
1892
+ * Sets the max bitrate on the RTCRtpSender so that the
1893
+ * bitrate of the enocder doesn't exceed the configured value.
1894
+ * This is needed for the desktop share until spec-complaint
1895
+ * simulcast is implemented.
1896
+ * @param {JitsiLocalTrack} localTrack - the local track whose
1897
+ * max bitrate is to be configured.
1898
+ */
1899
+TraceablePeerConnection.prototype.setMaxBitRate = function(localTrack) {
1900
+    const mediaType = localTrack.type;
1901
+
1902
+    if (!this.options.capScreenshareBitrate
1903
+        || mediaType === MediaType.AUDIO) {
1904
+
1905
+        return;
1906
+    }
1907
+    if (!this.peerconnection.getSenders) {
1908
+        logger.debug('Browser doesn\'t support RTCRtpSender');
1909
+
1910
+        return;
1911
+    }
1912
+    const videoType = localTrack.videoType;
1913
+    const trackId = localTrack.track.id;
1914
+
1915
+    this.peerconnection.getSenders()
1916
+        .filter(s => s.track && s.track.id === trackId)
1917
+        .forEach(sender => {
1918
+            try {
1919
+                const parameters = sender.getParameters();
1920
+
1921
+                if (parameters.encodings && parameters.encodings.length) {
1922
+                    logger.info('Setting max bitrate on video stream');
1923
+                    for (const encoding in parameters.encodings) {
1924
+                        if (parameters.encodings.hasOwnProperty(encoding)) {
1925
+                            parameters.encodings[encoding].maxBitrate
1926
+                                = videoType === 'desktop'
1927
+                                    ? DESKSTOP_SHARE_RATE
1928
+                                    : SIM_LAYER_BITRATES_BPS[encoding];
1929
+                        }
1930
+                    }
1931
+                    sender.setParameters(parameters);
1932
+                }
1933
+            } catch (err) {
1934
+                logger.error('Browser does not support getParameters/setParamters '
1935
+                    + 'or setting max bitrate on the encodings: ', err);
1936
+            }
1937
+        });
1938
+};
1939
+
1886 1940
 TraceablePeerConnection.prototype.setRemoteDescription = function(description) {
1887 1941
     this.trace('setRemoteDescription::preTransform', dumpSDP(description));
1888 1942
 
@@ -2240,6 +2294,17 @@ TraceablePeerConnection.prototype.createOffer = function(constraints) {
2240 2294
     return this._createOfferOrAnswer(true /* offer */, constraints);
2241 2295
 };
2242 2296
 
2297
+/**
2298
+ * Checks if a camera track has been added to the peerconnection
2299
+ * @param {TraceablePeerConnection} peerConnection
2300
+ * @return {boolean} <tt>true</tt> if the peerconnection has
2301
+ * a camera track for its video source <tt>false</tt> otherwise.
2302
+ */
2303
+function hasCameraTrack(peerConnection) {
2304
+    return peerConnection.getLocalTracks()
2305
+        .some(t => t.videoType === 'camera');
2306
+}
2307
+
2243 2308
 TraceablePeerConnection.prototype._createOfferOrAnswer = function(
2244 2309
         isOffer,
2245 2310
         constraints) {
@@ -2298,8 +2363,12 @@ TraceablePeerConnection.prototype._createOfferOrAnswer = function(
2298 2363
                     dumpSDP(resultSdp));
2299 2364
             }
2300 2365
 
2301
-            // Add simulcast streams if simulcast is enabled
2302
-            if (this.isSimulcastOn()) {
2366
+            // configure simulcast for camera tracks always and for
2367
+            // desktop tracks only when the testing flag for maxbitrates
2368
+            // in config.js is disabled.
2369
+            if (this.isSimulcastOn()
2370
+                && (!this.options.capScreenshareBitrate
2371
+                || (this.options.capScreenshareBitrate && hasCameraTrack(this)))) {
2303 2372
                 // eslint-disable-next-line no-param-reassign
2304 2373
                 resultSdp = this.simulcast.mungeLocalDescription(resultSdp);
2305 2374
                 this.trace(
@@ -2500,7 +2569,13 @@ TraceablePeerConnection.prototype.generateNewStreamSSRCInfo = function(track) {
2500 2569
     if (ssrcInfo) {
2501 2570
         logger.error(`Will overwrite local SSRCs for track ID: ${rtcId}`);
2502 2571
     }
2503
-    if (this.isSimulcastOn()) {
2572
+
2573
+    // configure simulcast for camera tracks always and for
2574
+    // desktop tracks only when the testing flag for maxbitrates
2575
+    // in config.js is disabled.
2576
+    if (this.isSimulcastOn()
2577
+        && (!this.options.capScreenshareBitrate
2578
+        || (this.options.capScreenshareBitrate && hasCameraTrack(this)))) {
2504 2579
         ssrcInfo = {
2505 2580
             ssrcs: [],
2506 2581
             groups: []

+ 40
- 2
modules/xmpp/JingleSessionPC.js Parādīt failu

@@ -274,7 +274,7 @@ export default class JingleSessionPC extends JingleSession {
274 274
         if (options.gatherStats) {
275 275
             pcOptions.maxstats = DEFAULT_MAX_STATS;
276 276
         }
277
-
277
+        pcOptions.capScreenshareBitrate = false;
278 278
         if (this.isP2P) {
279 279
             // simulcast needs to be disabled for P2P (121) calls
280 280
             pcOptions.disableSimulcast = true;
@@ -295,6 +295,24 @@ export default class JingleSessionPC extends JingleSession {
295 295
             pcOptions.enableFirefoxSimulcast
296 296
                 = options.testing && options.testing.enableFirefoxSimulcast;
297 297
             pcOptions.enableLayerSuspension = options.enableLayerSuspension;
298
+
299
+            // disable simulcast for screenshare and set the max bitrate to
300
+            // 500Kbps if the testing flag is present in config.js.
301
+            if (options.testing
302
+                && options.testing.capScreenshareBitrate
303
+                && typeof options.testing.capScreenshareBitrate === 'number') {
304
+                pcOptions.capScreenshareBitrate
305
+                    = Math.random()
306
+                    < options.testing.capScreenshareBitrate;
307
+            }
308
+        }
309
+
310
+        // send the corresponding amplitude events for simulcast screenshare
311
+        // and non-simulcast screenshare.
312
+        if (pcOptions.capScreenshareBitrate) {
313
+            Statistics.sendAnalyticsAndLog('nonSimulcastScreenSharing');
314
+        } else {
315
+            Statistics.sendAnalyticsAndLog('simulcastScreenSharing');
298 316
         }
299 317
 
300 318
         if (options.startSilent) {
@@ -1668,7 +1686,19 @@ export default class JingleSessionPC extends JingleSession {
1668 1686
 
1669 1687
             // NOTE the code below assumes that no more than 1 video track
1670 1688
             // can be added to the peer connection.
1671
-            // Transition from no video to video (possibly screen sharing)
1689
+            // Transition from camera to desktop share
1690
+            // or transition from one camera source to another.
1691
+            if (this.peerconnection.options.capScreenshareBitrate
1692
+                && oldTrack && newTrack && newTrack.isVideoTrack()) {
1693
+                // Clearing current primary SSRC will make
1694
+                // the SdpConsistency generate a new one which will result
1695
+                // with:
1696
+                // 1. source-remove for the old video stream.
1697
+                // 2. source-add for the new video stream.
1698
+                this.peerconnection.clearRecvonlySsrc();
1699
+            }
1700
+
1701
+            // Transition from no video to video (unmute).
1672 1702
             if (!oldTrack && newTrack && newTrack.isVideoTrack()) {
1673 1703
                 // Clearing current primary SSRC will make
1674 1704
                 // the SdpConsistency generate a new one which will result
@@ -1700,6 +1730,14 @@ export default class JingleSessionPC extends JingleSession {
1700 1730
 
1701 1731
                             this.notifyMySSRCUpdate(
1702 1732
                                 new SDP(oldLocalSdp), newLocalSDP);
1733
+
1734
+                            // configure max bitrate only when media is routed
1735
+                            // through JVB. For p2p case, browser takes care of
1736
+                            // adjusting the uplink based on the feedback it
1737
+                            // gets from the peer.
1738
+                            if (newTrack && !this.isP2P) {
1739
+                                this.peerconnection.setMaxBitRate(newTrack);
1740
+                            }
1703 1741
                             finishedCallback();
1704 1742
                         },
1705 1743
                         finishedCallback /* will be called with en error */);

Notiek ielāde…
Atcelt
Saglabāt