blob: be7b08c887ea5ebf739aee3835a178584c7f1058 [file] [log] [blame]
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +02001/*
2 * Copyright (c) 2018 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
11#include "video/video_quality_observer.h"
12
13#include <algorithm>
Sergey Silkin50e77452019-01-16 13:41:46 +010014#include <cmath>
Yves Gerey3e707812018-11-28 16:47:49 +010015#include <cstdint>
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +020016#include <string>
17
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +020018#include "rtc_base/logging.h"
19#include "rtc_base/strings/string_builder.h"
20#include "system_wrappers/include/metrics.h"
21
22namespace webrtc {
Sergey Silkin02371062019-01-31 16:45:42 +010023const uint32_t VideoQualityObserver::kMinFrameSamplesToDetectFreeze = 5;
24const uint32_t VideoQualityObserver::kMinIncreaseForFreezeMs = 150;
25const uint32_t VideoQualityObserver::kAvgInterframeDelaysWindowSizeFrames = 30;
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +020026
27namespace {
Sergey Silkin278f8252019-01-09 14:37:40 +010028constexpr int kMinVideoDurationMs = 3000;
29constexpr int kMinRequiredSamples = 1;
Sergey Silkin278f8252019-01-09 14:37:40 +010030constexpr int kPixelsInHighResolution =
31 960 * 540; // CPU-adapted HD still counts.
32constexpr int kPixelsInMediumResolution = 640 * 360;
33constexpr int kBlockyQpThresholdVp8 = 70;
Ilya Nikolaevskiy3b407ff2019-08-26 14:16:15 +020034constexpr int kBlockyQpThresholdVp9 = 180;
Sergey Silkin278f8252019-01-09 14:37:40 +010035constexpr int kMaxNumCachedBlockyFrames = 100;
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +020036// TODO(ilnik): Add H264/HEVC thresholds.
37} // namespace
38
39VideoQualityObserver::VideoQualityObserver(VideoContentType content_type)
Sergey Silkin278f8252019-01-09 14:37:40 +010040 : last_frame_rendered_ms_(-1),
Ilya Nikolaevskiycdc959f2018-10-10 13:15:09 +020041 num_frames_rendered_(0),
Sergey Silkin278f8252019-01-09 14:37:40 +010042 first_frame_rendered_ms_(-1),
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +020043 last_frame_pixels_(0),
Sergey Silkin278f8252019-01-09 14:37:40 +010044 is_last_frame_blocky_(false),
Sergey Silkin02371062019-01-31 16:45:42 +010045 last_unfreeze_time_ms_(0),
46 render_interframe_delays_(kAvgInterframeDelaysWindowSizeFrames),
Sergey Silkin50e77452019-01-16 13:41:46 +010047 sum_squared_interframe_delays_secs_(0.0),
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +020048 time_in_resolution_ms_(3, 0),
49 current_resolution_(Resolution::Low),
50 num_resolution_downgrades_(0),
51 time_in_blocky_video_ms_(0),
52 content_type_(content_type),
53 is_paused_(false) {}
54
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +020055void VideoQualityObserver::UpdateHistograms() {
56 // Don't report anything on an empty video stream.
Sergey Silkin278f8252019-01-09 14:37:40 +010057 if (num_frames_rendered_ == 0) {
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +020058 return;
59 }
60
61 char log_stream_buf[2 * 1024];
62 rtc::SimpleStringBuilder log_stream(log_stream_buf);
63
Sergey Silkin02371062019-01-31 16:45:42 +010064 if (last_frame_rendered_ms_ > last_unfreeze_time_ms_) {
Sergey Silkin278f8252019-01-09 14:37:40 +010065 smooth_playback_durations_.Add(last_frame_rendered_ms_ -
Sergey Silkin02371062019-01-31 16:45:42 +010066 last_unfreeze_time_ms_);
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +020067 }
68
69 std::string uma_prefix = videocontenttypehelpers::IsScreenshare(content_type_)
70 ? "WebRTC.Video.Screenshare"
71 : "WebRTC.Video";
72
73 auto mean_time_between_freezes =
74 smooth_playback_durations_.Avg(kMinRequiredSamples);
75 if (mean_time_between_freezes) {
76 RTC_HISTOGRAM_COUNTS_SPARSE_100000(uma_prefix + ".MeanTimeBetweenFreezesMs",
77 *mean_time_between_freezes);
78 log_stream << uma_prefix << ".MeanTimeBetweenFreezesMs "
79 << *mean_time_between_freezes << "\n";
80 }
81 auto avg_freeze_length = freezes_durations_.Avg(kMinRequiredSamples);
82 if (avg_freeze_length) {
83 RTC_HISTOGRAM_COUNTS_SPARSE_100000(uma_prefix + ".MeanFreezeDurationMs",
84 *avg_freeze_length);
85 log_stream << uma_prefix << ".MeanFreezeDurationMs " << *avg_freeze_length
86 << "\n";
87 }
88
Sergey Silkin278f8252019-01-09 14:37:40 +010089 int64_t video_duration_ms =
90 last_frame_rendered_ms_ - first_frame_rendered_ms_;
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +020091
Sergey Silkin278f8252019-01-09 14:37:40 +010092 if (video_duration_ms >= kMinVideoDurationMs) {
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +020093 int time_spent_in_hd_percentage = static_cast<int>(
Sergey Silkin278f8252019-01-09 14:37:40 +010094 time_in_resolution_ms_[Resolution::High] * 100 / video_duration_ms);
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +020095 RTC_HISTOGRAM_COUNTS_SPARSE_100(uma_prefix + ".TimeInHdPercentage",
96 time_spent_in_hd_percentage);
97 log_stream << uma_prefix << ".TimeInHdPercentage "
98 << time_spent_in_hd_percentage << "\n";
Sergey Silkinbea18ca2018-10-02 16:22:46 +020099
100 int time_with_blocky_video_percentage =
Sergey Silkin278f8252019-01-09 14:37:40 +0100101 static_cast<int>(time_in_blocky_video_ms_ * 100 / video_duration_ms);
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +0200102 RTC_HISTOGRAM_COUNTS_SPARSE_100(uma_prefix + ".TimeInBlockyVideoPercentage",
103 time_with_blocky_video_percentage);
104 log_stream << uma_prefix << ".TimeInBlockyVideoPercentage "
105 << time_with_blocky_video_percentage << "\n";
Sergey Silkinbea18ca2018-10-02 16:22:46 +0200106
107 int num_resolution_downgrades_per_minute =
Sergey Silkin278f8252019-01-09 14:37:40 +0100108 num_resolution_downgrades_ * 60000 / video_duration_ms;
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +0200109 RTC_HISTOGRAM_COUNTS_SPARSE_100(
110 uma_prefix + ".NumberResolutionDownswitchesPerMinute",
Sergey Silkinbea18ca2018-10-02 16:22:46 +0200111 num_resolution_downgrades_per_minute);
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +0200112 log_stream << uma_prefix << ".NumberResolutionDownswitchesPerMinute "
Sergey Silkinbea18ca2018-10-02 16:22:46 +0200113 << num_resolution_downgrades_per_minute << "\n";
114
115 int num_freezes_per_minute =
Sergey Silkin278f8252019-01-09 14:37:40 +0100116 freezes_durations_.NumSamples() * 60000 / video_duration_ms;
Sergey Silkinbea18ca2018-10-02 16:22:46 +0200117 RTC_HISTOGRAM_COUNTS_SPARSE_100(uma_prefix + ".NumberFreezesPerMinute",
118 num_freezes_per_minute);
119 log_stream << uma_prefix << ".NumberFreezesPerMinute "
120 << num_freezes_per_minute << "\n";
Sergey Silkin50e77452019-01-16 13:41:46 +0100121
122 if (sum_squared_interframe_delays_secs_ > 0.0) {
123 int harmonic_framerate_fps = std::round(
124 video_duration_ms / (1000 * sum_squared_interframe_delays_secs_));
125 RTC_HISTOGRAM_COUNTS_SPARSE_100(uma_prefix + ".HarmonicFrameRate",
126 harmonic_framerate_fps);
127 log_stream << uma_prefix << ".HarmonicFrameRate "
128 << harmonic_framerate_fps << "\n";
129 }
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +0200130 }
131 RTC_LOG(LS_INFO) << log_stream.str();
132}
133
Sergey Silkin278f8252019-01-09 14:37:40 +0100134void VideoQualityObserver::OnRenderedFrame(const VideoFrame& frame,
135 int64_t now_ms) {
Sergey Silkin02371062019-01-31 16:45:42 +0100136 RTC_DCHECK_LE(last_frame_rendered_ms_, now_ms);
137 RTC_DCHECK_LE(last_unfreeze_time_ms_, now_ms);
Ilya Nikolaevskiycdc959f2018-10-10 13:15:09 +0200138
Sergey Silkin02371062019-01-31 16:45:42 +0100139 if (num_frames_rendered_ == 0) {
140 first_frame_rendered_ms_ = last_unfreeze_time_ms_ = now_ms;
141 }
Ilya Nikolaevskiycdc959f2018-10-10 13:15:09 +0200142
Sergey Silkin1daa7e82019-01-10 14:29:46 +0100143 auto blocky_frame_it = blocky_frames_.find(frame.timestamp());
Sergey Silkin278f8252019-01-09 14:37:40 +0100144
Sergey Silkinfc6f3e52019-04-03 10:57:26 +0200145 if (num_frames_rendered_ > 0) {
Ilya Nikolaevskiycdc959f2018-10-10 13:15:09 +0200146 // Process inter-frame delay.
Sergey Silkin50e77452019-01-16 13:41:46 +0100147 const int64_t interframe_delay_ms = now_ms - last_frame_rendered_ms_;
Sergey Silkin02371062019-01-31 16:45:42 +0100148 const double interframe_delays_secs = interframe_delay_ms / 1000.0;
Sergey Silkinfc6f3e52019-04-03 10:57:26 +0200149
150 // Sum of squared inter frame intervals is used to calculate the harmonic
151 // frame rate metric. The metric aims to reflect overall experience related
152 // to smoothness of video playback and includes both freezes and pauses.
Sergey Silkin50e77452019-01-16 13:41:46 +0100153 sum_squared_interframe_delays_secs_ +=
154 interframe_delays_secs * interframe_delays_secs;
Sergey Silkin02371062019-01-31 16:45:42 +0100155
Sergey Silkinfc6f3e52019-04-03 10:57:26 +0200156 if (!is_paused_) {
157 render_interframe_delays_.AddSample(interframe_delay_ms);
Sergey Silkin02371062019-01-31 16:45:42 +0100158
Sergey Silkinfc6f3e52019-04-03 10:57:26 +0200159 bool was_freeze = false;
160 if (render_interframe_delays_.Size() >= kMinFrameSamplesToDetectFreeze) {
161 const absl::optional<int64_t> avg_interframe_delay =
162 render_interframe_delays_.GetAverageRoundedDown();
163 RTC_DCHECK(avg_interframe_delay);
164 was_freeze = interframe_delay_ms >=
165 std::max(3 * *avg_interframe_delay,
166 *avg_interframe_delay + kMinIncreaseForFreezeMs);
167 }
Sergey Silkin278f8252019-01-09 14:37:40 +0100168
Sergey Silkinfc6f3e52019-04-03 10:57:26 +0200169 if (was_freeze) {
170 freezes_durations_.Add(interframe_delay_ms);
171 smooth_playback_durations_.Add(last_frame_rendered_ms_ -
172 last_unfreeze_time_ms_);
173 last_unfreeze_time_ms_ = now_ms;
174 } else {
175 // Count spatial metrics if there were no freeze.
176 time_in_resolution_ms_[current_resolution_] += interframe_delay_ms;
177
178 if (is_last_frame_blocky_) {
179 time_in_blocky_video_ms_ += interframe_delay_ms;
180 }
Sergey Silkin278f8252019-01-09 14:37:40 +0100181 }
Ilya Nikolaevskiycdc959f2018-10-10 13:15:09 +0200182 }
183 }
184
185 if (is_paused_) {
186 // If the stream was paused since the previous frame, do not count the
187 // pause toward smooth playback. Explicitly count the part before it and
188 // start the new smooth playback interval from this frame.
189 is_paused_ = false;
Sergey Silkin02371062019-01-31 16:45:42 +0100190 if (last_frame_rendered_ms_ > last_unfreeze_time_ms_) {
Ilya Nikolaevskiycdc959f2018-10-10 13:15:09 +0200191 smooth_playback_durations_.Add(last_frame_rendered_ms_ -
Sergey Silkin02371062019-01-31 16:45:42 +0100192 last_unfreeze_time_ms_);
Ilya Nikolaevskiycdc959f2018-10-10 13:15:09 +0200193 }
Sergey Silkin02371062019-01-31 16:45:42 +0100194 last_unfreeze_time_ms_ = now_ms;
195
196 if (num_frames_rendered_ > 0) {
197 pauses_durations_.Add(now_ms - last_frame_rendered_ms_);
198 }
Ilya Nikolaevskiycdc959f2018-10-10 13:15:09 +0200199 }
200
Sergey Silkin278f8252019-01-09 14:37:40 +0100201 int64_t pixels = frame.width() * frame.height();
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +0200202 if (pixels >= kPixelsInHighResolution) {
203 current_resolution_ = Resolution::High;
204 } else if (pixels >= kPixelsInMediumResolution) {
205 current_resolution_ = Resolution::Medium;
206 } else {
207 current_resolution_ = Resolution::Low;
208 }
209
210 if (pixels < last_frame_pixels_) {
211 ++num_resolution_downgrades_;
212 }
213
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +0200214 last_frame_pixels_ = pixels;
Sergey Silkin278f8252019-01-09 14:37:40 +0100215 last_frame_rendered_ms_ = now_ms;
216
217 is_last_frame_blocky_ = blocky_frame_it != blocky_frames_.end();
218 if (is_last_frame_blocky_) {
219 blocky_frames_.erase(blocky_frames_.begin(), ++blocky_frame_it);
220 }
Sergey Silkin02371062019-01-31 16:45:42 +0100221
222 ++num_frames_rendered_;
Sergey Silkin278f8252019-01-09 14:37:40 +0100223}
224
225void VideoQualityObserver::OnDecodedFrame(const VideoFrame& frame,
226 absl::optional<uint8_t> qp,
227 VideoCodecType codec) {
228 if (qp) {
229 absl::optional<int> qp_blocky_threshold;
230 // TODO(ilnik): add other codec types when we have QP for them.
231 switch (codec) {
232 case kVideoCodecVP8:
233 qp_blocky_threshold = kBlockyQpThresholdVp8;
234 break;
235 case kVideoCodecVP9:
236 qp_blocky_threshold = kBlockyQpThresholdVp9;
237 break;
238 default:
239 qp_blocky_threshold = absl::nullopt;
240 }
241
Sergey Silkin1daa7e82019-01-10 14:29:46 +0100242 RTC_DCHECK(blocky_frames_.find(frame.timestamp()) == blocky_frames_.end());
Sergey Silkin278f8252019-01-09 14:37:40 +0100243
244 if (qp_blocky_threshold && *qp > *qp_blocky_threshold) {
245 // Cache blocky frame. Its duration will be calculated in render callback.
246 if (blocky_frames_.size() > kMaxNumCachedBlockyFrames) {
247 RTC_LOG(LS_WARNING) << "Overflow of blocky frames cache.";
248 blocky_frames_.erase(
249 blocky_frames_.begin(),
250 std::next(blocky_frames_.begin(), kMaxNumCachedBlockyFrames / 2));
251 }
252
Sergey Silkin1daa7e82019-01-10 14:29:46 +0100253 blocky_frames_.insert(frame.timestamp());
Sergey Silkin278f8252019-01-09 14:37:40 +0100254 }
255 }
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +0200256}
257
258void VideoQualityObserver::OnStreamInactive() {
259 is_paused_ = true;
260}
Sergey Silkin02371062019-01-31 16:45:42 +0100261
Elad Alon58e06572019-05-08 15:34:24 +0200262uint32_t VideoQualityObserver::NumFreezes() const {
Sergey Silkin02371062019-01-31 16:45:42 +0100263 return freezes_durations_.NumSamples();
264}
265
Elad Alon58e06572019-05-08 15:34:24 +0200266uint32_t VideoQualityObserver::NumPauses() const {
Sergey Silkin02371062019-01-31 16:45:42 +0100267 return pauses_durations_.NumSamples();
268}
269
Elad Alon58e06572019-05-08 15:34:24 +0200270uint32_t VideoQualityObserver::TotalFreezesDurationMs() const {
Sergey Silkin02371062019-01-31 16:45:42 +0100271 return freezes_durations_.Sum(kMinRequiredSamples).value_or(0);
272}
273
Elad Alon58e06572019-05-08 15:34:24 +0200274uint32_t VideoQualityObserver::TotalPausesDurationMs() const {
Sergey Silkin02371062019-01-31 16:45:42 +0100275 return pauses_durations_.Sum(kMinRequiredSamples).value_or(0);
276}
277
Elad Alon58e06572019-05-08 15:34:24 +0200278uint32_t VideoQualityObserver::TotalFramesDurationMs() const {
Sergey Silkin02371062019-01-31 16:45:42 +0100279 return last_frame_rendered_ms_ - first_frame_rendered_ms_;
280}
281
Elad Alon58e06572019-05-08 15:34:24 +0200282double VideoQualityObserver::SumSquaredFrameDurationsSec() const {
Sergey Silkin02371062019-01-31 16:45:42 +0100283 return sum_squared_interframe_delays_secs_;
284}
285
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +0200286} // namespace webrtc