OLD | NEW |
---|---|
1 /* | 1 /* |
2 * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. | 2 * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license | 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 | 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 | 6 * tree. An additional intellectual property rights grant can be found |
7 * in the file PATENTS. All contributing project authors may | 7 * in the file PATENTS. All contributing project authors may |
8 * be found in the AUTHORS file in the root of the source tree. | 8 * be found in the AUTHORS file in the root of the source tree. |
9 */ | 9 */ |
10 | 10 |
11 #include "webrtc/modules/video_coding/utility/quality_scaler.h" | 11 #include "webrtc/modules/video_coding/utility/quality_scaler.h" |
12 | 12 |
13 #include <math.h> | 13 #include <math.h> |
14 | 14 |
15 #include <algorithm> | 15 #include <algorithm> |
16 #include <memory> | |
16 | 17 |
17 #include "webrtc/base/checks.h" | 18 #include "webrtc/base/checks.h" |
19 #include "webrtc/base/logging.h" | |
20 #include "webrtc/base/task_queue.h" | |
18 | 21 |
19 // TODO(kthelgason): Some versions of Android have issues with log2. | 22 // TODO(kthelgason): Some versions of Android have issues with log2. |
20 // See https://code.google.com/p/android/issues/detail?id=212634 for details | 23 // See https://code.google.com/p/android/issues/detail?id=212634 for details |
21 #if defined(WEBRTC_ANDROID) | 24 #if defined(WEBRTC_ANDROID) |
22 #define log2(x) (log(x) / log(2)) | 25 #define log2(x) (log(x) / log(2)) |
23 #endif | 26 #endif |
24 | 27 |
25 namespace webrtc { | 28 namespace webrtc { |
26 | 29 |
27 namespace { | 30 namespace { |
28 // Threshold constant used until first downscale (to permit fast rampup). | 31 // Threshold constant used until first downscale (to permit fast rampup). |
29 static const int kMeasureSecondsFastUpscale = 2; | 32 static const int kMeasureSecondsFastUpscale = 2; |
30 static const int kMeasureSecondsUpscale = 5; | 33 static const int kMeasureSeconds = 5; |
31 static const int kMeasureSecondsDownscale = 5; | |
32 static const int kFramedropPercentThreshold = 60; | 34 static const int kFramedropPercentThreshold = 60; |
33 // Min width/height to downscale to, set to not go below QVGA, but with some | |
34 // margin to permit "almost-QVGA" resolutions, such as QCIF. | |
35 static const int kMinDownscaleDimension = 140; | |
36 // Initial resolutions corresponding to a bitrate. Aa bit above their actual | |
37 // values to permit near-VGA and near-QVGA resolutions to use the same | |
38 // mechanism. | |
39 static const int kVgaBitrateThresholdKbps = 500; | |
40 static const int kVgaNumPixels = 700 * 500; // 640x480 | |
41 static const int kQvgaBitrateThresholdKbps = 250; | |
42 static const int kQvgaNumPixels = 400 * 300; // 320x240 | |
43 | |
44 // QP scaling threshold defaults: | 35 // QP scaling threshold defaults: |
45 static const int kLowH264QpThreshold = 24; | 36 static const int kLowH264QpThreshold = 24; |
46 static const int kHighH264QpThreshold = 37; | 37 static const int kHighH264QpThreshold = 37; |
47 // QP is obtained from VP8-bitstream for HW, so the QP corresponds to the | 38 // QP is obtained from VP8-bitstream for HW, so the QP corresponds to the |
48 // bitstream range of [0, 127] and not the user-level range of [0,63]. | 39 // bitstream range of [0, 127] and not the user-level range of [0,63]. |
49 static const int kLowVp8QpThreshold = 29; | 40 static const int kLowVp8QpThreshold = 29; |
50 static const int kHighVp8QpThreshold = 95; | 41 static const int kHighVp8QpThreshold = 95; |
51 } // namespace | 42 } // namespace |
52 | 43 |
44 class QualityScaler::CheckQPTask : public rtc::QueuedTask { | |
45 public: | |
46 explicit CheckQPTask(QualityScaler* scaler) : scaler_(scaler) { | |
47 RTC_DCHECK(scaler != nullptr); | |
perkj_webrtc
2016/10/07 07:41:15
nit- this dcheck is fine, but since you do scaler_
kthelgason
2016/10/07 09:34:48
Acknowledged.
| |
48 LOG(LS_INFO) << "Created CheckQPTask. Scheduling on queue..."; | |
49 const auto q = rtc::TaskQueue::Current(); | |
50 q->PostDelayedTask(std::unique_ptr<rtc::QueuedTask>(this), | |
51 scaler_->GetTimeoutMs()); | |
52 } | |
53 private: | |
54 friend class QualityScaler; | |
55 void Stop() { stop_ = true; } | |
perkj_webrtc
2016/10/07 07:41:15
stop is not initialized in the ctor.
kthelgason
2016/10/07 09:34:48
Acknowledged.
| |
56 bool Run() override { | |
57 if (stop_) | |
58 return true; // TaskQueue will free this task. | |
59 scaler_->CheckQP(); | |
60 rtc::TaskQueue::Current()->PostDelayedTask( | |
61 std::unique_ptr<rtc::QueuedTask>(this), scaler_->GetTimeoutMs()); | |
62 return false; // Retain the task in order to reuse it. | |
63 } | |
64 | |
perkj_webrtc
2016/10/07 07:41:15
Add a SequencedTaskChecker to check that Stop is c
kthelgason
2016/10/07 09:34:48
Acknowledged.
| |
65 QualityScaler* const scaler_; | |
66 bool stop_; | |
67 }; | |
68 | |
53 // Default values. Should immediately get set to something more sensible. | 69 // Default values. Should immediately get set to something more sensible. |
54 QualityScaler::QualityScaler() | 70 QualityScaler::QualityScaler() |
55 : average_qp_(kMeasureSecondsUpscale * 30), | 71 : average_qp_(kMeasureSeconds * 30), |
56 framedrop_percent_(kMeasureSecondsUpscale * 30), | 72 framedrop_percent_(kMeasureSeconds * 30), |
57 low_qp_threshold_(-1) {} | 73 low_qp_threshold_(-1) { |
74 task_checker_.Detach(); | |
75 } | |
58 | 76 |
59 void QualityScaler::Init(VideoCodecType codec_type, | 77 QualityScaler::~QualityScaler() { |
60 int initial_bitrate_kbps, | 78 RTC_DCHECK(check_qp_task_ == nullptr) << "Must call Stop."; |
61 int width, | 79 } |
62 int height, | 80 |
81 void QualityScaler::Init(ScalingInterface* observer, | |
82 VideoCodecType codec_type, | |
63 int fps) { | 83 int fps) { |
84 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_); | |
64 int low = -1, high = -1; | 85 int low = -1, high = -1; |
65 switch (codec_type) { | 86 switch (codec_type) { |
66 case kVideoCodecH264: | 87 case kVideoCodecH264: |
67 low = kLowH264QpThreshold; | 88 low = kLowH264QpThreshold; |
68 high = kHighH264QpThreshold; | 89 high = kHighH264QpThreshold; |
69 break; | 90 break; |
70 case kVideoCodecVP8: | 91 case kVideoCodecVP8: |
71 low = kLowVp8QpThreshold; | 92 low = kLowVp8QpThreshold; |
72 high = kHighVp8QpThreshold; | 93 high = kHighVp8QpThreshold; |
73 break; | 94 break; |
74 default: | 95 default: |
75 RTC_NOTREACHED() << "Invalid codec type for QualityScaler."; | 96 RTC_NOTREACHED() << "Invalid codec type for QualityScaler."; |
76 } | 97 } |
77 Init(low, high, initial_bitrate_kbps, width, height, fps); | 98 Init(observer, low, high, fps); |
78 } | 99 } |
79 | 100 |
80 void QualityScaler::Init(int low_qp_threshold, | 101 void QualityScaler::Init(ScalingInterface* observer, |
102 int low_qp_threshold, | |
81 int high_qp_threshold, | 103 int high_qp_threshold, |
82 int initial_bitrate_kbps, | |
83 int width, | |
84 int height, | |
85 int fps) { | 104 int fps) { |
105 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_); | |
106 RTC_DCHECK(observer != nullptr); | |
107 observer_ = observer; | |
86 ClearSamples(); | 108 ClearSamples(); |
87 low_qp_threshold_ = low_qp_threshold; | 109 low_qp_threshold_ = low_qp_threshold; |
88 high_qp_threshold_ = high_qp_threshold; | 110 high_qp_threshold_ = high_qp_threshold; |
89 downscale_shift_ = 0; | |
90 fast_rampup_ = true; | 111 fast_rampup_ = true; |
112 | |
113 // Use a faster window for upscaling initially. | |
114 // This enables faster initial rampups without risking strong up-down | |
115 // behavior later. | |
116 measure_interval_ = kMeasureSecondsFastUpscale; | |
91 ReportFramerate(fps); | 117 ReportFramerate(fps); |
118 check_qp_task_ = new CheckQPTask(this); | |
119 } | |
92 | 120 |
93 const int init_width = width; | 121 void QualityScaler::Stop() { |
94 const int init_height = height; | 122 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_); |
95 if (initial_bitrate_kbps > 0) { | 123 check_qp_task_->Stop(); |
96 int init_num_pixels = width * height; | 124 check_qp_task_ = nullptr; |
97 if (initial_bitrate_kbps < kVgaBitrateThresholdKbps) | 125 } |
98 init_num_pixels = kVgaNumPixels; | 126 |
99 if (initial_bitrate_kbps < kQvgaBitrateThresholdKbps) | 127 int64_t QualityScaler::GetTimeoutMs() { |
100 init_num_pixels = kQvgaNumPixels; | 128 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_); |
101 while (width * height > init_num_pixels) { | 129 return measure_interval_ * 1000; |
102 ++downscale_shift_; | |
103 width /= 2; | |
104 height /= 2; | |
105 } | |
106 } | |
107 UpdateTargetResolution(init_width, init_height); | |
108 ReportFramerate(fps); | |
109 } | 130 } |
110 | 131 |
111 // Report framerate(fps) to estimate # of samples. | 132 // Report framerate(fps) to estimate # of samples. |
112 void QualityScaler::ReportFramerate(int framerate) { | 133 void QualityScaler::ReportFramerate(int framerate) { |
113 // Use a faster window for upscaling initially. | 134 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_); |
114 // This enables faster initial rampups without risking strong up-down | 135 average_qp_ = MovingAverage(framerate * measure_interval_); |
115 // behavior later. | 136 framedrop_percent_ = MovingAverage(framerate * measure_interval_); |
116 num_samples_upscale_ = framerate * (fast_rampup_ ? kMeasureSecondsFastUpscale | 137 } |
117 : kMeasureSecondsUpscale); | |
118 num_samples_downscale_ = framerate * kMeasureSecondsDownscale; | |
119 | 138 |
120 average_qp_ = | 139 void QualityScaler::ReportDroppedFrame() { |
121 MovingAverage(std::max(num_samples_upscale_, num_samples_downscale_)); | 140 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_); |
122 framedrop_percent_ = | 141 framedrop_percent_.AddSample(100); |
123 MovingAverage(std::max(num_samples_upscale_, num_samples_downscale_)); | |
124 } | 142 } |
125 | 143 |
126 void QualityScaler::ReportQP(int qp) { | 144 void QualityScaler::ReportQP(int qp) { |
145 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_); | |
127 framedrop_percent_.AddSample(0); | 146 framedrop_percent_.AddSample(0); |
128 average_qp_.AddSample(qp); | 147 average_qp_.AddSample(qp); |
129 } | 148 } |
130 | 149 |
131 void QualityScaler::ReportDroppedFrame() { | 150 void QualityScaler::CheckQP() { |
132 framedrop_percent_.AddSample(100); | 151 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_); |
133 } | |
134 | |
135 void QualityScaler::OnEncodeFrame(int width, int height) { | |
136 // Should be set through InitEncode -> Should be set by now. | 152 // Should be set through InitEncode -> Should be set by now. |
137 RTC_DCHECK_GE(low_qp_threshold_, 0); | 153 RTC_DCHECK_GE(low_qp_threshold_, 0); |
138 if (target_res_.width != width || target_res_.height != height) { | 154 LOG(LS_INFO) << "Checking if average QP exceeds threshold"; |
139 UpdateTargetResolution(width, height); | |
140 } | |
141 | |
142 // Check if we should scale down due to high frame drop. | 155 // Check if we should scale down due to high frame drop. |
143 const auto drop_rate = framedrop_percent_.GetAverage(num_samples_downscale_); | 156 const auto drop_rate = framedrop_percent_.GetAverage(); |
144 if (drop_rate && *drop_rate >= kFramedropPercentThreshold) { | 157 if (drop_rate && *drop_rate >= kFramedropPercentThreshold) { |
145 ScaleDown(); | 158 ReportQPHigh(); |
146 return; | 159 return; |
147 } | 160 } |
148 | 161 |
149 // Check if we should scale up or down based on QP. | 162 // Check if we should scale up or down based on QP. |
150 const auto avg_qp_down = average_qp_.GetAverage(num_samples_downscale_); | 163 const auto avg_qp= average_qp_.GetAverage(); |
151 if (avg_qp_down && *avg_qp_down > high_qp_threshold_) { | 164 if (avg_qp && *avg_qp> high_qp_threshold_) { |
152 ScaleDown(); | 165 ReportQPHigh(); |
153 return; | 166 return; |
154 } | 167 } |
155 const auto avg_qp_up = average_qp_.GetAverage(num_samples_upscale_); | 168 if (avg_qp && *avg_qp <= low_qp_threshold_) { |
156 if (avg_qp_up && *avg_qp_up <= low_qp_threshold_) { | |
157 // QP has been low. We want to try a higher resolution. | 169 // QP has been low. We want to try a higher resolution. |
158 ScaleUp(); | 170 ReportQPLow(); |
159 return; | 171 return; |
160 } | 172 } |
161 } | 173 } |
162 | 174 |
163 void QualityScaler::ScaleUp() { | 175 void QualityScaler::ReportQPLow() { |
164 downscale_shift_ = std::max(0, downscale_shift_ - 1); | 176 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_); |
177 LOG(LS_INFO) << "QP has been low, asking for higher resolution."; | |
165 ClearSamples(); | 178 ClearSamples(); |
179 observer_->ScaleUp(); | |
166 } | 180 } |
167 | 181 |
168 void QualityScaler::ScaleDown() { | 182 void QualityScaler::ReportQPHigh() { |
169 downscale_shift_ = std::min(maximum_shift_, downscale_shift_ + 1); | 183 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_); |
184 LOG(LS_INFO) << "QP has been high , asking for lower resolution."; | |
170 ClearSamples(); | 185 ClearSamples(); |
186 observer_->ScaleDown(); | |
171 // If we've scaled down, wait longer before scaling up again. | 187 // If we've scaled down, wait longer before scaling up again. |
172 if (fast_rampup_) { | 188 if (fast_rampup_) { |
173 fast_rampup_ = false; | 189 fast_rampup_ = false; |
174 num_samples_upscale_ = (num_samples_upscale_ / kMeasureSecondsFastUpscale) * | 190 measure_interval_ = kMeasureSeconds; |
175 kMeasureSecondsUpscale; | |
176 } | 191 } |
177 } | 192 } |
178 | 193 |
179 QualityScaler::Resolution QualityScaler::GetScaledResolution() const { | |
180 const int frame_width = target_res_.width >> downscale_shift_; | |
181 const int frame_height = target_res_.height >> downscale_shift_; | |
182 return Resolution{frame_width, frame_height}; | |
183 } | |
184 | |
185 rtc::scoped_refptr<VideoFrameBuffer> QualityScaler::GetScaledBuffer( | |
186 const rtc::scoped_refptr<VideoFrameBuffer>& frame) { | |
187 Resolution res = GetScaledResolution(); | |
188 const int src_width = frame->width(); | |
189 const int src_height = frame->height(); | |
190 | |
191 if (res.width == src_width && res.height == src_height) | |
192 return frame; | |
193 rtc::scoped_refptr<I420Buffer> scaled_buffer = | |
194 pool_.CreateBuffer(res.width, res.height); | |
195 | |
196 scaled_buffer->ScaleFrom(frame); | |
197 | |
198 return scaled_buffer; | |
199 } | |
200 | |
201 void QualityScaler::UpdateTargetResolution(int width, int height) { | |
202 if (width < kMinDownscaleDimension || height < kMinDownscaleDimension) { | |
203 maximum_shift_ = 0; | |
204 } else { | |
205 maximum_shift_ = static_cast<int>( | |
206 log2(std::min(width, height) / kMinDownscaleDimension)); | |
207 } | |
208 target_res_ = Resolution{width, height}; | |
209 } | |
210 | |
211 void QualityScaler::ClearSamples() { | 194 void QualityScaler::ClearSamples() { |
195 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_); | |
212 framedrop_percent_.Reset(); | 196 framedrop_percent_.Reset(); |
213 average_qp_.Reset(); | 197 average_qp_.Reset(); |
214 } | 198 } |
215 | 199 |
216 | 200 |
217 } // namespace webrtc | 201 } // namespace webrtc |
OLD | NEW |