|
@@ -1,8 +1,10 @@
|
1
|
1
|
import { getLogger } from 'jitsi-meet-logger';
|
2
|
2
|
|
3
|
3
|
import * as ConferenceEvents from '../../JitsiConferenceEvents';
|
|
4
|
+import CodecMimeType from '../../service/RTC/CodecMimeType';
|
4
|
5
|
import * as RTCEvents from '../../service/RTC/RTCEvents';
|
5
|
6
|
import * as ConnectionQualityEvents from '../../service/connectivity/ConnectionQualityEvents';
|
|
7
|
+import browser from '../browser';
|
6
|
8
|
|
7
|
9
|
const Resolutions = require('../../service/RTC/Resolutions');
|
8
|
10
|
const VideoType = require('../../service/RTC/VideoType');
|
|
@@ -16,46 +18,37 @@ const logger = getLogger(__filename);
|
16
|
18
|
*/
|
17
|
19
|
const STATS_MESSAGE_TYPE = 'stats';
|
18
|
20
|
|
19
|
|
-/**
|
20
|
|
- * See media/engine/simulcast.ss from webrtc.org
|
21
|
|
- */
|
22
|
21
|
const kSimulcastFormats = [
|
23
|
22
|
{ width: 1920,
|
24
|
23
|
height: 1080,
|
25
|
24
|
layers: 3,
|
26
|
|
- max: 5000,
|
27
|
|
- target: 4000,
|
28
|
|
- min: 800 },
|
|
25
|
+ target: 'high',
|
|
26
|
+ targetRN: 4000000 },
|
29
|
27
|
{ width: 1280,
|
30
|
28
|
height: 720,
|
31
|
29
|
layers: 3,
|
32
|
|
- max: 2500,
|
33
|
|
- target: 2500,
|
34
|
|
- min: 600 },
|
|
30
|
+ target: 'high',
|
|
31
|
+ targetRN: 2500000 },
|
35
|
32
|
{ width: 960,
|
36
|
33
|
height: 540,
|
37
|
34
|
layers: 3,
|
38
|
|
- max: 900,
|
39
|
|
- target: 900,
|
40
|
|
- min: 450 },
|
|
35
|
+ target: 'standard',
|
|
36
|
+ targetRN: 900000 },
|
41
|
37
|
{ width: 640,
|
42
|
38
|
height: 360,
|
43
|
39
|
layers: 2,
|
44
|
|
- max: 700,
|
45
|
|
- target: 500,
|
46
|
|
- min: 150 },
|
|
40
|
+ target: 'standard',
|
|
41
|
+ targetRN: 500000 },
|
47
|
42
|
{ width: 480,
|
48
|
43
|
height: 270,
|
49
|
44
|
layers: 2,
|
50
|
|
- max: 450,
|
51
|
|
- target: 350,
|
52
|
|
- min: 150 },
|
|
45
|
+ target: 'low',
|
|
46
|
+ targetRN: 350000 },
|
53
|
47
|
{ width: 320,
|
54
|
48
|
height: 180,
|
55
|
49
|
layers: 1,
|
56
|
|
- max: 200,
|
57
|
|
- target: 150,
|
58
|
|
- min: 30 }
|
|
50
|
+ target: 'low',
|
|
51
|
+ targetRN: 150000 }
|
59
|
52
|
];
|
60
|
53
|
|
61
|
54
|
/**
|
|
@@ -70,22 +63,14 @@ const MAX_TARGET_BITRATE = 2500;
|
70
|
63
|
*/
|
71
|
64
|
let startBitrate = 800;
|
72
|
65
|
|
73
|
|
-
|
74
|
|
-/**
|
75
|
|
- * The current cap (in kbps) put on the video stream (or null if there isn't
|
76
|
|
- * a cap). If there is a cap, we'll take it into account when calculating
|
77
|
|
- * the current quality.
|
78
|
|
- */
|
79
|
|
-let videoBitrateCap = null;
|
80
|
|
-
|
81
|
66
|
/**
|
82
|
67
|
* Gets the expected bitrate (in kbps) in perfect network conditions.
|
83
|
68
|
* @param simulcast {boolean} whether simulcast is enabled or not.
|
84
|
69
|
* @param resolution {Resolution} the resolution.
|
85
|
|
- * @param millisSinceStart {number} the number of milliseconds since sending
|
86
|
|
- * video started.
|
|
70
|
+ * @param millisSinceStart {number} the number of milliseconds since sending video started.
|
|
71
|
+ * @param videoQualitySettings {Object} the bitrate and codec settings for the local video source.
|
87
|
72
|
*/
|
88
|
|
-function getTarget(simulcast, resolution, millisSinceStart) {
|
|
73
|
+function getTarget(simulcast, resolution, millisSinceStart, videoQualitySettings) {
|
89
|
74
|
// Completely ignore the bitrate in the first 5 seconds, as the first
|
90
|
75
|
// event seems to fire very early and the value is suspicious and causes
|
91
|
76
|
// false positives.
|
|
@@ -96,44 +81,35 @@ function getTarget(simulcast, resolution, millisSinceStart) {
|
96
|
81
|
let target = 0;
|
97
|
82
|
let height = Math.min(resolution.height, resolution.width);
|
98
|
83
|
|
99
|
|
- if (simulcast) {
|
100
|
|
- // Find the first format with height no bigger than ours.
|
101
|
|
- let simulcastFormat = kSimulcastFormats.find(f => f.height <= height);
|
102
|
|
-
|
103
|
|
- if (simulcastFormat) {
|
104
|
|
- // Sum the target fields from all simulcast layers for the given
|
105
|
|
- // resolution (e.g. 720p + 360p + 180p).
|
106
|
|
- for (height = simulcastFormat.height; height >= 180; height /= 2) {
|
107
|
|
- const targetHeight = height;
|
108
|
|
-
|
109
|
|
- simulcastFormat
|
110
|
|
- = kSimulcastFormats.find(f => f.height === targetHeight);
|
111
|
|
- if (simulcastFormat) {
|
112
|
|
- target += simulcastFormat.target;
|
113
|
|
- } else {
|
114
|
|
- break;
|
115
|
|
- }
|
|
84
|
+ // Find the first format with height no bigger than ours.
|
|
85
|
+ let simulcastFormat = kSimulcastFormats.find(f => f.height <= height);
|
|
86
|
+
|
|
87
|
+ if (simulcastFormat && simulcast && videoQualitySettings.codec === CodecMimeType.VP8) {
|
|
88
|
+ // Sum the target fields from all simulcast layers for the given
|
|
89
|
+ // resolution (e.g. 720p + 360p + 180p) for VP8 simulcast.
|
|
90
|
+ for (height = simulcastFormat.height; height >= 180; height /= 2) {
|
|
91
|
+ const targetHeight = height;
|
|
92
|
+
|
|
93
|
+ simulcastFormat = kSimulcastFormats.find(f => f.height === targetHeight);
|
|
94
|
+ if (simulcastFormat) {
|
|
95
|
+ target += browser.isReactNative()
|
|
96
|
+ ? simulcastFormat.targetRN
|
|
97
|
+ : videoQualitySettings[simulcastFormat.target];
|
|
98
|
+ } else {
|
|
99
|
+ break;
|
116
|
100
|
}
|
117
|
101
|
}
|
118
|
|
- } else {
|
119
|
|
- // See GetMaxDefaultVideoBitrateKbps in
|
120
|
|
- // media/engine/webrtcvideoengine2.cc from webrtc.org
|
121
|
|
- const pixels = resolution.width * resolution.height;
|
122
|
|
-
|
123
|
|
- if (pixels <= 320 * 240) {
|
124
|
|
- target = 600;
|
125
|
|
- } else if (pixels <= 640 * 480) {
|
126
|
|
- target = 1700;
|
127
|
|
- } else if (pixels <= 960 * 540) {
|
128
|
|
- target = 2000;
|
129
|
|
- } else {
|
130
|
|
- target = 2500;
|
131
|
|
- }
|
|
102
|
+ } else if (simulcastFormat) {
|
|
103
|
+ // For VP9 SVC, H.264 (simulcast automatically disabled) and p2p, target bitrate will be
|
|
104
|
+ // same as that of the individual stream bitrate.
|
|
105
|
+ target = browser.isReactNative()
|
|
106
|
+ ? simulcastFormat.targetRN
|
|
107
|
+ : videoQualitySettings[simulcastFormat.target];
|
132
|
108
|
}
|
133
|
109
|
|
134
|
110
|
// Allow for an additional 1 second for ramp up -- delay any initial drop
|
135
|
|
- // of connection quality by 1 second.
|
136
|
|
- return Math.min(target, rampUp(Math.max(0, millisSinceStart - 1000)));
|
|
111
|
+ // of connection quality by 1 second. Convert target from bps to kbps.
|
|
112
|
+ return Math.min(target / 1000, rampUp(Math.max(0, millisSinceStart - 1000)));
|
137
|
113
|
}
|
138
|
114
|
|
139
|
115
|
/**
|
|
@@ -205,13 +181,6 @@ export default class ConnectionQuality {
|
205
|
181
|
*/
|
206
|
182
|
this._timeVideoUnmuted = -1;
|
207
|
183
|
|
208
|
|
- /**
|
209
|
|
- * The time at which a video bitrate cap was last removed. We use
|
210
|
|
- * this to calculate how much time we, as a sender, have had to
|
211
|
|
- * ramp-up
|
212
|
|
- */
|
213
|
|
- this._timeLastBwCapRemoved = -1;
|
214
|
|
-
|
215
|
184
|
// We assume a global startBitrate value for the sake of simplicity.
|
216
|
185
|
if (options.config.startBitrate && options.config.startBitrate > 0) {
|
217
|
186
|
startBitrate = options.config.startBitrate;
|
|
@@ -256,17 +225,8 @@ export default class ConnectionQuality {
|
256
|
225
|
this._updateRemoteStats(participant.getId(), payload);
|
257
|
226
|
});
|
258
|
227
|
|
259
|
|
- // Listen to local statistics events originating from the RTC module
|
260
|
|
- // and update the _localStats field.
|
261
|
|
- // Oh, and by the way, the resolutions of all remote participants are
|
262
|
|
- // also piggy-backed in these "local" statistics. It's obvious, really,
|
263
|
|
- // if one carefully reads the *code* (but not the docs) in
|
264
|
|
- // UI/VideoLayout/VideoLayout.js#updateLocalConnectionStats in
|
265
|
|
- // jitsi-meet
|
266
|
|
- // TODO: We should keep track of the remote resolution in _remoteStats,
|
267
|
|
- // and notify about changes via separate events.
|
268
|
|
- conference.statistics.addConnectionStatsListener(
|
269
|
|
- this._updateLocalStats.bind(this));
|
|
228
|
+ // Listen to local statistics events originating from the RTC module and update the _localStats field.
|
|
229
|
+ conference.statistics.addConnectionStatsListener(this._updateLocalStats.bind(this));
|
270
|
230
|
|
271
|
231
|
// Save the last time we were unmuted.
|
272
|
232
|
conference.on(
|
|
@@ -320,11 +280,9 @@ export default class ConnectionQuality {
|
320
|
280
|
|
321
|
281
|
/**
|
322
|
282
|
* Calculates a new "connection quality" value.
|
323
|
|
- * @param videoType {VideoType} the type of the video source (camera or
|
324
|
|
- * a screen capture).
|
|
283
|
+ * @param videoType {VideoType} the type of the video source (camera or a screen capture).
|
325
|
284
|
* @param isMuted {boolean} whether the local video is muted.
|
326
|
|
- * @param resolutionName {Resolution} the input resolution used by the
|
327
|
|
- * camera.
|
|
285
|
+ * @param resolutionName {Resolution} the input resolution used by the camera.
|
328
|
286
|
* @returns {*} the newly calculated connection quality.
|
329
|
287
|
*/
|
330
|
288
|
_calculateConnectionQuality(videoType, isMuted, resolutionName) {
|
|
@@ -383,46 +341,27 @@ export default class ConnectionQuality {
|
383
|
341
|
quality = 0; // Still 1 bar, but slower climb-up.
|
384
|
342
|
}
|
385
|
343
|
} else {
|
386
|
|
- // Calculate a value based on the sending bitrate.
|
387
|
|
-
|
388
|
|
- // Figure out if simulcast is in use
|
|
344
|
+ // Calculate a value based on the send video bitrate on the active TPC.
|
389
|
345
|
const activeTPC = this._conference.getActivePeerConnection();
|
390
|
|
- const isSimulcastOn
|
391
|
|
- = Boolean(activeTPC && activeTPC.isSimulcastOn());
|
392
|
346
|
|
393
|
|
- const newVideoBitrateCap
|
394
|
|
- = activeTPC && activeTPC.bandwidthLimiter
|
395
|
|
- && activeTPC.bandwidthLimiter.getBandwidthLimit('video');
|
|
347
|
+ if (activeTPC) {
|
|
348
|
+ const isSimulcastOn = activeTPC.isSimulcastOn();
|
|
349
|
+ const videoQualitySettings = activeTPC.getTargetVideoBitrates();
|
396
|
350
|
|
397
|
|
- // If we had a cap set but there isn't one now, then it has
|
398
|
|
- // just been 'lifted', so we should treat this like a new
|
399
|
|
- // ramp up.
|
400
|
|
- if (!newVideoBitrateCap && videoBitrateCap) {
|
401
|
|
- this._timeLastBwCapRemoved = window.performance.now();
|
|
351
|
+ // Add the codec info as well.
|
|
352
|
+ videoQualitySettings.codec = activeTPC.getConfiguredVideoCodec();
|
402
|
353
|
|
403
|
|
- // Set the start bitrate to whatever we were just capped to
|
404
|
|
- startBitrate = videoBitrateCap;
|
405
|
|
- }
|
406
|
|
- videoBitrateCap = newVideoBitrateCap;
|
407
|
|
-
|
408
|
|
- // time since sending of video was enabled.
|
409
|
|
- const millisSinceStart = window.performance.now()
|
410
|
|
- - Math.max(this._timeVideoUnmuted,
|
411
|
|
- this._timeIceConnected,
|
412
|
|
- this._timeLastBwCapRemoved);
|
413
|
|
-
|
414
|
|
- // expected sending bitrate in perfect conditions
|
415
|
|
- let target
|
416
|
|
- = getTarget(isSimulcastOn, resolution, millisSinceStart);
|
|
354
|
+ // Time since sending of video was enabled.
|
|
355
|
+ const millisSinceStart = window.performance.now()
|
|
356
|
+ - Math.max(this._timeVideoUnmuted, this._timeIceConnected);
|
417
|
357
|
|
418
|
|
- target = Math.min(0.9 * target, MAX_TARGET_BITRATE);
|
|
358
|
+ // Expected sending bitrate in perfect conditions.
|
|
359
|
+ let target = getTarget(isSimulcastOn, resolution, millisSinceStart, videoQualitySettings);
|
419
|
360
|
|
420
|
|
- if (videoBitrateCap) {
|
421
|
|
- target = Math.min(target, videoBitrateCap);
|
|
361
|
+ target = Math.min(target, MAX_TARGET_BITRATE);
|
|
362
|
+ quality = 100 * this._localStats.bitrate.upload / target;
|
422
|
363
|
}
|
423
|
364
|
|
424
|
|
- quality = 100 * this._localStats.bitrate.upload / target;
|
425
|
|
-
|
426
|
365
|
// Whatever the bitrate, drop early if there is significant loss
|
427
|
366
|
if (packetLoss && packetLoss >= 10) {
|
428
|
367
|
quality = Math.min(quality, 30);
|
|
@@ -433,15 +372,9 @@ export default class ConnectionQuality {
|
433
|
372
|
if (this._lastConnectionQualityUpdate > 0) {
|
434
|
373
|
const maxIncreasePerSecond = 2;
|
435
|
374
|
const prevConnectionQuality = this._localStats.connectionQuality;
|
436
|
|
- const diffSeconds
|
437
|
|
- = (window.performance.now() - this._lastConnectionQualityUpdate)
|
438
|
|
- / 1000;
|
439
|
|
-
|
440
|
|
- quality
|
441
|
|
- = Math.min(
|
442
|
|
- quality,
|
443
|
|
- prevConnectionQuality
|
444
|
|
- + (diffSeconds * maxIncreasePerSecond));
|
|
375
|
+ const diffSeconds = (window.performance.now() - this._lastConnectionQualityUpdate) / 1000;
|
|
376
|
+
|
|
377
|
+ quality = Math.min(quality, prevConnectionQuality + (diffSeconds * maxIncreasePerSecond));
|
445
|
378
|
}
|
446
|
379
|
|
447
|
380
|
return Math.min(100, quality);
|