blob: 28252b4cfa3db792e69fcc09df4e387d50ec9d25 [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
Danil Chapovalov0be8eba2022-07-06 13:17:54 +020016#include "api/units/time_delta.h"
Evan Shrubsolece0a11d2020-04-16 11:36:55 +020017#include "api/video/video_adaptation_reason.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020018#include "rtc_base/checks.h"
Åsa Persson517678c2019-05-06 14:17:35 +020019#include "rtc_base/experiments/quality_scaler_settings.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020020#include "rtc_base/logging.h"
Åsa Perssona945aee2018-04-24 16:53:25 +020021#include "rtc_base/numerics/exp_filter.h"
Henrik Boström012aa372020-04-27 17:40:55 +020022#include "rtc_base/weak_ptr.h"
kthelgason478681e2016-09-28 08:17:43 -070023
Kári Tristan Helgason5a20ed32016-09-15 10:56:19 +020024// TODO(kthelgason): Some versions of Android have issues with log2.
25// See https://code.google.com/p/android/issues/detail?id=212634 for details
26#if defined(WEBRTC_ANDROID)
27#define log2(x) (log(x) / log(2))
28#endif
kthelgason194f40a2016-09-14 02:14:58 -070029
pbos@webrtc.orga0d78272014-09-12 11:51:47 +000030namespace webrtc {
31
Peter Boström926dfcd2016-04-14 14:48:10 +020032namespace {
pboscbac40d2016-04-13 02:51:02 -070033// Threshold constant used until first downscale (to permit fast rampup).
kthelgason876222f2016-11-29 01:44:11 -080034static const int kMeasureMs = 2000;
35static const float kSamplePeriodScaleFactor = 2.5;
pbos@webrtc.orga0d78272014-09-12 11:51:47 +000036static const int kFramedropPercentThreshold = 60;
Åsa Persson517678c2019-05-06 14:17:35 +020037static const size_t kMinFramesNeededToScale = 2 * 30;
pbos@webrtc.orga0d78272014-09-12 11:51:47 +000038
kthelgason876222f2016-11-29 01:44:11 -080039} // namespace
kthelgason478681e2016-09-28 08:17:43 -070040
Åsa Perssona945aee2018-04-24 16:53:25 +020041class QualityScaler::QpSmoother {
42 public:
43 explicit QpSmoother(float alpha)
Sebastian Janssonb6789402019-03-01 15:40:49 +010044 : alpha_(alpha),
45 // The initial value of last_sample_ms doesn't matter since the smoother
46 // will ignore the time delta for the first update.
47 last_sample_ms_(0),
48 smoother_(alpha) {}
Åsa Perssona945aee2018-04-24 16:53:25 +020049
Danil Chapovalov0040b662018-06-18 10:48:16 +020050 absl::optional<int> GetAvg() const {
Åsa Perssona945aee2018-04-24 16:53:25 +020051 float value = smoother_.filtered();
52 if (value == rtc::ExpFilter::kValueUndefined) {
Danil Chapovalov0040b662018-06-18 10:48:16 +020053 return absl::nullopt;
Åsa Perssona945aee2018-04-24 16:53:25 +020054 }
55 return static_cast<int>(value);
56 }
57
Sebastian Janssonb6789402019-03-01 15:40:49 +010058 void Add(float sample, int64_t time_sent_us) {
59 int64_t now_ms = time_sent_us / 1000;
Åsa Perssona945aee2018-04-24 16:53:25 +020060 smoother_.Apply(static_cast<float>(now_ms - last_sample_ms_), sample);
61 last_sample_ms_ = now_ms;
62 }
63
64 void Reset() { smoother_.Reset(alpha_); }
65
66 private:
67 const float alpha_;
68 int64_t last_sample_ms_;
69 rtc::ExpFilter smoother_;
70};
71
Henrik Boström012aa372020-04-27 17:40:55 +020072// The QualityScaler checks for QP periodically by queuing CheckQpTasks. The
73// task will either run to completion and trigger a new task being queued, or it
74// will be destroyed because the QualityScaler is destroyed.
75//
76// When high or low QP is reported, the task will be pending until a callback is
77// invoked. This lets the QualityScalerQpUsageHandlerInterface react to QP usage
78// asynchronously and prevents checking for QP until the stream has potentially
79// been reconfigured.
80class QualityScaler::CheckQpTask {
81 public:
82 // The result of one CheckQpTask may influence the delay of the next
83 // CheckQpTask.
84 struct Result {
85 bool observed_enough_frames = false;
86 bool qp_usage_reported = false;
Henrik Boström012aa372020-04-27 17:40:55 +020087 };
88
89 CheckQpTask(QualityScaler* quality_scaler, Result previous_task_result)
90 : quality_scaler_(quality_scaler),
91 state_(State::kNotStarted),
92 previous_task_result_(previous_task_result),
93 weak_ptr_factory_(this) {}
94
95 void StartDelayedTask() {
96 RTC_DCHECK_EQ(state_, State::kNotStarted);
97 state_ = State::kCheckingQp;
98 TaskQueueBase::Current()->PostDelayedTask(
Danil Chapovalov0be8eba2022-07-06 13:17:54 +020099 [this_weak_ptr = weak_ptr_factory_.GetWeakPtr(), this] {
Henrik Boström012aa372020-04-27 17:40:55 +0200100 if (!this_weak_ptr) {
101 // The task has been cancelled through destruction.
102 return;
103 }
104 RTC_DCHECK_EQ(state_, State::kCheckingQp);
105 RTC_DCHECK_RUN_ON(&quality_scaler_->task_checker_);
106 switch (quality_scaler_->CheckQp()) {
107 case QualityScaler::CheckQpResult::kInsufficientSamples: {
108 result_.observed_enough_frames = false;
Artem Titovdcd7fc72021-08-09 13:02:57 +0200109 // After this line, `this` may be deleted.
Evan Shrubsolea1c77f62020-08-10 11:01:06 +0200110 break;
Henrik Boström012aa372020-04-27 17:40:55 +0200111 }
112 case QualityScaler::CheckQpResult::kNormalQp: {
113 result_.observed_enough_frames = true;
Evan Shrubsolea1c77f62020-08-10 11:01:06 +0200114 break;
Henrik Boström012aa372020-04-27 17:40:55 +0200115 }
116 case QualityScaler::CheckQpResult::kHighQp: {
117 result_.observed_enough_frames = true;
118 result_.qp_usage_reported = true;
Henrik Boström012aa372020-04-27 17:40:55 +0200119 quality_scaler_->fast_rampup_ = false;
Evan Shrubsolea1c77f62020-08-10 11:01:06 +0200120 quality_scaler_->handler_->OnReportQpUsageHigh();
121 quality_scaler_->ClearSamples();
122 break;
Henrik Boström012aa372020-04-27 17:40:55 +0200123 }
124 case QualityScaler::CheckQpResult::kLowQp: {
125 result_.observed_enough_frames = true;
126 result_.qp_usage_reported = true;
Evan Shrubsolea1c77f62020-08-10 11:01:06 +0200127 quality_scaler_->handler_->OnReportQpUsageLow();
128 quality_scaler_->ClearSamples();
129 break;
Henrik Boström012aa372020-04-27 17:40:55 +0200130 }
131 }
Evan Shrubsolea1c77f62020-08-10 11:01:06 +0200132 state_ = State::kCompleted;
133 // Starting the next task deletes the pending task. After this line,
Artem Titovdcd7fc72021-08-09 13:02:57 +0200134 // `this` has been deleted.
Evan Shrubsolea1c77f62020-08-10 11:01:06 +0200135 quality_scaler_->StartNextCheckQpTask();
Danil Chapovalov0be8eba2022-07-06 13:17:54 +0200136 },
137 TimeDelta::Millis(GetCheckingQpDelayMs()));
Henrik Boström012aa372020-04-27 17:40:55 +0200138 }
139
Henrik Boström012aa372020-04-27 17:40:55 +0200140 bool HasCompletedTask() const { return state_ == State::kCompleted; }
141
142 Result result() const {
143 RTC_DCHECK(HasCompletedTask());
144 return result_;
145 }
146
147 private:
148 enum class State {
149 kNotStarted,
150 kCheckingQp,
Henrik Boström012aa372020-04-27 17:40:55 +0200151 kCompleted,
152 };
153
Henrik Boström012aa372020-04-27 17:40:55 +0200154 // Determines the sampling period of CheckQpTasks.
155 int64_t GetCheckingQpDelayMs() const {
156 RTC_DCHECK_RUN_ON(&quality_scaler_->task_checker_);
157 if (quality_scaler_->fast_rampup_) {
158 return quality_scaler_->sampling_period_ms_;
159 }
160 if (quality_scaler_->experiment_enabled_ &&
161 !previous_task_result_.observed_enough_frames) {
162 // Use half the interval while waiting for enough frames.
163 return quality_scaler_->sampling_period_ms_ / 2;
164 }
Henrik Boström012aa372020-04-27 17:40:55 +0200165 if (quality_scaler_->scale_factor_ &&
166 !previous_task_result_.qp_usage_reported) {
167 // Last CheckQp did not call AdaptDown/Up, possibly reduce interval.
168 return quality_scaler_->sampling_period_ms_ *
169 quality_scaler_->scale_factor_.value();
170 }
171 return quality_scaler_->sampling_period_ms_ *
172 quality_scaler_->initial_scale_factor_;
173 }
174
Henrik Boström012aa372020-04-27 17:40:55 +0200175 QualityScaler* const quality_scaler_;
176 State state_;
177 const Result previous_task_result_;
178 Result result_;
179
180 rtc::WeakPtrFactory<CheckQpTask> weak_ptr_factory_;
181};
182
Henrik Boström012aa372020-04-27 17:40:55 +0200183QualityScaler::QualityScaler(QualityScalerQpUsageHandlerInterface* handler,
kthelgason876222f2016-11-29 01:44:11 -0800184 VideoEncoder::QpThresholds thresholds)
Henrik Boström012aa372020-04-27 17:40:55 +0200185 : QualityScaler(handler, thresholds, kMeasureMs) {}
kthelgason876222f2016-11-29 01:44:11 -0800186
187// Protected ctor, should not be called directly.
Henrik Boström012aa372020-04-27 17:40:55 +0200188QualityScaler::QualityScaler(QualityScalerQpUsageHandlerInterface* handler,
kthelgason876222f2016-11-29 01:44:11 -0800189 VideoEncoder::QpThresholds thresholds,
Harsh Maniar6e046662020-09-11 00:34:53 -0700190 int64_t default_sampling_period_ms)
Henrik Boström012aa372020-04-27 17:40:55 +0200191 : handler_(handler),
Åsa Persson0ad2d8a2018-04-19 11:06:11 +0200192 thresholds_(thresholds),
Harsh Maniar6e046662020-09-11 00:34:53 -0700193 sampling_period_ms_(QualityScalerSettings::ParseFromFieldTrials()
194 .SamplingPeriodMs()
195 .value_or(default_sampling_period_ms)),
kthelgason876222f2016-11-29 01:44:11 -0800196 fast_rampup_(true),
197 // Arbitrarily choose size based on 30 fps for 5 seconds.
Harsh Maniar6e046662020-09-11 00:34:53 -0700198 average_qp_(QualityScalerSettings::ParseFromFieldTrials()
199 .AverageQpWindow()
200 .value_or(5 * 30)),
Åsa Perssona945aee2018-04-24 16:53:25 +0200201 framedrop_percent_media_opt_(5 * 30),
202 framedrop_percent_all_(5 * 30),
203 experiment_enabled_(QualityScalingExperiment::Enabled()),
Åsa Persson517678c2019-05-06 14:17:35 +0200204 min_frames_needed_(
205 QualityScalerSettings::ParseFromFieldTrials().MinFrames().value_or(
206 kMinFramesNeededToScale)),
207 initial_scale_factor_(QualityScalerSettings::ParseFromFieldTrials()
208 .InitialScaleFactor()
209 .value_or(kSamplePeriodScaleFactor)),
210 scale_factor_(
Henrik Boström012aa372020-04-27 17:40:55 +0200211 QualityScalerSettings::ParseFromFieldTrials().ScaleFactor()) {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200212 RTC_DCHECK_RUN_ON(&task_checker_);
Åsa Perssona945aee2018-04-24 16:53:25 +0200213 if (experiment_enabled_) {
214 config_ = QualityScalingExperiment::GetConfig();
215 qp_smoother_high_.reset(new QpSmoother(config_.alpha_high));
216 qp_smoother_low_.reset(new QpSmoother(config_.alpha_low));
217 }
Henrik Boström012aa372020-04-27 17:40:55 +0200218 RTC_DCHECK(handler_ != nullptr);
219 StartNextCheckQpTask();
Mirko Bonadei675513b2017-11-09 11:09:25 +0100220 RTC_LOG(LS_INFO) << "QP thresholds: low: " << thresholds_.low
221 << ", high: " << thresholds_.high;
pbos@webrtc.orga0d78272014-09-12 11:51:47 +0000222}
223
kthelgason876222f2016-11-29 01:44:11 -0800224QualityScaler::~QualityScaler() {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200225 RTC_DCHECK_RUN_ON(&task_checker_);
kthelgason876222f2016-11-29 01:44:11 -0800226}
227
Henrik Boström012aa372020-04-27 17:40:55 +0200228void QualityScaler::StartNextCheckQpTask() {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200229 RTC_DCHECK_RUN_ON(&task_checker_);
Henrik Boström012aa372020-04-27 17:40:55 +0200230 RTC_DCHECK(!pending_qp_task_ || pending_qp_task_->HasCompletedTask())
231 << "A previous CheckQpTask has not completed yet!";
232 CheckQpTask::Result previous_task_result;
233 if (pending_qp_task_) {
234 previous_task_result = pending_qp_task_->result();
Åsa Perssona945aee2018-04-24 16:53:25 +0200235 }
Henrik Boström012aa372020-04-27 17:40:55 +0200236 pending_qp_task_ = std::make_unique<CheckQpTask>(this, previous_task_result);
237 pending_qp_task_->StartDelayedTask();
kthelgason876222f2016-11-29 01:44:11 -0800238}
239
Åsa Persson12314192019-06-20 15:45:07 +0200240void QualityScaler::SetQpThresholds(VideoEncoder::QpThresholds thresholds) {
241 RTC_DCHECK_RUN_ON(&task_checker_);
242 thresholds_ = thresholds;
243}
244
Åsa Perssona945aee2018-04-24 16:53:25 +0200245void QualityScaler::ReportDroppedFrameByMediaOpt() {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200246 RTC_DCHECK_RUN_ON(&task_checker_);
Åsa Perssona945aee2018-04-24 16:53:25 +0200247 framedrop_percent_media_opt_.AddSample(100);
248 framedrop_percent_all_.AddSample(100);
249}
250
251void QualityScaler::ReportDroppedFrameByEncoder() {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200252 RTC_DCHECK_RUN_ON(&task_checker_);
Åsa Perssona945aee2018-04-24 16:53:25 +0200253 framedrop_percent_all_.AddSample(100);
pbos@webrtc.orga0d78272014-09-12 11:51:47 +0000254}
255
Sebastian Janssonb6789402019-03-01 15:40:49 +0100256void QualityScaler::ReportQp(int qp, int64_t time_sent_us) {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200257 RTC_DCHECK_RUN_ON(&task_checker_);
Åsa Perssona945aee2018-04-24 16:53:25 +0200258 framedrop_percent_media_opt_.AddSample(0);
259 framedrop_percent_all_.AddSample(0);
kthelgason194f40a2016-09-14 02:14:58 -0700260 average_qp_.AddSample(qp);
Åsa Perssona945aee2018-04-24 16:53:25 +0200261 if (qp_smoother_high_)
Sebastian Janssonb6789402019-03-01 15:40:49 +0100262 qp_smoother_high_->Add(qp, time_sent_us);
Åsa Perssona945aee2018-04-24 16:53:25 +0200263 if (qp_smoother_low_)
Sebastian Janssonb6789402019-03-01 15:40:49 +0100264 qp_smoother_low_->Add(qp, time_sent_us);
pbos@webrtc.orga0d78272014-09-12 11:51:47 +0000265}
266
Åsa Perssone644a032019-11-08 15:56:00 +0100267bool QualityScaler::QpFastFilterLow() const {
268 RTC_DCHECK_RUN_ON(&task_checker_);
269 size_t num_frames = config_.use_all_drop_reasons
270 ? framedrop_percent_all_.Size()
271 : framedrop_percent_media_opt_.Size();
272 const size_t kMinNumFrames = 10;
273 if (num_frames < kMinNumFrames) {
274 return false; // Wait for more frames before making a decision.
275 }
276 absl::optional<int> avg_qp_high = qp_smoother_high_
277 ? qp_smoother_high_->GetAvg()
278 : average_qp_.GetAverageRoundedDown();
279 return (avg_qp_high) ? (avg_qp_high.value() <= thresholds_.low) : false;
280}
281
Henrik Boström012aa372020-04-27 17:40:55 +0200282QualityScaler::CheckQpResult QualityScaler::CheckQp() const {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200283 RTC_DCHECK_RUN_ON(&task_checker_);
jackychen61b4d512015-04-21 15:30:11 -0700284 // Should be set through InitEncode -> Should be set by now.
kthelgason876222f2016-11-29 01:44:11 -0800285 RTC_DCHECK_GE(thresholds_.low, 0);
kthelgason55a01352017-04-04 02:31:42 -0700286
Åsa Persson0ad2d8a2018-04-19 11:06:11 +0200287 // If we have not observed at least this many frames we can't make a good
288 // scaling decision.
Åsa Perssona945aee2018-04-24 16:53:25 +0200289 const size_t frames = config_.use_all_drop_reasons
Ilya Nikolaevskiy26341992018-11-05 12:55:18 +0100290 ? framedrop_percent_all_.Size()
291 : framedrop_percent_media_opt_.Size();
Åsa Persson517678c2019-05-06 14:17:35 +0200292 if (frames < min_frames_needed_) {
Henrik Boström012aa372020-04-27 17:40:55 +0200293 return CheckQpResult::kInsufficientSamples;
Åsa Perssona945aee2018-04-24 16:53:25 +0200294 }
kthelgason55a01352017-04-04 02:31:42 -0700295
kthelgason194f40a2016-09-14 02:14:58 -0700296 // Check if we should scale down due to high frame drop.
Danil Chapovalov0040b662018-06-18 10:48:16 +0200297 const absl::optional<int> drop_rate =
Ilya Nikolaevskiy26341992018-11-05 12:55:18 +0100298 config_.use_all_drop_reasons
299 ? framedrop_percent_all_.GetAverageRoundedDown()
300 : framedrop_percent_media_opt_.GetAverageRoundedDown();
kthelgason194f40a2016-09-14 02:14:58 -0700301 if (drop_rate && *drop_rate >= kFramedropPercentThreshold) {
Åsa Persson0ad2d8a2018-04-19 11:06:11 +0200302 RTC_LOG(LS_INFO) << "Reporting high QP, framedrop percent " << *drop_rate;
Henrik Boström012aa372020-04-27 17:40:55 +0200303 return CheckQpResult::kHighQp;
kthelgason194f40a2016-09-14 02:14:58 -0700304 }
305
306 // Check if we should scale up or down based on QP.
Ilya Nikolaevskiy26341992018-11-05 12:55:18 +0100307 const absl::optional<int> avg_qp_high =
308 qp_smoother_high_ ? qp_smoother_high_->GetAvg()
309 : average_qp_.GetAverageRoundedDown();
Danil Chapovalov0040b662018-06-18 10:48:16 +0200310 const absl::optional<int> avg_qp_low =
Ilya Nikolaevskiy26341992018-11-05 12:55:18 +0100311 qp_smoother_low_ ? qp_smoother_low_->GetAvg()
312 : average_qp_.GetAverageRoundedDown();
Åsa Perssona945aee2018-04-24 16:53:25 +0200313 if (avg_qp_high && avg_qp_low) {
314 RTC_LOG(LS_INFO) << "Checking average QP " << *avg_qp_high << " ("
315 << *avg_qp_low << ").";
316 if (*avg_qp_high > thresholds_.high) {
Henrik Boström012aa372020-04-27 17:40:55 +0200317 return CheckQpResult::kHighQp;
glaznevd1c44352017-03-23 14:40:08 -0700318 }
Åsa Perssona945aee2018-04-24 16:53:25 +0200319 if (*avg_qp_low <= thresholds_.low) {
glaznevd1c44352017-03-23 14:40:08 -0700320 // QP has been low. We want to try a higher resolution.
Henrik Boström012aa372020-04-27 17:40:55 +0200321 return CheckQpResult::kLowQp;
glaznevd1c44352017-03-23 14:40:08 -0700322 }
kthelgason194f40a2016-09-14 02:14:58 -0700323 }
Henrik Boström012aa372020-04-27 17:40:55 +0200324 return CheckQpResult::kNormalQp;
jackychen6e2ce6e2015-07-13 16:26:33 -0700325}
pbos@webrtc.orga0d78272014-09-12 11:51:47 +0000326
pbos@webrtc.orga0d78272014-09-12 11:51:47 +0000327void QualityScaler::ClearSamples() {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200328 RTC_DCHECK_RUN_ON(&task_checker_);
Åsa Perssona945aee2018-04-24 16:53:25 +0200329 framedrop_percent_media_opt_.Reset();
330 framedrop_percent_all_.Reset();
kthelgason194f40a2016-09-14 02:14:58 -0700331 average_qp_.Reset();
Åsa Perssona945aee2018-04-24 16:53:25 +0200332 if (qp_smoother_high_)
333 qp_smoother_high_->Reset();
334 if (qp_smoother_low_)
335 qp_smoother_low_->Reset();
pboscbac40d2016-04-13 02:51:02 -0700336}
Henrik Boström012aa372020-04-27 17:40:55 +0200337
338QualityScalerQpUsageHandlerInterface::~QualityScalerQpUsageHandlerInterface() {}
339
pbos@webrtc.orga0d78272014-09-12 11:51:47 +0000340} // namespace webrtc