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