blob: 71bf934295ff4896015e20f98449e623f80b35a7 [file] [log] [blame]
pbos@webrtc.orga0d78272014-09-12 11:51:47 +00001/*
2 * Copyright (c) 2014 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 */
Niels Möller718a7632016-06-13 13:06:01 +020010
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020011#include "modules/video_coding/utility/quality_scaler.h"
pbos@webrtc.orga0d78272014-09-12 11:51:47 +000012
kthelgason876222f2016-11-29 01:44:11 -080013#include <memory>
Sebastian Janssonecb68972019-01-18 10:30:54 +010014#include <utility>
Kári Tristan Helgason5a20ed32016-09-15 10:56:19 +020015
Evan Shrubsolece0a11d2020-04-16 11:36:55 +020016#include "api/video/video_adaptation_reason.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020017#include "rtc_base/checks.h"
Åsa Persson517678c2019-05-06 14:17:35 +020018#include "rtc_base/experiments/quality_scaler_settings.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020019#include "rtc_base/logging.h"
Åsa Perssona945aee2018-04-24 16:53:25 +020020#include "rtc_base/numerics/exp_filter.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020021#include "rtc_base/task_queue.h"
Henrik Boström012aa372020-04-27 17:40:55 +020022#include "rtc_base/task_utils/to_queued_task.h"
23#include "rtc_base/weak_ptr.h"
kthelgason478681e2016-09-28 08:17:43 -070024
Kári Tristan Helgason5a20ed32016-09-15 10:56:19 +020025// TODO(kthelgason): Some versions of Android have issues with log2.
26// See https://code.google.com/p/android/issues/detail?id=212634 for details
27#if defined(WEBRTC_ANDROID)
28#define log2(x) (log(x) / log(2))
29#endif
kthelgason194f40a2016-09-14 02:14:58 -070030
pbos@webrtc.orga0d78272014-09-12 11:51:47 +000031namespace webrtc {
32
Peter Boström926dfcd2016-04-14 14:48:10 +020033namespace {
Niels Möller225c7872018-02-22 15:03:53 +010034// TODO(nisse): Delete, delegate to encoders.
pboscbac40d2016-04-13 02:51:02 -070035// Threshold constant used until first downscale (to permit fast rampup).
kthelgason876222f2016-11-29 01:44:11 -080036static const int kMeasureMs = 2000;
37static const float kSamplePeriodScaleFactor = 2.5;
pbos@webrtc.orga0d78272014-09-12 11:51:47 +000038static const int kFramedropPercentThreshold = 60;
Åsa Persson517678c2019-05-06 14:17:35 +020039static const size_t kMinFramesNeededToScale = 2 * 30;
pbos@webrtc.orga0d78272014-09-12 11:51:47 +000040
kthelgason876222f2016-11-29 01:44:11 -080041} // namespace
kthelgason478681e2016-09-28 08:17:43 -070042
Åsa Perssona945aee2018-04-24 16:53:25 +020043class QualityScaler::QpSmoother {
44 public:
45 explicit QpSmoother(float alpha)
Sebastian Janssonb6789402019-03-01 15:40:49 +010046 : alpha_(alpha),
47 // The initial value of last_sample_ms doesn't matter since the smoother
48 // will ignore the time delta for the first update.
49 last_sample_ms_(0),
50 smoother_(alpha) {}
Åsa Perssona945aee2018-04-24 16:53:25 +020051
Danil Chapovalov0040b662018-06-18 10:48:16 +020052 absl::optional<int> GetAvg() const {
Åsa Perssona945aee2018-04-24 16:53:25 +020053 float value = smoother_.filtered();
54 if (value == rtc::ExpFilter::kValueUndefined) {
Danil Chapovalov0040b662018-06-18 10:48:16 +020055 return absl::nullopt;
Åsa Perssona945aee2018-04-24 16:53:25 +020056 }
57 return static_cast<int>(value);
58 }
59
Sebastian Janssonb6789402019-03-01 15:40:49 +010060 void Add(float sample, int64_t time_sent_us) {
61 int64_t now_ms = time_sent_us / 1000;
Åsa Perssona945aee2018-04-24 16:53:25 +020062 smoother_.Apply(static_cast<float>(now_ms - last_sample_ms_), sample);
63 last_sample_ms_ = now_ms;
64 }
65
66 void Reset() { smoother_.Reset(alpha_); }
67
68 private:
69 const float alpha_;
70 int64_t last_sample_ms_;
71 rtc::ExpFilter smoother_;
72};
73
Henrik Boström012aa372020-04-27 17:40:55 +020074// The QualityScaler checks for QP periodically by queuing CheckQpTasks. The
75// task will either run to completion and trigger a new task being queued, or it
76// will be destroyed because the QualityScaler is destroyed.
77//
78// When high or low QP is reported, the task will be pending until a callback is
79// invoked. This lets the QualityScalerQpUsageHandlerInterface react to QP usage
80// asynchronously and prevents checking for QP until the stream has potentially
81// been reconfigured.
82class QualityScaler::CheckQpTask {
83 public:
84 // The result of one CheckQpTask may influence the delay of the next
85 // CheckQpTask.
86 struct Result {
87 bool observed_enough_frames = false;
88 bool qp_usage_reported = false;
Henrik Boström012aa372020-04-27 17:40:55 +020089 };
90
91 CheckQpTask(QualityScaler* quality_scaler, Result previous_task_result)
92 : quality_scaler_(quality_scaler),
93 state_(State::kNotStarted),
94 previous_task_result_(previous_task_result),
95 weak_ptr_factory_(this) {}
96
97 void StartDelayedTask() {
98 RTC_DCHECK_EQ(state_, State::kNotStarted);
99 state_ = State::kCheckingQp;
100 TaskQueueBase::Current()->PostDelayedTask(
101 ToQueuedTask([this_weak_ptr = weak_ptr_factory_.GetWeakPtr(), this] {
102 if (!this_weak_ptr) {
103 // The task has been cancelled through destruction.
104 return;
105 }
106 RTC_DCHECK_EQ(state_, State::kCheckingQp);
107 RTC_DCHECK_RUN_ON(&quality_scaler_->task_checker_);
108 switch (quality_scaler_->CheckQp()) {
109 case QualityScaler::CheckQpResult::kInsufficientSamples: {
110 result_.observed_enough_frames = false;
111 // After this line, |this| may be deleted.
Evan Shrubsolea1c77f62020-08-10 11:01:06 +0200112 break;
Henrik Boström012aa372020-04-27 17:40:55 +0200113 }
114 case QualityScaler::CheckQpResult::kNormalQp: {
115 result_.observed_enough_frames = true;
Evan Shrubsolea1c77f62020-08-10 11:01:06 +0200116 break;
Henrik Boström012aa372020-04-27 17:40:55 +0200117 }
118 case QualityScaler::CheckQpResult::kHighQp: {
119 result_.observed_enough_frames = true;
120 result_.qp_usage_reported = true;
Henrik Boström012aa372020-04-27 17:40:55 +0200121 quality_scaler_->fast_rampup_ = false;
Evan Shrubsolea1c77f62020-08-10 11:01:06 +0200122 quality_scaler_->handler_->OnReportQpUsageHigh();
123 quality_scaler_->ClearSamples();
124 break;
Henrik Boström012aa372020-04-27 17:40:55 +0200125 }
126 case QualityScaler::CheckQpResult::kLowQp: {
127 result_.observed_enough_frames = true;
128 result_.qp_usage_reported = true;
Evan Shrubsolea1c77f62020-08-10 11:01:06 +0200129 quality_scaler_->handler_->OnReportQpUsageLow();
130 quality_scaler_->ClearSamples();
131 break;
Henrik Boström012aa372020-04-27 17:40:55 +0200132 }
133 }
Evan Shrubsolea1c77f62020-08-10 11:01:06 +0200134 state_ = State::kCompleted;
135 // Starting the next task deletes the pending task. After this line,
136 // |this| has been deleted.
137 quality_scaler_->StartNextCheckQpTask();
Henrik Boström012aa372020-04-27 17:40:55 +0200138 }),
139 GetCheckingQpDelayMs());
140 }
141
Henrik Boström012aa372020-04-27 17:40:55 +0200142 bool HasCompletedTask() const { return state_ == State::kCompleted; }
143
144 Result result() const {
145 RTC_DCHECK(HasCompletedTask());
146 return result_;
147 }
148
149 private:
150 enum class State {
151 kNotStarted,
152 kCheckingQp,
Henrik Boström012aa372020-04-27 17:40:55 +0200153 kCompleted,
154 };
155
Henrik Boström012aa372020-04-27 17:40:55 +0200156 // Determines the sampling period of CheckQpTasks.
157 int64_t GetCheckingQpDelayMs() const {
158 RTC_DCHECK_RUN_ON(&quality_scaler_->task_checker_);
159 if (quality_scaler_->fast_rampup_) {
160 return quality_scaler_->sampling_period_ms_;
161 }
162 if (quality_scaler_->experiment_enabled_ &&
163 !previous_task_result_.observed_enough_frames) {
164 // Use half the interval while waiting for enough frames.
165 return quality_scaler_->sampling_period_ms_ / 2;
166 }
Henrik Boström012aa372020-04-27 17:40:55 +0200167 if (quality_scaler_->scale_factor_ &&
168 !previous_task_result_.qp_usage_reported) {
169 // Last CheckQp did not call AdaptDown/Up, possibly reduce interval.
170 return quality_scaler_->sampling_period_ms_ *
171 quality_scaler_->scale_factor_.value();
172 }
173 return quality_scaler_->sampling_period_ms_ *
174 quality_scaler_->initial_scale_factor_;
175 }
176
Henrik Boström012aa372020-04-27 17:40:55 +0200177 QualityScaler* const quality_scaler_;
178 State state_;
179 const Result previous_task_result_;
180 Result result_;
181
182 rtc::WeakPtrFactory<CheckQpTask> weak_ptr_factory_;
183};
184
Henrik Boström012aa372020-04-27 17:40:55 +0200185QualityScaler::QualityScaler(QualityScalerQpUsageHandlerInterface* handler,
kthelgason876222f2016-11-29 01:44:11 -0800186 VideoEncoder::QpThresholds thresholds)
Henrik Boström012aa372020-04-27 17:40:55 +0200187 : QualityScaler(handler, thresholds, kMeasureMs) {}
kthelgason876222f2016-11-29 01:44:11 -0800188
189// Protected ctor, should not be called directly.
Henrik Boström012aa372020-04-27 17:40:55 +0200190QualityScaler::QualityScaler(QualityScalerQpUsageHandlerInterface* handler,
kthelgason876222f2016-11-29 01:44:11 -0800191 VideoEncoder::QpThresholds thresholds,
Åsa Persson0ad2d8a2018-04-19 11:06:11 +0200192 int64_t sampling_period_ms)
Henrik Boström012aa372020-04-27 17:40:55 +0200193 : handler_(handler),
Åsa Persson0ad2d8a2018-04-19 11:06:11 +0200194 thresholds_(thresholds),
195 sampling_period_ms_(sampling_period_ms),
kthelgason876222f2016-11-29 01:44:11 -0800196 fast_rampup_(true),
197 // Arbitrarily choose size based on 30 fps for 5 seconds.
198 average_qp_(5 * 30),
Åsa Perssona945aee2018-04-24 16:53:25 +0200199 framedrop_percent_media_opt_(5 * 30),
200 framedrop_percent_all_(5 * 30),
201 experiment_enabled_(QualityScalingExperiment::Enabled()),
Åsa Persson517678c2019-05-06 14:17:35 +0200202 min_frames_needed_(
203 QualityScalerSettings::ParseFromFieldTrials().MinFrames().value_or(
204 kMinFramesNeededToScale)),
205 initial_scale_factor_(QualityScalerSettings::ParseFromFieldTrials()
206 .InitialScaleFactor()
207 .value_or(kSamplePeriodScaleFactor)),
208 scale_factor_(
Henrik Boström012aa372020-04-27 17:40:55 +0200209 QualityScalerSettings::ParseFromFieldTrials().ScaleFactor()) {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200210 RTC_DCHECK_RUN_ON(&task_checker_);
Åsa Perssona945aee2018-04-24 16:53:25 +0200211 if (experiment_enabled_) {
212 config_ = QualityScalingExperiment::GetConfig();
213 qp_smoother_high_.reset(new QpSmoother(config_.alpha_high));
214 qp_smoother_low_.reset(new QpSmoother(config_.alpha_low));
215 }
Henrik Boström012aa372020-04-27 17:40:55 +0200216 RTC_DCHECK(handler_ != nullptr);
217 StartNextCheckQpTask();
Mirko Bonadei675513b2017-11-09 11:09:25 +0100218 RTC_LOG(LS_INFO) << "QP thresholds: low: " << thresholds_.low
219 << ", high: " << thresholds_.high;
pbos@webrtc.orga0d78272014-09-12 11:51:47 +0000220}
221
kthelgason876222f2016-11-29 01:44:11 -0800222QualityScaler::~QualityScaler() {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200223 RTC_DCHECK_RUN_ON(&task_checker_);
kthelgason876222f2016-11-29 01:44:11 -0800224}
225
Henrik Boström012aa372020-04-27 17:40:55 +0200226void QualityScaler::StartNextCheckQpTask() {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200227 RTC_DCHECK_RUN_ON(&task_checker_);
Henrik Boström012aa372020-04-27 17:40:55 +0200228 RTC_DCHECK(!pending_qp_task_ || pending_qp_task_->HasCompletedTask())
229 << "A previous CheckQpTask has not completed yet!";
230 CheckQpTask::Result previous_task_result;
231 if (pending_qp_task_) {
232 previous_task_result = pending_qp_task_->result();
Åsa Perssona945aee2018-04-24 16:53:25 +0200233 }
Henrik Boström012aa372020-04-27 17:40:55 +0200234 pending_qp_task_ = std::make_unique<CheckQpTask>(this, previous_task_result);
235 pending_qp_task_->StartDelayedTask();
kthelgason876222f2016-11-29 01:44:11 -0800236}
237
Åsa Persson12314192019-06-20 15:45:07 +0200238void QualityScaler::SetQpThresholds(VideoEncoder::QpThresholds thresholds) {
239 RTC_DCHECK_RUN_ON(&task_checker_);
240 thresholds_ = thresholds;
241}
242
Åsa Perssona945aee2018-04-24 16:53:25 +0200243void QualityScaler::ReportDroppedFrameByMediaOpt() {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200244 RTC_DCHECK_RUN_ON(&task_checker_);
Åsa Perssona945aee2018-04-24 16:53:25 +0200245 framedrop_percent_media_opt_.AddSample(100);
246 framedrop_percent_all_.AddSample(100);
247}
248
249void QualityScaler::ReportDroppedFrameByEncoder() {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200250 RTC_DCHECK_RUN_ON(&task_checker_);
Åsa Perssona945aee2018-04-24 16:53:25 +0200251 framedrop_percent_all_.AddSample(100);
pbos@webrtc.orga0d78272014-09-12 11:51:47 +0000252}
253
Sebastian Janssonb6789402019-03-01 15:40:49 +0100254void QualityScaler::ReportQp(int qp, int64_t time_sent_us) {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200255 RTC_DCHECK_RUN_ON(&task_checker_);
Åsa Perssona945aee2018-04-24 16:53:25 +0200256 framedrop_percent_media_opt_.AddSample(0);
257 framedrop_percent_all_.AddSample(0);
kthelgason194f40a2016-09-14 02:14:58 -0700258 average_qp_.AddSample(qp);
Åsa Perssona945aee2018-04-24 16:53:25 +0200259 if (qp_smoother_high_)
Sebastian Janssonb6789402019-03-01 15:40:49 +0100260 qp_smoother_high_->Add(qp, time_sent_us);
Åsa Perssona945aee2018-04-24 16:53:25 +0200261 if (qp_smoother_low_)
Sebastian Janssonb6789402019-03-01 15:40:49 +0100262 qp_smoother_low_->Add(qp, time_sent_us);
pbos@webrtc.orga0d78272014-09-12 11:51:47 +0000263}
264
Åsa Perssone644a032019-11-08 15:56:00 +0100265bool QualityScaler::QpFastFilterLow() const {
266 RTC_DCHECK_RUN_ON(&task_checker_);
267 size_t num_frames = config_.use_all_drop_reasons
268 ? framedrop_percent_all_.Size()
269 : framedrop_percent_media_opt_.Size();
270 const size_t kMinNumFrames = 10;
271 if (num_frames < kMinNumFrames) {
272 return false; // Wait for more frames before making a decision.
273 }
274 absl::optional<int> avg_qp_high = qp_smoother_high_
275 ? qp_smoother_high_->GetAvg()
276 : average_qp_.GetAverageRoundedDown();
277 return (avg_qp_high) ? (avg_qp_high.value() <= thresholds_.low) : false;
278}
279
Henrik Boström012aa372020-04-27 17:40:55 +0200280QualityScaler::CheckQpResult QualityScaler::CheckQp() const {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200281 RTC_DCHECK_RUN_ON(&task_checker_);
jackychen61b4d512015-04-21 15:30:11 -0700282 // Should be set through InitEncode -> Should be set by now.
kthelgason876222f2016-11-29 01:44:11 -0800283 RTC_DCHECK_GE(thresholds_.low, 0);
kthelgason55a01352017-04-04 02:31:42 -0700284
Åsa Persson0ad2d8a2018-04-19 11:06:11 +0200285 // If we have not observed at least this many frames we can't make a good
286 // scaling decision.
Åsa Perssona945aee2018-04-24 16:53:25 +0200287 const size_t frames = config_.use_all_drop_reasons
Ilya Nikolaevskiy26341992018-11-05 12:55:18 +0100288 ? framedrop_percent_all_.Size()
289 : framedrop_percent_media_opt_.Size();
Åsa Persson517678c2019-05-06 14:17:35 +0200290 if (frames < min_frames_needed_) {
Henrik Boström012aa372020-04-27 17:40:55 +0200291 return CheckQpResult::kInsufficientSamples;
Åsa Perssona945aee2018-04-24 16:53:25 +0200292 }
kthelgason55a01352017-04-04 02:31:42 -0700293
kthelgason194f40a2016-09-14 02:14:58 -0700294 // Check if we should scale down due to high frame drop.
Danil Chapovalov0040b662018-06-18 10:48:16 +0200295 const absl::optional<int> drop_rate =
Ilya Nikolaevskiy26341992018-11-05 12:55:18 +0100296 config_.use_all_drop_reasons
297 ? framedrop_percent_all_.GetAverageRoundedDown()
298 : framedrop_percent_media_opt_.GetAverageRoundedDown();
kthelgason194f40a2016-09-14 02:14:58 -0700299 if (drop_rate && *drop_rate >= kFramedropPercentThreshold) {
Åsa Persson0ad2d8a2018-04-19 11:06:11 +0200300 RTC_LOG(LS_INFO) << "Reporting high QP, framedrop percent " << *drop_rate;
Henrik Boström012aa372020-04-27 17:40:55 +0200301 return CheckQpResult::kHighQp;
kthelgason194f40a2016-09-14 02:14:58 -0700302 }
303
304 // Check if we should scale up or down based on QP.
Ilya Nikolaevskiy26341992018-11-05 12:55:18 +0100305 const absl::optional<int> avg_qp_high =
306 qp_smoother_high_ ? qp_smoother_high_->GetAvg()
307 : average_qp_.GetAverageRoundedDown();
Danil Chapovalov0040b662018-06-18 10:48:16 +0200308 const absl::optional<int> avg_qp_low =
Ilya Nikolaevskiy26341992018-11-05 12:55:18 +0100309 qp_smoother_low_ ? qp_smoother_low_->GetAvg()
310 : average_qp_.GetAverageRoundedDown();
Åsa Perssona945aee2018-04-24 16:53:25 +0200311 if (avg_qp_high && avg_qp_low) {
312 RTC_LOG(LS_INFO) << "Checking average QP " << *avg_qp_high << " ("
313 << *avg_qp_low << ").";
314 if (*avg_qp_high > thresholds_.high) {
Henrik Boström012aa372020-04-27 17:40:55 +0200315 return CheckQpResult::kHighQp;
glaznevd1c44352017-03-23 14:40:08 -0700316 }
Åsa Perssona945aee2018-04-24 16:53:25 +0200317 if (*avg_qp_low <= thresholds_.low) {
glaznevd1c44352017-03-23 14:40:08 -0700318 // QP has been low. We want to try a higher resolution.
Henrik Boström012aa372020-04-27 17:40:55 +0200319 return CheckQpResult::kLowQp;
glaznevd1c44352017-03-23 14:40:08 -0700320 }
kthelgason194f40a2016-09-14 02:14:58 -0700321 }
Henrik Boström012aa372020-04-27 17:40:55 +0200322 return CheckQpResult::kNormalQp;
jackychen6e2ce6e2015-07-13 16:26:33 -0700323}
pbos@webrtc.orga0d78272014-09-12 11:51:47 +0000324
pbos@webrtc.orga0d78272014-09-12 11:51:47 +0000325void QualityScaler::ClearSamples() {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200326 RTC_DCHECK_RUN_ON(&task_checker_);
Åsa Perssona945aee2018-04-24 16:53:25 +0200327 framedrop_percent_media_opt_.Reset();
328 framedrop_percent_all_.Reset();
kthelgason194f40a2016-09-14 02:14:58 -0700329 average_qp_.Reset();
Åsa Perssona945aee2018-04-24 16:53:25 +0200330 if (qp_smoother_high_)
331 qp_smoother_high_->Reset();
332 if (qp_smoother_low_)
333 qp_smoother_low_->Reset();
pboscbac40d2016-04-13 02:51:02 -0700334}
Henrik Boström012aa372020-04-27 17:40:55 +0200335
336QualityScalerQpUsageHandlerInterface::~QualityScalerQpUsageHandlerInterface() {}
337
pbos@webrtc.orga0d78272014-09-12 11:51:47 +0000338} // namespace webrtc