blob: b35a8c81641345d8c4eee92f19131c16ae30b2d9 [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>
14#include <string>
15
16#include "rtc_base/checks.h"
17#include "rtc_base/logging.h"
18#include "rtc_base/strings/string_builder.h"
19#include "system_wrappers/include/metrics.h"
20
21namespace webrtc {
22
23namespace {
24const int kMinFrameSamplesToDetectFreeze = 5;
25const int kMinCallDurationMs = 3000;
26const int kMinRequiredSamples = 1;
27const int kMinIncreaseForFreezeMs = 150;
28const int kPixelsInHighResolution = 960 * 540; // CPU-adapted HD still counts.
29const int kPixelsInMediumResolution = 640 * 360;
30const int kBlockyQpThresholdVp8 = 70;
31const int kBlockyQpThresholdVp9 = 60; // TODO(ilnik): tune this value.
32// TODO(ilnik): Add H264/HEVC thresholds.
33} // namespace
34
35VideoQualityObserver::VideoQualityObserver(VideoContentType content_type)
36 : last_frame_decoded_ms_(-1),
37 num_frames_decoded_(0),
38 first_frame_decoded_ms_(-1),
39 last_frame_pixels_(0),
40 last_frame_qp_(0),
41 last_unfreeze_time_(0),
42 time_in_resolution_ms_(3, 0),
43 current_resolution_(Resolution::Low),
44 num_resolution_downgrades_(0),
45 time_in_blocky_video_ms_(0),
46 content_type_(content_type),
47 is_paused_(false) {}
48
49VideoQualityObserver::~VideoQualityObserver() {
50 UpdateHistograms();
51}
52
53void VideoQualityObserver::UpdateHistograms() {
54 // Don't report anything on an empty video stream.
55 if (num_frames_decoded_ == 0) {
56 return;
57 }
58
59 char log_stream_buf[2 * 1024];
60 rtc::SimpleStringBuilder log_stream(log_stream_buf);
61
62 if (last_frame_decoded_ms_ > last_unfreeze_time_) {
63 smooth_playback_durations_.Add(last_frame_decoded_ms_ -
64 last_unfreeze_time_);
65 }
66
67 std::string uma_prefix = videocontenttypehelpers::IsScreenshare(content_type_)
68 ? "WebRTC.Video.Screenshare"
69 : "WebRTC.Video";
70
71 auto mean_time_between_freezes =
72 smooth_playback_durations_.Avg(kMinRequiredSamples);
73 if (mean_time_between_freezes) {
74 RTC_HISTOGRAM_COUNTS_SPARSE_100000(uma_prefix + ".MeanTimeBetweenFreezesMs",
75 *mean_time_between_freezes);
76 log_stream << uma_prefix << ".MeanTimeBetweenFreezesMs "
77 << *mean_time_between_freezes << "\n";
78 }
79 auto avg_freeze_length = freezes_durations_.Avg(kMinRequiredSamples);
80 if (avg_freeze_length) {
81 RTC_HISTOGRAM_COUNTS_SPARSE_100000(uma_prefix + ".MeanFreezeDurationMs",
82 *avg_freeze_length);
83 log_stream << uma_prefix << ".MeanFreezeDurationMs " << *avg_freeze_length
84 << "\n";
85 }
86
87 int64_t call_duration_ms = last_frame_decoded_ms_ - first_frame_decoded_ms_;
88
89 if (call_duration_ms >= kMinCallDurationMs) {
90 int time_spent_in_hd_percentage = static_cast<int>(
91 time_in_resolution_ms_[Resolution::High] * 100 / call_duration_ms);
92 int time_with_blocky_video_percentage =
93 static_cast<int>(time_in_blocky_video_ms_ * 100 / call_duration_ms);
94
95 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";
99 RTC_HISTOGRAM_COUNTS_SPARSE_100(uma_prefix + ".TimeInBlockyVideoPercentage",
100 time_with_blocky_video_percentage);
101 log_stream << uma_prefix << ".TimeInBlockyVideoPercentage "
102 << time_with_blocky_video_percentage << "\n";
103 RTC_HISTOGRAM_COUNTS_SPARSE_100(
104 uma_prefix + ".NumberResolutionDownswitchesPerMinute",
105 num_resolution_downgrades_ * 60000 / call_duration_ms);
106 log_stream << uma_prefix << ".NumberResolutionDownswitchesPerMinute "
107 << num_resolution_downgrades_ * 60000 / call_duration_ms << "\n";
108 }
109 RTC_LOG(LS_INFO) << log_stream.str();
110}
111
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200112void VideoQualityObserver::OnDecodedFrame(absl::optional<uint8_t> qp,
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +0200113 int width,
114 int height,
115 int64_t now_ms,
116 VideoCodecType codec) {
117 if (num_frames_decoded_ == 0) {
118 first_frame_decoded_ms_ = now_ms;
119 last_unfreeze_time_ = now_ms;
120 }
121
122 ++num_frames_decoded_;
123
124 if (!is_paused_ && num_frames_decoded_ > 1) {
125 // Process inter-frame delay.
126 int64_t interframe_delay_ms = now_ms - last_frame_decoded_ms_;
127 interframe_delays_.Add(interframe_delay_ms);
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200128 absl::optional<int> avg_interframe_delay =
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +0200129 interframe_delays_.Avg(kMinFrameSamplesToDetectFreeze);
130 // Check if it was a freeze.
Taylor Brandstetterf0b83c52018-05-30 12:40:10 -0700131 if (avg_interframe_delay &&
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +0200132 interframe_delay_ms >=
133 std::max(3 * *avg_interframe_delay,
134 *avg_interframe_delay + kMinIncreaseForFreezeMs)) {
135 freezes_durations_.Add(interframe_delay_ms);
136 smooth_playback_durations_.Add(last_frame_decoded_ms_ -
137 last_unfreeze_time_);
138 last_unfreeze_time_ = now_ms;
139 } else {
140 // Only count inter-frame delay as playback time if there
141 // was no freeze.
142 time_in_resolution_ms_[current_resolution_] += interframe_delay_ms;
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200143 absl::optional<int> qp_blocky_threshold;
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +0200144 // TODO(ilnik): add other codec types when we have QP for them.
145 switch (codec) {
146 case kVideoCodecVP8:
147 qp_blocky_threshold = kBlockyQpThresholdVp8;
148 break;
149 case kVideoCodecVP9:
150 qp_blocky_threshold = kBlockyQpThresholdVp9;
151 break;
152 default:
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200153 qp_blocky_threshold = absl::nullopt;
Ilya Nikolaevskiy94150ee2018-05-23 11:53:19 +0200154 }
155 if (qp_blocky_threshold && qp.value_or(0) > *qp_blocky_threshold) {
156 time_in_blocky_video_ms_ += interframe_delay_ms;
157 }
158 }
159 }
160
161 if (is_paused_) {
162 // If the stream was paused since the previous frame, do not count the
163 // pause toward smooth playback. Explicitly count the part before it and
164 // start the new smooth playback interval from this frame.
165 is_paused_ = false;
166 if (last_frame_decoded_ms_ > last_unfreeze_time_) {
167 smooth_playback_durations_.Add(last_frame_decoded_ms_ -
168 last_unfreeze_time_);
169 }
170 last_unfreeze_time_ = now_ms;
171 }
172
173 int64_t pixels = width * height;
174 if (pixels >= kPixelsInHighResolution) {
175 current_resolution_ = Resolution::High;
176 } else if (pixels >= kPixelsInMediumResolution) {
177 current_resolution_ = Resolution::Medium;
178 } else {
179 current_resolution_ = Resolution::Low;
180 }
181
182 if (pixels < last_frame_pixels_) {
183 ++num_resolution_downgrades_;
184 }
185
186 last_frame_decoded_ms_ = now_ms;
187 last_frame_qp_ = qp.value_or(0);
188 last_frame_pixels_ = pixels;
189}
190
191void VideoQualityObserver::OnStreamInactive() {
192 is_paused_ = true;
193}
194} // namespace webrtc