Переглянути джерело

fix(stats) Obtain resolution/fps from 'outbound-rtp' stats. (#2265)

* fix(stats) Obtain resolution/fps from 'outbound-rtp' stats.
'Track' based stats were dropped in Chrome 112, therefore send resolution and fps for the simulcast case needs to be calculated based on the 'outbound-rtp' streams that are currently active.

* squash: remove an unwanted log

* Squash: Address review comments.
Use outbound-rtp stats for both Firefox and Safari.
Firefox - Ignore active field if not present in the stats and calc fps using 'framesSent'.
master
Jaya Allamsetty 2 роки тому
джерело
коміт
c520877a03
Аккаунт користувача з таким Email не знайдено

+ 39
- 0
modules/RTC/TraceablePeerConnection.js Переглянути файл

@@ -680,6 +680,33 @@ TraceablePeerConnection.prototype.getAudioLevels = function(speakerList = []) {
680 680
     return audioLevels;
681 681
 };
682 682
 
683
+/**
684
+ * Checks if the browser is currently doing true simulcast where in three different media streams are being sent to the
685
+ * bridge. Currently this happens only when VP8 is the selected codec.
686
+ * @returns {boolean}
687
+ */
688
+TraceablePeerConnection.prototype.doesTrueSimulcast = function() {
689
+    return this.isSimulcastOn() && this.getConfiguredVideoCodec() === CodecMimeType.VP8;
690
+};
691
+
692
+/**
693
+ * Returns the SSRCs associated with a given local video track.
694
+ *
695
+ * @param {JitsiLocalTrack} localTrack
696
+ * @returns
697
+ */
698
+TraceablePeerConnection.prototype.getLocalVideoSSRCs = function(localTrack) {
699
+    const ssrcs = [];
700
+
701
+    if (!localTrack || !localTrack.isVideoTrack()) {
702
+        return ssrcs;
703
+    }
704
+
705
+    const ssrcGroup = this.isSimulcastOn() ? 'SIM' : 'FID';
706
+
707
+    return this.localSSRCs.get(localTrack.rtcId)?.groups?.find(group => group.semantics === ssrcGroup)?.ssrcs || ssrcs;
708
+};
709
+
683 710
 /**
684 711
  * Obtains local tracks for given {@link MediaType}. If the <tt>mediaType</tt>
685 712
  * argument is omitted the list of all local tracks will be returned.
@@ -1237,6 +1264,18 @@ TraceablePeerConnection.prototype._extractSSRCMap = function(desc) {
1237 1264
                     groupsMap.get(primarySSRC).push(group);
1238 1265
                 }
1239 1266
             }
1267
+
1268
+            const simGroup = mLine.ssrcGroups.find(group => group.semantics === 'SIM');
1269
+
1270
+            // Add a SIM group if its missing in the description (happens on Firefox).
1271
+            if (!simGroup) {
1272
+                const groupSsrcs = mLine.ssrcGroups.map(group => group.ssrcs[0]);
1273
+
1274
+                groupsMap.get(groupSsrcs[0]).push({
1275
+                    semantics: 'SIM',
1276
+                    ssrcs: groupSsrcs
1277
+                });
1278
+            }
1240 1279
         }
1241 1280
 
1242 1281
         let ssrcs = mLine.ssrcs;

+ 9
- 0
modules/browser/BrowserCapabilities.js Переглянути файл

@@ -219,6 +219,15 @@ export default class BrowserCapabilities extends BrowserDetection {
219 219
         return !this.isFirefox();
220 220
     }
221 221
 
222
+    /**
223
+     * Returns true if the browser supports track based statistics for the local video track. Otherwise,
224
+     * track resolution and framerate will be calculated based on the 'outbound-rtp' statistics.
225
+     * @returns {boolean}
226
+     */
227
+    supportsTrackBasedStats() {
228
+        return this.isChromiumBased() && this.isVersionLessThan(112);
229
+    }
230
+
222 231
     /**
223 232
      * Returns true if VP9 is supported by the client on the browser. VP9 is currently disabled on Firefox and Safari
224 233
      * because of issues with rendering. Please check https://bugzilla.mozilla.org/show_bug.cgi?id=1492500,

+ 121
- 85
modules/statistics/RTPStatsCollector.js Переглянути файл

@@ -275,61 +275,86 @@ StatsCollector.prototype._processAndEmitReport = function() {
275 275
         bitrateDownload += ssrcStats.bitrate.download;
276 276
         bitrateUpload += ssrcStats.bitrate.upload;
277 277
 
278
+        ssrcStats.resetBitrate();
279
+
278 280
         // collect resolutions and framerates
279 281
         const track = this.peerconnection.getTrackBySSRC(ssrc);
280 282
 
281
-        if (track) {
282
-            let audioCodec;
283
-            let videoCodec;
284
-
285
-            if (track.isAudioTrack()) {
286
-                audioBitrateDownload += ssrcStats.bitrate.download;
287
-                audioBitrateUpload += ssrcStats.bitrate.upload;
288
-                audioCodec = ssrcStats.codec;
289
-            } else {
290
-                videoBitrateDownload += ssrcStats.bitrate.download;
291
-                videoBitrateUpload += ssrcStats.bitrate.upload;
292
-                videoCodec = ssrcStats.codec;
293
-            }
294
-
295
-            const participantId = track.getParticipantId();
296
-
297
-            if (participantId) {
298
-                const resolution = ssrcStats.resolution;
283
+        if (!track) {
284
+            continue; // eslint-disable-line no-continue
285
+        }
299 286
 
300
-                if (resolution.width
301
-                        && resolution.height
302
-                        && resolution.width !== -1
303
-                        && resolution.height !== -1) {
304
-                    const userResolutions = resolutions[participantId] || {};
287
+        let audioCodec;
288
+        let videoCodec;
305 289
 
306
-                    userResolutions[ssrc] = resolution;
307
-                    resolutions[participantId] = userResolutions;
308
-                }
290
+        if (track.isAudioTrack()) {
291
+            audioBitrateDownload += ssrcStats.bitrate.download;
292
+            audioBitrateUpload += ssrcStats.bitrate.upload;
293
+            audioCodec = ssrcStats.codec;
294
+        } else {
295
+            videoBitrateDownload += ssrcStats.bitrate.download;
296
+            videoBitrateUpload += ssrcStats.bitrate.upload;
297
+            videoCodec = ssrcStats.codec;
298
+        }
309 299
 
310
-                if (ssrcStats.framerate > 0) {
311
-                    const userFramerates = framerates[participantId] || {};
300
+        const participantId = track.getParticipantId();
312 301
 
313
-                    userFramerates[ssrc] = ssrcStats.framerate;
314
-                    framerates[participantId] = userFramerates;
315
-                }
302
+        if (!participantId) {
303
+            // All tracks in ssrc-rewriting mode need not have a participant associated with it.
304
+            if (!FeatureFlags.isSsrcRewritingSupported()) {
305
+                logger.error(`No participant ID returned by ${track}`);
306
+            }
307
+            continue; // eslint-disable-line no-continue
308
+        }
316 309
 
317
-                const userCodecs = codecs[participantId] ?? { };
310
+        const userCodecs = codecs[participantId] ?? { };
318 311
 
319
-                userCodecs[ssrc] = {
320
-                    audio: audioCodec,
321
-                    video: videoCodec
322
-                };
312
+        userCodecs[ssrc] = {
313
+            audio: audioCodec,
314
+            video: videoCodec
315
+        };
323 316
 
324
-                codecs[participantId] = userCodecs;
317
+        codecs[participantId] = userCodecs;
318
+        const { resolution } = ssrcStats;
325 319
 
326
-            // All tracks in ssrc-rewriting mode need not have a participant associated with it.
327
-            } else if (!FeatureFlags.isSsrcRewritingSupported()) {
328
-                logger.error(`No participant ID returned by ${track}`);
320
+        if (!track.isVideoTrack()
321
+            || isNaN(resolution?.height)
322
+            || isNaN(resolution?.width)
323
+            || resolution.height === -1
324
+            || resolution.width === -1) {
325
+            continue; // eslint-disable-line no-continue
326
+        }
327
+        const userResolutions = resolutions[participantId] || {};
328
+
329
+        // If simulcast (VP8) is used, there will be 3 "outbound-rtp" streams with different resolutions and 3
330
+        // different SSRCs. Based on the requested resolution and the current cpu and available bandwidth
331
+        // values, some of the streams might get suspended. Therefore the actual send resolution needs to be
332
+        // calculated based on the outbound-rtp streams that are currently active for the simulcast case.
333
+        // However for the SVC case, there will be only 1 "outbound-rtp" stream which will have the correct
334
+        // send resolution width and height.
335
+        if (track.isLocal() && !browser.supportsTrackBasedStats() && this.peerconnection.doesTrueSimulcast()) {
336
+            const localSsrcs = this.peerconnection.getLocalVideoSSRCs(track);
337
+
338
+            for (const localSsrc of localSsrcs) {
339
+                const ssrcResolution = this.ssrc2stats.get(localSsrc)?.resolution;
340
+
341
+                // The code processes resolution stats only for 'outbound-rtp' streams that are currently active.
342
+                if (ssrcResolution?.height && ssrcResolution?.width) {
343
+                    resolution.height = Math.max(resolution.height, ssrcResolution.height);
344
+                    resolution.width = Math.max(resolution.width, ssrcResolution.width);
345
+                }
329 346
             }
330 347
         }
331 348
 
332
-        ssrcStats.resetBitrate();
349
+        userResolutions[ssrc] = resolution;
350
+        resolutions[participantId] = userResolutions;
351
+
352
+        if (ssrcStats.framerate > 0) {
353
+            const userFramerates = framerates[participantId] || {};
354
+
355
+            userFramerates[ssrc] = ssrcStats.framerate;
356
+            framerates[participantId] = userFramerates;
357
+        }
333 358
     }
334 359
 
335 360
     this.conferenceStats.bitrate = {
@@ -445,6 +470,26 @@ StatsCollector.prototype._calculateBitrate = function(now, before, fieldName) {
445 470
     return bitrateKbps;
446 471
 };
447 472
 
473
+/**
474
+ * Calculates the frames per second rate between before and now using a supplied field name and its value in stats.
475
+ * @param {RTCOutboundRtpStreamStats|RTCSentRtpStreamStats} now the current stats
476
+ * @param {RTCOutboundRtpStreamStats|RTCSentRtpStreamStats} before the previous stats
477
+ * @param {string} fieldName the field to use for calculations.
478
+ * @returns {number} the calculated frame rate between now and before.
479
+ */
480
+StatsCollector.prototype._calculateFps = function(now, before, fieldName) {
481
+    const timeMs = now.timestamp - before.timestamp;
482
+    let frameRate = 0;
483
+
484
+    if (timeMs > 0 && now[fieldName]) {
485
+        const numberOfFramesSinceBefore = now[fieldName] - before[fieldName];
486
+
487
+        frameRate = (numberOfFramesSinceBefore / timeMs) * 1000;
488
+    }
489
+
490
+    return frameRate;
491
+};
492
+
448 493
 /**
449 494
  * Stats processing for spec-compliant RTCPeerConnection#getStats.
450 495
  */
@@ -552,29 +597,37 @@ StatsCollector.prototype.processStatsReport = function() {
552 597
                 });
