1 /*
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 */
10
11 #include "modules/video_coding/utility/quality_scaler.h"
12
13 #include <math.h>
14
15 #include <algorithm>
16 #include <memory>
17
18 #include "rtc_base/checks.h"
19 #include "rtc_base/logging.h"
20 #include "rtc_base/task_queue.h"
21
22 // TODO(kthelgason): Some versions of Android have issues with log2.
23 // See https://code.google.com/p/android/issues/detail?id=212634 for details
24 #if defined(WEBRTC_ANDROID)
25 #define log2(x) (log(x) / log(2))
26 #endif
27
28 namespace webrtc {
29
30 namespace {
31 // Threshold constant used until first downscale (to permit fast rampup).
32 static const int kMeasureMs = 2000;
33 static const float kSamplePeriodScaleFactor = 2.5;
34 static const int kFramedropPercentThreshold = 60;
35 // QP scaling threshold defaults:
36 static const int kLowH264QpThreshold = 24;
37 static const int kHighH264QpThreshold = 37;
38 // QP is obtained from VP8-bitstream for HW, so the QP corresponds to the
39 // bitstream range of [0, 127] and not the user-level range of [0,63].
40 static const int kLowVp8QpThreshold = 29;
41 static const int kHighVp8QpThreshold = 95;
42 // QP is obtained from VP9-bitstream for HW, so the QP corresponds to the
43 // bitstream range of [0, 255] and not the user-level range of [0,63].
44 // Current VP9 settings are mapped from VP8 thresholds above.
45 static const int kLowVp9QpThreshold = 96;
46 static const int kHighVp9QpThreshold = 185;
47 static const int kMinFramesNeededToScale = 2 * 30;
48
CodecTypeToDefaultThresholds(VideoCodecType codec_type)49 static VideoEncoder::QpThresholds CodecTypeToDefaultThresholds(
50 VideoCodecType codec_type) {
51 int low = -1;
52 int high = -1;
53 switch (codec_type) {
54 case kVideoCodecH264:
55 low = kLowH264QpThreshold;
56 high = kHighH264QpThreshold;
57 break;
58 case kVideoCodecVP8:
59 low = kLowVp8QpThreshold;
60 high = kHighVp8QpThreshold;
61 break;
62 case kVideoCodecVP9:
63 low = kLowVp9QpThreshold;
64 high = kHighVp9QpThreshold;
65 break;
66 default:
67 RTC_NOTREACHED() << "Invalid codec type for QualityScaler.";
68 }
69 return VideoEncoder::QpThresholds(low, high);
70 }
71 } // namespace
72
73 class QualityScaler::CheckQPTask : public rtc::QueuedTask {
74 public:
CheckQPTask(QualityScaler * scaler)75 explicit CheckQPTask(QualityScaler* scaler) : scaler_(scaler) {
76 RTC_LOG(LS_INFO) << "Created CheckQPTask. Scheduling on queue...";
77 rtc::TaskQueue::Current()->PostDelayedTask(
78 std::unique_ptr<rtc::QueuedTask>(this), scaler_->GetSamplingPeriodMs());
79 }
Stop()80 void Stop() {
81 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
82 RTC_LOG(LS_INFO) << "Stopping QP Check task.";
83 stop_ = true;
84 }
85
86 private:
Run()87 bool Run() override {
88 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
89 if (stop_)
90 return true; // TaskQueue will free this task.
91 scaler_->CheckQP();
92 rtc::TaskQueue::Current()->PostDelayedTask(
93 std::unique_ptr<rtc::QueuedTask>(this), scaler_->GetSamplingPeriodMs());
94 return false; // Retain the task in order to reuse it.
95 }
96
97 QualityScaler* const scaler_;
98 bool stop_ = false;
99 rtc::SequencedTaskChecker task_checker_;
100 };
101
QualityScaler(AdaptationObserverInterface * observer,VideoCodecType codec_type)102 QualityScaler::QualityScaler(AdaptationObserverInterface* observer,
103 VideoCodecType codec_type)
104 : QualityScaler(observer, CodecTypeToDefaultThresholds(codec_type)) {}
105
QualityScaler(AdaptationObserverInterface * observer,VideoEncoder::QpThresholds thresholds)106 QualityScaler::QualityScaler(AdaptationObserverInterface* observer,
107 VideoEncoder::QpThresholds thresholds)
108 : QualityScaler(observer, thresholds, kMeasureMs) {}
109
110 // Protected ctor, should not be called directly.
QualityScaler(AdaptationObserverInterface * observer,VideoEncoder::QpThresholds thresholds,int64_t sampling_period)111 QualityScaler::QualityScaler(AdaptationObserverInterface* observer,
112 VideoEncoder::QpThresholds thresholds,
113 int64_t sampling_period)
114 : check_qp_task_(nullptr),
115 observer_(observer),
116 sampling_period_ms_(sampling_period),
117 fast_rampup_(true),
118 // Arbitrarily choose size based on 30 fps for 5 seconds.
119 average_qp_(5 * 30),
120 framedrop_percent_(5 * 30),
121 thresholds_(thresholds) {
122 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
123 RTC_DCHECK(observer_ != nullptr);
124 check_qp_task_ = new CheckQPTask(this);
125 RTC_LOG(LS_INFO) << "QP thresholds: low: " << thresholds_.low
126 << ", high: " << thresholds_.high;
127 }
128
~QualityScaler()129 QualityScaler::~QualityScaler() {
130 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
131 check_qp_task_->Stop();
132 }
133
GetSamplingPeriodMs() const134 int64_t QualityScaler::GetSamplingPeriodMs() const {
135 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
136 return fast_rampup_ ? sampling_period_ms_
137 : (sampling_period_ms_ * kSamplePeriodScaleFactor);
138 }
139
ReportDroppedFrame()140 void QualityScaler::ReportDroppedFrame() {
141 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
142 framedrop_percent_.AddSample(100);
143 }
144
ReportQP(int qp)145 void QualityScaler::ReportQP(int qp) {
146 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
147 framedrop_percent_.AddSample(0);
148 average_qp_.AddSample(qp);
149 }
150
CheckQP()151 void QualityScaler::CheckQP() {
152 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
153 // Should be set through InitEncode -> Should be set by now.
154 RTC_DCHECK_GE(thresholds_.low, 0);
155
156 // If we have not observed at least this many frames we can't
157 // make a good scaling decision.
158 if (framedrop_percent_.size() < kMinFramesNeededToScale)
159 return;
160
161 // Check if we should scale down due to high frame drop.
162 const rtc::Optional<int> drop_rate = framedrop_percent_.GetAverage();
163 if (drop_rate && *drop_rate >= kFramedropPercentThreshold) {
164 ReportQPHigh();
165 return;
166 }
167
168 // Check if we should scale up or down based on QP.
169 const rtc::Optional<int> avg_qp = average_qp_.GetAverage();
170 if (avg_qp) {
171 RTC_LOG(LS_INFO) << "Checking average QP " << *avg_qp;
172 if (*avg_qp > thresholds_.high) {
173 ReportQPHigh();
174 return;
175 }
176 if (*avg_qp <= thresholds_.low) {
177 // QP has been low. We want to try a higher resolution.
178 ReportQPLow();
179 return;
180 }
181 }
182 }
183
ReportQPLow()184 void QualityScaler::ReportQPLow() {
185 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
186 ClearSamples();
187 observer_->AdaptUp(AdaptationObserverInterface::AdaptReason::kQuality);
188 }
189
ReportQPHigh()190 void QualityScaler::ReportQPHigh() {
191 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
192 ClearSamples();
193 observer_->AdaptDown(AdaptationObserverInterface::AdaptReason::kQuality);
194 // If we've scaled down, wait longer before scaling up again.
195 if (fast_rampup_) {
196 fast_rampup_ = false;
197 }
198 }
199
ClearSamples()200 void QualityScaler::ClearSamples() {
201 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
202 framedrop_percent_.Reset();
203 average_qp_.Reset();
204 }
205 } // namespace webrtc
206