소스 검색

Layer suspension (#756)

* Support layer suspension

Add support for a message which notifies the endpoint whether or not it
is selected (meaning its HD stream is in use).  If it is not
selected and enableLayerSuspension is set to true then it will impose a
bandwidth limit in the SDP to suspend sending the higher layers.

* only add the IS_SELECTED_CHANGED listener in JingleSessionPC if layer
suspension is enabled

this prevents doing a local o/a when we don't need it
dev1
bbaldino 7 년 전
부모
커밋
e7b8191003
5개의 변경된 파일155개의 추가작업 그리고 0개의 파일을 삭제
  1. 77
    0
      modules/RTC/BandwidthLimiter.js
  2. 7
    0
      modules/RTC/BridgeChannel.js
  3. 49
    0
      modules/RTC/TraceablePeerConnection.js
  4. 20
    0
      modules/xmpp/JingleSessionPC.js
  5. 2
    0
      service/RTC/RTCEvents.js

+ 77
- 0
modules/RTC/BandwidthLimiter.js 파일 보기

@@ -0,0 +1,77 @@
1
+/* global __filename */
2
+
3
+import { getLogger } from 'jitsi-meet-logger';
4
+import transform from 'sdp-transform';
5
+
6
+const logger = getLogger(__filename);
7
+
8
+/**
9
+ * This will save a bandwidth limit (per mline) that should be used
10
+ * and then, when given an SDP, will enforce that limit.
11
+ * Note that this will affect *outgoing* bandwidth usage, but the SDP
12
+ * it must modify to implement that is the *remote* description
13
+ */
14
+export default class BandwidthLimiter {
15
+
16
+    /**
17
+     * Create a new BandwidthLimiter
18
+     */
19
+    constructor() {
20
+        /**
21
+         * @type {Map<String, Number>}
22
+         * Map of mline media type to a bandwidth limit (in kbps).
23
+         * Any mlines present in this map will have the associated
24
+         * bandwidth limit (or 'null' for no limit) enforced, meaning
25
+         * that it will potentially overwrite a limit already set in
26
+         * the given sdp.  However, if an mline is NOT present in
27
+         * this map, any limit in the given sdp will not be touched.
28
+         */
29
+        this._bandwidthLimits = new Map();
30
+    }
31
+
32
+    /**
33
+     * Set a bandwidth limit for given mline.  If limitKbps is null,
34
+     * the limit will be removed
35
+     * @param {String} mediaType the mline media type to set the
36
+     * bandwidth limit on
37
+     * @param {Number} limitKbps the bandwidth limit, in kbps
38
+     */
39
+    setBandwidthLimit(mediaType, limitKbps) {
40
+        this._bandwidthLimits.set(mediaType, limitKbps);
41
+    }
42
+
43
+    /**
44
+     * Enforce any configured bandwidth limits (or lack thereof) in the given
45
+     * sdp
46
+     * @param {String} sdp the session description
47
+     * @returns {String} a potentially modified session description
48
+     * with any configured bandwidth limits set
49
+     */
50
+    enforceBandwithLimit(sdp) {
51
+        logger.debug('Enforcing any configured bandwidth limits');
52
+        const desc = transform.parse(sdp);
53
+
54
+        desc.media.forEach(mLine => {
55
+            const limitKbps = this._bandwidthLimits.get(mLine.type);
56
+
57
+            if (typeof limitKbps !== 'undefined') {
58
+                if (limitKbps === null) {
59
+                    logger.debug(
60
+                        `Removing bandwidth limit for mline ${mLine.type}`);
61
+                    delete mLine.bandwidth;
62
+                } else {
63
+                    logger.debug(`Enforcing limit ${limitKbps}kbps`
64
+                        + ` for mline ${mLine.type}`);
65
+                    mLine.bandwidth = [
66
+                        {
67
+                            type: 'AS',
68
+                            limit: limitKbps
69
+                        }
70
+                    ];
71
+                }
72
+            }
73
+        });
74
+
75
+        return transform.write(desc);
76
+    }
77
+}

+ 7
- 0
modules/RTC/BridgeChannel.js 파일 보기

@@ -265,6 +265,13 @@ export default class BridgeChannel {
265 265
 
266 266
                 break;
267 267
             }
268
+            case 'SelectedUpdateEvent': {
269
+                const isSelected = obj.isSelected;
270
+
271
+                logger.info(`SelectedUpdateEvent isSelected? ${isSelected}`);
272
+                emitter.emit(RTCEvents.IS_SELECTED_CHANGED, isSelected);
273
+                break;
274
+            }
268 275
             default: {
269 276
                 logger.debug('Channel JSON-formatted message: ', obj);
270 277
 

+ 49
- 0
modules/RTC/TraceablePeerConnection.js 파일 보기

@@ -6,6 +6,7 @@ import transform from 'sdp-transform';
6 6
 import * as GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';
7 7
 import JitsiRemoteTrack from './JitsiRemoteTrack';
8 8
 import * as MediaType from '../../service/RTC/MediaType';
9
+import BandwidthLimiter from './BandwidthLimiter';
9 10
 import LocalSdpMunger from './LocalSdpMunger';
10 11
 import RTC from './RTC';
11 12
 import RTCUtils from './RTCUtils';
@@ -49,6 +50,9 @@ const SIM_LAYER_RIDS = [ SIM_LAYER_1_RID, SIM_LAYER_2_RID, SIM_LAYER_3_RID ];
49 50
  *      disabled by removing it from the SDP.
50 51
  * @param {boolean} options.preferH264 if set to 'true' H264 will be preferred
51 52
  * over other video codecs.
53
+ * @param {boolean} options.enableLayerSuspension if set to 'true', we will
54
+ * cap the video send bitrate when we are told we have not been selected by
55
+ * any endpoints (and therefore the non-thumbnail streams are not in use).
52 56
  *
53 57
  * FIXME: initially the purpose of TraceablePeerConnection was to be able to
54 58
  * debug the peer connection. Since many other responsibilities have been added
@@ -205,6 +209,8 @@ export default function TraceablePeerConnection(
205 209
      */
206 210
     this.localSdpMunger = new LocalSdpMunger(this);
207 211
 
212
+    this.bandwidthLimiter = new BandwidthLimiter();
213
+
208 214
     /**
209 215
      * TracablePeerConnection uses RTC's eventEmitter
210 216
      * @type {EventEmitter}
@@ -212,6 +218,12 @@ export default function TraceablePeerConnection(
212 218
     this.eventEmitter = rtc.eventEmitter;
213 219
     this.rtxModifier = new RtxModifier();
214 220
 
221
+    /**
222
+     * Whether or not this endpoint has been selected
223
+     * by a remote participant (via the bridge)
224
+     */
225
+    this.isSelected = true;
226
+
215 227
     // override as desired
216 228
     this.trace = (what, info) => {
217 229
         logger.debug(what, info);
@@ -1043,6 +1055,20 @@ function extractSSRCMap(desc) {
1043 1055
     return ssrcMap;
1044 1056
 }
1045 1057
 
1058
+/**
1059
+ * Get the bitrate cap we should enforce for video given whether or not
1060
+ * we are selected
1061
+ * @param {boolean} isSelected whether or not we (the local endpoint) is
1062
+ * selected by any other endpoints (meaning its HD stream is in use)
1063
+ * @return {Number} the bitrate cap in kbps, or null if there should be
1064
+ * no cap
1065
+ */
1066
+function getSuspensionBitrateKbps(isSelected) {
1067
+    // eslint-disable-next-line max-len
1068
+    // https://codesearch.chromium.org/chromium/src/third_party/webrtc/media/engine/simulcast.cc?l=55&rcl=28deb90728c06a35d8847d2aeda2fc1aee105c5e
1069
+    return isSelected ? null : 200;
1070
+}
1071
+
1046 1072
 /**
1047 1073
  * Takes a SessionDescription object and returns a "normalized" version.
1048 1074
  * Currently it only takes care of ordering the a=ssrc lines.
@@ -1852,6 +1878,21 @@ TraceablePeerConnection.prototype.setRemoteDescription = function(
1852 1878
         });
1853 1879
     }
1854 1880
 
1881
+    if (this.options.enableLayerSuspension) {
1882
+        logger.debug('Layer suspension enabled,'
1883
+            + `currently selected? ${this.isSelected}`);
1884
+        const bitrateCapKbps = getSuspensionBitrateKbps(this.isSelected);
1885
+
1886
+        this.bandwidthLimiter.setBandwidthLimit('video', bitrateCapKbps);
1887
+        logger.debug(`Layer suspension got bitrate cap of ${bitrateCapKbps}`);
1888
+        description.sdp
1889
+            = this.bandwidthLimiter.enforceBandwithLimit(description.sdp);
1890
+        this.trace(
1891
+            'setRemoteDescription::postTransform '
1892
+            + '(layer suspension bitrate cap)',
1893
+            dumpSDP(description));
1894
+    }
1895
+
1855 1896
     // If the browser uses unified plan, transform to it first
1856 1897
     if (browser.usesUnifiedPlan()) {
1857 1898
         // eslint-disable-next-line no-param-reassign
@@ -2486,6 +2527,14 @@ TraceablePeerConnection.prototype.generateNewStreamSSRCInfo = function(track) {
2486 2527
     return ssrcInfo;
2487 2528
 };
2488 2529
 
2530
+/**
2531
+ * Set whether or not the endpoint is 'selected' by other endpoints, meaning
2532
+ * it appears on their main stage
2533
+ */
2534
+TraceablePeerConnection.prototype.setIsSelected = function(isSelected) {
2535
+    this.isSelected = isSelected;
2536
+};
2537
+
2489 2538
 /**
2490 2539
  * Creates a text representation of this <tt>TraceablePeerConnection</tt>
2491 2540
  * instance.

+ 20
- 0
modules/xmpp/JingleSessionPC.js 파일 보기

@@ -17,6 +17,7 @@ import SDPUtil from './SDPUtil';
17 17
 import SignalingLayerImpl from './SignalingLayerImpl';
18 18
 
19 19
 import browser from '../browser';
20
+import RTCEvents from '../../service/RTC/RTCEvents';
20 21
 import Statistics from '../statistics/statistics';
21 22
 import XMPPEvents from '../../service/xmpp/XMPPEvents';
22 23
 import GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';
@@ -282,6 +283,8 @@ export default class JingleSessionPC extends JingleSession {
282 283
             pcOptions.enableFirefoxSimulcast
283 284
                 = this.room.options.testing
284 285
                     && this.room.options.testing.enableFirefoxSimulcast;
286
+            pcOptions.enableLayerSuspension
287
+                = this.room.options.enableLayerSuspension;
285 288
         }
286 289
 
287 290
         this.peerconnection
@@ -477,6 +480,23 @@ export default class JingleSessionPC extends JingleSession {
477 480
 
478 481
         // The signaling layer will bind it's listeners at this point
479 482
         this.signalingLayer.setChatRoom(this.room);
483
+
484
+        if (!this.isP2P && this.room.options.enableLayerSuspension) {
485
+            // If this is the bridge session, we'll listen for
486
+            // IS_SELECTED_CHANGED events and notify the peer connection
487
+            this.rtc.addListener(RTCEvents.IS_SELECTED_CHANGED,
488
+                isSelected => {
489
+                    this.peerconnection.setIsSelected(isSelected);
490
+                    logger.info('Doing local O/A due to '
491
+                        + 'IS_SELECTED_CHANGED event');
492
+                    this.modificationQueue.push(finishedCallback => {
493
+                        this._renegotiate()
494
+                            .then(finishedCallback)
495
+                            .catch(finishedCallback);
496
+                    });
497
+                }
498
+            );
499
+        }
480 500
     }
481 501
 
482 502
     /**

+ 2
- 0
service/RTC/RTCEvents.js 파일 보기

@@ -14,6 +14,8 @@ const RTCEvents = {
14 14
     DOMINANT_SPEAKER_CHANGED: 'rtc.dominant_speaker_changed',
15 15
     LASTN_ENDPOINT_CHANGED: 'rtc.lastn_endpoint_changed',
16 16
 
17
+    IS_SELECTED_CHANGED: 'rtc.is_selected_change',
18
+
17 19
     /**
18 20
      * Event emitted when {@link RTC.setLastN} method is called to update with
19 21
      * the new value set.

Loading…
취소
저장