blob: 4f208a1d5ef9539e2079ad03873df68039bd9aac [file] [log] [blame]
Tommi74fc5742020-04-27 08:43:061/*
Tommi553c8692020-05-05 13:35:452 * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
Tommi74fc5742020-04-27 08:43:063 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11#include "video/receive_statistics_proxy2.h"
12
13#include <algorithm>
14#include <cmath>
15#include <utility>
16
17#include "modules/video_coding/include/video_codec_interface.h"
18#include "rtc_base/checks.h"
19#include "rtc_base/logging.h"
20#include "rtc_base/strings/string_builder.h"
Tommi47173c92020-05-13 19:26:3721#include "rtc_base/thread.h"
Tommi74fc5742020-04-27 08:43:0622#include "rtc_base/time_utils.h"
23#include "system_wrappers/include/clock.h"
Tommi74fc5742020-04-27 08:43:0624#include "system_wrappers/include/metrics.h"
Tommid7e08c82020-05-10 09:24:4325#include "video/video_receive_stream2.h"
Tommi74fc5742020-04-27 08:43:0626
27namespace webrtc {
28namespace internal {
29namespace {
Artem Titovab30d722021-07-27 14:22:1130// Periodic time interval for processing samples for `freq_offset_counter_`.
Tommi74fc5742020-04-27 08:43:0631const int64_t kFreqOffsetProcessIntervalMs = 40000;
32
33// Configuration for bad call detection.
34const int kBadCallMinRequiredSamples = 10;
35const int kMinSampleLengthMs = 990;
36const int kNumMeasurements = 10;
37const int kNumMeasurementsVariance = kNumMeasurements * 1.5;
38const float kBadFraction = 0.8f;
39// For fps:
40// Low means low enough to be bad, high means high enough to be good
41const int kLowFpsThreshold = 12;
42const int kHighFpsThreshold = 14;
43// For qp and fps variance:
44// Low means low enough to be good, high means high enough to be bad
45const int kLowQpThresholdVp8 = 60;
46const int kHighQpThresholdVp8 = 70;
47const int kLowVarianceThreshold = 1;
48const int kHighVarianceThreshold = 2;
49
50// Some metrics are reported as a maximum over this period.
51// This should be synchronized with a typical getStats polling interval in
52// the clients.
53const int kMovingMaxWindowMs = 1000;
54
55// How large window we use to calculate the framerate/bitrate.
56const int kRateStatisticsWindowSizeMs = 1000;
57
58// Some sane ballpark estimate for maximum common value of inter-frame delay.
59// Values below that will be stored explicitly in the array,
60// values above - in the map.
61const int kMaxCommonInterframeDelayMs = 500;
62
63const char* UmaPrefixForContentType(VideoContentType content_type) {
64 if (videocontenttypehelpers::IsScreenshare(content_type))
65 return "WebRTC.Video.Screenshare";
66 return "WebRTC.Video";
67}
68
69std::string UmaSuffixForContentType(VideoContentType content_type) {
70 char ss_buf[1024];
71 rtc::SimpleStringBuilder ss(ss_buf);
72 int simulcast_id = videocontenttypehelpers::GetSimulcastId(content_type);
73 if (simulcast_id > 0) {
74 ss << ".S" << simulcast_id - 1;
75 }
76 int experiment_id = videocontenttypehelpers::GetExperimentId(content_type);
77 if (experiment_id > 0) {
78 ss << ".ExperimentGroup" << experiment_id - 1;
79 }
80 return ss.str();
81}
82
Tommi47173c92020-05-13 19:26:3783// TODO(https://bugs.webrtc.org/11572): Workaround for an issue with some
84// rtc::Thread instances and/or implementations that don't register as the
85// current task queue.
86bool IsCurrentTaskQueueOrThread(TaskQueueBase* task_queue) {
87 if (task_queue->IsCurrent())
88 return true;
89
90 rtc::Thread* current_thread = rtc::ThreadManager::Instance()->CurrentThread();
91 if (!current_thread)
92 return false;
93
94 return static_cast<TaskQueueBase*>(current_thread) == task_queue;
95}
96
Tommi74fc5742020-04-27 08:43:0697} // namespace
98
Johannes Kronda843fe2022-06-23 11:18:2699ReceiveStatisticsProxy::ReceiveStatisticsProxy(uint32_t remote_ssrc,
100 Clock* clock,
101 TaskQueueBase* worker_thread)
Tommi74fc5742020-04-27 08:43:06102 : clock_(clock),
Tommi74fc5742020-04-27 08:43:06103 start_ms_(clock->TimeInMilliseconds()),
Tommi74fc5742020-04-27 08:43:06104 last_sample_time_(clock->TimeInMilliseconds()),
105 fps_threshold_(kLowFpsThreshold,
106 kHighFpsThreshold,
107 kBadFraction,
108 kNumMeasurements),
109 qp_threshold_(kLowQpThresholdVp8,
110 kHighQpThresholdVp8,
111 kBadFraction,
112 kNumMeasurements),
113 variance_threshold_(kLowVarianceThreshold,
114 kHighVarianceThreshold,
115 kBadFraction,
116 kNumMeasurementsVariance),
117 num_bad_states_(0),
118 num_certain_states_(0),
Tommib2db9892021-08-23 20:44:49119 remote_ssrc_(remote_ssrc),
Tommi74fc5742020-04-27 08:43:06120 // 1000ms window, scale 1000 for ms to s.
121 decode_fps_estimator_(1000, 1000),
122 renders_fps_estimator_(1000, 1000),
123 render_fps_tracker_(100, 10u),
124 render_pixel_tracker_(100, 10u),
Tommi553c8692020-05-05 13:35:45125 video_quality_observer_(new VideoQualityObserver()),
Tommi74fc5742020-04-27 08:43:06126 interframe_delay_max_moving_(kMovingMaxWindowMs),
127 freq_offset_counter_(clock, nullptr, kFreqOffsetProcessIntervalMs),
Tommi74fc5742020-04-27 08:43:06128 last_content_type_(VideoContentType::UNSPECIFIED),
129 last_codec_type_(kVideoCodecVP8),
130 num_delayed_frames_rendered_(0),
131 sum_missed_render_deadline_ms_(0),
Tommi553c8692020-05-05 13:35:45132 timing_frame_info_counter_(kMovingMaxWindowMs),
133 worker_thread_(worker_thread) {
134 RTC_DCHECK(worker_thread);
135 decode_queue_.Detach();
136 incoming_render_queue_.Detach();
Tommib2db9892021-08-23 20:44:49137 stats_.ssrc = remote_ssrc_;
Tommi553c8692020-05-05 13:35:45138}
139
140ReceiveStatisticsProxy::~ReceiveStatisticsProxy() {
141 RTC_DCHECK_RUN_ON(&main_thread_);
Tommi74fc5742020-04-27 08:43:06142}
143
144void ReceiveStatisticsProxy::UpdateHistograms(
145 absl::optional<int> fraction_lost,
146 const StreamDataCounters& rtp_stats,
147 const StreamDataCounters* rtx_stats) {
Tommid93bf122020-05-10 18:24:59148 RTC_DCHECK_RUN_ON(&main_thread_);
Tommi74fc5742020-04-27 08:43:06149
150 char log_stream_buf[8 * 1024];
151 rtc::SimpleStringBuilder log_stream(log_stream_buf);
Tommi553c8692020-05-05 13:35:45152
Tommi74fc5742020-04-27 08:43:06153 int stream_duration_sec = (clock_->TimeInMilliseconds() - start_ms_) / 1000;
Tommi553c8692020-05-05 13:35:45154
Tommi74fc5742020-04-27 08:43:06155 if (stats_.frame_counts.key_frames > 0 ||
156 stats_.frame_counts.delta_frames > 0) {
157 RTC_HISTOGRAM_COUNTS_100000("WebRTC.Video.ReceiveStreamLifetimeInSeconds",
158 stream_duration_sec);
159 log_stream << "WebRTC.Video.ReceiveStreamLifetimeInSeconds "
160 << stream_duration_sec << '\n';
161 }
162
163 log_stream << "Frames decoded " << stats_.frames_decoded << '\n';
164
165 if (num_unique_frames_) {
166 int num_dropped_frames = *num_unique_frames_ - stats_.frames_decoded;
167 RTC_HISTOGRAM_COUNTS_1000("WebRTC.Video.DroppedFrames.Receiver",
168 num_dropped_frames);
169 log_stream << "WebRTC.Video.DroppedFrames.Receiver " << num_dropped_frames
170 << '\n';
171 }
172
173 if (fraction_lost && stream_duration_sec >= metrics::kMinRunTimeInSeconds) {
174 RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.ReceivedPacketsLostInPercent",
175 *fraction_lost);
176 log_stream << "WebRTC.Video.ReceivedPacketsLostInPercent " << *fraction_lost
177 << '\n';
178 }
179
180 if (first_decoded_frame_time_ms_) {
181 const int64_t elapsed_ms =
182 (clock_->TimeInMilliseconds() - *first_decoded_frame_time_ms_);
183 if (elapsed_ms >=
184 metrics::kMinRunTimeInSeconds * rtc::kNumMillisecsPerSec) {
185 int decoded_fps = static_cast<int>(
186 (stats_.frames_decoded * 1000.0f / elapsed_ms) + 0.5f);
187 RTC_HISTOGRAM_COUNTS_100("WebRTC.Video.DecodedFramesPerSecond",
188 decoded_fps);
189 log_stream << "WebRTC.Video.DecodedFramesPerSecond " << decoded_fps
190 << '\n';
191
192 const uint32_t frames_rendered = stats_.frames_rendered;
193 if (frames_rendered > 0) {
194 RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.DelayedFramesToRenderer",
195 static_cast<int>(num_delayed_frames_rendered_ *
196 100 / frames_rendered));
197 if (num_delayed_frames_rendered_ > 0) {
198 RTC_HISTOGRAM_COUNTS_1000(
199 "WebRTC.Video.DelayedFramesToRenderer_AvgDelayInMs",
200 static_cast<int>(sum_missed_render_deadline_ms_ /
201 num_delayed_frames_rendered_));
202 }
203 }
204 }
205 }
206
207 const int kMinRequiredSamples = 200;
208 int samples = static_cast<int>(render_fps_tracker_.TotalSampleCount());
209 if (samples >= kMinRequiredSamples) {
210 int rendered_fps = round(render_fps_tracker_.ComputeTotalRate());
211 RTC_HISTOGRAM_COUNTS_100("WebRTC.Video.RenderFramesPerSecond",
212 rendered_fps);
213 log_stream << "WebRTC.Video.RenderFramesPerSecond " << rendered_fps << '\n';
214 RTC_HISTOGRAM_COUNTS_100000(
215 "WebRTC.Video.RenderSqrtPixelsPerSecond",
216 round(render_pixel_tracker_.ComputeTotalRate()));
217 }
218
219 absl::optional<int> sync_offset_ms =
220 sync_offset_counter_.Avg(kMinRequiredSamples);
221 if (sync_offset_ms) {
222 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.AVSyncOffsetInMs",
223 *sync_offset_ms);
224 log_stream << "WebRTC.Video.AVSyncOffsetInMs " << *sync_offset_ms << '\n';
225 }
226 AggregatedStats freq_offset_stats = freq_offset_counter_.GetStats();
227 if (freq_offset_stats.num_samples > 0) {
228 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.RtpToNtpFreqOffsetInKhz",
229 freq_offset_stats.average);
230 log_stream << "WebRTC.Video.RtpToNtpFreqOffsetInKhz "
231 << freq_offset_stats.ToString() << '\n';
232 }
233
234 int num_total_frames =
235 stats_.frame_counts.key_frames + stats_.frame_counts.delta_frames;
236 if (num_total_frames >= kMinRequiredSamples) {
237 int num_key_frames = stats_.frame_counts.key_frames;
238 int key_frames_permille =
239 (num_key_frames * 1000 + num_total_frames / 2) / num_total_frames;
240 RTC_HISTOGRAM_COUNTS_1000("WebRTC.Video.KeyFramesReceivedInPermille",
241 key_frames_permille);
242 log_stream << "WebRTC.Video.KeyFramesReceivedInPermille "
243 << key_frames_permille << '\n';
244 }
245
Tommi674b0c812020-05-10 19:52:17246 absl::optional<int> qp = qp_counters_.vp8.Avg(kMinRequiredSamples);
247 if (qp) {
248 RTC_HISTOGRAM_COUNTS_200("WebRTC.Video.Decoded.Vp8.Qp", *qp);
249 log_stream << "WebRTC.Video.Decoded.Vp8.Qp " << *qp << '\n';
Tommi74fc5742020-04-27 08:43:06250 }
Tommi674b0c812020-05-10 19:52:17251
Tommi74fc5742020-04-27 08:43:06252 absl::optional<int> decode_ms = decode_time_counter_.Avg(kMinRequiredSamples);
253 if (decode_ms) {
254 RTC_HISTOGRAM_COUNTS_1000("WebRTC.Video.DecodeTimeInMs", *decode_ms);
255 log_stream << "WebRTC.Video.DecodeTimeInMs " << *decode_ms << '\n';
256 }
257 absl::optional<int> jb_delay_ms =
258 jitter_buffer_delay_counter_.Avg(kMinRequiredSamples);
259 if (jb_delay_ms) {
260 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.JitterBufferDelayInMs",
261 *jb_delay_ms);
262 log_stream << "WebRTC.Video.JitterBufferDelayInMs " << *jb_delay_ms << '\n';
263 }
264
265 absl::optional<int> target_delay_ms =
266 target_delay_counter_.Avg(kMinRequiredSamples);
267 if (target_delay_ms) {
268 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.TargetDelayInMs",
269 *target_delay_ms);
270 log_stream << "WebRTC.Video.TargetDelayInMs " << *target_delay_ms << '\n';
271 }
272 absl::optional<int> current_delay_ms =
273 current_delay_counter_.Avg(kMinRequiredSamples);
274 if (current_delay_ms) {
275 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.CurrentDelayInMs",
276 *current_delay_ms);
277 log_stream << "WebRTC.Video.CurrentDelayInMs " << *current_delay_ms << '\n';
278 }
279 absl::optional<int> delay_ms = delay_counter_.Avg(kMinRequiredSamples);
280 if (delay_ms)
281 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.OnewayDelayInMs", *delay_ms);
282
283 // Aggregate content_specific_stats_ by removing experiment or simulcast
284 // information;
285 std::map<VideoContentType, ContentSpecificStats> aggregated_stats;
286 for (const auto& it : content_specific_stats_) {
287 // Calculate simulcast specific metrics (".S0" ... ".S2" suffixes).
288 VideoContentType content_type = it.first;
289 if (videocontenttypehelpers::GetSimulcastId(content_type) > 0) {
290 // Aggregate on experiment id.
291 videocontenttypehelpers::SetExperimentId(&content_type, 0);
292 aggregated_stats[content_type].Add(it.second);
293 }
294 // Calculate experiment specific metrics (".ExperimentGroup[0-7]" suffixes).
295 content_type = it.first;
296 if (videocontenttypehelpers::GetExperimentId(content_type) > 0) {
297 // Aggregate on simulcast id.
298 videocontenttypehelpers::SetSimulcastId(&content_type, 0);
299 aggregated_stats[content_type].Add(it.second);
300 }
301 // Calculate aggregated metrics (no suffixes. Aggregated on everything).
302 content_type = it.first;
303 videocontenttypehelpers::SetSimulcastId(&content_type, 0);
304 videocontenttypehelpers::SetExperimentId(&content_type, 0);
305 aggregated_stats[content_type].Add(it.second);
306 }
307
308 for (const auto& it : aggregated_stats) {
309 // For the metric Foo we report the following slices:
310 // WebRTC.Video.Foo,
311 // WebRTC.Video.Screenshare.Foo,
312 // WebRTC.Video.Foo.S[0-3],
313 // WebRTC.Video.Foo.ExperimentGroup[0-7],
314 // WebRTC.Video.Screenshare.Foo.S[0-3],
315 // WebRTC.Video.Screenshare.Foo.ExperimentGroup[0-7].
316 auto content_type = it.first;
317 auto stats = it.second;
318 std::string uma_prefix = UmaPrefixForContentType(content_type);
319 std::string uma_suffix = UmaSuffixForContentType(content_type);
320 // Metrics can be sliced on either simulcast id or experiment id but not
321 // both.
322 RTC_DCHECK(videocontenttypehelpers::GetExperimentId(content_type) == 0 ||
323 videocontenttypehelpers::GetSimulcastId(content_type) == 0);
324
325 absl::optional<int> e2e_delay_ms =
326 stats.e2e_delay_counter.Avg(kMinRequiredSamples);
327 if (e2e_delay_ms) {
328 RTC_HISTOGRAM_COUNTS_SPARSE_10000(
329 uma_prefix + ".EndToEndDelayInMs" + uma_suffix, *e2e_delay_ms);
330 log_stream << uma_prefix << ".EndToEndDelayInMs" << uma_suffix << " "
331 << *e2e_delay_ms << '\n';
332 }
333 absl::optional<int> e2e_delay_max_ms = stats.e2e_delay_counter.Max();
334 if (e2e_delay_max_ms && e2e_delay_ms) {
335 RTC_HISTOGRAM_COUNTS_SPARSE_100000(
336 uma_prefix + ".EndToEndDelayMaxInMs" + uma_suffix, *e2e_delay_max_ms);
337 log_stream << uma_prefix << ".EndToEndDelayMaxInMs" << uma_suffix << " "
338 << *e2e_delay_max_ms << '\n';
339 }
340 absl::optional<int> interframe_delay_ms =
341 stats.interframe_delay_counter.Avg(kMinRequiredSamples);
342 if (interframe_delay_ms) {
343 RTC_HISTOGRAM_COUNTS_SPARSE_10000(
344 uma_prefix + ".InterframeDelayInMs" + uma_suffix,
345 *interframe_delay_ms);
346 log_stream << uma_prefix << ".InterframeDelayInMs" << uma_suffix << " "
347 << *interframe_delay_ms << '\n';
348 }
349 absl::optional<int> interframe_delay_max_ms =
350 stats.interframe_delay_counter.Max();
351 if (interframe_delay_max_ms && interframe_delay_ms) {
352 RTC_HISTOGRAM_COUNTS_SPARSE_10000(
353 uma_prefix + ".InterframeDelayMaxInMs" + uma_suffix,
354 *interframe_delay_max_ms);
355 log_stream << uma_prefix << ".InterframeDelayMaxInMs" << uma_suffix << " "
356 << *interframe_delay_max_ms << '\n';
357 }
358
359 absl::optional<uint32_t> interframe_delay_95p_ms =
360 stats.interframe_delay_percentiles.GetPercentile(0.95f);
361 if (interframe_delay_95p_ms && interframe_delay_ms != -1) {
362 RTC_HISTOGRAM_COUNTS_SPARSE_10000(
363 uma_prefix + ".InterframeDelay95PercentileInMs" + uma_suffix,
364 *interframe_delay_95p_ms);
365 log_stream << uma_prefix << ".InterframeDelay95PercentileInMs"
366 << uma_suffix << " " << *interframe_delay_95p_ms << '\n';
367 }
368
369 absl::optional<int> width = stats.received_width.Avg(kMinRequiredSamples);
370 if (width) {
371 RTC_HISTOGRAM_COUNTS_SPARSE_10000(
372 uma_prefix + ".ReceivedWidthInPixels" + uma_suffix, *width);
373 log_stream << uma_prefix << ".ReceivedWidthInPixels" << uma_suffix << " "
374 << *width << '\n';
375 }
376
377 absl::optional<int> height = stats.received_height.Avg(kMinRequiredSamples);
378 if (height) {
379 RTC_HISTOGRAM_COUNTS_SPARSE_10000(
380 uma_prefix + ".ReceivedHeightInPixels" + uma_suffix, *height);
381 log_stream << uma_prefix << ".ReceivedHeightInPixels" << uma_suffix << " "
382 << *height << '\n';
383 }
384
385 if (content_type != VideoContentType::UNSPECIFIED) {
386 // Don't report these 3 metrics unsliced, as more precise variants
387 // are reported separately in this method.
388 float flow_duration_sec = stats.flow_duration_ms / 1000.0;
389 if (flow_duration_sec >= metrics::kMinRunTimeInSeconds) {
390 int media_bitrate_kbps = static_cast<int>(stats.total_media_bytes * 8 /
391 flow_duration_sec / 1000);
392 RTC_HISTOGRAM_COUNTS_SPARSE_10000(
393 uma_prefix + ".MediaBitrateReceivedInKbps" + uma_suffix,
394 media_bitrate_kbps);
395 log_stream << uma_prefix << ".MediaBitrateReceivedInKbps" << uma_suffix
396 << " " << media_bitrate_kbps << '\n';
397 }
398
399 int num_total_frames =
400 stats.frame_counts.key_frames + stats.frame_counts.delta_frames;
401 if (num_total_frames >= kMinRequiredSamples) {
402 int num_key_frames = stats.frame_counts.key_frames;
403 int key_frames_permille =
404 (num_key_frames * 1000 + num_total_frames / 2) / num_total_frames;
405 RTC_HISTOGRAM_COUNTS_SPARSE_1000(
406 uma_prefix + ".KeyFramesReceivedInPermille" + uma_suffix,
407 key_frames_permille);
408 log_stream << uma_prefix << ".KeyFramesReceivedInPermille" << uma_suffix
409 << " " << key_frames_permille << '\n';
410 }
411
412 absl::optional<int> qp = stats.qp_counter.Avg(kMinRequiredSamples);
413 if (qp) {
414 RTC_HISTOGRAM_COUNTS_SPARSE_200(
415 uma_prefix + ".Decoded.Vp8.Qp" + uma_suffix, *qp);
416 log_stream << uma_prefix << ".Decoded.Vp8.Qp" << uma_suffix << " "
417 << *qp << '\n';
418 }
419 }
420 }
421
422 StreamDataCounters rtp_rtx_stats = rtp_stats;
423 if (rtx_stats)
424 rtp_rtx_stats.Add(*rtx_stats);
Tommi553c8692020-05-05 13:35:45425
Tommi74fc5742020-04-27 08:43:06426 int64_t elapsed_sec =
427 rtp_rtx_stats.TimeSinceFirstPacketInMs(clock_->TimeInMilliseconds()) /
428 1000;
429 if (elapsed_sec >= metrics::kMinRunTimeInSeconds) {
430 RTC_HISTOGRAM_COUNTS_10000(
431 "WebRTC.Video.BitrateReceivedInKbps",
432 static_cast<int>(rtp_rtx_stats.transmitted.TotalBytes() * 8 /
433 elapsed_sec / 1000));
434 int media_bitrate_kbs = static_cast<int>(rtp_stats.MediaPayloadBytes() * 8 /
435 elapsed_sec / 1000);
436 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.MediaBitrateReceivedInKbps",
437 media_bitrate_kbs);
438 log_stream << "WebRTC.Video.MediaBitrateReceivedInKbps "
439 << media_bitrate_kbs << '\n';
440 RTC_HISTOGRAM_COUNTS_10000(
441 "WebRTC.Video.PaddingBitrateReceivedInKbps",
442 static_cast<int>(rtp_rtx_stats.transmitted.padding_bytes * 8 /
443 elapsed_sec / 1000));
444 RTC_HISTOGRAM_COUNTS_10000(
445 "WebRTC.Video.RetransmittedBitrateReceivedInKbps",
446 static_cast<int>(rtp_rtx_stats.retransmitted.TotalBytes() * 8 /
447 elapsed_sec / 1000));
448 if (rtx_stats) {
449 RTC_HISTOGRAM_COUNTS_10000(
450 "WebRTC.Video.RtxBitrateReceivedInKbps",
451 static_cast<int>(rtx_stats->transmitted.TotalBytes() * 8 /
452 elapsed_sec / 1000));
453 }
454 const RtcpPacketTypeCounter& counters = stats_.rtcp_packet_type_counts;
455 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.NackPacketsSentPerMinute",
456 counters.nack_packets * 60 / elapsed_sec);
457 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.FirPacketsSentPerMinute",
458 counters.fir_packets * 60 / elapsed_sec);
459 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.PliPacketsSentPerMinute",
460 counters.pli_packets * 60 / elapsed_sec);
461 if (counters.nack_requests > 0) {
462 RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.UniqueNackRequestsSentInPercent",
463 counters.UniqueNackRequestsInPercent());
464 }
465 }
466
467 if (num_certain_states_ >= kBadCallMinRequiredSamples) {
468 RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.BadCall.Any",
469 100 * num_bad_states_ / num_certain_states_);
470 }
471 absl::optional<double> fps_fraction =
472 fps_threshold_.FractionHigh(kBadCallMinRequiredSamples);
473 if (fps_fraction) {
474 RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.BadCall.FrameRate",
475 static_cast<int>(100 * (1 - *fps_fraction)));
476 }
477 absl::optional<double> variance_fraction =
478 variance_threshold_.FractionHigh(kBadCallMinRequiredSamples);
479 if (variance_fraction) {
480 RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.BadCall.FrameRateVariance",
481 static_cast<int>(100 * *variance_fraction));
482 }
483 absl::optional<double> qp_fraction =
484 qp_threshold_.FractionHigh(kBadCallMinRequiredSamples);
485 if (qp_fraction) {
486 RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.BadCall.Qp",
487 static_cast<int>(100 * *qp_fraction));
488 }
489
490 RTC_LOG(LS_INFO) << log_stream.str();
Tommi553c8692020-05-05 13:35:45491 video_quality_observer_->UpdateHistograms(
492 videocontenttypehelpers::IsScreenshare(last_content_type_));
Tommi74fc5742020-04-27 08:43:06493}
494
Tommid7e08c82020-05-10 09:24:43495void ReceiveStatisticsProxy::QualitySample(Timestamp now) {
496 RTC_DCHECK_RUN_ON(&main_thread_);
Tommi553c8692020-05-05 13:35:45497
Tommid7e08c82020-05-10 09:24:43498 if (last_sample_time_ + kMinSampleLengthMs > now.ms())
Tommi74fc5742020-04-27 08:43:06499 return;
500
501 double fps =
Tommid7e08c82020-05-10 09:24:43502 render_fps_tracker_.ComputeRateForInterval(now.ms() - last_sample_time_);
Tommi74fc5742020-04-27 08:43:06503 absl::optional<int> qp = qp_sample_.Avg(1);
504
505 bool prev_fps_bad = !fps_threshold_.IsHigh().value_or(true);
506 bool prev_qp_bad = qp_threshold_.IsHigh().value_or(false);
507 bool prev_variance_bad = variance_threshold_.IsHigh().value_or(false);
508 bool prev_any_bad = prev_fps_bad || prev_qp_bad || prev_variance_bad;
509
510 fps_threshold_.AddMeasurement(static_cast<int>(fps));
511 if (qp)
512 qp_threshold_.AddMeasurement(*qp);
513 absl::optional<double> fps_variance_opt = fps_threshold_.CalculateVariance();
514 double fps_variance = fps_variance_opt.value_or(0);
515 if (fps_variance_opt) {
516 variance_threshold_.AddMeasurement(static_cast<int>(fps_variance));
517 }
518
519 bool fps_bad = !fps_threshold_.IsHigh().value_or(true);
520 bool qp_bad = qp_threshold_.IsHigh().value_or(false);
521 bool variance_bad = variance_threshold_.IsHigh().value_or(false);
522 bool any_bad = fps_bad || qp_bad || variance_bad;
523
524 if (!prev_any_bad && any_bad) {
Tommid7e08c82020-05-10 09:24:43525 RTC_LOG(LS_INFO) << "Bad call (any) start: " << now.ms();
Tommi74fc5742020-04-27 08:43:06526 } else if (prev_any_bad && !any_bad) {
Tommid7e08c82020-05-10 09:24:43527 RTC_LOG(LS_INFO) << "Bad call (any) end: " << now.ms();
Tommi74fc5742020-04-27 08:43:06528 }
529
530 if (!prev_fps_bad && fps_bad) {
Tommid7e08c82020-05-10 09:24:43531 RTC_LOG(LS_INFO) << "Bad call (fps) start: " << now.ms();
Tommi74fc5742020-04-27 08:43:06532 } else if (prev_fps_bad && !fps_bad) {
Tommid7e08c82020-05-10 09:24:43533 RTC_LOG(LS_INFO) << "Bad call (fps) end: " << now.ms();
Tommi74fc5742020-04-27 08:43:06534 }
535
536 if (!prev_qp_bad && qp_bad) {
Tommid7e08c82020-05-10 09:24:43537 RTC_LOG(LS_INFO) << "Bad call (qp) start: " << now.ms();
Tommi74fc5742020-04-27 08:43:06538 } else if (prev_qp_bad && !qp_bad) {
Tommid7e08c82020-05-10 09:24:43539 RTC_LOG(LS_INFO) << "Bad call (qp) end: " << now.ms();
Tommi74fc5742020-04-27 08:43:06540 }
541
542 if (!prev_variance_bad && variance_bad) {
Tommid7e08c82020-05-10 09:24:43543 RTC_LOG(LS_INFO) << "Bad call (variance) start: " << now.ms();
Tommi74fc5742020-04-27 08:43:06544 } else if (prev_variance_bad && !variance_bad) {
Tommid7e08c82020-05-10 09:24:43545 RTC_LOG(LS_INFO) << "Bad call (variance) end: " << now.ms();
Tommi74fc5742020-04-27 08:43:06546 }
547
Tommid7e08c82020-05-10 09:24:43548 RTC_LOG(LS_VERBOSE) << "SAMPLE: sample_length: "
549 << (now.ms() - last_sample_time_) << " fps: " << fps
550 << " fps_bad: " << fps_bad << " qp: " << qp.value_or(-1)
551 << " qp_bad: " << qp_bad
Tommi74fc5742020-04-27 08:43:06552 << " variance_bad: " << variance_bad
553 << " fps_variance: " << fps_variance;
554
Tommid7e08c82020-05-10 09:24:43555 last_sample_time_ = now.ms();
Tommi74fc5742020-04-27 08:43:06556 qp_sample_.Reset();
557
558 if (fps_threshold_.IsHigh() || variance_threshold_.IsHigh() ||
559 qp_threshold_.IsHigh()) {
560 if (any_bad)
561 ++num_bad_states_;
562 ++num_certain_states_;
563 }
564}
565
566void ReceiveStatisticsProxy::UpdateFramerate(int64_t now_ms) const {
Tommid93bf122020-05-10 18:24:59567 RTC_DCHECK_RUN_ON(&main_thread_);
568
Tommi74fc5742020-04-27 08:43:06569 int64_t old_frames_ms = now_ms - kRateStatisticsWindowSizeMs;
570 while (!frame_window_.empty() &&
571 frame_window_.begin()->first < old_frames_ms) {
572 frame_window_.erase(frame_window_.begin());
573 }
574
575 size_t framerate =
576 (frame_window_.size() * 1000 + 500) / kRateStatisticsWindowSizeMs;
Tommid93bf122020-05-10 18:24:59577
Tommi74fc5742020-04-27 08:43:06578 stats_.network_frame_rate = static_cast<int>(framerate);
579}
580
Tommi74fc5742020-04-27 08:43:06581absl::optional<int64_t>
582ReceiveStatisticsProxy::GetCurrentEstimatedPlayoutNtpTimestampMs(
583 int64_t now_ms) const {
Tommi553c8692020-05-05 13:35:45584 RTC_DCHECK_RUN_ON(&main_thread_);
Tommi74fc5742020-04-27 08:43:06585 if (!last_estimated_playout_ntp_timestamp_ms_ ||
586 !last_estimated_playout_time_ms_) {
587 return absl::nullopt;
588 }
589 int64_t elapsed_ms = now_ms - *last_estimated_playout_time_ms_;
590 return *last_estimated_playout_ntp_timestamp_ms_ + elapsed_ms;
591}
592
Tommif6f45432022-05-20 13:21:20593VideoReceiveStreamInterface::Stats ReceiveStatisticsProxy::GetStats() const {
Tommi553c8692020-05-05 13:35:45594 RTC_DCHECK_RUN_ON(&main_thread_);
595
Tommif6f45432022-05-20 13:21:20596 // Like VideoReceiveStreamInterface::GetStats, called on the worker thread
Danil Chapovalov9e09a1f2022-09-08 16:38:10597 // from StatsCollector::ExtractMediaInfo via worker_thread()->BlockingCall().
Tommi553c8692020-05-05 13:35:45598 // WebRtcVideoChannel::GetStats(), GetVideoReceiverInfo.
599
Tommi74fc5742020-04-27 08:43:06600 // Get current frame rates here, as only updating them on new frames prevents
601 // us from ever correctly displaying frame rate of 0.
602 int64_t now_ms = clock_->TimeInMilliseconds();
603 UpdateFramerate(now_ms);
Tommid93bf122020-05-10 18:24:59604
Tommi74fc5742020-04-27 08:43:06605 stats_.render_frame_rate = renders_fps_estimator_.Rate(now_ms).value_or(0);
606 stats_.decode_frame_rate = decode_fps_estimator_.Rate(now_ms).value_or(0);
Tommid93bf122020-05-10 18:24:59607
608 if (last_decoded_frame_time_ms_) {
609 // Avoid using a newer timestamp than might be pending for decoded frames.
610 // If we do use now_ms, we might roll the max window to a value that is
611 // higher than that of a decoded frame timestamp that we haven't yet
612 // captured the data for (i.e. pending call to OnDecodedFrame).
613 stats_.interframe_delay_max_ms =
614 interframe_delay_max_moving_.Max(*last_decoded_frame_time_ms_)
615 .value_or(-1);
616 } else {
Artem Titovab30d722021-07-27 14:22:11617 // We're paused. Avoid changing the state of `interframe_delay_max_moving_`.
Tommid93bf122020-05-10 18:24:59618 stats_.interframe_delay_max_ms = -1;
619 }
620
Tommi74fc5742020-04-27 08:43:06621 stats_.freeze_count = video_quality_observer_->NumFreezes();
622 stats_.pause_count = video_quality_observer_->NumPauses();
623 stats_.total_freezes_duration_ms =
624 video_quality_observer_->TotalFreezesDurationMs();
625 stats_.total_pauses_duration_ms =
626 video_quality_observer_->TotalPausesDurationMs();
Sergey Silkined0dd8e2022-12-20 12:58:15627 stats_.total_inter_frame_delay =
628 static_cast<double>(video_quality_observer_->TotalFramesDurationMs()) /
629 rtc::kNumMillisecsPerSec;
630 stats_.total_squared_inter_frame_delay =
Tommi74fc5742020-04-27 08:43:06631 video_quality_observer_->SumSquaredFrameDurationsSec();
Sergey Silkined0dd8e2022-12-20 12:58:15632
Tommi74fc5742020-04-27 08:43:06633 stats_.content_type = last_content_type_;
634 stats_.timing_frame_info = timing_frame_info_counter_.Max(now_ms);
635 stats_.jitter_buffer_delay_seconds =
636 static_cast<double>(current_delay_counter_.Sum(1).value_or(0)) /
637 rtc::kNumMillisecsPerSec;
638 stats_.jitter_buffer_emitted_count = current_delay_counter_.NumSamples();
639 stats_.estimated_playout_ntp_timestamp_ms =
640 GetCurrentEstimatedPlayoutNtpTimestampMs(now_ms);
641 return stats_;
642}
643
644void ReceiveStatisticsProxy::OnIncomingPayloadType(int payload_type) {
Tommi553c8692020-05-05 13:35:45645 RTC_DCHECK_RUN_ON(&decode_queue_);
Danil Chapovalov95eeaa72022-07-06 08:14:29646 worker_thread_->PostTask(SafeTask(task_safety_.flag(), [payload_type, this] {
Tommia98cea82020-05-13 13:06:19647 RTC_DCHECK_RUN_ON(&main_thread_);
648 stats_.current_payload_type = payload_type;
649 }));
Tommi74fc5742020-04-27 08:43:06650}
651
Evan Shrubsole09da10e2022-10-14 14:38:31652void ReceiveStatisticsProxy::OnDecoderInfo(
653 const VideoDecoder::DecoderInfo& decoder_info) {
Tommi553c8692020-05-05 13:35:45654 RTC_DCHECK_RUN_ON(&decode_queue_);
Danil Chapovalov95eeaa72022-07-06 08:14:29655 worker_thread_->PostTask(SafeTask(
Evan Shrubsole09da10e2022-10-14 14:38:31656 task_safety_.flag(),
657 [this, name = decoder_info.implementation_name,
658 is_hardware_accelerated = decoder_info.is_hardware_accelerated]() {
Tommi674b0c812020-05-10 19:52:17659 RTC_DCHECK_RUN_ON(&main_thread_);
660 stats_.decoder_implementation_name = name;
Evan Shrubsole09da10e2022-10-14 14:38:31661 stats_.power_efficient_decoder = is_hardware_accelerated;
Tommi674b0c812020-05-10 19:52:17662 }));
Tommi74fc5742020-04-27 08:43:06663}
664
665void ReceiveStatisticsProxy::OnFrameBufferTimingsUpdated(
666 int max_decode_ms,
667 int current_delay_ms,
668 int target_delay_ms,
669 int jitter_buffer_ms,
670 int min_playout_delay_ms,
671 int render_delay_ms) {
Evan Shrubsole9a999052021-12-12 14:27:00672 RTC_DCHECK_RUN_ON(&main_thread_);
673 stats_.max_decode_ms = max_decode_ms;
674 stats_.current_delay_ms = current_delay_ms;
675 stats_.target_delay_ms = target_delay_ms;
676 stats_.jitter_buffer_ms = jitter_buffer_ms;
677 stats_.min_playout_delay_ms = min_playout_delay_ms;
678 stats_.render_delay_ms = render_delay_ms;
679 jitter_buffer_delay_counter_.Add(jitter_buffer_ms);
680 target_delay_counter_.Add(target_delay_ms);
681 current_delay_counter_.Add(current_delay_ms);
682 // Network delay (rtt/2) + target_delay_ms (jitter delay + decode time +
683 // render delay).
684 delay_counter_.Add(target_delay_ms + avg_rtt_ms_ / 2);
Tommi74fc5742020-04-27 08:43:06685}
686
687void ReceiveStatisticsProxy::OnUniqueFramesCounted(int num_unique_frames) {
Tommi553c8692020-05-05 13:35:45688 RTC_DCHECK_RUN_ON(&main_thread_);
Tommi74fc5742020-04-27 08:43:06689 num_unique_frames_.emplace(num_unique_frames);
690}
691
692void ReceiveStatisticsProxy::OnTimingFrameInfoUpdated(
693 const TimingFrameInfo& info) {
Evan Shrubsole9a999052021-12-12 14:27:00694 RTC_DCHECK_RUN_ON(&main_thread_);
695 if (info.flags != VideoSendTiming::kInvalid) {
696 int64_t now_ms = clock_->TimeInMilliseconds();
697 timing_frame_info_counter_.Add(info, now_ms);
698 }
Tommi74fc5742020-04-27 08:43:06699
Evan Shrubsole9a999052021-12-12 14:27:00700 // Measure initial decoding latency between the first frame arriving and
701 // the first frame being decoded.
702 if (!first_frame_received_time_ms_.has_value()) {
703 first_frame_received_time_ms_ = info.receive_finish_ms;
704 }
705 if (stats_.first_frame_received_to_decoded_ms == -1 &&
706 first_decoded_frame_time_ms_) {
707 stats_.first_frame_received_to_decoded_ms =
708 *first_decoded_frame_time_ms_ - *first_frame_received_time_ms_;
709 }
Tommi74fc5742020-04-27 08:43:06710}
711
712void ReceiveStatisticsProxy::RtcpPacketTypesCounterUpdated(
713 uint32_t ssrc,
714 const RtcpPacketTypeCounter& packet_counter) {
Tommi553c8692020-05-05 13:35:45715 if (ssrc != remote_ssrc_)
Tommi74fc5742020-04-27 08:43:06716 return;
Tommi553c8692020-05-05 13:35:45717
Tommi47173c92020-05-13 19:26:37718 if (!IsCurrentTaskQueueOrThread(worker_thread_)) {
Tomas Gunnarssonf25761d2020-06-03 20:55:33719 // RtpRtcpInterface::Configuration has a single
720 // RtcpPacketTypeCounterObserver and that same configuration may be used for
721 // both receiver and sender (see ModuleRtpRtcpImpl::ModuleRtpRtcpImpl). The
722 // RTCPSender implementation currently makes calls to this function on a
Tommi553c8692020-05-05 13:35:45723 // process thread whereas the RTCPReceiver implementation calls back on the
724 // [main] worker thread.
725 // So until the sender implementation has been updated, we work around this
726 // here by posting the update to the expected thread. We make a by value
Artem Titovab30d722021-07-27 14:22:11727 // copy of the `task_safety_` to handle the case if the queued task
728 // runs after the `ReceiveStatisticsProxy` has been deleted. In such a
Tommi553c8692020-05-05 13:35:45729 // case the packet_counter update won't be recorded.
730 worker_thread_->PostTask(
Danil Chapovalov95eeaa72022-07-06 08:14:29731 SafeTask(task_safety_.flag(), [ssrc, packet_counter, this]() {
Tommi553c8692020-05-05 13:35:45732 RtcpPacketTypesCounterUpdated(ssrc, packet_counter);
733 }));
734 return;
735 }
736
737 RTC_DCHECK_RUN_ON(&main_thread_);
Tommi74fc5742020-04-27 08:43:06738 stats_.rtcp_packet_type_counts = packet_counter;
739}
740
741void ReceiveStatisticsProxy::OnCname(uint32_t ssrc, absl::string_view cname) {
Tommi553c8692020-05-05 13:35:45742 RTC_DCHECK_RUN_ON(&main_thread_);
Tommi74fc5742020-04-27 08:43:06743 // TODO(pbos): Handle both local and remote ssrcs here and RTC_DCHECK that we
744 // receive stats from one of them.
Tommi553c8692020-05-05 13:35:45745 if (remote_ssrc_ != ssrc)
Tommi74fc5742020-04-27 08:43:06746 return;
Tommi553c8692020-05-05 13:35:45747
Tommi74fc5742020-04-27 08:43:06748 stats_.c_name = std::string(cname);
749}
750
751void ReceiveStatisticsProxy::OnDecodedFrame(const VideoFrame& frame,
752 absl::optional<uint8_t> qp,
Philipp Hancked970b092022-06-17 05:34:23753 TimeDelta decode_time,
Tommi74fc5742020-04-27 08:43:06754 VideoContentType content_type) {
Philipp Hanckea204ad22022-07-08 16:43:25755 TimeDelta processing_delay = TimeDelta::Zero();
Philipp Hanckea16a6a62022-04-25 10:21:30756 webrtc::Timestamp current_time = clock_->CurrentTime();
757 // TODO(bugs.webrtc.org/13984): some tests do not fill packet_infos().
Philipp Hanckea204ad22022-07-08 16:43:25758 TimeDelta assembly_time = TimeDelta::Zero();
Philipp Hanckea16a6a62022-04-25 10:21:30759 if (frame.packet_infos().size() > 0) {
Philipp Hancke0359ba22022-05-05 13:55:36760 const auto [first_packet, last_packet] = std::minmax_element(
Philipp Hanckea16a6a62022-04-25 10:21:30761 frame.packet_infos().cbegin(), frame.packet_infos().cend(),
762 [](const webrtc::RtpPacketInfo& a, const webrtc::RtpPacketInfo& b) {
763 return a.receive_time() < b.receive_time();
764 });
Philipp Hancke0359ba22022-05-05 13:55:36765 if (first_packet->receive_time().IsFinite()) {
766 processing_delay = current_time - first_packet->receive_time();
767 // Extract frame assembly time (i.e. time between earliest and latest
768 // packet arrival). Note: for single-packet frames this will be 0.
769 assembly_time =
770 last_packet->receive_time() - first_packet->receive_time();
771 }
Philipp Hanckea16a6a62022-04-25 10:21:30772 }
Tommid93bf122020-05-10 18:24:59773 // See VCMDecodedFrameCallback::Decoded for more info on what thread/queue we
774 // may be on. E.g. on iOS this gets called on
Tommi553c8692020-05-05 13:35:45775 // "com.apple.coremedia.decompressionsession.clientcallback"
Philipp Hanckea16a6a62022-04-25 10:21:30776 VideoFrameMetaData meta(frame, current_time);
Philipp Hancke0359ba22022-05-05 13:55:36777 worker_thread_->PostTask(
Danil Chapovalov95eeaa72022-07-06 08:14:29778 SafeTask(task_safety_.flag(), [meta, qp, decode_time, processing_delay,
779 assembly_time, content_type, this]() {
Philipp Hancked970b092022-06-17 05:34:23780 OnDecodedFrame(meta, qp, decode_time, processing_delay, assembly_time,
781 content_type);
Philipp Hancke0359ba22022-05-05 13:55:36782 }));
Tommid93bf122020-05-10 18:24:59783}
Tommi553c8692020-05-05 13:35:45784
Tommid93bf122020-05-10 18:24:59785void ReceiveStatisticsProxy::OnDecodedFrame(
786 const VideoFrameMetaData& frame_meta,
787 absl::optional<uint8_t> qp,
Philipp Hancked970b092022-06-17 05:34:23788 TimeDelta decode_time,
Philipp Hanckea204ad22022-07-08 16:43:25789 TimeDelta processing_delay,
790 TimeDelta assembly_time,
Tommid93bf122020-05-10 18:24:59791 VideoContentType content_type) {
792 RTC_DCHECK_RUN_ON(&main_thread_);
Tommi74fc5742020-04-27 08:43:06793
Tommi553c8692020-05-05 13:35:45794 const bool is_screenshare =
795 videocontenttypehelpers::IsScreenshare(content_type);
796 const bool was_screenshare =
797 videocontenttypehelpers::IsScreenshare(last_content_type_);
798
799 if (is_screenshare != was_screenshare) {
Tommi74fc5742020-04-27 08:43:06800 // Reset the quality observer if content type is switched. But first report
801 // stats for the previous part of the call.
Tommi553c8692020-05-05 13:35:45802 video_quality_observer_->UpdateHistograms(was_screenshare);
803 video_quality_observer_.reset(new VideoQualityObserver());
Tommi74fc5742020-04-27 08:43:06804 }
805
Tommid93bf122020-05-10 18:24:59806 video_quality_observer_->OnDecodedFrame(frame_meta.rtp_timestamp, qp,
Tommid7e08c82020-05-10 09:24:43807 last_codec_type_);
Tommi74fc5742020-04-27 08:43:06808
809 ContentSpecificStats* content_specific_stats =
810 &content_specific_stats_[content_type];
Tommi674b0c812020-05-10 19:52:17811
Tommi74fc5742020-04-27 08:43:06812 ++stats_.frames_decoded;
813 if (qp) {
814 if (!stats_.qp_sum) {
815 if (stats_.frames_decoded != 1) {
816 RTC_LOG(LS_WARNING)
817 << "Frames decoded was not 1 when first qp value was received.";
818 }
819 stats_.qp_sum = 0;
820 }
821 *stats_.qp_sum += *qp;
822 content_specific_stats->qp_counter.Add(*qp);
823 } else if (stats_.qp_sum) {
824 RTC_LOG(LS_WARNING)
825 << "QP sum was already set and no QP was given for a frame.";
826 stats_.qp_sum.reset();
827 }
Philipp Hancked970b092022-06-17 05:34:23828 decode_time_counter_.Add(decode_time.ms());
829 stats_.decode_ms = decode_time.ms();
830 stats_.total_decode_time += decode_time;
Philipp Hanckea16a6a62022-04-25 10:21:30831 stats_.total_processing_delay += processing_delay;
Philipp Hancke0359ba22022-05-05 13:55:36832 stats_.total_assembly_time += assembly_time;
833 if (!assembly_time.IsZero()) {
834 ++stats_.frames_assembled_from_multiple_packets;
835 }
Tommi74fc5742020-04-27 08:43:06836
837 last_content_type_ = content_type;
Tommid93bf122020-05-10 18:24:59838 decode_fps_estimator_.Update(1, frame_meta.decode_timestamp.ms());
839
Tommi74fc5742020-04-27 08:43:06840 if (last_decoded_frame_time_ms_) {
Tommid93bf122020-05-10 18:24:59841 int64_t interframe_delay_ms =
842 frame_meta.decode_timestamp.ms() - *last_decoded_frame_time_ms_;
Tommi74fc5742020-04-27 08:43:06843 RTC_DCHECK_GE(interframe_delay_ms, 0);
Tommid93bf122020-05-10 18:24:59844 interframe_delay_max_moving_.Add(interframe_delay_ms,
845 frame_meta.decode_timestamp.ms());
Tommi74fc5742020-04-27 08:43:06846 content_specific_stats->interframe_delay_counter.Add(interframe_delay_ms);
847 content_specific_stats->interframe_delay_percentiles.Add(
848 interframe_delay_ms);
849 content_specific_stats->flow_duration_ms += interframe_delay_ms;
850 }
851 if (stats_.frames_decoded == 1) {
Tommid93bf122020-05-10 18:24:59852 first_decoded_frame_time_ms_.emplace(frame_meta.decode_timestamp.ms());
Tommi74fc5742020-04-27 08:43:06853 }
Tommid93bf122020-05-10 18:24:59854 last_decoded_frame_time_ms_.emplace(frame_meta.decode_timestamp.ms());
Tommi74fc5742020-04-27 08:43:06855}
856
Tommid7e08c82020-05-10 09:24:43857void ReceiveStatisticsProxy::OnRenderedFrame(
858 const VideoFrameMetaData& frame_meta) {
859 RTC_DCHECK_RUN_ON(&main_thread_);
860 // Called from VideoReceiveStream2::OnFrame.
Tommi553c8692020-05-05 13:35:45861
Tommid7e08c82020-05-10 09:24:43862 RTC_DCHECK_GT(frame_meta.width, 0);
863 RTC_DCHECK_GT(frame_meta.height, 0);
864
Tommid7e08c82020-05-10 09:24:43865 video_quality_observer_->OnRenderedFrame(frame_meta);
Tommi74fc5742020-04-27 08:43:06866
867 ContentSpecificStats* content_specific_stats =
868 &content_specific_stats_[last_content_type_];
Tommid7e08c82020-05-10 09:24:43869 renders_fps_estimator_.Update(1, frame_meta.decode_timestamp.ms());
Tommi674b0c812020-05-10 19:52:17870
Tommi74fc5742020-04-27 08:43:06871 ++stats_.frames_rendered;
Tommid7e08c82020-05-10 09:24:43872 stats_.width = frame_meta.width;
873 stats_.height = frame_meta.height;
Tommi674b0c812020-05-10 19:52:17874
Tommi74fc5742020-04-27 08:43:06875 render_fps_tracker_.AddSamples(1);
Tommid7e08c82020-05-10 09:24:43876 render_pixel_tracker_.AddSamples(sqrt(frame_meta.width * frame_meta.height));
877 content_specific_stats->received_width.Add(frame_meta.width);
878 content_specific_stats->received_height.Add(frame_meta.height);
Tommi74fc5742020-04-27 08:43:06879
880 // Consider taking stats_.render_delay_ms into account.
Tommid7e08c82020-05-10 09:24:43881 const int64_t time_until_rendering_ms =
882 frame_meta.render_time_ms() - frame_meta.decode_timestamp.ms();
Tommi74fc5742020-04-27 08:43:06883 if (time_until_rendering_ms < 0) {
884 sum_missed_render_deadline_ms_ += -time_until_rendering_ms;
885 ++num_delayed_frames_rendered_;
886 }
887
Tommid7e08c82020-05-10 09:24:43888 if (frame_meta.ntp_time_ms > 0) {
889 int64_t delay_ms =
890 clock_->CurrentNtpInMilliseconds() - frame_meta.ntp_time_ms;
Tommi74fc5742020-04-27 08:43:06891 if (delay_ms >= 0) {
892 content_specific_stats->e2e_delay_counter.Add(delay_ms);
893 }
894 }
Tommi674b0c812020-05-10 19:52:17895
Tommid7e08c82020-05-10 09:24:43896 QualitySample(frame_meta.decode_timestamp);
Tommi74fc5742020-04-27 08:43:06897}
898
899void ReceiveStatisticsProxy::OnSyncOffsetUpdated(int64_t video_playout_ntp_ms,
900 int64_t sync_offset_ms,
901 double estimated_freq_khz) {
Tommi55107c82021-06-16 14:31:18902 RTC_DCHECK_RUN_ON(&main_thread_);
Tommi74fc5742020-04-27 08:43:06903
Tommi55107c82021-06-16 14:31:18904 const int64_t now_ms = clock_->TimeInMilliseconds();
905 sync_offset_counter_.Add(std::abs(sync_offset_ms));
906 stats_.sync_offset_ms = sync_offset_ms;
907 last_estimated_playout_ntp_timestamp_ms_ = video_playout_ntp_ms;
908 last_estimated_playout_time_ms_ = now_ms;
Tommi74fc5742020-04-27 08:43:06909
Tommi55107c82021-06-16 14:31:18910 const double kMaxFreqKhz = 10000.0;
911 int offset_khz = kMaxFreqKhz;
912 // Should not be zero or negative. If so, report max.
913 if (estimated_freq_khz < kMaxFreqKhz && estimated_freq_khz > 0.0)
914 offset_khz = static_cast<int>(std::fabs(estimated_freq_khz - 90.0) + 0.5);
915
916 freq_offset_counter_.Add(offset_khz);
Tommi74fc5742020-04-27 08:43:06917}
918
919void ReceiveStatisticsProxy::OnCompleteFrame(bool is_keyframe,
920 size_t size_bytes,
921 VideoContentType content_type) {
Tommid93bf122020-05-10 18:24:59922 RTC_DCHECK_RUN_ON(&main_thread_);
Tommi674b0c812020-05-10 19:52:17923
Tommi74fc5742020-04-27 08:43:06924 if (is_keyframe) {
925 ++stats_.frame_counts.key_frames;
926 } else {
927 ++stats_.frame_counts.delta_frames;
928 }
929
930 // Content type extension is set only for keyframes and should be propagated
931 // for all the following delta frames. Here we may receive frames out of order
932 // and miscategorise some delta frames near the layer switch.
933 // This may slightly offset calculated bitrate and keyframes permille metrics.
934 VideoContentType propagated_content_type =
935 is_keyframe ? content_type : last_content_type_;
936
937 ContentSpecificStats* content_specific_stats =
938 &content_specific_stats_[propagated_content_type];
939
940 content_specific_stats->total_media_bytes += size_bytes;
941 if (is_keyframe) {
942 ++content_specific_stats->frame_counts.key_frames;
943 } else {
944 ++content_specific_stats->frame_counts.delta_frames;
945 }
946
947 int64_t now_ms = clock_->TimeInMilliseconds();
948 frame_window_.insert(std::make_pair(now_ms, size_bytes));
949 UpdateFramerate(now_ms);
950}
951
952void ReceiveStatisticsProxy::OnDroppedFrames(uint32_t frames_dropped) {
Tommi430951a2020-05-19 21:27:29953 // Can be called on either the decode queue or the worker thread
954 // See FrameBuffer2 for more details.
Danil Chapovalov95eeaa72022-07-06 08:14:29955 worker_thread_->PostTask(
956 SafeTask(task_safety_.flag(), [frames_dropped, this]() {
957 RTC_DCHECK_RUN_ON(&main_thread_);
958 stats_.frames_dropped += frames_dropped;
959 }));
Tommi74fc5742020-04-27 08:43:06960}
961
962void ReceiveStatisticsProxy::OnPreDecode(VideoCodecType codec_type, int qp) {
Evan Shrubsolea535ee62022-08-17 09:51:25963 RTC_DCHECK_RUN_ON(&main_thread_);
964 last_codec_type_ = codec_type;
965 if (last_codec_type_ == kVideoCodecVP8 && qp != -1) {
966 qp_counters_.vp8.Add(qp);
967 qp_sample_.Add(qp);
968 }
Tommi74fc5742020-04-27 08:43:06969}
970
971void ReceiveStatisticsProxy::OnStreamInactive() {
Tommid93bf122020-05-10 18:24:59972 RTC_DCHECK_RUN_ON(&main_thread_);
Tommi553c8692020-05-05 13:35:45973
Tommi74fc5742020-04-27 08:43:06974 // TODO(sprang): Figure out any other state that should be reset.
975
Tommi74fc5742020-04-27 08:43:06976 // Don't report inter-frame delay if stream was paused.
977 last_decoded_frame_time_ms_.reset();
Tommid93bf122020-05-10 18:24:59978
Tommi74fc5742020-04-27 08:43:06979 video_quality_observer_->OnStreamInactive();
980}
981
Tommi674b0c812020-05-10 19:52:17982void ReceiveStatisticsProxy::OnRttUpdate(int64_t avg_rtt_ms) {
Tommi822a8742020-05-10 22:42:30983 RTC_DCHECK_RUN_ON(&main_thread_);
Tommiffd0a842020-05-11 06:24:47984 avg_rtt_ms_ = avg_rtt_ms;
Tommi74fc5742020-04-27 08:43:06985}
986
987void ReceiveStatisticsProxy::DecoderThreadStarting() {
988 RTC_DCHECK_RUN_ON(&main_thread_);
989}
990
991void ReceiveStatisticsProxy::DecoderThreadStopped() {
992 RTC_DCHECK_RUN_ON(&main_thread_);
Tommi553c8692020-05-05 13:35:45993 decode_queue_.Detach();
Tommi74fc5742020-04-27 08:43:06994}
995
996ReceiveStatisticsProxy::ContentSpecificStats::ContentSpecificStats()
997 : interframe_delay_percentiles(kMaxCommonInterframeDelayMs) {}
998
999ReceiveStatisticsProxy::ContentSpecificStats::~ContentSpecificStats() = default;
1000
1001void ReceiveStatisticsProxy::ContentSpecificStats::Add(
1002 const ContentSpecificStats& other) {
1003 e2e_delay_counter.Add(other.e2e_delay_counter);
1004 interframe_delay_counter.Add(other.interframe_delay_counter);
1005 flow_duration_ms += other.flow_duration_ms;
1006 total_media_bytes += other.total_media_bytes;
1007 received_height.Add(other.received_height);
1008 received_width.Add(other.received_width);
1009 qp_counter.Add(other.qp_counter);
1010 frame_counts.key_frames += other.frame_counts.key_frames;
1011 frame_counts.delta_frames += other.frame_counts.delta_frames;
1012 interframe_delay_percentiles.Add(other.interframe_delay_percentiles);
1013}
1014
1015} // namespace internal
1016} // namespace webrtc