blob: c3d8b2e425685381210d2309534e5a6d323df5d9 [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"
kthelgason478681e2016-09-28 08:17:43 -070022
Kári Tristan Helgason5a20ed32016-09-15 10:56:19 +020023// TODO(kthelgason): Some versions of Android have issues with log2.
24// See https://code.google.com/p/android/issues/detail?id=212634 for details
25#if defined(WEBRTC_ANDROID)
26#define log2(x) (log(x) / log(2))
27#endif
kthelgason194f40a2016-09-14 02:14:58 -070028
pbos@webrtc.orga0d78272014-09-12 11:51:47 +000029namespace webrtc {
30
Peter Boström926dfcd2016-04-14 14:48:10 +020031namespace {
Niels Möller225c7872018-02-22 15:03:53 +010032// TODO(nisse): Delete, delegate to encoders.
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
Evan Shrubsole73a5e912020-01-28 15:10:32 +010072QualityScaler::QualityScaler(AdaptationObserverInterface* observer,
kthelgason876222f2016-11-29 01:44:11 -080073 VideoEncoder::QpThresholds thresholds)
Evan Shrubsole73a5e912020-01-28 15:10:32 +010074 : QualityScaler(observer, thresholds, kMeasureMs) {}
kthelgason876222f2016-11-29 01:44:11 -080075
76// Protected ctor, should not be called directly.
Evan Shrubsole73a5e912020-01-28 15:10:32 +010077QualityScaler::QualityScaler(AdaptationObserverInterface* observer,
kthelgason876222f2016-11-29 01:44:11 -080078 VideoEncoder::QpThresholds thresholds,
Åsa Persson0ad2d8a2018-04-19 11:06:11 +020079 int64_t sampling_period_ms)
Sebastian Janssonecb68972019-01-18 10:30:54 +010080 : observer_(observer),
Åsa Persson0ad2d8a2018-04-19 11:06:11 +020081 thresholds_(thresholds),
82 sampling_period_ms_(sampling_period_ms),
kthelgason876222f2016-11-29 01:44:11 -080083 fast_rampup_(true),
84 // Arbitrarily choose size based on 30 fps for 5 seconds.
85 average_qp_(5 * 30),
Åsa Perssona945aee2018-04-24 16:53:25 +020086 framedrop_percent_media_opt_(5 * 30),
87 framedrop_percent_all_(5 * 30),
88 experiment_enabled_(QualityScalingExperiment::Enabled()),
Åsa Persson517678c2019-05-06 14:17:35 +020089 observed_enough_frames_(false),
90 min_frames_needed_(
91 QualityScalerSettings::ParseFromFieldTrials().MinFrames().value_or(
92 kMinFramesNeededToScale)),
93 initial_scale_factor_(QualityScalerSettings::ParseFromFieldTrials()
94 .InitialScaleFactor()
95 .value_or(kSamplePeriodScaleFactor)),
96 scale_factor_(
97 QualityScalerSettings::ParseFromFieldTrials().ScaleFactor()),
Åsa Perssonf5e5d252019-08-16 17:24:59 +020098 adapt_called_(false),
99 adapt_failed_(false) {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200100 RTC_DCHECK_RUN_ON(&task_checker_);
Åsa Perssona945aee2018-04-24 16:53:25 +0200101 if (experiment_enabled_) {
102 config_ = QualityScalingExperiment::GetConfig();
103 qp_smoother_high_.reset(new QpSmoother(config_.alpha_high));
104 qp_smoother_low_.reset(new QpSmoother(config_.alpha_low));
105 }
kthelgason876222f2016-11-29 01:44:11 -0800106 RTC_DCHECK(observer_ != nullptr);
Sebastian Janssonecb68972019-01-18 10:30:54 +0100107 check_qp_task_ = RepeatingTaskHandle::DelayedStart(
Danil Chapovalov55284022020-02-07 14:53:52 +0100108 TaskQueueBase::Current(), TimeDelta::Millis(GetSamplingPeriodMs()),
109 [this]() {
Sebastian Janssonecb68972019-01-18 10:30:54 +0100110 CheckQp();
Danil Chapovalov55284022020-02-07 14:53:52 +0100111 return TimeDelta::Millis(GetSamplingPeriodMs());
Sebastian Janssonecb68972019-01-18 10:30:54 +0100112 });
Mirko Bonadei675513b2017-11-09 11:09:25 +0100113 RTC_LOG(LS_INFO) << "QP thresholds: low: " << thresholds_.low
114 << ", high: " << thresholds_.high;
pbos@webrtc.orga0d78272014-09-12 11:51:47 +0000115}
116
kthelgason876222f2016-11-29 01:44:11 -0800117QualityScaler::~QualityScaler() {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200118 RTC_DCHECK_RUN_ON(&task_checker_);
Sebastian Janssonecb68972019-01-18 10:30:54 +0100119 check_qp_task_.Stop();
kthelgason876222f2016-11-29 01:44:11 -0800120}
121
122int64_t QualityScaler::GetSamplingPeriodMs() const {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200123 RTC_DCHECK_RUN_ON(&task_checker_);
Åsa Perssona945aee2018-04-24 16:53:25 +0200124 if (fast_rampup_) {
125 return sampling_period_ms_;
126 }
127 if (experiment_enabled_ && !observed_enough_frames_) {
128 // Use half the interval while waiting for enough frames.
129 return sampling_period_ms_ / 2;
130 }
Åsa Perssonf5e5d252019-08-16 17:24:59 +0200131 if (adapt_failed_) {
132 // Check shortly again.
133 return sampling_period_ms_ / 8;
134 }
135 if (scale_factor_ && !adapt_called_) {
136 // Last CheckQp did not call AdaptDown/Up, possibly reduce interval.
Åsa Persson517678c2019-05-06 14:17:35 +0200137 return sampling_period_ms_ * scale_factor_.value();
138 }
139 return sampling_period_ms_ * initial_scale_factor_;
kthelgason876222f2016-11-29 01:44:11 -0800140}
141
Åsa Persson12314192019-06-20 15:45:07 +0200142void QualityScaler::SetQpThresholds(VideoEncoder::QpThresholds thresholds) {
143 RTC_DCHECK_RUN_ON(&task_checker_);
144 thresholds_ = thresholds;
145}
146
Åsa Perssona945aee2018-04-24 16:53:25 +0200147void QualityScaler::ReportDroppedFrameByMediaOpt() {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200148 RTC_DCHECK_RUN_ON(&task_checker_);
Åsa Perssona945aee2018-04-24 16:53:25 +0200149 framedrop_percent_media_opt_.AddSample(100);
150 framedrop_percent_all_.AddSample(100);
151}
152
153void QualityScaler::ReportDroppedFrameByEncoder() {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200154 RTC_DCHECK_RUN_ON(&task_checker_);
Åsa Perssona945aee2018-04-24 16:53:25 +0200155 framedrop_percent_all_.AddSample(100);
pbos@webrtc.orga0d78272014-09-12 11:51:47 +0000156}
157
Sebastian Janssonb6789402019-03-01 15:40:49 +0100158void QualityScaler::ReportQp(int qp, int64_t time_sent_us) {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200159 RTC_DCHECK_RUN_ON(&task_checker_);
Åsa Perssona945aee2018-04-24 16:53:25 +0200160 framedrop_percent_media_opt_.AddSample(0);
161 framedrop_percent_all_.AddSample(0);
kthelgason194f40a2016-09-14 02:14:58 -0700162 average_qp_.AddSample(qp);
Åsa Perssona945aee2018-04-24 16:53:25 +0200163 if (qp_smoother_high_)
Sebastian Janssonb6789402019-03-01 15:40:49 +0100164 qp_smoother_high_->Add(qp, time_sent_us);
Åsa Perssona945aee2018-04-24 16:53:25 +0200165 if (qp_smoother_low_)
Sebastian Janssonb6789402019-03-01 15:40:49 +0100166 qp_smoother_low_->Add(qp, time_sent_us);
pbos@webrtc.orga0d78272014-09-12 11:51:47 +0000167}
168
Åsa Perssone644a032019-11-08 15:56:00 +0100169bool QualityScaler::QpFastFilterLow() const {
170 RTC_DCHECK_RUN_ON(&task_checker_);
171 size_t num_frames = config_.use_all_drop_reasons
172 ? framedrop_percent_all_.Size()
173 : framedrop_percent_media_opt_.Size();
174 const size_t kMinNumFrames = 10;
175 if (num_frames < kMinNumFrames) {
176 return false; // Wait for more frames before making a decision.
177 }
178 absl::optional<int> avg_qp_high = qp_smoother_high_
179 ? qp_smoother_high_->GetAvg()
180 : average_qp_.GetAverageRoundedDown();
181 return (avg_qp_high) ? (avg_qp_high.value() <= thresholds_.low) : false;
182}
183
Åsa Persson04d5f1d2018-04-20 15:19:11 +0200184void QualityScaler::CheckQp() {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200185 RTC_DCHECK_RUN_ON(&task_checker_);
jackychen61b4d512015-04-21 15:30:11 -0700186 // Should be set through InitEncode -> Should be set by now.
kthelgason876222f2016-11-29 01:44:11 -0800187 RTC_DCHECK_GE(thresholds_.low, 0);
Åsa Perssonf5e5d252019-08-16 17:24:59 +0200188 adapt_failed_ = false;
189 adapt_called_ = false;
kthelgason55a01352017-04-04 02:31:42 -0700190
Åsa Persson0ad2d8a2018-04-19 11:06:11 +0200191 // If we have not observed at least this many frames we can't make a good
192 // scaling decision.
Åsa Perssona945aee2018-04-24 16:53:25 +0200193 const size_t frames = config_.use_all_drop_reasons
Ilya Nikolaevskiy26341992018-11-05 12:55:18 +0100194 ? framedrop_percent_all_.Size()
195 : framedrop_percent_media_opt_.Size();
Åsa Persson517678c2019-05-06 14:17:35 +0200196 if (frames < min_frames_needed_) {
Åsa Perssona945aee2018-04-24 16:53:25 +0200197 observed_enough_frames_ = false;
kthelgason55a01352017-04-04 02:31:42 -0700198 return;
Åsa Perssona945aee2018-04-24 16:53:25 +0200199 }
200 observed_enough_frames_ = true;
kthelgason55a01352017-04-04 02:31:42 -0700201
kthelgason194f40a2016-09-14 02:14:58 -0700202 // Check if we should scale down due to high frame drop.
Danil Chapovalov0040b662018-06-18 10:48:16 +0200203 const absl::optional<int> drop_rate =
Ilya Nikolaevskiy26341992018-11-05 12:55:18 +0100204 config_.use_all_drop_reasons
205 ? framedrop_percent_all_.GetAverageRoundedDown()
206 : framedrop_percent_media_opt_.GetAverageRoundedDown();
kthelgason194f40a2016-09-14 02:14:58 -0700207 if (drop_rate && *drop_rate >= kFramedropPercentThreshold) {
Åsa Persson0ad2d8a2018-04-19 11:06:11 +0200208 RTC_LOG(LS_INFO) << "Reporting high QP, framedrop percent " << *drop_rate;
Åsa Persson04d5f1d2018-04-20 15:19:11 +0200209 ReportQpHigh();
kthelgason194f40a2016-09-14 02:14:58 -0700210 return;
211 }
212
213 // Check if we should scale up or down based on QP.
Ilya Nikolaevskiy26341992018-11-05 12:55:18 +0100214 const absl::optional<int> avg_qp_high =
215 qp_smoother_high_ ? qp_smoother_high_->GetAvg()
216 : average_qp_.GetAverageRoundedDown();
Danil Chapovalov0040b662018-06-18 10:48:16 +0200217 const absl::optional<int> avg_qp_low =
Ilya Nikolaevskiy26341992018-11-05 12:55:18 +0100218 qp_smoother_low_ ? qp_smoother_low_->GetAvg()
219 : average_qp_.GetAverageRoundedDown();
Åsa Perssona945aee2018-04-24 16:53:25 +0200220 if (avg_qp_high && avg_qp_low) {
221 RTC_LOG(LS_INFO) << "Checking average QP " << *avg_qp_high << " ("
222 << *avg_qp_low << ").";
223 if (*avg_qp_high > thresholds_.high) {
Åsa Persson04d5f1d2018-04-20 15:19:11 +0200224 ReportQpHigh();
glaznevd1c44352017-03-23 14:40:08 -0700225 return;
226 }
Åsa Perssona945aee2018-04-24 16:53:25 +0200227 if (*avg_qp_low <= thresholds_.low) {
glaznevd1c44352017-03-23 14:40:08 -0700228 // QP has been low. We want to try a higher resolution.
Åsa Persson04d5f1d2018-04-20 15:19:11 +0200229 ReportQpLow();
glaznevd1c44352017-03-23 14:40:08 -0700230 return;
231 }
kthelgason194f40a2016-09-14 02:14:58 -0700232 }
233}
234
Åsa Persson04d5f1d2018-04-20 15:19:11 +0200235void QualityScaler::ReportQpLow() {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200236 RTC_DCHECK_RUN_ON(&task_checker_);
kthelgason194f40a2016-09-14 02:14:58 -0700237 ClearSamples();
Evan Shrubsolece0a11d2020-04-16 11:36:55 +0200238 observer_->AdaptUp(VideoAdaptationReason::kQuality);
Åsa Perssonf5e5d252019-08-16 17:24:59 +0200239 adapt_called_ = true;
kthelgason194f40a2016-09-14 02:14:58 -0700240}
241
Åsa Persson04d5f1d2018-04-20 15:19:11 +0200242void QualityScaler::ReportQpHigh() {
Sebastian Janssonb55015e2019-04-09 13:44:04 +0200243 RTC_DCHECK_RUN_ON(&task_checker_);
Åsa Perssonf5e5d252019-08-16 17:24:59 +0200244
Evan Shrubsolece0a11d2020-04-16 11:36:55 +0200245 if (observer_->AdaptDown(VideoAdaptationReason::kQuality)) {
Åsa Perssonf5e5d252019-08-16 17:24:59 +0200246 ClearSamples();
247 } else {
248 adapt_failed_ = true;
249 }
250
kthelgason194f40a2016-09-14 02:14:58 -0700251 // If we've scaled down, wait longer before scaling up again.
252 if (fast_rampup_) {
253 fast_rampup_ = false;
kthelgason194f40a2016-09-14 02:14:58 -0700254 }
Åsa Perssonf5e5d252019-08-16 17:24:59 +0200255 adapt_called_ = true;
jackychen6e2ce6e2015-07-13 16:26:33 -0700256}
pbos@webrtc.orga0d78272014-09-12 11:51:47 +0000257
pbos@webrtc.orga0d78272014-09-12 11:51:47 +0000258void QualityScaler::ClearSamples() {
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_.Reset();
261 framedrop_percent_all_.Reset();
kthelgason194f40a2016-09-14 02:14:58 -0700262 average_qp_.Reset();
Åsa Perssona945aee2018-04-24 16:53:25 +0200263 if (qp_smoother_high_)
264 qp_smoother_high_->Reset();
265 if (qp_smoother_low_)
266 qp_smoother_low_->Reset();
pboscbac40d2016-04-13 02:51:02 -0700267}
pbos@webrtc.orga0d78272014-09-12 11:51:47 +0000268} // namespace webrtc