blob: 2859ac2e2291955558e01c2394cf5fd04fc7b9ec [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,
Harsh Maniar6e046662020-09-11 00:34:53 -0700192 int64_t default_sampling_period_ms)
Henrik Boström012aa372020-04-27 17:40:55 +0200193 : handler_(handler),
Åsa Persson0ad2d8a2018-04-19 11:06:11 +0200194 thresholds_(thresholds),
Harsh Maniar6e046662020-09-11 00:34:53 -0700195 sampling_period_ms_(QualityScalerSettings::ParseFromFieldTrials()
196 .SamplingPeriodMs()
197 .value_or(default_sampling_period_ms)),
kthelgason876222f2016-11-29 01:44:11 -0800198 fast_rampup_(true),
199 // Arbitrarily choose size based on 30 fps for 5 seconds.
Harsh Maniar6e046662020-09-11 00:34:53 -0700200 average_qp_(QualityScalerSettings::ParseFromFieldTrials()
201 .AverageQpWindow()
202 .value_or(5 * 30)),
Åsa Perssona945aee2018-04-24 16:53:25 +0200203 framedrop_percent_media_opt_(5 * 30),
204 framedrop_percent_all_(5 * 30),
205 experiment_enabled_(QualityScalingExperiment::Enabled()),
Åsa Persson517678c2019-05-06 14:17:35 +0200206 min_frames_needed_(
207 QualityScalerSettings::ParseFromFieldTrials().MinFrames().value_or(
208 kMinFramesNeededToScale)),
209 initial_scale_factor_(QualityScalerSettings::ParseFromFieldTrials()
210 .InitialScaleFactor()
211 .value_or(kSamplePeriodScaleFactor)),
212 scale_factor_(
Henrik Boström012aa372020-04-27 17:40:55 +0200213 QualityScalerSettings::ParseFromFieldTrials().ScaleFactor()) {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200214 RTC_DCHECK_RUN_ON(&task_checker_);
Åsa Perssona945aee2018-04-24 16:53:25 +0200215 if (experiment_enabled_) {
216 config_ = QualityScalingExperiment::GetConfig();
217 qp_smoother_high_.reset(new QpSmoother(config_.alpha_high));
218 qp_smoother_low_.reset(new QpSmoother(config_.alpha_low));
219 }
Henrik Boström012aa372020-04-27 17:40:55 +0200220 RTC_DCHECK(handler_ != nullptr);
221 StartNextCheckQpTask();
Mirko Bonadei675513b2017-11-09 11:09:25 +0100222 RTC_LOG(LS_INFO) << "QP thresholds: low: " << thresholds_.low
223 << ", high: " << thresholds_.high;
pbos@webrtc.orga0d78272014-09-12 11:51:47 +0000224}
225
kthelgason876222f2016-11-29 01:44:11 -0800226QualityScaler::~QualityScaler() {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200227 RTC_DCHECK_RUN_ON(&task_checker_);
kthelgason876222f2016-11-29 01:44:11 -0800228}
229
Henrik Boström012aa372020-04-27 17:40:55 +0200230void QualityScaler::StartNextCheckQpTask() {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200231 RTC_DCHECK_RUN_ON(&task_checker_);
Henrik Boström012aa372020-04-27 17:40:55 +0200232 RTC_DCHECK(!pending_qp_task_ || pending_qp_task_->HasCompletedTask())
233 << "A previous CheckQpTask has not completed yet!";
234 CheckQpTask::Result previous_task_result;
235 if (pending_qp_task_) {
236 previous_task_result = pending_qp_task_->result();
Åsa Perssona945aee2018-04-24 16:53:25 +0200237 }
Henrik Boström012aa372020-04-27 17:40:55 +0200238 pending_qp_task_ = std::make_unique<CheckQpTask>(this, previous_task_result);
239 pending_qp_task_->StartDelayedTask();
kthelgason876222f2016-11-29 01:44:11 -0800240}
241
Åsa Persson12314192019-06-20 15:45:07 +0200242void QualityScaler::SetQpThresholds(VideoEncoder::QpThresholds thresholds) {
243 RTC_DCHECK_RUN_ON(&task_checker_);
244 thresholds_ = thresholds;
245}
246
Åsa Perssona945aee2018-04-24 16:53:25 +0200247void QualityScaler::ReportDroppedFrameByMediaOpt() {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200248 RTC_DCHECK_RUN_ON(&task_checker_);
Åsa Perssona945aee2018-04-24 16:53:25 +0200249 framedrop_percent_media_opt_.AddSample(100);
250 framedrop_percent_all_.AddSample(100);
251}
252
253void QualityScaler::ReportDroppedFrameByEncoder() {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200254 RTC_DCHECK_RUN_ON(&task_checker_);
Åsa Perssona945aee2018-04-24 16:53:25 +0200255 framedrop_percent_all_.AddSample(100);
pbos@webrtc.orga0d78272014-09-12 11:51:47 +0000256}
257
Sebastian Janssonb6789402019-03-01 15:40:49 +0100258void QualityScaler::ReportQp(int qp, int64_t time_sent_us) {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200259 RTC_DCHECK_RUN_ON(&task_checker_);
Åsa Perssona945aee2018-04-24 16:53:25 +0200260 framedrop_percent_media_opt_.AddSample(0);
261 framedrop_percent_all_.AddSample(0);
kthelgason194f40a2016-09-14 02:14:58 -0700262 average_qp_.AddSample(qp);
Åsa Perssona945aee2018-04-24 16:53:25 +0200263 if (qp_smoother_high_)
Sebastian Janssonb6789402019-03-01 15:40:49 +0100264 qp_smoother_high_->Add(qp, time_sent_us);
Åsa Perssona945aee2018-04-24 16:53:25 +0200265 if (qp_smoother_low_)
Sebastian Janssonb6789402019-03-01 15:40:49 +0100266 qp_smoother_low_->Add(qp, time_sent_us);
pbos@webrtc.orga0d78272014-09-12 11:51:47 +0000267}
268
Åsa Perssone644a032019-11-08 15:56:00 +0100269bool QualityScaler::QpFastFilterLow() const {
270 RTC_DCHECK_RUN_ON(&task_checker_);
271 size_t num_frames = config_.use_all_drop_reasons
272 ? framedrop_percent_all_.Size()
273 : framedrop_percent_media_opt_.Size();
274 const size_t kMinNumFrames = 10;
275 if (num_frames < kMinNumFrames) {
276 return false; // Wait for more frames before making a decision.
277 }
278 absl::optional<int> avg_qp_high = qp_smoother_high_
279 ? qp_smoother_high_->GetAvg()
280 : average_qp_.GetAverageRoundedDown();
281 return (avg_qp_high) ? (avg_qp_high.value() <= thresholds_.low) : false;
282}
283
Henrik Boström012aa372020-04-27 17:40:55 +0200284QualityScaler::CheckQpResult QualityScaler::CheckQp() const {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200285 RTC_DCHECK_RUN_ON(&task_checker_);
jackychen61b4d512015-04-21 15:30:11 -0700286 // Should be set through InitEncode -> Should be set by now.
kthelgason876222f2016-11-29 01:44:11 -0800287 RTC_DCHECK_GE(thresholds_.low, 0);
kthelgason55a01352017-04-04 02:31:42 -0700288
Åsa Persson0ad2d8a2018-04-19 11:06:11 +0200289 // If we have not observed at least this many frames we can't make a good
290 // scaling decision.
Åsa Perssona945aee2018-04-24 16:53:25 +0200291 const size_t frames = config_.use_all_drop_reasons
Ilya Nikolaevskiy26341992018-11-05 12:55:18 +0100292 ? framedrop_percent_all_.Size()
293 : framedrop_percent_media_opt_.Size();
Åsa Persson517678c2019-05-06 14:17:35 +0200294 if (frames < min_frames_needed_) {
Henrik Boström012aa372020-04-27 17:40:55 +0200295 return CheckQpResult::kInsufficientSamples;
Åsa Perssona945aee2018-04-24 16:53:25 +0200296 }
kthelgason55a01352017-04-04 02:31:42 -0700297
kthelgason194f40a2016-09-14 02:14:58 -0700298 // Check if we should scale down due to high frame drop.
Danil Chapovalov0040b662018-06-18 10:48:16 +0200299 const absl::optional<int> drop_rate =
Ilya Nikolaevskiy26341992018-11-05 12:55:18 +0100300 config_.use_all_drop_reasons
301 ? framedrop_percent_all_.GetAverageRoundedDown()
302 : framedrop_percent_media_opt_.GetAverageRoundedDown();
kthelgason194f40a2016-09-14 02:14:58 -0700303 if (drop_rate && *drop_rate >= kFramedropPercentThreshold) {
Åsa Persson0ad2d8a2018-04-19 11:06:11 +0200304 RTC_LOG(LS_INFO) << "Reporting high QP, framedrop percent " << *drop_rate;
Henrik Boström012aa372020-04-27 17:40:55 +0200305 return CheckQpResult::kHighQp;
kthelgason194f40a2016-09-14 02:14:58 -0700306 }
307
308 // Check if we should scale up or down based on QP.
Ilya Nikolaevskiy26341992018-11-05 12:55:18 +0100309 const absl::optional<int> avg_qp_high =
310 qp_smoother_high_ ? qp_smoother_high_->GetAvg()
311 : average_qp_.GetAverageRoundedDown();
Danil Chapovalov0040b662018-06-18 10:48:16 +0200312 const absl::optional<int> avg_qp_low =
Ilya Nikolaevskiy26341992018-11-05 12:55:18 +0100313 qp_smoother_low_ ? qp_smoother_low_->GetAvg()
314 : average_qp_.GetAverageRoundedDown();
Åsa Perssona945aee2018-04-24 16:53:25 +0200315 if (avg_qp_high && avg_qp_low) {
316 RTC_LOG(LS_INFO) << "Checking average QP " << *avg_qp_high << " ("
317 << *avg_qp_low << ").";
318 if (*avg_qp_high > thresholds_.high) {
Henrik Boström012aa372020-04-27 17:40:55 +0200319 return CheckQpResult::kHighQp;
glaznevd1c44352017-03-23 14:40:08 -0700320 }
Åsa Perssona945aee2018-04-24 16:53:25 +0200321 if (*avg_qp_low <= thresholds_.low) {
glaznevd1c44352017-03-23 14:40:08 -0700322 // QP has been low. We want to try a higher resolution.
Henrik Boström012aa372020-04-27 17:40:55 +0200323 return CheckQpResult::kLowQp;
glaznevd1c44352017-03-23 14:40:08 -0700324 }
kthelgason194f40a2016-09-14 02:14:58 -0700325 }
Henrik Boström012aa372020-04-27 17:40:55 +0200326 return CheckQpResult::kNormalQp;
jackychen6e2ce6e2015-07-13 16:26:33 -0700327}
pbos@webrtc.orga0d78272014-09-12 11:51:47 +0000328
pbos@webrtc.orga0d78272014-09-12 11:51:47 +0000329void QualityScaler::ClearSamples() {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200330 RTC_DCHECK_RUN_ON(&task_checker_);
Åsa Perssona945aee2018-04-24 16:53:25 +0200331 framedrop_percent_media_opt_.Reset();
332 framedrop_percent_all_.Reset();
kthelgason194f40a2016-09-14 02:14:58 -0700333 average_qp_.Reset();
Åsa Perssona945aee2018-04-24 16:53:25 +0200334 if (qp_smoother_high_)
335 qp_smoother_high_->Reset();
336 if (qp_smoother_low_)
337 qp_smoother_low_->Reset();
pboscbac40d2016-04-13 02:51:02 -0700338}
Henrik Boström012aa372020-04-27 17:40:55 +0200339
340QualityScalerQpUsageHandlerInterface::~QualityScalerQpUsageHandlerInterface() {}
341
pbos@webrtc.orga0d78272014-09-12 11:51:47 +0000342} // namespace webrtc