blob: 82951c8a50a8b6efc82796248d01a6c36577b41a [file] [log] [blame]
sprang@webrtc.org09315702014-02-07 12:06:29 +00001/*
2 * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
3 *
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
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020011#include "video/receive_statistics_proxy.h"
sprang@webrtc.org09315702014-02-07 12:06:29 +000012
philipela45102f2017-02-22 05:30:39 -080013#include <algorithm>
asaperssonf839dcc2015-10-08 00:41:59 -070014#include <cmath>
philipela45102f2017-02-22 05:30:39 -080015#include <utility>
asaperssonf839dcc2015-10-08 00:41:59 -070016
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020017#include "modules/video_coding/include/video_codec_interface.h"
18#include "rtc_base/checks.h"
19#include "rtc_base/logging.h"
Tommifef05002018-02-27 13:51:08 +010020#include "rtc_base/strings/string_builder.h"
Steve Anton10542f22019-01-11 09:11:00 -080021#include "rtc_base/time_utils.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020022#include "system_wrappers/include/clock.h"
Johannes Krone76b3ab2019-10-22 13:22:26 +020023#include "system_wrappers/include/field_trial.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020024#include "system_wrappers/include/metrics.h"
sprang@webrtc.org09315702014-02-07 12:06:29 +000025
26namespace webrtc {
asaperssonde9e5ff2016-11-02 07:14:03 -070027namespace {
28// Periodic time interval for processing samples for |freq_offset_counter_|.
29const int64_t kFreqOffsetProcessIntervalMs = 40000;
palmkvist349092b2016-12-13 02:45:57 -080030
31// Configuration for bad call detection.
palmkvista40672a2017-01-13 05:58:34 -080032const int kBadCallMinRequiredSamples = 10;
palmkvist349092b2016-12-13 02:45:57 -080033const int kMinSampleLengthMs = 990;
34const int kNumMeasurements = 10;
35const int kNumMeasurementsVariance = kNumMeasurements * 1.5;
36const float kBadFraction = 0.8f;
37// For fps:
38// Low means low enough to be bad, high means high enough to be good
39const int kLowFpsThreshold = 12;
40const int kHighFpsThreshold = 14;
41// For qp and fps variance:
42// Low means low enough to be good, high means high enough to be bad
43const int kLowQpThresholdVp8 = 60;
44const int kHighQpThresholdVp8 = 70;
45const int kLowVarianceThreshold = 1;
46const int kHighVarianceThreshold = 2;
philipela45102f2017-02-22 05:30:39 -080047
ilnika79cc282017-08-23 05:24:10 -070048// Some metrics are reported as a maximum over this period.
Ilya Nikolaevskiyb06b3582017-10-16 17:59:12 +020049// This should be synchronized with a typical getStats polling interval in
50// the clients.
51const int kMovingMaxWindowMs = 1000;
ilnika79cc282017-08-23 05:24:10 -070052
philipela45102f2017-02-22 05:30:39 -080053// How large window we use to calculate the framerate/bitrate.
54const int kRateStatisticsWindowSizeMs = 1000;
ilnik6d5b4d62017-08-30 03:32:14 -070055
Ilya Nikolaevskiydaa4f7a2017-10-06 12:29:47 +020056// Some sane ballpark estimate for maximum common value of inter-frame delay.
57// Values below that will be stored explicitly in the array,
58// values above - in the map.
59const int kMaxCommonInterframeDelayMs = 500;
60
Tommifef05002018-02-27 13:51:08 +010061const char* UmaPrefixForContentType(VideoContentType content_type) {
62 if (videocontenttypehelpers::IsScreenshare(content_type))
63 return "WebRTC.Video.Screenshare";
64 return "WebRTC.Video";
ilnik6d5b4d62017-08-30 03:32:14 -070065}
66
67std::string UmaSuffixForContentType(VideoContentType content_type) {
Karl Wiberg881f1682018-03-08 15:03:23 +010068 char ss_buf[1024];
69 rtc::SimpleStringBuilder ss(ss_buf);
ilnik6d5b4d62017-08-30 03:32:14 -070070 int simulcast_id = videocontenttypehelpers::GetSimulcastId(content_type);
71 if (simulcast_id > 0) {
72 ss << ".S" << simulcast_id - 1;
73 }
74 int experiment_id = videocontenttypehelpers::GetExperimentId(content_type);
75 if (experiment_id > 0) {
76 ss << ".ExperimentGroup" << experiment_id - 1;
77 }
78 return ss.str();
79}
Tommifef05002018-02-27 13:51:08 +010080
asaperssonde9e5ff2016-11-02 07:14:03 -070081} // namespace
sprang@webrtc.org09315702014-02-07 12:06:29 +000082
sprang0ab8e812016-02-24 01:35:40 -080083ReceiveStatisticsProxy::ReceiveStatisticsProxy(
Tommi733b5472016-06-10 17:58:01 +020084 const VideoReceiveStream::Config* config,
sprang0ab8e812016-02-24 01:35:40 -080085 Clock* clock)
pbos@webrtc.org55707692014-12-19 15:45:03 +000086 : clock_(clock),
Tommi733b5472016-06-10 17:58:01 +020087 config_(*config),
asapersson4374a092016-07-27 00:39:09 -070088 start_ms_(clock->TimeInMilliseconds()),
Johannes Krone76b3ab2019-10-22 13:22:26 +020089 enable_decode_time_histograms_(
90 !field_trial::IsEnabled("WebRTC-DecodeTimeHistogramsKillSwitch")),
palmkvist349092b2016-12-13 02:45:57 -080091 last_sample_time_(clock->TimeInMilliseconds()),
92 fps_threshold_(kLowFpsThreshold,
93 kHighFpsThreshold,
94 kBadFraction,
95 kNumMeasurements),
96 qp_threshold_(kLowQpThresholdVp8,
97 kHighQpThresholdVp8,
98 kBadFraction,
99 kNumMeasurements),
100 variance_threshold_(kLowVarianceThreshold,
101 kHighVarianceThreshold,
102 kBadFraction,
103 kNumMeasurementsVariance),
palmkvista40672a2017-01-13 05:58:34 -0800104 num_bad_states_(0),
105 num_certain_states_(0),
sprang@webrtc.org09315702014-02-07 12:06:29 +0000106 // 1000ms window, scale 1000 for ms to s.
107 decode_fps_estimator_(1000, 1000),
Tim Psiaki63046262015-09-14 10:38:08 -0700108 renders_fps_estimator_(1000, 1000),
Honghai Zhang82d78622016-05-06 11:29:15 -0700109 render_fps_tracker_(100, 10u),
asaperssonde9e5ff2016-11-02 07:14:03 -0700110 render_pixel_tracker_(100, 10u),
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +0200111 video_quality_observer_(
112 new VideoQualityObserver(VideoContentType::UNSPECIFIED)),
ilnika79cc282017-08-23 05:24:10 -0700113 interframe_delay_max_moving_(kMovingMaxWindowMs),
asapersson0c43f772016-11-30 01:42:26 -0800114 freq_offset_counter_(clock, nullptr, kFreqOffsetProcessIntervalMs),
ilnik00d802b2017-04-11 10:34:31 -0700115 avg_rtt_ms_(0),
ilnik75204c52017-09-04 03:35:40 -0700116 last_content_type_(VideoContentType::UNSPECIFIED),
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +0200117 last_codec_type_(kVideoCodecVP8),
Åsa Persson81327d52018-06-05 13:34:33 +0200118 num_delayed_frames_rendered_(0),
119 sum_missed_render_deadline_ms_(0),
ilnik75204c52017-09-04 03:35:40 -0700120 timing_frame_info_counter_(kMovingMaxWindowMs) {
Sebastian Janssonc01367d2019-04-08 15:20:44 +0200121 decode_thread_.Detach();
122 network_thread_.Detach();
Tommi733b5472016-06-10 17:58:01 +0200123 stats_.ssrc = config_.rtp.remote_ssrc;
sprang@webrtc.org09315702014-02-07 12:06:29 +0000124}
125
Niels Möller9a9f18a2019-08-02 13:52:37 +0200126void ReceiveStatisticsProxy::UpdateHistograms(
Niels Möllerd7819652019-08-13 14:43:02 +0200127 absl::optional<int> fraction_lost,
128 const StreamDataCounters& rtp_stats,
129 const StreamDataCounters* rtx_stats) {
Niels Möller9a9f18a2019-08-02 13:52:37 +0200130 // Not actually running on the decoder thread, but must be called after
131 // DecoderThreadStopped, which detaches the thread checker. It is therefore
132 // safe to access |qp_counters_|, which were updated on the decode thread
133 // earlier.
Tommi132e28e2018-02-24 17:57:33 +0100134 RTC_DCHECK_RUN_ON(&decode_thread_);
Niels Möller9a9f18a2019-08-02 13:52:37 +0200135
136 rtc::CritScope lock(&crit_);
137
Karl Wiberg881f1682018-03-08 15:03:23 +0100138 char log_stream_buf[8 * 1024];
139 rtc::SimpleStringBuilder log_stream(log_stream_buf);
ilnik6d5b4d62017-08-30 03:32:14 -0700140 int stream_duration_sec = (clock_->TimeInMilliseconds() - start_ms_) / 1000;
141 if (stats_.frame_counts.key_frames > 0 ||
142 stats_.frame_counts.delta_frames > 0) {
143 RTC_HISTOGRAM_COUNTS_100000("WebRTC.Video.ReceiveStreamLifetimeInSeconds",
144 stream_duration_sec);
Tommifef05002018-02-27 13:51:08 +0100145 log_stream << "WebRTC.Video.ReceiveStreamLifetimeInSeconds "
146 << stream_duration_sec << '\n';
ilnik6d5b4d62017-08-30 03:32:14 -0700147 }
asapersson4374a092016-07-27 00:39:09 -0700148
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +0200149 log_stream << "Frames decoded " << stats_.frames_decoded << '\n';
Ilya Nikolaevskiyd397a0d2018-02-21 15:57:09 +0100150
151 if (num_unique_frames_) {
152 int num_dropped_frames = *num_unique_frames_ - stats_.frames_decoded;
153 RTC_HISTOGRAM_COUNTS_1000("WebRTC.Video.DroppedFrames.Receiver",
154 num_dropped_frames);
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +0200155 log_stream << "WebRTC.Video.DroppedFrames.Receiver " << num_dropped_frames
156 << '\n';
Ilya Nikolaevskiyd397a0d2018-02-21 15:57:09 +0100157 }
158
Niels Möller9a9f18a2019-08-02 13:52:37 +0200159 if (fraction_lost && stream_duration_sec >= metrics::kMinRunTimeInSeconds) {
160 RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.ReceivedPacketsLostInPercent",
161 *fraction_lost);
162 log_stream << "WebRTC.Video.ReceivedPacketsLostInPercent " << *fraction_lost
163 << '\n';
Åsa Persson3c391cb2015-04-27 10:09:49 +0200164 }
asapersson0c43f772016-11-30 01:42:26 -0800165
Åsa Perssonb9b07ea2018-01-24 17:04:07 +0100166 if (first_decoded_frame_time_ms_) {
167 const int64_t elapsed_ms =
168 (clock_->TimeInMilliseconds() - *first_decoded_frame_time_ms_);
169 if (elapsed_ms >=
170 metrics::kMinRunTimeInSeconds * rtc::kNumMillisecsPerSec) {
Åsa Perssonafb8d5c2019-05-21 14:42:29 +0200171 int decoded_fps = static_cast<int>(
172 (stats_.frames_decoded * 1000.0f / elapsed_ms) + 0.5f);
173 RTC_HISTOGRAM_COUNTS_100("WebRTC.Video.DecodedFramesPerSecond",
174 decoded_fps);
175 log_stream << "WebRTC.Video.DecodedFramesPerSecond " << decoded_fps
176 << '\n';
Åsa Persson81327d52018-06-05 13:34:33 +0200177
178 const uint32_t frames_rendered = stats_.frames_rendered;
179 if (frames_rendered > 0) {
180 RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.DelayedFramesToRenderer",
181 static_cast<int>(num_delayed_frames_rendered_ *
182 100 / frames_rendered));
183 if (num_delayed_frames_rendered_ > 0) {
184 RTC_HISTOGRAM_COUNTS_1000(
185 "WebRTC.Video.DelayedFramesToRenderer_AvgDelayInMs",
186 static_cast<int>(sum_missed_render_deadline_ms_ /
187 num_delayed_frames_rendered_));
188 }
189 }
Åsa Perssonb9b07ea2018-01-24 17:04:07 +0100190 }
191 }
192
asapersson6718e972015-07-24 00:20:58 -0700193 const int kMinRequiredSamples = 200;
asaperssonf839dcc2015-10-08 00:41:59 -0700194 int samples = static_cast<int>(render_fps_tracker_.TotalSampleCount());
asapersson2077f2f2017-05-11 05:37:35 -0700195 if (samples >= kMinRequiredSamples) {
Åsa Perssonafb8d5c2019-05-21 14:42:29 +0200196 int rendered_fps = round(render_fps_tracker_.ComputeTotalRate());
asapersson1d02d3e2016-09-09 22:40:25 -0700197 RTC_HISTOGRAM_COUNTS_100("WebRTC.Video.RenderFramesPerSecond",
Åsa Perssonafb8d5c2019-05-21 14:42:29 +0200198 rendered_fps);
199 log_stream << "WebRTC.Video.RenderFramesPerSecond " << rendered_fps << '\n';
asapersson1d02d3e2016-09-09 22:40:25 -0700200 RTC_HISTOGRAM_COUNTS_100000(
asapersson28ba9272016-01-25 05:58:23 -0800201 "WebRTC.Video.RenderSqrtPixelsPerSecond",
Tim Psiakiad13d2f2015-11-10 16:34:50 -0800202 round(render_pixel_tracker_.ComputeTotalRate()));
asaperssonf839dcc2015-10-08 00:41:59 -0700203 }
ilnik6d5b4d62017-08-30 03:32:14 -0700204
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200205 absl::optional<int> sync_offset_ms =
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200206 sync_offset_counter_.Avg(kMinRequiredSamples);
207 if (sync_offset_ms) {
208 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.AVSyncOffsetInMs",
209 *sync_offset_ms);
210 log_stream << "WebRTC.Video.AVSyncOffsetInMs " << *sync_offset_ms << '\n';
pbos35fdb2a2016-05-03 03:32:10 -0700211 }
asaperssonde9e5ff2016-11-02 07:14:03 -0700212 AggregatedStats freq_offset_stats = freq_offset_counter_.GetStats();
213 if (freq_offset_stats.num_samples > 0) {
214 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.RtpToNtpFreqOffsetInKhz",
215 freq_offset_stats.average);
Tommifef05002018-02-27 13:51:08 +0100216 log_stream << "WebRTC.Video.RtpToNtpFreqOffsetInKhz "
217 << freq_offset_stats.ToString() << '\n';
asaperssonde9e5ff2016-11-02 07:14:03 -0700218 }
asaperssonf8cdd182016-03-15 01:00:47 -0700219
asaperssonb99baf82017-04-20 04:05:43 -0700220 int num_total_frames =
221 stats_.frame_counts.key_frames + stats_.frame_counts.delta_frames;
222 if (num_total_frames >= kMinRequiredSamples) {
223 int num_key_frames = stats_.frame_counts.key_frames;
philipela45102f2017-02-22 05:30:39 -0800224 int key_frames_permille =
asaperssonb99baf82017-04-20 04:05:43 -0700225 (num_key_frames * 1000 + num_total_frames / 2) / num_total_frames;
philipela45102f2017-02-22 05:30:39 -0800226 RTC_HISTOGRAM_COUNTS_1000("WebRTC.Video.KeyFramesReceivedInPermille",
227 key_frames_permille);
Tommifef05002018-02-27 13:51:08 +0100228 log_stream << "WebRTC.Video.KeyFramesReceivedInPermille "
229 << key_frames_permille << '\n';
philipela45102f2017-02-22 05:30:39 -0800230 }
231
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200232 absl::optional<int> qp = qp_counters_.vp8.Avg(kMinRequiredSamples);
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200233 if (qp) {
234 RTC_HISTOGRAM_COUNTS_200("WebRTC.Video.Decoded.Vp8.Qp", *qp);
235 log_stream << "WebRTC.Video.Decoded.Vp8.Qp " << *qp << '\n';
asapersson2077f2f2017-05-11 05:37:35 -0700236 }
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200237 absl::optional<int> decode_ms = decode_time_counter_.Avg(kMinRequiredSamples);
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200238 if (decode_ms) {
239 RTC_HISTOGRAM_COUNTS_1000("WebRTC.Video.DecodeTimeInMs", *decode_ms);
240 log_stream << "WebRTC.Video.DecodeTimeInMs " << *decode_ms << '\n';
asapersson2077f2f2017-05-11 05:37:35 -0700241 }
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200242 absl::optional<int> jb_delay_ms =
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200243 jitter_buffer_delay_counter_.Avg(kMinRequiredSamples);
244 if (jb_delay_ms) {
philipela45102f2017-02-22 05:30:39 -0800245 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.JitterBufferDelayInMs",
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200246 *jb_delay_ms);
247 log_stream << "WebRTC.Video.JitterBufferDelayInMs " << *jb_delay_ms << '\n';
asapersson8688a4e2016-04-27 23:42:35 -0700248 }
philipela45102f2017-02-22 05:30:39 -0800249
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200250 absl::optional<int> target_delay_ms =
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200251 target_delay_counter_.Avg(kMinRequiredSamples);
252 if (target_delay_ms) {
253 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.TargetDelayInMs",
254 *target_delay_ms);
255 log_stream << "WebRTC.Video.TargetDelayInMs " << *target_delay_ms << '\n';
asapersson8688a4e2016-04-27 23:42:35 -0700256 }
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200257 absl::optional<int> current_delay_ms =
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200258 current_delay_counter_.Avg(kMinRequiredSamples);
259 if (current_delay_ms) {
asapersson1d02d3e2016-09-09 22:40:25 -0700260 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.CurrentDelayInMs",
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200261 *current_delay_ms);
262 log_stream << "WebRTC.Video.CurrentDelayInMs " << *current_delay_ms << '\n';
asapersson8688a4e2016-04-27 23:42:35 -0700263 }
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200264 absl::optional<int> delay_ms = delay_counter_.Avg(kMinRequiredSamples);
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200265 if (delay_ms)
266 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.OnewayDelayInMs", *delay_ms);
sprang0ab8e812016-02-24 01:35:40 -0800267
ilnik6d5b4d62017-08-30 03:32:14 -0700268 // Aggregate content_specific_stats_ by removing experiment or simulcast
269 // information;
270 std::map<VideoContentType, ContentSpecificStats> aggregated_stats;
Mirko Bonadei739baf02019-01-27 17:29:42 +0100271 for (const auto& it : content_specific_stats_) {
ilnik6d5b4d62017-08-30 03:32:14 -0700272 // Calculate simulcast specific metrics (".S0" ... ".S2" suffixes).
273 VideoContentType content_type = it.first;
274 if (videocontenttypehelpers::GetSimulcastId(content_type) > 0) {
275 // Aggregate on experiment id.
276 videocontenttypehelpers::SetExperimentId(&content_type, 0);
277 aggregated_stats[content_type].Add(it.second);
278 }
279 // Calculate experiment specific metrics (".ExperimentGroup[0-7]" suffixes).
280 content_type = it.first;
281 if (videocontenttypehelpers::GetExperimentId(content_type) > 0) {
282 // Aggregate on simulcast id.
283 videocontenttypehelpers::SetSimulcastId(&content_type, 0);
284 aggregated_stats[content_type].Add(it.second);
285 }
286 // Calculate aggregated metrics (no suffixes. Aggregated on everything).
287 content_type = it.first;
288 videocontenttypehelpers::SetSimulcastId(&content_type, 0);
289 videocontenttypehelpers::SetExperimentId(&content_type, 0);
290 aggregated_stats[content_type].Add(it.second);
ilnik00d802b2017-04-11 10:34:31 -0700291 }
292
Mirko Bonadei739baf02019-01-27 17:29:42 +0100293 for (const auto& it : aggregated_stats) {
ilnik6d5b4d62017-08-30 03:32:14 -0700294 // For the metric Foo we report the following slices:
295 // WebRTC.Video.Foo,
296 // WebRTC.Video.Screenshare.Foo,
297 // WebRTC.Video.Foo.S[0-3],
298 // WebRTC.Video.Foo.ExperimentGroup[0-7],
299 // WebRTC.Video.Screenshare.Foo.S[0-3],
300 // WebRTC.Video.Screenshare.Foo.ExperimentGroup[0-7].
301 auto content_type = it.first;
302 auto stats = it.second;
303 std::string uma_prefix = UmaPrefixForContentType(content_type);
304 std::string uma_suffix = UmaSuffixForContentType(content_type);
305 // Metrics can be sliced on either simulcast id or experiment id but not
306 // both.
307 RTC_DCHECK(videocontenttypehelpers::GetExperimentId(content_type) == 0 ||
308 videocontenttypehelpers::GetSimulcastId(content_type) == 0);
ilnik00d802b2017-04-11 10:34:31 -0700309
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200310 absl::optional<int> e2e_delay_ms =
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200311 stats.e2e_delay_counter.Avg(kMinRequiredSamples);
312 if (e2e_delay_ms) {
ilnik6d5b4d62017-08-30 03:32:14 -0700313 RTC_HISTOGRAM_COUNTS_SPARSE_10000(
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200314 uma_prefix + ".EndToEndDelayInMs" + uma_suffix, *e2e_delay_ms);
Tommifef05002018-02-27 13:51:08 +0100315 log_stream << uma_prefix << ".EndToEndDelayInMs" << uma_suffix << " "
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200316 << *e2e_delay_ms << '\n';
ilnik6d5b4d62017-08-30 03:32:14 -0700317 }
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200318 absl::optional<int> e2e_delay_max_ms = stats.e2e_delay_counter.Max();
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200319 if (e2e_delay_max_ms && e2e_delay_ms) {
ilnik6d5b4d62017-08-30 03:32:14 -0700320 RTC_HISTOGRAM_COUNTS_SPARSE_100000(
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200321 uma_prefix + ".EndToEndDelayMaxInMs" + uma_suffix, *e2e_delay_max_ms);
Tommifef05002018-02-27 13:51:08 +0100322 log_stream << uma_prefix << ".EndToEndDelayMaxInMs" << uma_suffix << " "
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200323 << *e2e_delay_max_ms << '\n';
ilnik6d5b4d62017-08-30 03:32:14 -0700324 }
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200325 absl::optional<int> interframe_delay_ms =
ilnik6d5b4d62017-08-30 03:32:14 -0700326 stats.interframe_delay_counter.Avg(kMinRequiredSamples);
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200327 if (interframe_delay_ms) {
ilnik6d5b4d62017-08-30 03:32:14 -0700328 RTC_HISTOGRAM_COUNTS_SPARSE_10000(
329 uma_prefix + ".InterframeDelayInMs" + uma_suffix,
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200330 *interframe_delay_ms);
Tommifef05002018-02-27 13:51:08 +0100331 log_stream << uma_prefix << ".InterframeDelayInMs" << uma_suffix << " "
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200332 << *interframe_delay_ms << '\n';
ilnik6d5b4d62017-08-30 03:32:14 -0700333 }
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200334 absl::optional<int> interframe_delay_max_ms =
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200335 stats.interframe_delay_counter.Max();
336 if (interframe_delay_max_ms && interframe_delay_ms) {
ilnik6d5b4d62017-08-30 03:32:14 -0700337 RTC_HISTOGRAM_COUNTS_SPARSE_10000(
338 uma_prefix + ".InterframeDelayMaxInMs" + uma_suffix,
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200339 *interframe_delay_max_ms);
Tommifef05002018-02-27 13:51:08 +0100340 log_stream << uma_prefix << ".InterframeDelayMaxInMs" << uma_suffix << " "
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200341 << *interframe_delay_max_ms << '\n';
ilnik6d5b4d62017-08-30 03:32:14 -0700342 }
ilnik00d802b2017-04-11 10:34:31 -0700343
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200344 absl::optional<uint32_t> interframe_delay_95p_ms =
Ilya Nikolaevskiydaa4f7a2017-10-06 12:29:47 +0200345 stats.interframe_delay_percentiles.GetPercentile(0.95f);
346 if (interframe_delay_95p_ms && interframe_delay_ms != -1) {
347 RTC_HISTOGRAM_COUNTS_SPARSE_10000(
348 uma_prefix + ".InterframeDelay95PercentileInMs" + uma_suffix,
349 *interframe_delay_95p_ms);
Tommifef05002018-02-27 13:51:08 +0100350 log_stream << uma_prefix << ".InterframeDelay95PercentileInMs"
351 << uma_suffix << " " << *interframe_delay_95p_ms << '\n';
Ilya Nikolaevskiydaa4f7a2017-10-06 12:29:47 +0200352 }
353
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200354 absl::optional<int> width = stats.received_width.Avg(kMinRequiredSamples);
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200355 if (width) {
ilnik6d5b4d62017-08-30 03:32:14 -0700356 RTC_HISTOGRAM_COUNTS_SPARSE_10000(
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200357 uma_prefix + ".ReceivedWidthInPixels" + uma_suffix, *width);
Tommifef05002018-02-27 13:51:08 +0100358 log_stream << uma_prefix << ".ReceivedWidthInPixels" << uma_suffix << " "
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200359 << *width << '\n';
ilnik6d5b4d62017-08-30 03:32:14 -0700360 }
asapersson1490f7a2016-09-23 02:09:46 -0700361
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200362 absl::optional<int> height = stats.received_height.Avg(kMinRequiredSamples);
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200363 if (height) {
ilnik6d5b4d62017-08-30 03:32:14 -0700364 RTC_HISTOGRAM_COUNTS_SPARSE_10000(
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200365 uma_prefix + ".ReceivedHeightInPixels" + uma_suffix, *height);
Tommifef05002018-02-27 13:51:08 +0100366 log_stream << uma_prefix << ".ReceivedHeightInPixels" << uma_suffix << " "
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200367 << *height << '\n';
ilnik6d5b4d62017-08-30 03:32:14 -0700368 }
ilnik4257ab22017-07-03 01:15:58 -0700369
ilnik6d5b4d62017-08-30 03:32:14 -0700370 if (content_type != VideoContentType::UNSPECIFIED) {
371 // Don't report these 3 metrics unsliced, as more precise variants
372 // are reported separately in this method.
373 float flow_duration_sec = stats.flow_duration_ms / 1000.0;
374 if (flow_duration_sec >= metrics::kMinRunTimeInSeconds) {
375 int media_bitrate_kbps = static_cast<int>(stats.total_media_bytes * 8 /
376 flow_duration_sec / 1000);
377 RTC_HISTOGRAM_COUNTS_SPARSE_10000(
378 uma_prefix + ".MediaBitrateReceivedInKbps" + uma_suffix,
379 media_bitrate_kbps);
Tommifef05002018-02-27 13:51:08 +0100380 log_stream << uma_prefix << ".MediaBitrateReceivedInKbps" << uma_suffix
381 << " " << media_bitrate_kbps << '\n';
ilnik6d5b4d62017-08-30 03:32:14 -0700382 }
383
384 int num_total_frames =
385 stats.frame_counts.key_frames + stats.frame_counts.delta_frames;
386 if (num_total_frames >= kMinRequiredSamples) {
387 int num_key_frames = stats.frame_counts.key_frames;
388 int key_frames_permille =
389 (num_key_frames * 1000 + num_total_frames / 2) / num_total_frames;
390 RTC_HISTOGRAM_COUNTS_SPARSE_1000(
391 uma_prefix + ".KeyFramesReceivedInPermille" + uma_suffix,
392 key_frames_permille);
Tommifef05002018-02-27 13:51:08 +0100393 log_stream << uma_prefix << ".KeyFramesReceivedInPermille" << uma_suffix
394 << " " << key_frames_permille << '\n';
ilnik6d5b4d62017-08-30 03:32:14 -0700395 }
396
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200397 absl::optional<int> qp = stats.qp_counter.Avg(kMinRequiredSamples);
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200398 if (qp) {
ilnik6d5b4d62017-08-30 03:32:14 -0700399 RTC_HISTOGRAM_COUNTS_SPARSE_200(
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200400 uma_prefix + ".Decoded.Vp8.Qp" + uma_suffix, *qp);
401 log_stream << uma_prefix << ".Decoded.Vp8.Qp" << uma_suffix << " "
402 << *qp << '\n';
ilnik6d5b4d62017-08-30 03:32:14 -0700403 }
404 }
ilnik4257ab22017-07-03 01:15:58 -0700405 }
406
Niels Möllerd7819652019-08-13 14:43:02 +0200407 StreamDataCounters rtp_rtx_stats = rtp_stats;
408 if (rtx_stats)
409 rtp_rtx_stats.Add(*rtx_stats);
sprang0ab8e812016-02-24 01:35:40 -0800410 int64_t elapsed_sec =
Niels Möllerd7819652019-08-13 14:43:02 +0200411 rtp_rtx_stats.TimeSinceFirstPacketInMs(clock_->TimeInMilliseconds()) /
412 1000;
asapersson2077f2f2017-05-11 05:37:35 -0700413 if (elapsed_sec >= metrics::kMinRunTimeInSeconds) {
asapersson1d02d3e2016-09-09 22:40:25 -0700414 RTC_HISTOGRAM_COUNTS_10000(
sprang0ab8e812016-02-24 01:35:40 -0800415 "WebRTC.Video.BitrateReceivedInKbps",
Niels Möllerd7819652019-08-13 14:43:02 +0200416 static_cast<int>(rtp_rtx_stats.transmitted.TotalBytes() * 8 /
417 elapsed_sec / 1000));
418 int media_bitrate_kbs = static_cast<int>(rtp_stats.MediaPayloadBytes() * 8 /
419 elapsed_sec / 1000);
ilnik6d5b4d62017-08-30 03:32:14 -0700420 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.MediaBitrateReceivedInKbps",
421 media_bitrate_kbs);
Tommifef05002018-02-27 13:51:08 +0100422 log_stream << "WebRTC.Video.MediaBitrateReceivedInKbps "
423 << media_bitrate_kbs << '\n';
asapersson1d02d3e2016-09-09 22:40:25 -0700424 RTC_HISTOGRAM_COUNTS_10000(
sprang0ab8e812016-02-24 01:35:40 -0800425 "WebRTC.Video.PaddingBitrateReceivedInKbps",
Niels Möllerd7819652019-08-13 14:43:02 +0200426 static_cast<int>(rtp_rtx_stats.transmitted.padding_bytes * 8 /
427 elapsed_sec / 1000));
asapersson1d02d3e2016-09-09 22:40:25 -0700428 RTC_HISTOGRAM_COUNTS_10000(
sprang0ab8e812016-02-24 01:35:40 -0800429 "WebRTC.Video.RetransmittedBitrateReceivedInKbps",
Niels Möllerd7819652019-08-13 14:43:02 +0200430 static_cast<int>(rtp_rtx_stats.retransmitted.TotalBytes() * 8 /
431 elapsed_sec / 1000));
432 if (rtx_stats) {
433 RTC_HISTOGRAM_COUNTS_10000(
434 "WebRTC.Video.RtxBitrateReceivedInKbps",
435 static_cast<int>(rtx_stats->transmitted.TotalBytes() * 8 /
436 elapsed_sec / 1000));
sprang0ab8e812016-02-24 01:35:40 -0800437 }
sprang07fb9be2016-02-24 07:55:00 -0800438 const RtcpPacketTypeCounter& counters = stats_.rtcp_packet_type_counts;
asapersson1d02d3e2016-09-09 22:40:25 -0700439 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.NackPacketsSentPerMinute",
440 counters.nack_packets * 60 / elapsed_sec);
441 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.FirPacketsSentPerMinute",
442 counters.fir_packets * 60 / elapsed_sec);
443 RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.PliPacketsSentPerMinute",
444 counters.pli_packets * 60 / elapsed_sec);
sprang07fb9be2016-02-24 07:55:00 -0800445 if (counters.nack_requests > 0) {
asapersson1d02d3e2016-09-09 22:40:25 -0700446 RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.UniqueNackRequestsSentInPercent",
447 counters.UniqueNackRequestsInPercent());
sprang07fb9be2016-02-24 07:55:00 -0800448 }
sprang0ab8e812016-02-24 01:35:40 -0800449 }
palmkvista40672a2017-01-13 05:58:34 -0800450
451 if (num_certain_states_ >= kBadCallMinRequiredSamples) {
452 RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.BadCall.Any",
453 100 * num_bad_states_ / num_certain_states_);
454 }
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200455 absl::optional<double> fps_fraction =
palmkvista40672a2017-01-13 05:58:34 -0800456 fps_threshold_.FractionHigh(kBadCallMinRequiredSamples);
457 if (fps_fraction) {
458 RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.BadCall.FrameRate",
459 static_cast<int>(100 * (1 - *fps_fraction)));
460 }
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200461 absl::optional<double> variance_fraction =
palmkvista40672a2017-01-13 05:58:34 -0800462 variance_threshold_.FractionHigh(kBadCallMinRequiredSamples);
463 if (variance_fraction) {
464 RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.BadCall.FrameRateVariance",
465 static_cast<int>(100 * *variance_fraction));
466 }
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200467 absl::optional<double> qp_fraction =
palmkvista40672a2017-01-13 05:58:34 -0800468 qp_threshold_.FractionHigh(kBadCallMinRequiredSamples);
469 if (qp_fraction) {
470 RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.BadCall.Qp",
471 static_cast<int>(100 * *qp_fraction));
472 }
Tommifef05002018-02-27 13:51:08 +0100473
474 RTC_LOG(LS_INFO) << log_stream.str();
Niels Möller9a9f18a2019-08-02 13:52:37 +0200475 video_quality_observer_->UpdateHistograms();
Åsa Persson3c391cb2015-04-27 10:09:49 +0200476}
sprang@webrtc.org09315702014-02-07 12:06:29 +0000477
palmkvist349092b2016-12-13 02:45:57 -0800478void ReceiveStatisticsProxy::QualitySample() {
479 int64_t now = clock_->TimeInMilliseconds();
480 if (last_sample_time_ + kMinSampleLengthMs > now)
481 return;
482
483 double fps =
484 render_fps_tracker_.ComputeRateForInterval(now - last_sample_time_);
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200485 absl::optional<int> qp = qp_sample_.Avg(1);
palmkvist349092b2016-12-13 02:45:57 -0800486
487 bool prev_fps_bad = !fps_threshold_.IsHigh().value_or(true);
488 bool prev_qp_bad = qp_threshold_.IsHigh().value_or(false);
489 bool prev_variance_bad = variance_threshold_.IsHigh().value_or(false);
490 bool prev_any_bad = prev_fps_bad || prev_qp_bad || prev_variance_bad;
491
492 fps_threshold_.AddMeasurement(static_cast<int>(fps));
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200493 if (qp)
494 qp_threshold_.AddMeasurement(*qp);
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200495 absl::optional<double> fps_variance_opt = fps_threshold_.CalculateVariance();
palmkvist349092b2016-12-13 02:45:57 -0800496 double fps_variance = fps_variance_opt.value_or(0);
497 if (fps_variance_opt) {
498 variance_threshold_.AddMeasurement(static_cast<int>(fps_variance));
499 }
500
501 bool fps_bad = !fps_threshold_.IsHigh().value_or(true);
502 bool qp_bad = qp_threshold_.IsHigh().value_or(false);
503 bool variance_bad = variance_threshold_.IsHigh().value_or(false);
504 bool any_bad = fps_bad || qp_bad || variance_bad;
505
506 if (!prev_any_bad && any_bad) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100507 RTC_LOG(LS_INFO) << "Bad call (any) start: " << now;
palmkvist349092b2016-12-13 02:45:57 -0800508 } else if (prev_any_bad && !any_bad) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100509 RTC_LOG(LS_INFO) << "Bad call (any) end: " << now;
palmkvist349092b2016-12-13 02:45:57 -0800510 }
511
512 if (!prev_fps_bad && fps_bad) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100513 RTC_LOG(LS_INFO) << "Bad call (fps) start: " << now;
palmkvist349092b2016-12-13 02:45:57 -0800514 } else if (prev_fps_bad && !fps_bad) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100515 RTC_LOG(LS_INFO) << "Bad call (fps) end: " << now;
palmkvist349092b2016-12-13 02:45:57 -0800516 }
517
518 if (!prev_qp_bad && qp_bad) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100519 RTC_LOG(LS_INFO) << "Bad call (qp) start: " << now;
palmkvist349092b2016-12-13 02:45:57 -0800520 } else if (prev_qp_bad && !qp_bad) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100521 RTC_LOG(LS_INFO) << "Bad call (qp) end: " << now;
palmkvist349092b2016-12-13 02:45:57 -0800522 }
523
524 if (!prev_variance_bad && variance_bad) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100525 RTC_LOG(LS_INFO) << "Bad call (variance) start: " << now;
palmkvist349092b2016-12-13 02:45:57 -0800526 } else if (prev_variance_bad && !variance_bad) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100527 RTC_LOG(LS_INFO) << "Bad call (variance) end: " << now;
palmkvist349092b2016-12-13 02:45:57 -0800528 }
529
Mirko Bonadei675513b2017-11-09 11:09:25 +0100530 RTC_LOG(LS_VERBOSE) << "SAMPLE: sample_length: " << (now - last_sample_time_)
531 << " fps: " << fps << " fps_bad: " << fps_bad
Ilya Nikolaevskiy0beed5d2018-05-22 10:54:30 +0200532 << " qp: " << qp.value_or(-1) << " qp_bad: " << qp_bad
Mirko Bonadei675513b2017-11-09 11:09:25 +0100533 << " variance_bad: " << variance_bad
534 << " fps_variance: " << fps_variance;
palmkvist349092b2016-12-13 02:45:57 -0800535
536 last_sample_time_ = now;
537 qp_sample_.Reset();
palmkvista40672a2017-01-13 05:58:34 -0800538
539 if (fps_threshold_.IsHigh() || variance_threshold_.IsHigh() ||
540 qp_threshold_.IsHigh()) {
541 if (any_bad)
542 ++num_bad_states_;
543 ++num_certain_states_;
544 }
palmkvist349092b2016-12-13 02:45:57 -0800545}
546
asapersson0255acb2017-03-28 02:44:58 -0700547void ReceiveStatisticsProxy::UpdateFramerate(int64_t now_ms) const {
philipela45102f2017-02-22 05:30:39 -0800548 int64_t old_frames_ms = now_ms - kRateStatisticsWindowSizeMs;
549 while (!frame_window_.empty() &&
550 frame_window_.begin()->first < old_frames_ms) {
philipela45102f2017-02-22 05:30:39 -0800551 frame_window_.erase(frame_window_.begin());
552 }
553
554 size_t framerate =
555 (frame_window_.size() * 1000 + 500) / kRateStatisticsWindowSizeMs;
philipela45102f2017-02-22 05:30:39 -0800556 stats_.network_frame_rate = static_cast<int>(framerate);
philipela45102f2017-02-22 05:30:39 -0800557}
558
Johannes Krone76b3ab2019-10-22 13:22:26 +0200559void ReceiveStatisticsProxy::UpdateDecodeTimeHistograms(
560 int width,
561 int height,
562 int decode_time_ms) const {
563 bool is_4k = (width == 3840 || width == 4096) && height == 2160;
564 bool is_hd = width == 1920 && height == 1080;
565 // Only update histograms for 4k/HD and VP9/H264.
566 if ((is_4k || is_hd) && (last_codec_type_ == kVideoCodecVP9 ||
567 last_codec_type_ == kVideoCodecH264)) {
568 const std::string kDecodeTimeUmaPrefix =
569 "WebRTC.Video.DecodeTimePerFrameInMs.";
570
571 // Each histogram needs its own line for it to not be reused in the wrong
572 // way when the format changes.
573 if (last_codec_type_ == kVideoCodecVP9) {
574 bool is_sw_decoder =
575 stats_.decoder_implementation_name.compare(0, 6, "libvpx") == 0;
576 if (is_4k) {
577 if (is_sw_decoder)
578 RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "Vp9.4k.Sw",
579 decode_time_ms);
580 else
581 RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "Vp9.4k.Hw",
582 decode_time_ms);
583 } else {
584 if (is_sw_decoder)
585 RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "Vp9.Hd.Sw",
586 decode_time_ms);
587 else
588 RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "Vp9.Hd.Hw",
589 decode_time_ms);
590 }
591 } else {
592 bool is_sw_decoder =
593 stats_.decoder_implementation_name.compare(0, 6, "FFmpeg") == 0;
594 if (is_4k) {
595 if (is_sw_decoder)
596 RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "H264.4k.Sw",
597 decode_time_ms);
598 else
599 RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "H264.4k.Hw",
600 decode_time_ms);
601
602 } else {
603 if (is_sw_decoder)
604 RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "H264.Hd.Sw",
605 decode_time_ms);
606 else
607 RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "H264.Hd.Hw",
608 decode_time_ms);
609 }
610 }
611 }
612}
613
Åsa Perssonfcf79cc2019-10-22 15:23:44 +0200614absl::optional<int64_t>
615ReceiveStatisticsProxy::GetCurrentEstimatedPlayoutNtpTimestampMs(
616 int64_t now_ms) const {
617 if (!last_estimated_playout_ntp_timestamp_ms_ ||
618 !last_estimated_playout_time_ms_) {
619 return absl::nullopt;
620 }
621 int64_t elapsed_ms = now_ms - *last_estimated_playout_time_ms_;
622 return *last_estimated_playout_ntp_timestamp_ms_ + elapsed_ms;
623}
624
sprang@webrtc.org09315702014-02-07 12:06:29 +0000625VideoReceiveStream::Stats ReceiveStatisticsProxy::GetStats() const {
Peter Boströmf2f82832015-05-01 13:00:41 +0200626 rtc::CritScope lock(&crit_);
sprang948b2752017-05-04 02:47:13 -0700627 // Get current frame rates here, as only updating them on new frames prevents
628 // us from ever correctly displaying frame rate of 0.
629 int64_t now_ms = clock_->TimeInMilliseconds();
630 UpdateFramerate(now_ms);
631 stats_.render_frame_rate = renders_fps_estimator_.Rate(now_ms).value_or(0);
632 stats_.decode_frame_rate = decode_fps_estimator_.Rate(now_ms).value_or(0);
ilnika79cc282017-08-23 05:24:10 -0700633 stats_.interframe_delay_max_ms =
634 interframe_delay_max_moving_.Max(now_ms).value_or(-1);
Sergey Silkin02371062019-01-31 16:45:42 +0100635 stats_.freeze_count = video_quality_observer_->NumFreezes();
636 stats_.pause_count = video_quality_observer_->NumPauses();
637 stats_.total_freezes_duration_ms =
638 video_quality_observer_->TotalFreezesDurationMs();
639 stats_.total_pauses_duration_ms =
640 video_quality_observer_->TotalPausesDurationMs();
641 stats_.total_frames_duration_ms =
642 video_quality_observer_->TotalFramesDurationMs();
643 stats_.sum_squared_frame_durations =
644 video_quality_observer_->SumSquaredFrameDurationsSec();
ilnik2e1b40b2017-09-04 07:57:17 -0700645 stats_.content_type = last_content_type_;
Sergey Silkin02371062019-01-31 16:45:42 +0100646 stats_.timing_frame_info = timing_frame_info_counter_.Max(now_ms);
Guido Urdaneta67378412019-05-28 17:38:08 +0200647 stats_.jitter_buffer_delay_seconds =
648 static_cast<double>(current_delay_counter_.Sum(1).value_or(0)) /
649 rtc::kNumMillisecsPerSec;
650 stats_.jitter_buffer_emitted_count = current_delay_counter_.NumSamples();
Åsa Perssonfcf79cc2019-10-22 15:23:44 +0200651 stats_.estimated_playout_ntp_timestamp_ms =
652 GetCurrentEstimatedPlayoutNtpTimestampMs(now_ms);
pbos@webrtc.org55707692014-12-19 15:45:03 +0000653 return stats_;
sprang@webrtc.org09315702014-02-07 12:06:29 +0000654}
655
pbosf42376c2015-08-28 07:35:32 -0700656void ReceiveStatisticsProxy::OnIncomingPayloadType(int payload_type) {
657 rtc::CritScope lock(&crit_);
658 stats_.current_payload_type = payload_type;
659}
660
Peter Boströmb7d9a972015-12-18 16:01:11 +0100661void ReceiveStatisticsProxy::OnDecoderImplementationName(
662 const char* implementation_name) {
663 rtc::CritScope lock(&crit_);
664 stats_.decoder_implementation_name = implementation_name;
665}
sprang@webrtc.org09315702014-02-07 12:06:29 +0000666
philipela45102f2017-02-22 05:30:39 -0800667void ReceiveStatisticsProxy::OnFrameBufferTimingsUpdated(
philipela45102f2017-02-22 05:30:39 -0800668 int max_decode_ms,
669 int current_delay_ms,
670 int target_delay_ms,
671 int jitter_buffer_ms,
672 int min_playout_delay_ms,
673 int render_delay_ms) {
Peter Boströmf2f82832015-05-01 13:00:41 +0200674 rtc::CritScope lock(&crit_);
pbos@webrtc.org09c77b92015-02-25 10:42:16 +0000675 stats_.max_decode_ms = max_decode_ms;
676 stats_.current_delay_ms = current_delay_ms;
677 stats_.target_delay_ms = target_delay_ms;
678 stats_.jitter_buffer_ms = jitter_buffer_ms;
679 stats_.min_playout_delay_ms = min_playout_delay_ms;
680 stats_.render_delay_ms = render_delay_ms;
asapersson8688a4e2016-04-27 23:42:35 -0700681 jitter_buffer_delay_counter_.Add(jitter_buffer_ms);
682 target_delay_counter_.Add(target_delay_ms);
683 current_delay_counter_.Add(current_delay_ms);
asaperssona1862882016-04-18 00:41:05 -0700684 // Network delay (rtt/2) + target_delay_ms (jitter delay + decode time +
685 // render delay).
philipela45102f2017-02-22 05:30:39 -0800686 delay_counter_.Add(target_delay_ms + avg_rtt_ms_ / 2);
pbos@webrtc.org98c04b32014-12-18 13:12:52 +0000687}
688
Ilya Nikolaevskiyd397a0d2018-02-21 15:57:09 +0100689void ReceiveStatisticsProxy::OnUniqueFramesCounted(int num_unique_frames) {
690 rtc::CritScope lock(&crit_);
691 num_unique_frames_.emplace(num_unique_frames);
692}
693
ilnik2edc6842017-07-06 03:06:50 -0700694void ReceiveStatisticsProxy::OnTimingFrameInfoUpdated(
695 const TimingFrameInfo& info) {
696 rtc::CritScope lock(&crit_);
Benjamin Wright3f10ca82018-12-07 03:45:19 -0800697 if (info.flags != VideoSendTiming::kInvalid) {
698 int64_t now_ms = clock_->TimeInMilliseconds();
699 timing_frame_info_counter_.Add(info, now_ms);
700 }
Benjamin Wright514f0842018-12-10 09:55:17 -0800701
702 // Measure initial decoding latency between the first frame arriving and the
703 // first frame being decoded.
704 if (!first_frame_received_time_ms_.has_value()) {
705 first_frame_received_time_ms_ = info.receive_finish_ms;
706 }
707 if (stats_.first_frame_received_to_decoded_ms == -1 &&
708 first_decoded_frame_time_ms_) {
709 stats_.first_frame_received_to_decoded_ms =
710 *first_decoded_frame_time_ms_ - *first_frame_received_time_ms_;
711 }
ilnik2edc6842017-07-06 03:06:50 -0700712}
713
pbos@webrtc.org1d0fa5d2015-02-19 12:47:00 +0000714void ReceiveStatisticsProxy::RtcpPacketTypesCounterUpdated(
715 uint32_t ssrc,
716 const RtcpPacketTypeCounter& packet_counter) {
Peter Boströmf2f82832015-05-01 13:00:41 +0200717 rtc::CritScope lock(&crit_);
pbos@webrtc.org1d0fa5d2015-02-19 12:47:00 +0000718 if (stats_.ssrc != ssrc)
719 return;
720 stats_.rtcp_packet_type_counts = packet_counter;
721}
722
Niels Möller4d7c4052019-08-05 12:45:19 +0200723void ReceiveStatisticsProxy::OnCname(uint32_t ssrc, absl::string_view cname) {
Peter Boströmf2f82832015-05-01 13:00:41 +0200724 rtc::CritScope lock(&crit_);
henrikg91d6ede2015-09-17 00:24:34 -0700725 // TODO(pbos): Handle both local and remote ssrcs here and RTC_DCHECK that we
pbos@webrtc.org1d0fa5d2015-02-19 12:47:00 +0000726 // receive stats from one of them.
727 if (stats_.ssrc != ssrc)
728 return;
Niels Möller4d7c4052019-08-05 12:45:19 +0200729 stats_.c_name = std::string(cname);
pbos@webrtc.orgce4e9a32014-12-18 13:50:16 +0000730}
731
Sergey Silkin278f8252019-01-09 14:37:40 +0100732void ReceiveStatisticsProxy::OnDecodedFrame(const VideoFrame& frame,
733 absl::optional<uint8_t> qp,
Johannes Kronbfd343b2019-07-01 10:07:50 +0200734 int32_t decode_time_ms,
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +0200735 VideoContentType content_type) {
Peter Boströmf2f82832015-05-01 13:00:41 +0200736 rtc::CritScope lock(&crit_);
ilnik6d5b4d62017-08-30 03:32:14 -0700737
Sergey Silkin278f8252019-01-09 14:37:40 +0100738 uint64_t now_ms = clock_->TimeInMilliseconds();
Ilya Nikolaevskiy3f670e02017-10-10 11:18:49 +0200739
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +0200740 if (videocontenttypehelpers::IsScreenshare(content_type) !=
741 videocontenttypehelpers::IsScreenshare(last_content_type_)) {
Niels Möller9a9f18a2019-08-02 13:52:37 +0200742 // Reset the quality observer if content type is switched. But first report
743 // stats for the previous part of the call.
744 video_quality_observer_->UpdateHistograms();
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +0200745 video_quality_observer_.reset(new VideoQualityObserver(content_type));
746 }
747
Sergey Silkin278f8252019-01-09 14:37:40 +0100748 video_quality_observer_->OnDecodedFrame(frame, qp, last_codec_type_);
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +0200749
ilnik6d5b4d62017-08-30 03:32:14 -0700750 ContentSpecificStats* content_specific_stats =
751 &content_specific_stats_[content_type];
sakale5ba44e2016-10-26 07:09:24 -0700752 ++stats_.frames_decoded;
sakalcc452e12017-02-09 04:53:45 -0800753 if (qp) {
754 if (!stats_.qp_sum) {
755 if (stats_.frames_decoded != 1) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100756 RTC_LOG(LS_WARNING)
sakalcc452e12017-02-09 04:53:45 -0800757 << "Frames decoded was not 1 when first qp value was received.";
sakalcc452e12017-02-09 04:53:45 -0800758 }
Oskar Sundbom8e07c132018-01-08 16:45:42 +0100759 stats_.qp_sum = 0;
sakalcc452e12017-02-09 04:53:45 -0800760 }
761 *stats_.qp_sum += *qp;
ilnik6d5b4d62017-08-30 03:32:14 -0700762 content_specific_stats->qp_counter.Add(*qp);
sakalcc452e12017-02-09 04:53:45 -0800763 } else if (stats_.qp_sum) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100764 RTC_LOG(LS_WARNING)
sakalcc452e12017-02-09 04:53:45 -0800765 << "QP sum was already set and no QP was given for a frame.";
Mirta Dvornicic608083b2019-10-03 15:52:36 +0200766 stats_.qp_sum.reset();
sakalcc452e12017-02-09 04:53:45 -0800767 }
Johannes Kronbfd343b2019-07-01 10:07:50 +0200768 decode_time_counter_.Add(decode_time_ms);
769 stats_.decode_ms = decode_time_ms;
770 stats_.total_decode_time_ms += decode_time_ms;
Johannes Krone76b3ab2019-10-22 13:22:26 +0200771 if (enable_decode_time_histograms_) {
772 UpdateDecodeTimeHistograms(frame.width(), frame.height(), decode_time_ms);
773 }
774
ilnik00d802b2017-04-11 10:34:31 -0700775 last_content_type_ = content_type;
Sergey Silkin278f8252019-01-09 14:37:40 +0100776 decode_fps_estimator_.Update(1, now_ms);
ilnik4257ab22017-07-03 01:15:58 -0700777 if (last_decoded_frame_time_ms_) {
Sergey Silkin278f8252019-01-09 14:37:40 +0100778 int64_t interframe_delay_ms = now_ms - *last_decoded_frame_time_ms_;
ilnik4257ab22017-07-03 01:15:58 -0700779 RTC_DCHECK_GE(interframe_delay_ms, 0);
Johannes Kron00376e12019-11-25 10:25:42 +0100780 double interframe_delay = interframe_delay_ms / 1000.0;
781 stats_.total_inter_frame_delay += interframe_delay;
782 stats_.total_squared_inter_frame_delay +=
783 interframe_delay * interframe_delay;
Sergey Silkin278f8252019-01-09 14:37:40 +0100784 interframe_delay_max_moving_.Add(interframe_delay_ms, now_ms);
ilnik6d5b4d62017-08-30 03:32:14 -0700785 content_specific_stats->interframe_delay_counter.Add(interframe_delay_ms);
Ilya Nikolaevskiydaa4f7a2017-10-06 12:29:47 +0200786 content_specific_stats->interframe_delay_percentiles.Add(
787 interframe_delay_ms);
ilnik6d5b4d62017-08-30 03:32:14 -0700788 content_specific_stats->flow_duration_ms += interframe_delay_ms;
ilnik4257ab22017-07-03 01:15:58 -0700789 }
Benjamin Wright514f0842018-12-10 09:55:17 -0800790 if (stats_.frames_decoded == 1) {
Sergey Silkin278f8252019-01-09 14:37:40 +0100791 first_decoded_frame_time_ms_.emplace(now_ms);
Benjamin Wright514f0842018-12-10 09:55:17 -0800792 }
Sergey Silkin278f8252019-01-09 14:37:40 +0100793 last_decoded_frame_time_ms_.emplace(now_ms);
sprang@webrtc.org09315702014-02-07 12:06:29 +0000794}
795
asapersson1490f7a2016-09-23 02:09:46 -0700796void ReceiveStatisticsProxy::OnRenderedFrame(const VideoFrame& frame) {
797 int width = frame.width();
798 int height = frame.height();
asaperssonf839dcc2015-10-08 00:41:59 -0700799 RTC_DCHECK_GT(width, 0);
800 RTC_DCHECK_GT(height, 0);
Åsa Persson81327d52018-06-05 13:34:33 +0200801 int64_t now_ms = clock_->TimeInMilliseconds();
Peter Boströmf2f82832015-05-01 13:00:41 +0200802 rtc::CritScope lock(&crit_);
Ilya Nikolaevskiycdc959f2018-10-10 13:15:09 +0200803
Sergey Silkin278f8252019-01-09 14:37:40 +0100804 video_quality_observer_->OnRenderedFrame(frame, now_ms);
Ilya Nikolaevskiycdc959f2018-10-10 13:15:09 +0200805
ilnik6d5b4d62017-08-30 03:32:14 -0700806 ContentSpecificStats* content_specific_stats =
807 &content_specific_stats_[last_content_type_];
Åsa Persson81327d52018-06-05 13:34:33 +0200808 renders_fps_estimator_.Update(1, now_ms);
hbos50cfe1f2017-01-23 07:21:55 -0800809 ++stats_.frames_rendered;
asapersson2e5cfcd2016-08-11 08:41:18 -0700810 stats_.width = width;
811 stats_.height = height;
Tim Psiaki63046262015-09-14 10:38:08 -0700812 render_fps_tracker_.AddSamples(1);
asaperssonf839dcc2015-10-08 00:41:59 -0700813 render_pixel_tracker_.AddSamples(sqrt(width * height));
ilnik6d5b4d62017-08-30 03:32:14 -0700814 content_specific_stats->received_width.Add(width);
815 content_specific_stats->received_height.Add(height);
asapersson1490f7a2016-09-23 02:09:46 -0700816
Åsa Persson81327d52018-06-05 13:34:33 +0200817 // Consider taking stats_.render_delay_ms into account.
818 const int64_t time_until_rendering_ms = frame.render_time_ms() - now_ms;
819 if (time_until_rendering_ms < 0) {
820 sum_missed_render_deadline_ms_ += -time_until_rendering_ms;
821 ++num_delayed_frames_rendered_;
822 }
823
asapersson1490f7a2016-09-23 02:09:46 -0700824 if (frame.ntp_time_ms() > 0) {
825 int64_t delay_ms = clock_->CurrentNtpInMilliseconds() - frame.ntp_time_ms();
ilnik00d802b2017-04-11 10:34:31 -0700826 if (delay_ms >= 0) {
ilnik6d5b4d62017-08-30 03:32:14 -0700827 content_specific_stats->e2e_delay_counter.Add(delay_ms);
ilnik00d802b2017-04-11 10:34:31 -0700828 }
asapersson1490f7a2016-09-23 02:09:46 -0700829 }
Niels Möller9b0b1e02019-03-22 13:56:46 +0100830 QualitySample();
sprang@webrtc.org09315702014-02-07 12:06:29 +0000831}
832
Åsa Perssonfcf79cc2019-10-22 15:23:44 +0200833void ReceiveStatisticsProxy::OnSyncOffsetUpdated(int64_t video_playout_ntp_ms,
834 int64_t sync_offset_ms,
asaperssonde9e5ff2016-11-02 07:14:03 -0700835 double estimated_freq_khz) {
asaperssonf8cdd182016-03-15 01:00:47 -0700836 rtc::CritScope lock(&crit_);
837 sync_offset_counter_.Add(std::abs(sync_offset_ms));
838 stats_.sync_offset_ms = sync_offset_ms;
Åsa Perssonfcf79cc2019-10-22 15:23:44 +0200839 last_estimated_playout_ntp_timestamp_ms_ = video_playout_ntp_ms;
840 last_estimated_playout_time_ms_ = clock_->TimeInMilliseconds();
asaperssonde9e5ff2016-11-02 07:14:03 -0700841
842 const double kMaxFreqKhz = 10000.0;
843 int offset_khz = kMaxFreqKhz;
844 // Should not be zero or negative. If so, report max.
845 if (estimated_freq_khz < kMaxFreqKhz && estimated_freq_khz > 0.0)
846 offset_khz = static_cast<int>(std::fabs(estimated_freq_khz - 90.0) + 0.5);
847
848 freq_offset_counter_.Add(offset_khz);
asaperssonf8cdd182016-03-15 01:00:47 -0700849}
850
philipela45102f2017-02-22 05:30:39 -0800851void ReceiveStatisticsProxy::OnCompleteFrame(bool is_keyframe,
ilnik6d5b4d62017-08-30 03:32:14 -0700852 size_t size_bytes,
853 VideoContentType content_type) {
philipela45102f2017-02-22 05:30:39 -0800854 rtc::CritScope lock(&crit_);
ilnik6d5b4d62017-08-30 03:32:14 -0700855 if (is_keyframe) {
philipela45102f2017-02-22 05:30:39 -0800856 ++stats_.frame_counts.key_frames;
ilnik6d5b4d62017-08-30 03:32:14 -0700857 } else {
philipela45102f2017-02-22 05:30:39 -0800858 ++stats_.frame_counts.delta_frames;
ilnik6d5b4d62017-08-30 03:32:14 -0700859 }
860
Ilya Nikolaevskiyf203d732018-10-17 14:41:52 +0200861 // Content type extension is set only for keyframes and should be propagated
862 // for all the following delta frames. Here we may receive frames out of order
863 // and miscategorise some delta frames near the layer switch.
864 // This may slightly offset calculated bitrate and keyframes permille metrics.
865 VideoContentType propagated_content_type =
866 is_keyframe ? content_type : last_content_type_;
867
ilnik6d5b4d62017-08-30 03:32:14 -0700868 ContentSpecificStats* content_specific_stats =
Ilya Nikolaevskiyf203d732018-10-17 14:41:52 +0200869 &content_specific_stats_[propagated_content_type];
ilnik6d5b4d62017-08-30 03:32:14 -0700870
871 content_specific_stats->total_media_bytes += size_bytes;
872 if (is_keyframe) {
873 ++content_specific_stats->frame_counts.key_frames;
874 } else {
875 ++content_specific_stats->frame_counts.delta_frames;
876 }
philipela45102f2017-02-22 05:30:39 -0800877
878 int64_t now_ms = clock_->TimeInMilliseconds();
philipela45102f2017-02-22 05:30:39 -0800879 frame_window_.insert(std::make_pair(now_ms, size_bytes));
asapersson0255acb2017-03-28 02:44:58 -0700880 UpdateFramerate(now_ms);
philipela45102f2017-02-22 05:30:39 -0800881}
882
Johannes Kron0c141c52019-08-26 15:04:43 +0200883void ReceiveStatisticsProxy::OnDroppedFrames(uint32_t frames_dropped) {
884 rtc::CritScope lock(&crit_);
885 stats_.frames_dropped += frames_dropped;
886}
887
Niels Möller147013a2018-10-01 15:56:33 +0200888void ReceiveStatisticsProxy::OnPreDecode(VideoCodecType codec_type, int qp) {
Tommi132e28e2018-02-24 17:57:33 +0100889 RTC_DCHECK_RUN_ON(&decode_thread_);
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +0200890 rtc::CritScope lock(&crit_);
Niels Möller147013a2018-10-01 15:56:33 +0200891 last_codec_type_ = codec_type;
892 if (last_codec_type_ == kVideoCodecVP8 && qp != -1) {
893 qp_counters_.vp8.Add(qp);
894 qp_sample_.Add(qp);
asapersson86b01602015-10-20 23:55:26 -0700895 }
896}
897
sprang3e86e7e2017-08-22 09:23:28 -0700898void ReceiveStatisticsProxy::OnStreamInactive() {
899 // TODO(sprang): Figure out any other state that should be reset.
900
901 rtc::CritScope lock(&crit_);
902 // Don't report inter-frame delay if stream was paused.
903 last_decoded_frame_time_ms_.reset();
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +0200904 video_quality_observer_->OnStreamInactive();
sprang3e86e7e2017-08-22 09:23:28 -0700905}
906
philipela45102f2017-02-22 05:30:39 -0800907void ReceiveStatisticsProxy::OnRttUpdate(int64_t avg_rtt_ms,
908 int64_t max_rtt_ms) {
909 rtc::CritScope lock(&crit_);
910 avg_rtt_ms_ = avg_rtt_ms;
911}
912
Tommi132e28e2018-02-24 17:57:33 +0100913void ReceiveStatisticsProxy::DecoderThreadStarting() {
914 RTC_DCHECK_RUN_ON(&main_thread_);
915}
916
917void ReceiveStatisticsProxy::DecoderThreadStopped() {
918 RTC_DCHECK_RUN_ON(&main_thread_);
Sebastian Janssonc01367d2019-04-08 15:20:44 +0200919 decode_thread_.Detach();
Tommi132e28e2018-02-24 17:57:33 +0100920}
921
Ilya Nikolaevskiydaa4f7a2017-10-06 12:29:47 +0200922ReceiveStatisticsProxy::ContentSpecificStats::ContentSpecificStats()
923 : interframe_delay_percentiles(kMaxCommonInterframeDelayMs) {}
924
Mirko Bonadei8fdcac32018-08-28 16:30:18 +0200925ReceiveStatisticsProxy::ContentSpecificStats::~ContentSpecificStats() = default;
926
ilnik6d5b4d62017-08-30 03:32:14 -0700927void ReceiveStatisticsProxy::ContentSpecificStats::Add(
928 const ContentSpecificStats& other) {
929 e2e_delay_counter.Add(other.e2e_delay_counter);
930 interframe_delay_counter.Add(other.interframe_delay_counter);
931 flow_duration_ms += other.flow_duration_ms;
932 total_media_bytes += other.total_media_bytes;
933 received_height.Add(other.received_height);
934 received_width.Add(other.received_width);
935 qp_counter.Add(other.qp_counter);
936 frame_counts.key_frames += other.frame_counts.key_frames;
937 frame_counts.delta_frames += other.frame_counts.delta_frames;
Ilya Nikolaevskiydaa4f7a2017-10-06 12:29:47 +0200938 interframe_delay_percentiles.Add(other.interframe_delay_percentiles);
ilnik6d5b4d62017-08-30 03:32:14 -0700939}
sprang@webrtc.org09315702014-02-07 12:06:29 +0000940} // namespace webrtc