blob: 671416a00730fae83513c66f395ce3cfa07f9077 [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
Rasmus Brandt090fe082023-05-09 10:53:0111#include "video/receive_statistics_proxy.h"
Tommi74fc5742020-04-27 08:43:0612
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
Tommi74fc5742020-04-27 08:43:0633// Some metrics are reported as a maximum over this period.
34// This should be synchronized with a typical getStats polling interval in
35// the clients.
36const int kMovingMaxWindowMs = 1000;
37
38// How large window we use to calculate the framerate/bitrate.
39const int kRateStatisticsWindowSizeMs = 1000;
40
41// Some sane ballpark estimate for maximum common value of inter-frame delay.
42// Values below that will be stored explicitly in the array,
43// values above - in the map.
44const int kMaxCommonInterframeDelayMs = 500;
45
46const char* UmaPrefixForContentType(VideoContentType content_type) {
47 if (videocontenttypehelpers::IsScreenshare(content_type))
48 return "WebRTC.Video.Screenshare";
49 return "WebRTC.Video";
50}
51
Tommi47173c92020-05-13 19:26:3752// TODO(https://bugs.webrtc.org/11572): Workaround for an issue with some
53// rtc::Thread instances and/or implementations that don't register as the
54// current task queue.
55bool IsCurrentTaskQueueOrThread(TaskQueueBase* task_queue) {
56 if (task_queue->IsCurrent())
57 return true;
58
59 rtc::Thread* current_thread = rtc::ThreadManager::Instance()->CurrentThread();
60 if (!current_thread)
61 return false;
62
63 return static_cast<TaskQueueBase*>(current_thread) == task_queue;
64}
65
Tommi74fc5742020-04-27 08:43:0666} // namespace
67
Johannes Kronda843fe2022-06-23 11:18:2668ReceiveStatisticsProxy::ReceiveStatisticsProxy(uint32_t remote_ssrc,
69 Clock* clock,
70 TaskQueueBase* worker_thread)
Tommi74fc5742020-04-27 08:43:0671 : clock_(clock),
Tommi74fc5742020-04-27 08:43:0672 start_ms_(clock->TimeInMilliseconds()),
Tommib2db9892021-08-23 20:44:4973 remote_ssrc_(remote_ssrc),
Tommi74fc5742020-04-27 08:43:0674 // 1000ms window, scale 1000 for ms to s.
75 decode_fps_estimator_(1000, 1000),
76 renders_fps_estimator_(1000, 1000),
77 render_fps_tracker_(100, 10u),
78 render_pixel_tracker_(100, 10u),
Tommi553c8692020-05-05 13:35:4579 video_quality_observer_(new VideoQualityObserver()),
Tommi74fc5742020-04-27 08:43:0680 interframe_delay_max_moving_(kMovingMaxWindowMs),
81 freq_offset_counter_(clock, nullptr, kFreqOffsetProcessIntervalMs),
Tommi74fc5742020-04-27 08:43:0682 last_content_type_(VideoContentType::UNSPECIFIED),
83 last_codec_type_(kVideoCodecVP8),
84 num_delayed_frames_rendered_(0),
85 sum_missed_render_deadline_ms_(0),
Tommi553c8692020-05-05 13:35:4586 timing_frame_info_counter_(kMovingMaxWindowMs),
87 worker_thread_(worker_thread) {
88 RTC_DCHECK(worker_thread);
89 decode_queue_.Detach();
90 incoming_render_queue_.Detach();
Tommib2db9892021-08-23 20:44:4991 stats_.ssrc = remote_ssrc_;
Tommi553c8692020-05-05 13:35:4592}
93
94ReceiveStatisticsProxy::~ReceiveStatisticsProxy() {
95 RTC_DCHECK_RUN_ON(&main_thread_);
Tommi74fc5742020-04-27 08:43:0696}
97
98void ReceiveStatisticsProxy::UpdateHistograms(
Florent Castelli8037fc62024-08-29 13:00:4099 std::optional<int> fraction_lost,
Tommi74fc5742020-04-27 08:43:06100 const StreamDataCounters& rtp_stats,
101 const StreamDataCounters* rtx_stats) {
Tommid93bf122020-05-10 18:24:59102 RTC_DCHECK_RUN_ON(&main_thread_);
Tommi74fc5742020-04-27 08:43:06103
104 char log_stream_buf[8 * 1024];
105 rtc::SimpleStringBuilder log_stream(log_stream_buf);
Tommi553c8692020-05-05 13:35:45106
Tommi74fc5742020-04-27 08:43:06107 int stream_duration_sec = (clock_->TimeInMilliseconds() - start_ms_) / 1000;
Tommi553c8692020-05-05 13:35:45108
Tommi74fc5742020-04-27 08:43:06109 if (stats_.frame_counts.key_frames > 0 ||
110 stats_.frame_counts.delta_frames > 0) {
111 RTC_HISTOGRAM_COUNTS_100000("WebRTC.Video.ReceiveStreamLifetimeInSeconds",
112 stream_duration_sec);
113 log_stream << "WebRTC.Video.ReceiveStreamLifetimeInSeconds "
114 << stream_duration_sec << '\n';
115 }
116
117 log_stream << "Frames decoded " << stats_.frames_decoded << '\n';
118
119 if (num_unique_frames_) {
120 int num_dropped_frames = *num_unique_frames_ - stats_.frames_decoded;
121 RTC_HISTOGRAM_COUNTS_1000("WebRTC.Video.DroppedFrames.Receiver",
122 num_dropped_frames);
123 log_stream << "WebRTC.Video.DroppedFrames.Receiver " << num_dropped_frames
124 << '\n';
125 }
126
127 if (fraction_lost && stream_duration_sec >= metrics::kMinRunTimeInSeconds) {
128 RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.ReceivedPacketsLostInPercent",
129 *fraction_lost);
130 log_stream << "WebRTC.Video.ReceivedPacketsLostInPercent " << *fraction_lost
131 << '\n';
132 }
133
134 if (first_decoded_frame_time_ms_) {
135 const int64_t elapsed_ms =
136 (clock_->TimeInMilliseconds() - *first_decoded_frame_time_ms_);
137 if (elapsed_ms >=
138 metrics::kMinRunTimeInSeconds * rtc::kNumMillisecsPerSec) {
139 int decoded_fps = static_cast<int>(
140 (stats_.frames_decoded * 1000.0f / elapsed_ms) + 0.5f);
141 RTC_HISTOGRAM_COUNTS_100("WebRTC.Video.DecodedFramesPerSecond",
142 decoded_fps);
143 log_stream << "WebRTC.Video.DecodedFramesPerSecond " << decoded_fps
144 << '\n';
145
146 const uint32_t frames_rendered = stats_.frames_rendered;
147 if (frames_rendered > 0) {
148 RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.DelayedFramesToRenderer",
149 static_cast<int>(num_delayed_frames_rendered_ *
150 100 / frames_rendered));
151 if (num_delayed_frames_rendered_ > 0) {
152 RTC_HISTOGRAM_COUNTS_1000(
153 "WebRTC.Video.DelayedFramesToRenderer_AvgDelayInMs",
154 static_cast<int>(sum_missed_render_deadline_ms_ /
155 num_delayed_frames_rendered_));
156 }
157 }
158 }
159 }
160
161 const int kMinRequiredSamples = 200;
162 int samples = static_cast<int>(render_fps_tracker_.TotalSampleCount());
163 if (samples >= kMinRequiredSamples) {
164 int rendered_fps = round(render_fps_tracker_.ComputeTotalRate());
165 RTC_HISTOGRAM_COUNTS_100("WebRTC.Video.RenderFramesPerSecond",
166 rendered_fps);
167 log_stream << "WebRTC.Video.RenderFramesPerSecond " << rendered_fps << '\n';
168 RTC_HISTOGRAM_COUNTS_100000(
169 "WebRTC.Video.RenderSqrtPixelsPerSecond",
170 round(render_pixel_tracker_.ComputeTotalRate()));
171 }
172
Florent Castelli8037fc62024-08-29 13:00:40173 std::optional<int> sync_offset_ms =
Tommi74fc5742020-04-27 08:43:06174 sync_offset_counter_.Avg(kMinRequiredSamples);
175 if (sync_offset_ms) {
176 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.AVSyncOffsetInMs",
177 *sync_offset_ms);
178 log_stream << "WebRTC.Video.AVSyncOffsetInMs " << *sync_offset_ms << '\n';
179 }
180 AggregatedStats freq_offset_stats = freq_offset_counter_.GetStats();
181 if (freq_offset_stats.num_samples > 0) {
182 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.RtpToNtpFreqOffsetInKhz",
183 freq_offset_stats.average);
184 log_stream << "WebRTC.Video.RtpToNtpFreqOffsetInKhz "
185 << freq_offset_stats.ToString() << '\n';
186 }
187
188 int num_total_frames =
189 stats_.frame_counts.key_frames + stats_.frame_counts.delta_frames;
190 if (num_total_frames >= kMinRequiredSamples) {
191 int num_key_frames = stats_.frame_counts.key_frames;
192 int key_frames_permille =
193 (num_key_frames * 1000 + num_total_frames / 2) / num_total_frames;
194 RTC_HISTOGRAM_COUNTS_1000("WebRTC.Video.KeyFramesReceivedInPermille",
195 key_frames_permille);
196 log_stream << "WebRTC.Video.KeyFramesReceivedInPermille "
197 << key_frames_permille << '\n';
198 }
199
Florent Castelli8037fc62024-08-29 13:00:40200 std::optional<int> qp = qp_counters_.vp8.Avg(kMinRequiredSamples);
Tommi674b0c812020-05-10 19:52:17201 if (qp) {
202 RTC_HISTOGRAM_COUNTS_200("WebRTC.Video.Decoded.Vp8.Qp", *qp);
203 log_stream << "WebRTC.Video.Decoded.Vp8.Qp " << *qp << '\n';
Tommi74fc5742020-04-27 08:43:06204 }
Tommi674b0c812020-05-10 19:52:17205
Florent Castelli8037fc62024-08-29 13:00:40206 std::optional<int> decode_ms = decode_time_counter_.Avg(kMinRequiredSamples);
Tommi74fc5742020-04-27 08:43:06207 if (decode_ms) {
208 RTC_HISTOGRAM_COUNTS_1000("WebRTC.Video.DecodeTimeInMs", *decode_ms);
209 log_stream << "WebRTC.Video.DecodeTimeInMs " << *decode_ms << '\n';
210 }
Florent Castelli8037fc62024-08-29 13:00:40211 std::optional<int> jb_delay_ms =
Rasmus Brandt621cb292023-05-24 11:29:14212 jitter_delay_counter_.Avg(kMinRequiredSamples);
Tommi74fc5742020-04-27 08:43:06213 if (jb_delay_ms) {
214 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.JitterBufferDelayInMs",
215 *jb_delay_ms);
216 log_stream << "WebRTC.Video.JitterBufferDelayInMs " << *jb_delay_ms << '\n';
217 }
218
Florent Castelli8037fc62024-08-29 13:00:40219 std::optional<int> target_delay_ms =
Tommi74fc5742020-04-27 08:43:06220 target_delay_counter_.Avg(kMinRequiredSamples);
221 if (target_delay_ms) {
222 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.TargetDelayInMs",
223 *target_delay_ms);
224 log_stream << "WebRTC.Video.TargetDelayInMs " << *target_delay_ms << '\n';
225 }
Florent Castelli8037fc62024-08-29 13:00:40226 std::optional<int> current_delay_ms =
Tommi74fc5742020-04-27 08:43:06227 current_delay_counter_.Avg(kMinRequiredSamples);
228 if (current_delay_ms) {
229 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.CurrentDelayInMs",
230 *current_delay_ms);
231 log_stream << "WebRTC.Video.CurrentDelayInMs " << *current_delay_ms << '\n';
232 }
Florent Castelli8037fc62024-08-29 13:00:40233 std::optional<int> delay_ms = oneway_delay_counter_.Avg(kMinRequiredSamples);
Tommi74fc5742020-04-27 08:43:06234 if (delay_ms)
235 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.OnewayDelayInMs", *delay_ms);
236
Tommi74fc5742020-04-27 08:43:06237 for (const auto& it : content_specific_stats_) {
Tommi74fc5742020-04-27 08:43:06238 // For the metric Foo we report the following slices:
239 // WebRTC.Video.Foo,
240 // WebRTC.Video.Screenshare.Foo,
Tommi74fc5742020-04-27 08:43:06241 auto content_type = it.first;
242 auto stats = it.second;
243 std::string uma_prefix = UmaPrefixForContentType(content_type);
Tommi74fc5742020-04-27 08:43:06244
Florent Castelli8037fc62024-08-29 13:00:40245 std::optional<int> e2e_delay_ms =
Tommi74fc5742020-04-27 08:43:06246 stats.e2e_delay_counter.Avg(kMinRequiredSamples);
247 if (e2e_delay_ms) {
Harald Alvestrand00f11222023-07-21 07:23:01248 RTC_HISTOGRAM_COUNTS_SPARSE_10000(uma_prefix + ".EndToEndDelayInMs",
249 *e2e_delay_ms);
250 log_stream << uma_prefix << ".EndToEndDelayInMs"
251 << " " << *e2e_delay_ms << '\n';
Tommi74fc5742020-04-27 08:43:06252 }
Florent Castelli8037fc62024-08-29 13:00:40253 std::optional<int> e2e_delay_max_ms = stats.e2e_delay_counter.Max();
Tommi74fc5742020-04-27 08:43:06254 if (e2e_delay_max_ms && e2e_delay_ms) {
Harald Alvestrand00f11222023-07-21 07:23:01255 RTC_HISTOGRAM_COUNTS_SPARSE_100000(uma_prefix + ".EndToEndDelayMaxInMs",
256 *e2e_delay_max_ms);
257 log_stream << uma_prefix << ".EndToEndDelayMaxInMs"
258 << " " << *e2e_delay_max_ms << '\n';
Tommi74fc5742020-04-27 08:43:06259 }
Florent Castelli8037fc62024-08-29 13:00:40260 std::optional<int> interframe_delay_ms =
Tommi74fc5742020-04-27 08:43:06261 stats.interframe_delay_counter.Avg(kMinRequiredSamples);
262 if (interframe_delay_ms) {
Harald Alvestrand00f11222023-07-21 07:23:01263 RTC_HISTOGRAM_COUNTS_SPARSE_10000(uma_prefix + ".InterframeDelayInMs",
264 *interframe_delay_ms);
265 log_stream << uma_prefix << ".InterframeDelayInMs"
266 << " " << *interframe_delay_ms << '\n';
Tommi74fc5742020-04-27 08:43:06267 }
Florent Castelli8037fc62024-08-29 13:00:40268 std::optional<int> interframe_delay_max_ms =
Tommi74fc5742020-04-27 08:43:06269 stats.interframe_delay_counter.Max();
270 if (interframe_delay_max_ms && interframe_delay_ms) {
Harald Alvestrand00f11222023-07-21 07:23:01271 RTC_HISTOGRAM_COUNTS_SPARSE_10000(uma_prefix + ".InterframeDelayMaxInMs",
272 *interframe_delay_max_ms);
273 log_stream << uma_prefix << ".InterframeDelayMaxInMs"
274 << " " << *interframe_delay_max_ms << '\n';
Tommi74fc5742020-04-27 08:43:06275 }
276
Florent Castelli8037fc62024-08-29 13:00:40277 std::optional<uint32_t> interframe_delay_95p_ms =
Tommi74fc5742020-04-27 08:43:06278 stats.interframe_delay_percentiles.GetPercentile(0.95f);
279 if (interframe_delay_95p_ms && interframe_delay_ms != -1) {
280 RTC_HISTOGRAM_COUNTS_SPARSE_10000(
Harald Alvestrand00f11222023-07-21 07:23:01281 uma_prefix + ".InterframeDelay95PercentileInMs",
Tommi74fc5742020-04-27 08:43:06282 *interframe_delay_95p_ms);
283 log_stream << uma_prefix << ".InterframeDelay95PercentileInMs"
Harald Alvestrand00f11222023-07-21 07:23:01284 << " " << *interframe_delay_95p_ms << '\n';
Tommi74fc5742020-04-27 08:43:06285 }
286
Florent Castelli8037fc62024-08-29 13:00:40287 std::optional<int> width = stats.received_width.Avg(kMinRequiredSamples);
Tommi74fc5742020-04-27 08:43:06288 if (width) {
Harald Alvestrand00f11222023-07-21 07:23:01289 RTC_HISTOGRAM_COUNTS_SPARSE_10000(uma_prefix + ".ReceivedWidthInPixels",
290 *width);
291 log_stream << uma_prefix << ".ReceivedWidthInPixels"
292 << " " << *width << '\n';
Tommi74fc5742020-04-27 08:43:06293 }
294
Florent Castelli8037fc62024-08-29 13:00:40295 std::optional<int> height = stats.received_height.Avg(kMinRequiredSamples);
Tommi74fc5742020-04-27 08:43:06296 if (height) {
Harald Alvestrand00f11222023-07-21 07:23:01297 RTC_HISTOGRAM_COUNTS_SPARSE_10000(uma_prefix + ".ReceivedHeightInPixels",
298 *height);
299 log_stream << uma_prefix << ".ReceivedHeightInPixels"
300 << " " << *height << '\n';
Tommi74fc5742020-04-27 08:43:06301 }
302
Florent Castelli8037fc62024-08-29 13:00:40303 std::optional<double> corruption_score = stats.corruption_score.GetMean();
Emil Vardar3f1e51d2024-08-27 12:14:33304 if (corruption_score) {
305 // Granularity level: 2e-3.
306 RTC_HISTOGRAM_COUNTS_SPARSE(uma_prefix + ".CorruptionLikelihoodPermille",
307 static_cast<int>(*corruption_score * 1000),
308 /*min=*/0, /*max=*/1000,
309 /*bucket_count=*/500);
310 log_stream << uma_prefix << ".CorruptionLikelihoodPermille" << " "
311 << static_cast<int>(*corruption_score * 1000) << '\n';
312 }
313
Tommi74fc5742020-04-27 08:43:06314 if (content_type != VideoContentType::UNSPECIFIED) {
315 // Don't report these 3 metrics unsliced, as more precise variants
316 // are reported separately in this method.
317 float flow_duration_sec = stats.flow_duration_ms / 1000.0;
318 if (flow_duration_sec >= metrics::kMinRunTimeInSeconds) {
319 int media_bitrate_kbps = static_cast<int>(stats.total_media_bytes * 8 /
320 flow_duration_sec / 1000);
321 RTC_HISTOGRAM_COUNTS_SPARSE_10000(
Harald Alvestrand00f11222023-07-21 07:23:01322 uma_prefix + ".MediaBitrateReceivedInKbps", media_bitrate_kbps);
323 log_stream << uma_prefix << ".MediaBitrateReceivedInKbps"
Tommi74fc5742020-04-27 08:43:06324 << " " << media_bitrate_kbps << '\n';
325 }
326
327 int num_total_frames =
328 stats.frame_counts.key_frames + stats.frame_counts.delta_frames;
329 if (num_total_frames >= kMinRequiredSamples) {
330 int num_key_frames = stats.frame_counts.key_frames;
331 int key_frames_permille =
332 (num_key_frames * 1000 + num_total_frames / 2) / num_total_frames;
333 RTC_HISTOGRAM_COUNTS_SPARSE_1000(
Harald Alvestrand00f11222023-07-21 07:23:01334 uma_prefix + ".KeyFramesReceivedInPermille", key_frames_permille);
335 log_stream << uma_prefix << ".KeyFramesReceivedInPermille"
Tommi74fc5742020-04-27 08:43:06336 << " " << key_frames_permille << '\n';
337 }
338
Florent Castelli8037fc62024-08-29 13:00:40339 std::optional<int> qp = stats.qp_counter.Avg(kMinRequiredSamples);
Tommi74fc5742020-04-27 08:43:06340 if (qp) {
Harald Alvestrand00f11222023-07-21 07:23:01341 RTC_HISTOGRAM_COUNTS_SPARSE_200(uma_prefix + ".Decoded.Vp8.Qp", *qp);
342 log_stream << uma_prefix << ".Decoded.Vp8.Qp"
343 << " " << *qp << '\n';
Tommi74fc5742020-04-27 08:43:06344 }
345 }
346 }
347
348 StreamDataCounters rtp_rtx_stats = rtp_stats;
349 if (rtx_stats)
350 rtp_rtx_stats.Add(*rtx_stats);
Tommi553c8692020-05-05 13:35:45351
Danil Chapovalov0f1a2c52023-05-26 09:30:26352 TimeDelta elapsed = rtp_rtx_stats.TimeSinceFirstPacket(clock_->CurrentTime());
353 if (elapsed >= TimeDelta::Seconds(metrics::kMinRunTimeInSeconds)) {
354 int64_t elapsed_sec = elapsed.seconds();
Tommi74fc5742020-04-27 08:43:06355 RTC_HISTOGRAM_COUNTS_10000(
356 "WebRTC.Video.BitrateReceivedInKbps",
357 static_cast<int>(rtp_rtx_stats.transmitted.TotalBytes() * 8 /
358 elapsed_sec / 1000));
359 int media_bitrate_kbs = static_cast<int>(rtp_stats.MediaPayloadBytes() * 8 /
360 elapsed_sec / 1000);
361 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.MediaBitrateReceivedInKbps",
362 media_bitrate_kbs);
363 log_stream << "WebRTC.Video.MediaBitrateReceivedInKbps "
364 << media_bitrate_kbs << '\n';
365 RTC_HISTOGRAM_COUNTS_10000(
366 "WebRTC.Video.PaddingBitrateReceivedInKbps",
367 static_cast<int>(rtp_rtx_stats.transmitted.padding_bytes * 8 /
368 elapsed_sec / 1000));
369 RTC_HISTOGRAM_COUNTS_10000(
370 "WebRTC.Video.RetransmittedBitrateReceivedInKbps",
371 static_cast<int>(rtp_rtx_stats.retransmitted.TotalBytes() * 8 /
372 elapsed_sec / 1000));
373 if (rtx_stats) {
374 RTC_HISTOGRAM_COUNTS_10000(
375 "WebRTC.Video.RtxBitrateReceivedInKbps",
376 static_cast<int>(rtx_stats->transmitted.TotalBytes() * 8 /
377 elapsed_sec / 1000));
378 }
379 const RtcpPacketTypeCounter& counters = stats_.rtcp_packet_type_counts;
380 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.NackPacketsSentPerMinute",
381 counters.nack_packets * 60 / elapsed_sec);
382 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.FirPacketsSentPerMinute",
383 counters.fir_packets * 60 / elapsed_sec);
384 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.PliPacketsSentPerMinute",
385 counters.pli_packets * 60 / elapsed_sec);
386 if (counters.nack_requests > 0) {
387 RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.UniqueNackRequestsSentInPercent",
388 counters.UniqueNackRequestsInPercent());
389 }
390 }
391
Tommi74fc5742020-04-27 08:43:06392 RTC_LOG(LS_INFO) << log_stream.str();
Tommi553c8692020-05-05 13:35:45393 video_quality_observer_->UpdateHistograms(
394 videocontenttypehelpers::IsScreenshare(last_content_type_));
Tommi74fc5742020-04-27 08:43:06395}
396
Tommi74fc5742020-04-27 08:43:06397void ReceiveStatisticsProxy::UpdateFramerate(int64_t now_ms) const {
Tommid93bf122020-05-10 18:24:59398 RTC_DCHECK_RUN_ON(&main_thread_);
399
Tommi74fc5742020-04-27 08:43:06400 int64_t old_frames_ms = now_ms - kRateStatisticsWindowSizeMs;
401 while (!frame_window_.empty() &&
402 frame_window_.begin()->first < old_frames_ms) {
403 frame_window_.erase(frame_window_.begin());
404 }
405
406 size_t framerate =
407 (frame_window_.size() * 1000 + 500) / kRateStatisticsWindowSizeMs;
Tommid93bf122020-05-10 18:24:59408
Tommi74fc5742020-04-27 08:43:06409 stats_.network_frame_rate = static_cast<int>(framerate);
410}
411
Florent Castelli8037fc62024-08-29 13:00:40412std::optional<int64_t>
Tommi74fc5742020-04-27 08:43:06413ReceiveStatisticsProxy::GetCurrentEstimatedPlayoutNtpTimestampMs(
414 int64_t now_ms) const {
Tommi553c8692020-05-05 13:35:45415 RTC_DCHECK_RUN_ON(&main_thread_);
Tommi74fc5742020-04-27 08:43:06416 if (!last_estimated_playout_ntp_timestamp_ms_ ||
417 !last_estimated_playout_time_ms_) {
Florent Castelli8037fc62024-08-29 13:00:40418 return std::nullopt;
Tommi74fc5742020-04-27 08:43:06419 }
420 int64_t elapsed_ms = now_ms - *last_estimated_playout_time_ms_;
421 return *last_estimated_playout_ntp_timestamp_ms_ + elapsed_ms;
422}
423
Tommif6f45432022-05-20 13:21:20424VideoReceiveStreamInterface::Stats ReceiveStatisticsProxy::GetStats() const {
Tommi553c8692020-05-05 13:35:45425 RTC_DCHECK_RUN_ON(&main_thread_);
426
Tommif6f45432022-05-20 13:21:20427 // Like VideoReceiveStreamInterface::GetStats, called on the worker thread
Danil Chapovalov9e09a1f2022-09-08 16:38:10428 // from StatsCollector::ExtractMediaInfo via worker_thread()->BlockingCall().
Tommi553c8692020-05-05 13:35:45429 // WebRtcVideoChannel::GetStats(), GetVideoReceiverInfo.
430
Tommi74fc5742020-04-27 08:43:06431 // Get current frame rates here, as only updating them on new frames prevents
432 // us from ever correctly displaying frame rate of 0.
433 int64_t now_ms = clock_->TimeInMilliseconds();
434 UpdateFramerate(now_ms);
Tommid93bf122020-05-10 18:24:59435
Tommi74fc5742020-04-27 08:43:06436 stats_.render_frame_rate = renders_fps_estimator_.Rate(now_ms).value_or(0);
437 stats_.decode_frame_rate = decode_fps_estimator_.Rate(now_ms).value_or(0);
Tommid93bf122020-05-10 18:24:59438
439 if (last_decoded_frame_time_ms_) {
440 // Avoid using a newer timestamp than might be pending for decoded frames.
441 // If we do use now_ms, we might roll the max window to a value that is
442 // higher than that of a decoded frame timestamp that we haven't yet
443 // captured the data for (i.e. pending call to OnDecodedFrame).
444 stats_.interframe_delay_max_ms =
445 interframe_delay_max_moving_.Max(*last_decoded_frame_time_ms_)
446 .value_or(-1);
447 } else {
Artem Titovab30d722021-07-27 14:22:11448 // We're paused. Avoid changing the state of `interframe_delay_max_moving_`.
Tommid93bf122020-05-10 18:24:59449 stats_.interframe_delay_max_ms = -1;
450 }
451
Tommi74fc5742020-04-27 08:43:06452 stats_.freeze_count = video_quality_observer_->NumFreezes();
453 stats_.pause_count = video_quality_observer_->NumPauses();
454 stats_.total_freezes_duration_ms =
455 video_quality_observer_->TotalFreezesDurationMs();
456 stats_.total_pauses_duration_ms =
457 video_quality_observer_->TotalPausesDurationMs();
Sergey Silkined0dd8e2022-12-20 12:58:15458 stats_.total_inter_frame_delay =
459 static_cast<double>(video_quality_observer_->TotalFramesDurationMs()) /
460 rtc::kNumMillisecsPerSec;
461 stats_.total_squared_inter_frame_delay =
Tommi74fc5742020-04-27 08:43:06462 video_quality_observer_->SumSquaredFrameDurationsSec();
Sergey Silkined0dd8e2022-12-20 12:58:15463
Tommi74fc5742020-04-27 08:43:06464 stats_.content_type = last_content_type_;
465 stats_.timing_frame_info = timing_frame_info_counter_.Max(now_ms);
Tommi74fc5742020-04-27 08:43:06466 stats_.estimated_playout_ntp_timestamp_ms =
467 GetCurrentEstimatedPlayoutNtpTimestampMs(now_ms);
468 return stats_;
469}
470
471void ReceiveStatisticsProxy::OnIncomingPayloadType(int payload_type) {
Tommi553c8692020-05-05 13:35:45472 RTC_DCHECK_RUN_ON(&decode_queue_);
Danil Chapovalov95eeaa72022-07-06 08:14:29473 worker_thread_->PostTask(SafeTask(task_safety_.flag(), [payload_type, this] {
Tommia98cea82020-05-13 13:06:19474 RTC_DCHECK_RUN_ON(&main_thread_);
475 stats_.current_payload_type = payload_type;
476 }));
Tommi74fc5742020-04-27 08:43:06477}
478
Evan Shrubsole09da10e2022-10-14 14:38:31479void ReceiveStatisticsProxy::OnDecoderInfo(
480 const VideoDecoder::DecoderInfo& decoder_info) {
Tommi553c8692020-05-05 13:35:45481 RTC_DCHECK_RUN_ON(&decode_queue_);
Danil Chapovalov95eeaa72022-07-06 08:14:29482 worker_thread_->PostTask(SafeTask(
Evan Shrubsole09da10e2022-10-14 14:38:31483 task_safety_.flag(),
484 [this, name = decoder_info.implementation_name,
485 is_hardware_accelerated = decoder_info.is_hardware_accelerated]() {
Tommi674b0c812020-05-10 19:52:17486 RTC_DCHECK_RUN_ON(&main_thread_);
487 stats_.decoder_implementation_name = name;
Evan Shrubsole09da10e2022-10-14 14:38:31488 stats_.power_efficient_decoder = is_hardware_accelerated;
Tommi674b0c812020-05-10 19:52:17489 }));
Tommi74fc5742020-04-27 08:43:06490}
491
Rasmus Brandtf0820ff2023-05-25 07:37:16492void ReceiveStatisticsProxy::OnDecodableFrame(TimeDelta jitter_buffer_delay,
493 TimeDelta target_delay,
494 TimeDelta minimum_delay) {
Rasmus Brandt621cb292023-05-24 11:29:14495 RTC_DCHECK_RUN_ON(&main_thread_);
496 // Cumulative stats exposed through standardized GetStats.
Rasmus Brandt621cb292023-05-24 11:29:14497 stats_.jitter_buffer_delay += jitter_buffer_delay;
Rasmus Brandtf0820ff2023-05-25 07:37:16498 stats_.jitter_buffer_target_delay += target_delay;
Rasmus Brandt621cb292023-05-24 11:29:14499 ++stats_.jitter_buffer_emitted_count;
Rasmus Brandtf0820ff2023-05-25 07:37:16500 stats_.jitter_buffer_minimum_delay += minimum_delay;
Rasmus Brandt621cb292023-05-24 11:29:14501}
502
Tommi74fc5742020-04-27 08:43:06503void ReceiveStatisticsProxy::OnFrameBufferTimingsUpdated(
Rasmus Brandt24f9a8b2023-05-08 14:32:48504 int estimated_max_decode_time_ms,
Tommi74fc5742020-04-27 08:43:06505 int current_delay_ms,
506 int target_delay_ms,
Rasmus Brandt621cb292023-05-24 11:29:14507 int jitter_delay_ms,
Tommi74fc5742020-04-27 08:43:06508 int min_playout_delay_ms,
509 int render_delay_ms) {
Evan Shrubsole9a999052021-12-12 14:27:00510 RTC_DCHECK_RUN_ON(&main_thread_);
Rasmus Brandt621cb292023-05-24 11:29:14511 // Instantaneous stats exposed through legacy GetStats.
Rasmus Brandt24f9a8b2023-05-08 14:32:48512 stats_.max_decode_ms = estimated_max_decode_time_ms;
Evan Shrubsole9a999052021-12-12 14:27:00513 stats_.current_delay_ms = current_delay_ms;
514 stats_.target_delay_ms = target_delay_ms;
Rasmus Brandt621cb292023-05-24 11:29:14515 stats_.jitter_buffer_ms = jitter_delay_ms;
Evan Shrubsole9a999052021-12-12 14:27:00516 stats_.min_playout_delay_ms = min_playout_delay_ms;
517 stats_.render_delay_ms = render_delay_ms;
Rasmus Brandt621cb292023-05-24 11:29:14518
519 // UMA stats.
520 jitter_delay_counter_.Add(jitter_delay_ms);
Evan Shrubsole9a999052021-12-12 14:27:00521 target_delay_counter_.Add(target_delay_ms);
522 current_delay_counter_.Add(current_delay_ms);
Rasmus Brandt0df40d12023-05-08 09:32:59523 // Estimated one-way delay: network delay (rtt/2) + target_delay_ms (jitter
524 // delay + decode time + render delay).
525 oneway_delay_counter_.Add(target_delay_ms + avg_rtt_ms_ / 2);
Tommi74fc5742020-04-27 08:43:06526}
527
528void ReceiveStatisticsProxy::OnUniqueFramesCounted(int num_unique_frames) {
Tommi553c8692020-05-05 13:35:45529 RTC_DCHECK_RUN_ON(&main_thread_);
Tommi74fc5742020-04-27 08:43:06530 num_unique_frames_.emplace(num_unique_frames);
531}
532
533void ReceiveStatisticsProxy::OnTimingFrameInfoUpdated(
534 const TimingFrameInfo& info) {
Evan Shrubsole9a999052021-12-12 14:27:00535 RTC_DCHECK_RUN_ON(&main_thread_);
536 if (info.flags != VideoSendTiming::kInvalid) {
537 int64_t now_ms = clock_->TimeInMilliseconds();
538 timing_frame_info_counter_.Add(info, now_ms);
539 }
Tommi74fc5742020-04-27 08:43:06540
Evan Shrubsole9a999052021-12-12 14:27:00541 // Measure initial decoding latency between the first frame arriving and
542 // the first frame being decoded.
543 if (!first_frame_received_time_ms_.has_value()) {
544 first_frame_received_time_ms_ = info.receive_finish_ms;
545 }
546 if (stats_.first_frame_received_to_decoded_ms == -1 &&
547 first_decoded_frame_time_ms_) {
548 stats_.first_frame_received_to_decoded_ms =
549 *first_decoded_frame_time_ms_ - *first_frame_received_time_ms_;
550 }
Tommi74fc5742020-04-27 08:43:06551}
552
553void ReceiveStatisticsProxy::RtcpPacketTypesCounterUpdated(
554 uint32_t ssrc,
555 const RtcpPacketTypeCounter& packet_counter) {
Tommi553c8692020-05-05 13:35:45556 if (ssrc != remote_ssrc_)
Tommi74fc5742020-04-27 08:43:06557 return;
Tommi553c8692020-05-05 13:35:45558
Tommi47173c92020-05-13 19:26:37559 if (!IsCurrentTaskQueueOrThread(worker_thread_)) {
Tomas Gunnarssonf25761d2020-06-03 20:55:33560 // RtpRtcpInterface::Configuration has a single
561 // RtcpPacketTypeCounterObserver and that same configuration may be used for
562 // both receiver and sender (see ModuleRtpRtcpImpl::ModuleRtpRtcpImpl). The
563 // RTCPSender implementation currently makes calls to this function on a
Tommi553c8692020-05-05 13:35:45564 // process thread whereas the RTCPReceiver implementation calls back on the
565 // [main] worker thread.
566 // So until the sender implementation has been updated, we work around this
567 // here by posting the update to the expected thread. We make a by value
Artem Titovab30d722021-07-27 14:22:11568 // copy of the `task_safety_` to handle the case if the queued task
569 // runs after the `ReceiveStatisticsProxy` has been deleted. In such a
Tommi553c8692020-05-05 13:35:45570 // case the packet_counter update won't be recorded.
571 worker_thread_->PostTask(
Danil Chapovalov95eeaa72022-07-06 08:14:29572 SafeTask(task_safety_.flag(), [ssrc, packet_counter, this]() {
Tommi553c8692020-05-05 13:35:45573 RtcpPacketTypesCounterUpdated(ssrc, packet_counter);
574 }));
575 return;
576 }
577
578 RTC_DCHECK_RUN_ON(&main_thread_);
Tommi74fc5742020-04-27 08:43:06579 stats_.rtcp_packet_type_counts = packet_counter;
580}
581
582void ReceiveStatisticsProxy::OnCname(uint32_t ssrc, absl::string_view cname) {
Tommi553c8692020-05-05 13:35:45583 RTC_DCHECK_RUN_ON(&main_thread_);
Tommi74fc5742020-04-27 08:43:06584 // TODO(pbos): Handle both local and remote ssrcs here and RTC_DCHECK that we
585 // receive stats from one of them.
Tommi553c8692020-05-05 13:35:45586 if (remote_ssrc_ != ssrc)
Tommi74fc5742020-04-27 08:43:06587 return;
Tommi553c8692020-05-05 13:35:45588
Tommi74fc5742020-04-27 08:43:06589 stats_.c_name = std::string(cname);
590}
591
592void ReceiveStatisticsProxy::OnDecodedFrame(const VideoFrame& frame,
Florent Castelli8037fc62024-08-29 13:00:40593 std::optional<uint8_t> qp,
Philipp Hancked970b092022-06-17 05:34:23594 TimeDelta decode_time,
Philipp Hancke51657432023-08-15 08:20:50595 VideoContentType content_type,
596 VideoFrameType frame_type) {
Philipp Hanckea204ad22022-07-08 16:43:25597 TimeDelta processing_delay = TimeDelta::Zero();
Philipp Hanckea16a6a62022-04-25 10:21:30598 webrtc::Timestamp current_time = clock_->CurrentTime();
599 // TODO(bugs.webrtc.org/13984): some tests do not fill packet_infos().
Philipp Hanckea204ad22022-07-08 16:43:25600 TimeDelta assembly_time = TimeDelta::Zero();
Philipp Hanckea16a6a62022-04-25 10:21:30601 if (frame.packet_infos().size() > 0) {
Philipp Hancke0359ba22022-05-05 13:55:36602 const auto [first_packet, last_packet] = std::minmax_element(
Philipp Hanckea16a6a62022-04-25 10:21:30603 frame.packet_infos().cbegin(), frame.packet_infos().cend(),
604 [](const webrtc::RtpPacketInfo& a, const webrtc::RtpPacketInfo& b) {
605 return a.receive_time() < b.receive_time();
606 });
Philipp Hancke0359ba22022-05-05 13:55:36607 if (first_packet->receive_time().IsFinite()) {
608 processing_delay = current_time - first_packet->receive_time();
609 // Extract frame assembly time (i.e. time between earliest and latest
610 // packet arrival). Note: for single-packet frames this will be 0.
611 assembly_time =
612 last_packet->receive_time() - first_packet->receive_time();
613 }
Philipp Hanckea16a6a62022-04-25 10:21:30614 }
Tommid93bf122020-05-10 18:24:59615 // See VCMDecodedFrameCallback::Decoded for more info on what thread/queue we
616 // may be on. E.g. on iOS this gets called on
Tommi553c8692020-05-05 13:35:45617 // "com.apple.coremedia.decompressionsession.clientcallback"
Philipp Hanckea16a6a62022-04-25 10:21:30618 VideoFrameMetaData meta(frame, current_time);
Philipp Hancke51657432023-08-15 08:20:50619 worker_thread_->PostTask(SafeTask(
620 task_safety_.flag(), [meta, qp, decode_time, processing_delay,
621 assembly_time, content_type, frame_type, this]() {
Philipp Hancked970b092022-06-17 05:34:23622 OnDecodedFrame(meta, qp, decode_time, processing_delay, assembly_time,
Philipp Hancke51657432023-08-15 08:20:50623 content_type, frame_type);
Philipp Hancke0359ba22022-05-05 13:55:36624 }));
Tommid93bf122020-05-10 18:24:59625}
Tommi553c8692020-05-05 13:35:45626
Tommid93bf122020-05-10 18:24:59627void ReceiveStatisticsProxy::OnDecodedFrame(
628 const VideoFrameMetaData& frame_meta,
Florent Castelli8037fc62024-08-29 13:00:40629 std::optional<uint8_t> qp,
Philipp Hancked970b092022-06-17 05:34:23630 TimeDelta decode_time,
Philipp Hanckea204ad22022-07-08 16:43:25631 TimeDelta processing_delay,
632 TimeDelta assembly_time,
Philipp Hancke51657432023-08-15 08:20:50633 VideoContentType content_type,
634 VideoFrameType frame_type) {
Tommid93bf122020-05-10 18:24:59635 RTC_DCHECK_RUN_ON(&main_thread_);
Tommi74fc5742020-04-27 08:43:06636
Tommi553c8692020-05-05 13:35:45637 const bool is_screenshare =
638 videocontenttypehelpers::IsScreenshare(content_type);
639 const bool was_screenshare =
640 videocontenttypehelpers::IsScreenshare(last_content_type_);
641
642 if (is_screenshare != was_screenshare) {
Tommi74fc5742020-04-27 08:43:06643 // Reset the quality observer if content type is switched. But first report
644 // stats for the previous part of the call.
Tommi553c8692020-05-05 13:35:45645 video_quality_observer_->UpdateHistograms(was_screenshare);
646 video_quality_observer_.reset(new VideoQualityObserver());
Tommi74fc5742020-04-27 08:43:06647 }
648
Tommid93bf122020-05-10 18:24:59649 video_quality_observer_->OnDecodedFrame(frame_meta.rtp_timestamp, qp,
Tommid7e08c82020-05-10 09:24:43650 last_codec_type_);
Tommi74fc5742020-04-27 08:43:06651
652 ContentSpecificStats* content_specific_stats =
653 &content_specific_stats_[content_type];
Tommi674b0c812020-05-10 19:52:17654
Tommi74fc5742020-04-27 08:43:06655 ++stats_.frames_decoded;
Philipp Hancke51657432023-08-15 08:20:50656 if (frame_type == VideoFrameType::kVideoFrameKey) {
657 ++stats_.frame_counts.key_frames;
658 } else {
659 ++stats_.frame_counts.delta_frames;
660 }
Tommi74fc5742020-04-27 08:43:06661 if (qp) {
662 if (!stats_.qp_sum) {
663 if (stats_.frames_decoded != 1) {
664 RTC_LOG(LS_WARNING)
665 << "Frames decoded was not 1 when first qp value was received.";
666 }
667 stats_.qp_sum = 0;
668 }
669 *stats_.qp_sum += *qp;
670 content_specific_stats->qp_counter.Add(*qp);
671 } else if (stats_.qp_sum) {
672 RTC_LOG(LS_WARNING)
673 << "QP sum was already set and no QP was given for a frame.";
674 stats_.qp_sum.reset();
675 }
Philipp Hancked970b092022-06-17 05:34:23676 decode_time_counter_.Add(decode_time.ms());
677 stats_.decode_ms = decode_time.ms();
678 stats_.total_decode_time += decode_time;
Philipp Hanckea16a6a62022-04-25 10:21:30679 stats_.total_processing_delay += processing_delay;
Philipp Hancke0359ba22022-05-05 13:55:36680 stats_.total_assembly_time += assembly_time;
681 if (!assembly_time.IsZero()) {
682 ++stats_.frames_assembled_from_multiple_packets;
683 }
Tommi74fc5742020-04-27 08:43:06684
685 last_content_type_ = content_type;
Tommid93bf122020-05-10 18:24:59686 decode_fps_estimator_.Update(1, frame_meta.decode_timestamp.ms());
687
Tommi74fc5742020-04-27 08:43:06688 if (last_decoded_frame_time_ms_) {
Tommid93bf122020-05-10 18:24:59689 int64_t interframe_delay_ms =
690 frame_meta.decode_timestamp.ms() - *last_decoded_frame_time_ms_;
Tommi74fc5742020-04-27 08:43:06691 RTC_DCHECK_GE(interframe_delay_ms, 0);
Tommid93bf122020-05-10 18:24:59692 interframe_delay_max_moving_.Add(interframe_delay_ms,
693 frame_meta.decode_timestamp.ms());
Tommi74fc5742020-04-27 08:43:06694 content_specific_stats->interframe_delay_counter.Add(interframe_delay_ms);
695 content_specific_stats->interframe_delay_percentiles.Add(
696 interframe_delay_ms);
697 content_specific_stats->flow_duration_ms += interframe_delay_ms;
698 }
699 if (stats_.frames_decoded == 1) {
Tommid93bf122020-05-10 18:24:59700 first_decoded_frame_time_ms_.emplace(frame_meta.decode_timestamp.ms());
Tommi74fc5742020-04-27 08:43:06701 }
Tommid93bf122020-05-10 18:24:59702 last_decoded_frame_time_ms_.emplace(frame_meta.decode_timestamp.ms());
Tommi74fc5742020-04-27 08:43:06703}
704
Tommid7e08c82020-05-10 09:24:43705void ReceiveStatisticsProxy::OnRenderedFrame(
706 const VideoFrameMetaData& frame_meta) {
707 RTC_DCHECK_RUN_ON(&main_thread_);
708 // Called from VideoReceiveStream2::OnFrame.
Tommi553c8692020-05-05 13:35:45709
Tommid7e08c82020-05-10 09:24:43710 RTC_DCHECK_GT(frame_meta.width, 0);
711 RTC_DCHECK_GT(frame_meta.height, 0);
712
Tommid7e08c82020-05-10 09:24:43713 video_quality_observer_->OnRenderedFrame(frame_meta);
Tommi74fc5742020-04-27 08:43:06714
715 ContentSpecificStats* content_specific_stats =
716 &content_specific_stats_[last_content_type_];
Tommid7e08c82020-05-10 09:24:43717 renders_fps_estimator_.Update(1, frame_meta.decode_timestamp.ms());
Tommi674b0c812020-05-10 19:52:17718
Tommi74fc5742020-04-27 08:43:06719 ++stats_.frames_rendered;
Tommid7e08c82020-05-10 09:24:43720 stats_.width = frame_meta.width;
721 stats_.height = frame_meta.height;
Tommi674b0c812020-05-10 19:52:17722
Tommi74fc5742020-04-27 08:43:06723 render_fps_tracker_.AddSamples(1);
Tommid7e08c82020-05-10 09:24:43724 render_pixel_tracker_.AddSamples(sqrt(frame_meta.width * frame_meta.height));
725 content_specific_stats->received_width.Add(frame_meta.width);
726 content_specific_stats->received_height.Add(frame_meta.height);
Tommi74fc5742020-04-27 08:43:06727
728 // Consider taking stats_.render_delay_ms into account.
Tommid7e08c82020-05-10 09:24:43729 const int64_t time_until_rendering_ms =
730 frame_meta.render_time_ms() - frame_meta.decode_timestamp.ms();
Tommi74fc5742020-04-27 08:43:06731 if (time_until_rendering_ms < 0) {
732 sum_missed_render_deadline_ms_ += -time_until_rendering_ms;
733 ++num_delayed_frames_rendered_;
734 }
735
Tommid7e08c82020-05-10 09:24:43736 if (frame_meta.ntp_time_ms > 0) {
737 int64_t delay_ms =
738 clock_->CurrentNtpInMilliseconds() - frame_meta.ntp_time_ms;
Tommi74fc5742020-04-27 08:43:06739 if (delay_ms >= 0) {
740 content_specific_stats->e2e_delay_counter.Add(delay_ms);
741 }
742 }
Tommi74fc5742020-04-27 08:43:06743}
744
745void ReceiveStatisticsProxy::OnSyncOffsetUpdated(int64_t video_playout_ntp_ms,
746 int64_t sync_offset_ms,
747 double estimated_freq_khz) {
Tommi55107c82021-06-16 14:31:18748 RTC_DCHECK_RUN_ON(&main_thread_);
Tommi74fc5742020-04-27 08:43:06749
Tommi55107c82021-06-16 14:31:18750 const int64_t now_ms = clock_->TimeInMilliseconds();
751 sync_offset_counter_.Add(std::abs(sync_offset_ms));
752 stats_.sync_offset_ms = sync_offset_ms;
753 last_estimated_playout_ntp_timestamp_ms_ = video_playout_ntp_ms;
754 last_estimated_playout_time_ms_ = now_ms;
Tommi74fc5742020-04-27 08:43:06755
Tommi55107c82021-06-16 14:31:18756 const double kMaxFreqKhz = 10000.0;
757 int offset_khz = kMaxFreqKhz;
758 // Should not be zero or negative. If so, report max.
759 if (estimated_freq_khz < kMaxFreqKhz && estimated_freq_khz > 0.0)
760 offset_khz = static_cast<int>(std::fabs(estimated_freq_khz - 90.0) + 0.5);
761
762 freq_offset_counter_.Add(offset_khz);
Tommi74fc5742020-04-27 08:43:06763}
764
765void ReceiveStatisticsProxy::OnCompleteFrame(bool is_keyframe,
766 size_t size_bytes,
767 VideoContentType content_type) {
Tommid93bf122020-05-10 18:24:59768 RTC_DCHECK_RUN_ON(&main_thread_);
Tommi674b0c812020-05-10 19:52:17769
Tommi74fc5742020-04-27 08:43:06770 // Content type extension is set only for keyframes and should be propagated
771 // for all the following delta frames. Here we may receive frames out of order
772 // and miscategorise some delta frames near the layer switch.
773 // This may slightly offset calculated bitrate and keyframes permille metrics.
774 VideoContentType propagated_content_type =
775 is_keyframe ? content_type : last_content_type_;
776
777 ContentSpecificStats* content_specific_stats =
778 &content_specific_stats_[propagated_content_type];
779
780 content_specific_stats->total_media_bytes += size_bytes;
781 if (is_keyframe) {
782 ++content_specific_stats->frame_counts.key_frames;
783 } else {
784 ++content_specific_stats->frame_counts.delta_frames;
785 }
786
787 int64_t now_ms = clock_->TimeInMilliseconds();
788 frame_window_.insert(std::make_pair(now_ms, size_bytes));
789 UpdateFramerate(now_ms);
790}
791
792void ReceiveStatisticsProxy::OnDroppedFrames(uint32_t frames_dropped) {
Tommi430951a2020-05-19 21:27:29793 // Can be called on either the decode queue or the worker thread
794 // See FrameBuffer2 for more details.
Danil Chapovalov95eeaa72022-07-06 08:14:29795 worker_thread_->PostTask(
796 SafeTask(task_safety_.flag(), [frames_dropped, this]() {
797 RTC_DCHECK_RUN_ON(&main_thread_);
798 stats_.frames_dropped += frames_dropped;
799 }));
Tommi74fc5742020-04-27 08:43:06800}
801
802void ReceiveStatisticsProxy::OnPreDecode(VideoCodecType codec_type, int qp) {
Evan Shrubsolea535ee62022-08-17 09:51:25803 RTC_DCHECK_RUN_ON(&main_thread_);
804 last_codec_type_ = codec_type;
805 if (last_codec_type_ == kVideoCodecVP8 && qp != -1) {
806 qp_counters_.vp8.Add(qp);
Evan Shrubsolea535ee62022-08-17 09:51:25807 }
Tommi74fc5742020-04-27 08:43:06808}
809
810void ReceiveStatisticsProxy::OnStreamInactive() {
Tommid93bf122020-05-10 18:24:59811 RTC_DCHECK_RUN_ON(&main_thread_);
Tommi553c8692020-05-05 13:35:45812
Tommi74fc5742020-04-27 08:43:06813 // TODO(sprang): Figure out any other state that should be reset.
814
Tommi74fc5742020-04-27 08:43:06815 // Don't report inter-frame delay if stream was paused.
816 last_decoded_frame_time_ms_.reset();
Tommid93bf122020-05-10 18:24:59817
Tommi74fc5742020-04-27 08:43:06818 video_quality_observer_->OnStreamInactive();
819}
820
Tommi674b0c812020-05-10 19:52:17821void ReceiveStatisticsProxy::OnRttUpdate(int64_t avg_rtt_ms) {
Tommi822a8742020-05-10 22:42:30822 RTC_DCHECK_RUN_ON(&main_thread_);
Tommiffd0a842020-05-11 06:24:47823 avg_rtt_ms_ = avg_rtt_ms;
Tommi74fc5742020-04-27 08:43:06824}
825
Emil Vardar3f1e51d2024-08-27 12:14:33826void ReceiveStatisticsProxy::OnCorruptionScore(double corruption_score,
827 VideoContentType content_type) {
Emil Vardar129f2282024-10-14 14:40:56828 worker_thread_->PostTask(SafeTask(task_safety_.flag(), [corruption_score,
829 content_type, this] {
830 RTC_DCHECK_RUN_ON(&main_thread_);
Emil Vardar3f1e51d2024-08-27 12:14:33831
Emil Vardar129f2282024-10-14 14:40:56832 if (!stats_.corruption_score_sum.has_value()) {
833 RTC_DCHECK(!stats_.corruption_score_squared_sum.has_value());
834 RTC_DCHECK_EQ(stats_.corruption_score_count, 0);
835 stats_.corruption_score_sum = 0;
836 stats_.corruption_score_squared_sum = 0;
837 }
838 *stats_.corruption_score_sum += corruption_score;
839 *stats_.corruption_score_squared_sum += corruption_score * corruption_score;
840 ++stats_.corruption_score_count;
Emil Vardar3f1e51d2024-08-27 12:14:33841
Emil Vardar129f2282024-10-14 14:40:56842 ContentSpecificStats* content_specific_stats =
843 &content_specific_stats_[content_type];
844 content_specific_stats->corruption_score.AddSample(corruption_score);
845 }));
Emil Vardar3f1e51d2024-08-27 12:14:33846}
847
Tommi74fc5742020-04-27 08:43:06848void ReceiveStatisticsProxy::DecoderThreadStarting() {
849 RTC_DCHECK_RUN_ON(&main_thread_);
850}
851
852void ReceiveStatisticsProxy::DecoderThreadStopped() {
853 RTC_DCHECK_RUN_ON(&main_thread_);
Tommi553c8692020-05-05 13:35:45854 decode_queue_.Detach();
Tommi74fc5742020-04-27 08:43:06855}
856
857ReceiveStatisticsProxy::ContentSpecificStats::ContentSpecificStats()
858 : interframe_delay_percentiles(kMaxCommonInterframeDelayMs) {}
859
860ReceiveStatisticsProxy::ContentSpecificStats::~ContentSpecificStats() = default;
861
862void ReceiveStatisticsProxy::ContentSpecificStats::Add(
863 const ContentSpecificStats& other) {
864 e2e_delay_counter.Add(other.e2e_delay_counter);
865 interframe_delay_counter.Add(other.interframe_delay_counter);
866 flow_duration_ms += other.flow_duration_ms;
867 total_media_bytes += other.total_media_bytes;
868 received_height.Add(other.received_height);
869 received_width.Add(other.received_width);
870 qp_counter.Add(other.qp_counter);
871 frame_counts.key_frames += other.frame_counts.key_frames;
872 frame_counts.delta_frames += other.frame_counts.delta_frames;
873 interframe_delay_percentiles.Add(other.interframe_delay_percentiles);
Emil Vardar3f1e51d2024-08-27 12:14:33874 corruption_score.MergeStatistics(other.corruption_score);
Tommi74fc5742020-04-27 08:43:06875}
876
877} // namespace internal
878} // namespace webrtc