blob: a5e2d6f5de760909093733ace3b13f2168c7f546 [file] [log] [blame]
mflodman@webrtc.orge6168f52013-06-26 11:23:01 +00001/*
2 * Copyright (c) 2013 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 "webrtc/video_engine/overuse_frame_detector.h"
12
mflodman@webrtc.orgd4412fe2013-07-31 16:42:21 +000013#include <assert.h>
pbos@webrtc.orga9575702013-08-30 17:16:32 +000014#include <math.h>
mflodman@webrtc.orgd4412fe2013-07-31 16:42:21 +000015
asapersson@webrtc.org9e5b0342013-12-04 13:47:44 +000016#include <algorithm>
17#include <list>
18
asapersson@webrtc.orge2af6222013-09-23 20:05:39 +000019#include "webrtc/modules/video_coding/utility/include/exp_filter.h"
mflodman@webrtc.orge6168f52013-06-26 11:23:01 +000020#include "webrtc/system_wrappers/interface/clock.h"
21#include "webrtc/system_wrappers/interface/critical_section_wrapper.h"
pbos@webrtc.orga9575702013-08-30 17:16:32 +000022#include "webrtc/system_wrappers/interface/trace.h"
mflodman@webrtc.org6879c8a2013-07-23 11:35:00 +000023#include "webrtc/video_engine/include/vie_base.h"
mflodman@webrtc.orge6168f52013-06-26 11:23:01 +000024
25namespace webrtc {
26
mflodman@webrtc.orgd4412fe2013-07-31 16:42:21 +000027// TODO(mflodman) Test different values for all of these to trigger correctly,
28// avoid fluctuations etc.
mflodman@webrtc.orgd4412fe2013-07-31 16:42:21 +000029namespace {
pbos@webrtc.orga9575702013-08-30 17:16:32 +000030const int64_t kProcessIntervalMs = 5000;
mflodman@webrtc.orgd4412fe2013-07-31 16:42:21 +000031
asapersson@webrtc.orge2af6222013-09-23 20:05:39 +000032// Consecutive checks above threshold to trigger overuse.
pbos@webrtc.orga9575702013-08-30 17:16:32 +000033const int kConsecutiveChecksAboveThreshold = 2;
mflodman@webrtc.orgd4412fe2013-07-31 16:42:21 +000034
asapersson@webrtc.orge2af6222013-09-23 20:05:39 +000035// Minimum samples required to perform a check.
36const size_t kMinFrameSampleCount = 15;
37
38// Weight factor to apply to the standard deviation.
39const float kWeightFactor = 0.997f;
40
41// Weight factor to apply to the average.
42const float kWeightFactorMean = 0.98f;
43
pbos@webrtc.orga9575702013-08-30 17:16:32 +000044// Delay between consecutive rampups. (Used for quick recovery.)
45const int kQuickRampUpDelayMs = 10 * 1000;
46// Delay between rampup attempts. Initially uses standard, scales up to max.
47const int kStandardRampUpDelayMs = 30 * 1000;
48const int kMaxRampUpDelayMs = 120 * 1000;
49// Expontential back-off factor, to prevent annoying up-down behaviour.
50const double kRampUpBackoffFactor = 2.0;
mflodman@webrtc.orgd4412fe2013-07-31 16:42:21 +000051
asapersson@webrtc.orgc7ff8f92013-11-26 11:12:33 +000052// The initial average encode time (set to a fairly small value).
53const float kInitialAvgEncodeTimeMs = 5.0f;
asapersson@webrtc.org9e5b0342013-12-04 13:47:44 +000054
55// The maximum exponent to use in VCMExpFilter.
56const float kSampleDiffMs = 33.0f;
57const float kMaxExp = 7.0f;
58
mflodman@webrtc.orgd4412fe2013-07-31 16:42:21 +000059} // namespace
60
asapersson@webrtc.orge2af6222013-09-23 20:05:39 +000061Statistics::Statistics() :
62 sum_(0.0),
63 count_(0),
64 filtered_samples_(new VCMExpFilter(kWeightFactorMean)),
65 filtered_variance_(new VCMExpFilter(kWeightFactor)) {
66}
67
68void Statistics::Reset() {
69 sum_ = 0.0;
70 count_ = 0;
71}
72
73void Statistics::AddSample(float sample_ms) {
74 sum_ += sample_ms;
75 ++count_;
76
77 if (count_ < kMinFrameSampleCount) {
78 // Initialize filtered samples.
79 filtered_samples_->Reset(kWeightFactorMean);
80 filtered_samples_->Apply(1.0f, InitialMean());
81 filtered_variance_->Reset(kWeightFactor);
82 filtered_variance_->Apply(1.0f, InitialVariance());
83 return;
84 }
85
asapersson@webrtc.org9e5b0342013-12-04 13:47:44 +000086 float exp = sample_ms / kSampleDiffMs;
87 exp = std::min(exp, kMaxExp);
asapersson@webrtc.orge2af6222013-09-23 20:05:39 +000088 filtered_samples_->Apply(exp, sample_ms);
89 filtered_variance_->Apply(exp, (sample_ms - filtered_samples_->Value()) *
90 (sample_ms - filtered_samples_->Value()));
91}
92
93float Statistics::InitialMean() const {
94 if (count_ == 0)
95 return 0.0;
96 return sum_ / count_;
97}
98
99float Statistics::InitialVariance() const {
100 // Start in between the underuse and overuse threshold.
101 float average_stddev = (kNormalUseStdDevMs + kOveruseStdDevMs)/2.0f;
102 return average_stddev * average_stddev;
103}
104
105float Statistics::Mean() const { return filtered_samples_->Value(); }
106
107float Statistics::StdDev() const {
108 return sqrt(std::max(filtered_variance_->Value(), 0.0f));
109}
110
111uint64_t Statistics::Count() const { return count_; }
112
asapersson@webrtc.org9e5b0342013-12-04 13:47:44 +0000113
114// Class for calculating the average encode time.
115class OveruseFrameDetector::EncodeTimeAvg {
116 public:
117 EncodeTimeAvg()
118 : kWeightFactor(0.5f),
119 filtered_encode_time_ms_(new VCMExpFilter(kWeightFactor)) {
120 filtered_encode_time_ms_->Apply(1.0f, kInitialAvgEncodeTimeMs);
121 }
122 ~EncodeTimeAvg() {}
123
124 void AddEncodeSample(float encode_time_ms, int64_t diff_last_sample_ms) {
125 float exp = diff_last_sample_ms / kSampleDiffMs;
126 exp = std::min(exp, kMaxExp);
127 filtered_encode_time_ms_->Apply(exp, encode_time_ms);
128 }
129
130 int filtered_encode_time_ms() const {
131 return static_cast<int>(filtered_encode_time_ms_->Value() + 0.5);
132 }
133
134 private:
135 const float kWeightFactor;
136 scoped_ptr<VCMExpFilter> filtered_encode_time_ms_;
137};
138
139// Class for calculating the encode usage.
140class OveruseFrameDetector::EncodeUsage {
141 public:
142 EncodeUsage()
143 : kWeightFactorFrameDiff(0.998f),
144 kWeightFactorEncodeTime(0.995f),
145 filtered_encode_time_ms_(new VCMExpFilter(kWeightFactorEncodeTime)),
146 filtered_frame_diff_ms_(new VCMExpFilter(kWeightFactorFrameDiff)) {
147 filtered_encode_time_ms_->Apply(1.0f, kInitialAvgEncodeTimeMs);
148 filtered_frame_diff_ms_->Apply(1.0f, kSampleDiffMs);
149 }
150 ~EncodeUsage() {}
151
152 void AddSample(float sample_ms) {
153 float exp = sample_ms / kSampleDiffMs;
154 exp = std::min(exp, kMaxExp);
155 filtered_frame_diff_ms_->Apply(exp, sample_ms);
156 }
157
158 void AddEncodeSample(float encode_time_ms, int64_t diff_last_sample_ms) {
159 float exp = diff_last_sample_ms / kSampleDiffMs;
160 exp = std::min(exp, kMaxExp);
161 filtered_encode_time_ms_->Apply(exp, encode_time_ms);
162 }
163
164 int UsageInPercent() const {
165 float frame_diff_ms = std::max(filtered_frame_diff_ms_->Value(), 1.0f);
166 float encode_usage_percent =
167 100.0f * filtered_encode_time_ms_->Value() / frame_diff_ms;
168 return static_cast<int>(encode_usage_percent + 0.5);
169 }
170
171 private:
172 const float kWeightFactorFrameDiff;
173 const float kWeightFactorEncodeTime;
174 scoped_ptr<VCMExpFilter> filtered_encode_time_ms_;
175 scoped_ptr<VCMExpFilter> filtered_frame_diff_ms_;
176};
177
178// Class for calculating the capture queue delay change.
179class OveruseFrameDetector::CaptureQueueDelay {
180 public:
181 CaptureQueueDelay()
182 : kWeightFactor(0.5f),
183 delay_ms_(0),
184 filtered_delay_ms_per_s_(new VCMExpFilter(kWeightFactor)) {
185 filtered_delay_ms_per_s_->Apply(1.0f, 0.0f);
186 }
187 ~CaptureQueueDelay() {}
188
189 void FrameCaptured(int64_t now) {
190 const size_t kMaxSize = 200;
191 if (frames_.size() > kMaxSize) {
192 frames_.pop_front();
193 }
194 frames_.push_back(now);
195 }
196
197 void FrameProcessingStarted(int64_t now) {
198 if (frames_.empty()) {
199 return;
200 }
201 delay_ms_ = now - frames_.front();
202 frames_.pop_front();
203 }
204
205 void CalculateDelayChange(int64_t diff_last_sample_ms) {
206 if (diff_last_sample_ms <= 0) {
207 return;
208 }
209 float exp = static_cast<float>(diff_last_sample_ms) / kProcessIntervalMs;
210 exp = std::min(exp, kMaxExp);
211 filtered_delay_ms_per_s_->Apply(exp,
212 delay_ms_ * 1000.0f / diff_last_sample_ms);
213 ClearFrames();
214 }
215
216 void ClearFrames() {
217 frames_.clear();
218 }
219
220 int delay_ms() const {
221 return delay_ms_;
222 }
223
224 int filtered_delay_ms_per_s() const {
225 return static_cast<int>(filtered_delay_ms_per_s_->Value() + 0.5);
226 }
227
228 private:
229 const float kWeightFactor;
230 std::list<int64_t> frames_;
231 int delay_ms_;
232 scoped_ptr<VCMExpFilter> filtered_delay_ms_per_s_;
233};
234
asapersson@webrtc.orge2af6222013-09-23 20:05:39 +0000235OveruseFrameDetector::OveruseFrameDetector(Clock* clock,
236 float normaluse_stddev_ms,
237 float overuse_stddev_ms)
mflodman@webrtc.orge6168f52013-06-26 11:23:01 +0000238 : crit_(CriticalSectionWrapper::CreateCriticalSection()),
asapersson@webrtc.orge2af6222013-09-23 20:05:39 +0000239 normaluse_stddev_ms_(normaluse_stddev_ms),
240 overuse_stddev_ms_(overuse_stddev_ms),
mflodman@webrtc.org6879c8a2013-07-23 11:35:00 +0000241 observer_(NULL),
mflodman@webrtc.orge6168f52013-06-26 11:23:01 +0000242 clock_(clock),
pbos@webrtc.orga9575702013-08-30 17:16:32 +0000243 next_process_time_(clock_->TimeInMilliseconds()),
244 last_capture_time_(0),
245 last_overuse_time_(0),
246 checks_above_threshold_(0),
247 last_rampup_time_(0),
248 in_quick_rampup_(false),
asapersson@webrtc.orge2af6222013-09-23 20:05:39 +0000249 current_rampup_delay_ms_(kStandardRampUpDelayMs),
asapersson@webrtc.orgb24d3352013-11-20 13:51:40 +0000250 num_pixels_(0),
asapersson@webrtc.org9e5b0342013-12-04 13:47:44 +0000251 last_capture_jitter_ms_(-1),
252 last_encode_sample_ms_(0),
253 encode_time_(new EncodeTimeAvg()),
254 encode_usage_(new EncodeUsage()),
255 capture_queue_delay_(new CaptureQueueDelay()) {
256}
mflodman@webrtc.orge6168f52013-06-26 11:23:01 +0000257
258OveruseFrameDetector::~OveruseFrameDetector() {
259}
260
mflodman@webrtc.org6879c8a2013-07-23 11:35:00 +0000261void OveruseFrameDetector::SetObserver(CpuOveruseObserver* observer) {
262 CriticalSectionScoped cs(crit_.get());
263 observer_ = observer;
264}
265
asapersson@webrtc.org9e5b0342013-12-04 13:47:44 +0000266int OveruseFrameDetector::AvgEncodeTimeMs() const {
267 CriticalSectionScoped cs(crit_.get());
268 return encode_time_->filtered_encode_time_ms();
269}
270
271int OveruseFrameDetector::EncodeUsagePercent() const {
272 CriticalSectionScoped cs(crit_.get());
273 return encode_usage_->UsageInPercent();
274}
275
276int OveruseFrameDetector::AvgCaptureQueueDelayMsPerS() const {
277 CriticalSectionScoped cs(crit_.get());
278 return capture_queue_delay_->filtered_delay_ms_per_s();
279}
280
281int OveruseFrameDetector::CaptureQueueDelayMsPerS() const {
282 CriticalSectionScoped cs(crit_.get());
283 return capture_queue_delay_->delay_ms();
284}
285
286int32_t OveruseFrameDetector::TimeUntilNextProcess() {
287 CriticalSectionScoped cs(crit_.get());
288 return next_process_time_ - clock_->TimeInMilliseconds();
289}
290
asapersson@webrtc.orge2af6222013-09-23 20:05:39 +0000291void OveruseFrameDetector::FrameCaptured(int width, int height) {
mflodman@webrtc.orgd4412fe2013-07-31 16:42:21 +0000292 CriticalSectionScoped cs(crit_.get());
asapersson@webrtc.orge2af6222013-09-23 20:05:39 +0000293
294 int num_pixels = width * height;
295 if (num_pixels != num_pixels_) {
296 // Frame size changed, reset statistics.
297 num_pixels_ = num_pixels;
298 capture_deltas_.Reset();
299 last_capture_time_ = 0;
asapersson@webrtc.org9e5b0342013-12-04 13:47:44 +0000300 capture_queue_delay_->ClearFrames();
asapersson@webrtc.orge2af6222013-09-23 20:05:39 +0000301 }
302
pbos@webrtc.orga9575702013-08-30 17:16:32 +0000303 int64_t time = clock_->TimeInMilliseconds();
304 if (last_capture_time_ != 0) {
305 capture_deltas_.AddSample(time - last_capture_time_);
asapersson@webrtc.org9e5b0342013-12-04 13:47:44 +0000306 encode_usage_->AddSample(time - last_capture_time_);
pbos@webrtc.orga9575702013-08-30 17:16:32 +0000307 }
308 last_capture_time_ = time;
asapersson@webrtc.org9e5b0342013-12-04 13:47:44 +0000309
310 capture_queue_delay_->FrameCaptured(time);
311}
312
313void OveruseFrameDetector::FrameProcessingStarted() {
314 CriticalSectionScoped cs(crit_.get());
315 capture_queue_delay_->FrameProcessingStarted(clock_->TimeInMilliseconds());
mflodman@webrtc.orge6168f52013-06-26 11:23:01 +0000316}
317
asapersson@webrtc.orgc7ff8f92013-11-26 11:12:33 +0000318void OveruseFrameDetector::FrameEncoded(int encode_time_ms) {
asapersson@webrtc.orgb24d3352013-11-20 13:51:40 +0000319 CriticalSectionScoped cs(crit_.get());
asapersson@webrtc.org9e5b0342013-12-04 13:47:44 +0000320 int64_t time = clock_->TimeInMilliseconds();
321 if (last_encode_sample_ms_ != 0) {
322 int64_t diff_ms = time - last_encode_sample_ms_;
323 encode_time_->AddEncodeSample(encode_time_ms, diff_ms);
324 encode_usage_->AddEncodeSample(encode_time_ms, diff_ms);
325 }
326 last_encode_sample_ms_ = time;
asapersson@webrtc.orgc7ff8f92013-11-26 11:12:33 +0000327}
328
329int OveruseFrameDetector::last_capture_jitter_ms() const {
330 CriticalSectionScoped cs(crit_.get());
asapersson@webrtc.org9e5b0342013-12-04 13:47:44 +0000331 return last_capture_jitter_ms_;
mflodman@webrtc.orge6168f52013-06-26 11:23:01 +0000332}
333
334int32_t OveruseFrameDetector::Process() {
335 CriticalSectionScoped cs(crit_.get());
pbos@webrtc.orga9575702013-08-30 17:16:32 +0000336
mflodman@webrtc.org6879c8a2013-07-23 11:35:00 +0000337 int64_t now = clock_->TimeInMilliseconds();
pbos@webrtc.orga9575702013-08-30 17:16:32 +0000338
339 // Used to protect against Process() being called too often.
340 if (now < next_process_time_)
mflodman@webrtc.orge6168f52013-06-26 11:23:01 +0000341 return 0;
342
asapersson@webrtc.org9e5b0342013-12-04 13:47:44 +0000343 int64_t diff_ms = now - next_process_time_ + kProcessIntervalMs;
pbos@webrtc.orga9575702013-08-30 17:16:32 +0000344 next_process_time_ = now + kProcessIntervalMs;
mflodman@webrtc.orgd4412fe2013-07-31 16:42:21 +0000345
asapersson@webrtc.orge2af6222013-09-23 20:05:39 +0000346 // Don't trigger overuse unless we've seen a certain number of frames.
347 if (capture_deltas_.Count() < kMinFrameSampleCount)
mflodman@webrtc.org6879c8a2013-07-23 11:35:00 +0000348 return 0;
349
asapersson@webrtc.org9e5b0342013-12-04 13:47:44 +0000350 capture_queue_delay_->CalculateDelayChange(diff_ms);
351
mflodman@webrtc.orgd4412fe2013-07-31 16:42:21 +0000352 if (IsOverusing()) {
pbos@webrtc.orga9575702013-08-30 17:16:32 +0000353 // If the last thing we did was going up, and now have to back down, we need
354 // to check if this peak was short. If so we should back off to avoid going
355 // back and forth between this load, the system doesn't seem to handle it.
356 bool check_for_backoff = last_rampup_time_ > last_overuse_time_;
357 if (check_for_backoff) {
358 if (now - last_rampup_time_ < kStandardRampUpDelayMs) {
359 // Going up was not ok for very long, back off.
360 current_rampup_delay_ms_ *= kRampUpBackoffFactor;
361 if (current_rampup_delay_ms_ > kMaxRampUpDelayMs)
362 current_rampup_delay_ms_ = kMaxRampUpDelayMs;
363 } else {
364 // Not currently backing off, reset rampup delay.
365 current_rampup_delay_ms_ = kStandardRampUpDelayMs;
366 }
367 }
368
369 last_overuse_time_ = now;
370 in_quick_rampup_ = false;
371 checks_above_threshold_ = 0;
372
373 if (observer_ != NULL)
374 observer_->OveruseDetected();
mflodman@webrtc.orgd4412fe2013-07-31 16:42:21 +0000375 } else if (IsUnderusing(now)) {
pbos@webrtc.orga9575702013-08-30 17:16:32 +0000376 last_rampup_time_ = now;
377 in_quick_rampup_ = true;
378
379 if (observer_ != NULL)
380 observer_->NormalUsage();
mflodman@webrtc.orge6168f52013-06-26 11:23:01 +0000381 }
pbos@webrtc.orga9575702013-08-30 17:16:32 +0000382
pbos@webrtc.orga9575702013-08-30 17:16:32 +0000383 WEBRTC_TRACE(
384 webrtc::kTraceInfo,
385 webrtc::kTraceVideo,
386 -1,
387 "Capture input stats: avg: %.2fms, std_dev: %.2fms (rampup delay: "
388 "%dms, overuse: >=%.2fms, "
389 "underuse: <%.2fms)",
390 capture_deltas_.Mean(),
391 capture_deltas_.StdDev(),
392 in_quick_rampup_ ? kQuickRampUpDelayMs : current_rampup_delay_ms_,
asapersson@webrtc.orge2af6222013-09-23 20:05:39 +0000393 overuse_stddev_ms_,
394 normaluse_stddev_ms_);
mflodman@webrtc.orgd4412fe2013-07-31 16:42:21 +0000395
asapersson@webrtc.org9e5b0342013-12-04 13:47:44 +0000396 last_capture_jitter_ms_ = static_cast<int>(capture_deltas_.StdDev() + 0.5);
asapersson@webrtc.orge2af6222013-09-23 20:05:39 +0000397 return 0;
398}
399
400bool OveruseFrameDetector::IsOverusing() {
401 if (capture_deltas_.StdDev() >= overuse_stddev_ms_) {
pbos@webrtc.orga9575702013-08-30 17:16:32 +0000402 ++checks_above_threshold_;
403 } else {
404 checks_above_threshold_ = 0;
405 }
406
407 return checks_above_threshold_ >= kConsecutiveChecksAboveThreshold;
mflodman@webrtc.orgd4412fe2013-07-31 16:42:21 +0000408}
409
410bool OveruseFrameDetector::IsUnderusing(int64_t time_now) {
pbos@webrtc.orga9575702013-08-30 17:16:32 +0000411 int delay = in_quick_rampup_ ? kQuickRampUpDelayMs : current_rampup_delay_ms_;
412 if (time_now < last_rampup_time_ + delay)
mflodman@webrtc.orgd4412fe2013-07-31 16:42:21 +0000413 return false;
mflodman@webrtc.orgd4412fe2013-07-31 16:42:21 +0000414
asapersson@webrtc.orge2af6222013-09-23 20:05:39 +0000415 return capture_deltas_.StdDev() < normaluse_stddev_ms_;
mflodman@webrtc.orgd4412fe2013-07-31 16:42:21 +0000416}
mflodman@webrtc.orge6168f52013-06-26 11:23:01 +0000417} // namespace webrtc