553 598
             }
554 599
 
555
-            // Get the resolution and framerate for only remote video sources here. For the local video sources,
556
-            // 'track' stats will be used since they have the updated resolution based on the simulcast streams
557
-            // currently being sent. Promise based getStats reports three 'outbound-rtp' streams and there will be
558
-            // more calculations needed to determine what is the highest resolution stream sent by the client if the
559
-            // 'outbound-rtp' stats are used.
560
-            if (now.type === 'inbound-rtp') {
561
-                const resolution = {
562
-                    height: now.frameHeight,
563
-                    width: now.frameWidth
564
-                };
565
-                const frameRate = now.framesPerSecond;
600
+            let resolution;
566 601
 
567
-                if (resolution.height && resolution.width) {
568
-                    ssrcStats.setResolution(resolution);
569
-                }
570
-                ssrcStats.setFramerate(Math.round(frameRate || 0));
602
+            // Process the stats for 'inbound-rtp' streams always and 'outbound-rtp' only if the browser is
603
+            // Chromium based and version 112 and later since 'track' based stats are no longer available there
604
+            // for calculating send resolution and frame rate.
605
+            if (typeof now.frameHeight !== 'undefined' && typeof now.frameWidth !== 'undefined') {
606
+                // Assume the stream is active if the field is missing in the stats(Firefox)
607
+                const isStreamActive = now.active ?? true;
571 608
 
572
-                if (before) {
573
-                    ssrcStats.addBitrate({
574
-                        'download': this._calculateBitrate(now, before, 'bytesReceived'),
575
-                        'upload': 0
576
-                    });
609
+                if (now.type === 'inbound-rtp' || (!browser.supportsTrackBasedStats() && isStreamActive)) {
610
+                    resolution = {
611
+                        height: now.frameHeight,
612
+                        width: now.frameWidth
613
+                    };
577 614
                 }
615
+            }
616
+            ssrcStats.setResolution(resolution);
617
+
618
+            let frameRate = now.framesPerSecond;
619
+
620
+            if (!frameRate && before) {
621
+                frameRate = this._calculateFps(now, before, 'framesSent');
622
+            }
623
+
624
+            ssrcStats.setFramerate(Math.round(frameRate || 0));
625
+
626
+            if (now.type === 'inbound-rtp' && before) {
627
+                ssrcStats.addBitrate({
628
+                    'download': this._calculateBitrate(now, before, 'bytesReceived'),
629
+                    'upload': 0
630
+                });
578 631
             } else if (before) {
579 632
                 byteSentStats[ssrc] = this.getNonNegativeValue(now.bytesSent);
580 633
                 ssrcStats.addBitrate({
@@ -596,10 +649,11 @@ StatsCollector.prototype.processStatsReport = function() {
596 649
                 codecShortType && ssrcStats.setCodec(codecShortType);
597 650
             }
598 651
 
599
-        // Use track stats for resolution and framerate of the local video source.
600
-        // RTCVideoHandlerStats - https://w3c.github.io/webrtc-stats/#vststats-dict*
601
-        // RTCMediaHandlerStats - https://w3c.github.io/webrtc-stats/#mststats-dict*
602
-        } else if (now.type === 'track' && now.kind === MediaType.VIDEO && !now.remoteSource) {
652
+        // Continue to use the 'track' based stats for Firefox and Safari and older versions of Chromium.
653
+        } else if (browser.supportsTrackBasedStats()
654
+            && now.type === 'track'
655
+            && now.kind === MediaType.VIDEO
656
+            && !now.remoteSource) {
603 657
             const resolution = {
604 658
                 height: now.frameHeight,
605 659
                 width: now.frameWidth
@@ -629,27 +683,9 @@ StatsCollector.prototype.processStatsReport = function() {
629 683
             // Therefore, it needs to be divided by the total number of active simulcast streams.
630 684
             let frameRate = now.framesPerSecond;
631 685
 
632
-            if (!frameRate) {
633
-                if (before) {
634
-                    const timeMs = now.timestamp - before.timestamp;
635
-
636
-                    if (timeMs > 0 && now.framesSent) {
637
-                        const numberOfFramesSinceBefore = now.framesSent - before.framesSent;
638
-
639
-                        frameRate = (numberOfFramesSinceBefore / timeMs) * 1000;
640
-                    }
641
-                }
642
-
643
-                if (!frameRate) {
644
-                    return;
645
-                }
686
+            if (!frameRate && before) {
687
+                frameRate = this._calculateFps(now, before, 'framesSent');
646 688
             }
647
-
648
-            // Get the number of simulcast streams currently enabled from TPC.
649
-            const numberOfActiveStreams = this.peerconnection.getActiveSimulcastStreams();
650
-
651
-            // Reset frame rate to 0 when video is suspended as a result of endpoint falling out of last-n.
652
-            frameRate = numberOfActiveStreams ? Math.round(frameRate / numberOfActiveStreams) : 0;
653 689
             ssrcStats.setFramerate(frameRate);
654 690
         }
655 691
     });

Завантаження…
Відмінити
Зберегти