Browse Source

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 years ago
parent
commit
c520877a03
No account linked to committer's email address

+ 39
- 0
modules/RTC/TraceablePeerConnection.js View File

680
     return audioLevels;
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
  * Obtains local tracks for given {@link MediaType}. If the <tt>mediaType</tt>
711
  * Obtains local tracks for given {@link MediaType}. If the <tt>mediaType</tt>
685
  * argument is omitted the list of all local tracks will be returned.
712
  * argument is omitted the list of all local tracks will be returned.
1237
                     groupsMap.get(primarySSRC).push(group);
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
         let ssrcs = mLine.ssrcs;
1281
         let ssrcs = mLine.ssrcs;

+ 9
- 0
modules/browser/BrowserCapabilities.js View File

219
         return !this.isFirefox();
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
      * Returns true if VP9 is supported by the client on the browser. VP9 is currently disabled on Firefox and Safari
232
      * Returns true if VP9 is supported by the client on the browser. VP9 is currently disabled on Firefox and Safari
224
      * because of issues with rendering. Please check https://bugzilla.mozilla.org/show_bug.cgi?id=1492500,
233
      * because of issues with rendering. Please check https://bugzilla.mozilla.org/show_bug.cgi?id=1492500,

+ 121
- 85
modules/statistics/RTPStatsCollector.js View File

275
         bitrateDownload += ssrcStats.bitrate.download;
275
         bitrateDownload += ssrcStats.bitrate.download;
276
         bitrateUpload += ssrcStats.bitrate.upload;
276
         bitrateUpload += ssrcStats.bitrate.upload;
277
 
277
 
278
+        ssrcStats.resetBitrate();
279
+
278
         // collect resolutions and framerates
280
         // collect resolutions and framerates
279
         const track = this.peerconnection.getTrackBySSRC(ssrc);
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
     this.conferenceStats.bitrate = {
360
     this.conferenceStats.bitrate = {
445
     return bitrateKbps;
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
  * Stats processing for spec-compliant RTCPeerConnection#getStats.
494
  * Stats processing for spec-compliant RTCPeerConnection#getStats.
450
  */
495
  */
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
             } else if (before) {
631
             } else if (before) {
579
                 byteSentStats[ssrc] = this.getNonNegativeValue(now.bytesSent);
632
                 byteSentStats[ssrc] = this.getNonNegativeValue(now.bytesSent);
580
                 ssrcStats.addBitrate({
633
                 ssrcStats.addBitrate({
596
                 codecShortType && ssrcStats.setCodec(codecShortType);
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
             const resolution = {
657
             const resolution = {
604
                 height: now.frameHeight,
658
                 height: now.frameHeight,
605
                 width: now.frameWidth
659
                 width: now.frameWidth
629
             // Therefore, it needs to be divided by the total number of active simulcast streams.
683
             // Therefore, it needs to be divided by the total number of active simulcast streams.
630
             let frameRate = now.framesPerSecond;
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
             ssrcStats.setFramerate(frameRate);
689
             ssrcStats.setFramerate(frameRate);
654
         }
690
         }
655
     });
691
     });

Loading…
Cancel
Save