Explorar el Código

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 hace 7 años
padre
commit
e7b8191003

+ 77
- 0
modules/RTC/BandwidthLimiter.js Ver fichero

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 Ver fichero

265
 
265
 
266
                 break;
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
             default: {
275
             default: {
269
                 logger.debug('Channel JSON-formatted message: ', obj);
276
                 logger.debug('Channel JSON-formatted message: ', obj);
270
 
277
 

+ 49
- 0
modules/RTC/TraceablePeerConnection.js Ver fichero

6
 import * as GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';
6
 import * as GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';
7
 import JitsiRemoteTrack from './JitsiRemoteTrack';
7
 import JitsiRemoteTrack from './JitsiRemoteTrack';
8
 import * as MediaType from '../../service/RTC/MediaType';
8
 import * as MediaType from '../../service/RTC/MediaType';
9
+import BandwidthLimiter from './BandwidthLimiter';
9
 import LocalSdpMunger from './LocalSdpMunger';
10
 import LocalSdpMunger from './LocalSdpMunger';
10
 import RTC from './RTC';
11
 import RTC from './RTC';
11
 import RTCUtils from './RTCUtils';
12
 import RTCUtils from './RTCUtils';
49
  *      disabled by removing it from the SDP.
50
  *      disabled by removing it from the SDP.
50
  * @param {boolean} options.preferH264 if set to 'true' H264 will be preferred
51
  * @param {boolean} options.preferH264 if set to 'true' H264 will be preferred
51
  * over other video codecs.
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
  * FIXME: initially the purpose of TraceablePeerConnection was to be able to
57
  * FIXME: initially the purpose of TraceablePeerConnection was to be able to
54
  * debug the peer connection. Since many other responsibilities have been added
58
  * debug the peer connection. Since many other responsibilities have been added
205
      */
209
      */
206
     this.localSdpMunger = new LocalSdpMunger(this);
210
     this.localSdpMunger = new LocalSdpMunger(this);
207
 
211
 
212
+    this.bandwidthLimiter = new BandwidthLimiter();
213
+
208
     /**
214
     /**
209
      * TracablePeerConnection uses RTC's eventEmitter
215
      * TracablePeerConnection uses RTC's eventEmitter
210
      * @type {EventEmitter}
216
      * @type {EventEmitter}
212
     this.eventEmitter = rtc.eventEmitter;
218
     this.eventEmitter = rtc.eventEmitter;
213
     this.rtxModifier = new RtxModifier();
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
     // override as desired
227
     // override as desired
216
     this.trace = (what, info) => {
228
     this.trace = (what, info) => {
217
         logger.debug(what, info);
229
         logger.debug(what, info);
1043
     return ssrcMap;
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
  * Takes a SessionDescription object and returns a "normalized" version.
1073
  * Takes a SessionDescription object and returns a "normalized" version.
1048
  * Currently it only takes care of ordering the a=ssrc lines.
1074
  * Currently it only takes care of ordering the a=ssrc lines.
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
     // If the browser uses unified plan, transform to it first
1896
     // If the browser uses unified plan, transform to it first
1856
     if (browser.usesUnifiedPlan()) {
1897
     if (browser.usesUnifiedPlan()) {
1857
         // eslint-disable-next-line no-param-reassign
1898
         // eslint-disable-next-line no-param-reassign
2486
     return ssrcInfo;
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
  * Creates a text representation of this <tt>TraceablePeerConnection</tt>
2539
  * Creates a text representation of this <tt>TraceablePeerConnection</tt>
2491
  * instance.
2540
  * instance.

+ 20
- 0
modules/xmpp/JingleSessionPC.js Ver fichero

17
 import SignalingLayerImpl from './SignalingLayerImpl';
17
 import SignalingLayerImpl from './SignalingLayerImpl';
18
 
18
 
19
 import browser from '../browser';
19
 import browser from '../browser';
20
+import RTCEvents from '../../service/RTC/RTCEvents';
20
 import Statistics from '../statistics/statistics';
21
 import Statistics from '../statistics/statistics';
21
 import XMPPEvents from '../../service/xmpp/XMPPEvents';
22
 import XMPPEvents from '../../service/xmpp/XMPPEvents';
22
 import GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';
23
 import GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';
282
             pcOptions.enableFirefoxSimulcast
283
             pcOptions.enableFirefoxSimulcast
283
                 = this.room.options.testing
284
                 = this.room.options.testing
284
                     && this.room.options.testing.enableFirefoxSimulcast;
285
                     && this.room.options.testing.enableFirefoxSimulcast;
286
+            pcOptions.enableLayerSuspension
287
+                = this.room.options.enableLayerSuspension;
285
         }
288
         }
286
 
289
 
287
         this.peerconnection
290
         this.peerconnection
477
 
480
 
478
         // The signaling layer will bind it's listeners at this point
481
         // The signaling layer will bind it's listeners at this point
479
         this.signalingLayer.setChatRoom(this.room);
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 Ver fichero

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

Loading…
Cancelar
Guardar