blob: 74e70ecb7ba6636acc94f2c1efbf053851872b91 [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
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020011#include "video/overuse_frame_detector.h"
mflodman@webrtc.orge6168f52013-06-26 11:23:01 +000012
pbos@webrtc.orga9575702013-08-30 17:16:32 +000013#include <math.h>
Piotr Tworek5e4833c2017-12-12 12:09:31 +010014#include <stdio.h>
mflodman@webrtc.orgd4412fe2013-07-31 16:42:21 +000015
asapersson@webrtc.org9e5b0342013-12-04 13:47:44 +000016#include <algorithm>
17#include <list>
asapersson@webrtc.org734a5322014-06-10 06:35:22 +000018#include <map>
sprangc5d62e22017-04-02 23:53:04 -070019#include <string>
20#include <utility>
asapersson@webrtc.org9e5b0342013-12-04 13:47:44 +000021
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020022#include "api/video/video_frame.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020023#include "rtc_base/checks.h"
24#include "rtc_base/logging.h"
Niels Möller7dc26b72017-12-06 10:27:48 +010025#include "rtc_base/numerics/exp_filter.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020026#include "rtc_base/timeutils.h"
27#include "system_wrappers/include/field_trial.h"
mflodman@webrtc.orge6168f52013-06-26 11:23:01 +000028
pbosa1025072016-05-14 03:04:19 -070029#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
torbjorng448468d2016-02-10 08:11:57 -080030#include <mach/mach.h>
pbosa1025072016-05-14 03:04:19 -070031#endif // defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
torbjorng448468d2016-02-10 08:11:57 -080032
mflodman@webrtc.orge6168f52013-06-26 11:23:01 +000033namespace webrtc {
34
mflodman@webrtc.orgd4412fe2013-07-31 16:42:21 +000035namespace {
perkjd52063f2016-09-07 06:32:18 -070036const int64_t kCheckForOveruseIntervalMs = 5000;
37const int64_t kTimeToFirstCheckForOveruseMs = 100;
mflodman@webrtc.orgd4412fe2013-07-31 16:42:21 +000038
pbos@webrtc.orga9575702013-08-30 17:16:32 +000039// Delay between consecutive rampups. (Used for quick recovery.)
40const int kQuickRampUpDelayMs = 10 * 1000;
41// Delay between rampup attempts. Initially uses standard, scales up to max.
asapersson@webrtc.org23a4d852014-08-13 14:33:49 +000042const int kStandardRampUpDelayMs = 40 * 1000;
asapersson@webrtc.org2881ab12014-06-12 08:46:46 +000043const int kMaxRampUpDelayMs = 240 * 1000;
pbos@webrtc.orga9575702013-08-30 17:16:32 +000044// Expontential back-off factor, to prevent annoying up-down behaviour.
45const double kRampUpBackoffFactor = 2.0;
mflodman@webrtc.orgd4412fe2013-07-31 16:42:21 +000046
asapersson@webrtc.orgd9803072014-06-16 14:27:19 +000047// Max number of overuses detected before always applying the rampup delay.
asapersson@webrtc.org23a4d852014-08-13 14:33:49 +000048const int kMaxOverusesBeforeApplyRampupDelay = 4;
asapersson@webrtc.org9e5b0342013-12-04 13:47:44 +000049
Niels Möller7dc26b72017-12-06 10:27:48 +010050// The maximum exponent to use in VCMExpFilter.
51const float kMaxExp = 7.0f;
52// Default value used before first reconfiguration.
53const int kDefaultFrameRate = 30;
54// Default sample diff, default frame rate.
55const float kDefaultSampleDiffMs = 1000.0f / kDefaultFrameRate;
56// A factor applied to the sample diff on OnTargetFramerateUpdated to determine
57// a max limit for the sample diff. For instance, with a framerate of 30fps,
58// the sample diff is capped to (1000 / 30) * 1.35 = 45ms. This prevents
59// triggering too soon if there are individual very large outliers.
60const float kMaxSampleDiffMarginFactor = 1.35f;
61// Minimum framerate allowed for usage calculation. This prevents crazy long
62// encode times from being accepted if the frame rate happens to be low.
63const int kMinFramerate = 7;
64const int kMaxFramerate = 30;
65
sprangb1ca0732017-02-01 08:38:12 -080066const auto kScaleReasonCpu = AdaptationObserverInterface::AdaptReason::kCpu;
torbjorng448468d2016-02-10 08:11:57 -080067
asapersson@webrtc.org9aed0022014-10-16 06:57:12 +000068// Class for calculating the processing usage on the send-side (the average
69// processing time of a frame divided by the average time difference between
70// captured frames).
Niels Möller83dbeac2017-12-14 16:39:44 +010071class SendProcessingUsage1 : public OveruseFrameDetector::ProcessingUsage {
asapersson@webrtc.org9e5b0342013-12-04 13:47:44 +000072 public:
Niels Möller83dbeac2017-12-14 16:39:44 +010073 explicit SendProcessingUsage1(const CpuOveruseOptions& options)
Niels Möller7dc26b72017-12-06 10:27:48 +010074 : kWeightFactorFrameDiff(0.998f),
75 kWeightFactorProcessing(0.995f),
76 kInitialSampleDiffMs(40.0f),
Niels Möller7dc26b72017-12-06 10:27:48 +010077 options_(options),
Niels Möllere08cf3a2017-12-07 15:23:58 +010078 count_(0),
79 last_processed_capture_time_us_(-1),
Niels Möller7dc26b72017-12-06 10:27:48 +010080 max_sample_diff_ms_(kDefaultSampleDiffMs * kMaxSampleDiffMarginFactor),
81 filtered_processing_ms_(new rtc::ExpFilter(kWeightFactorProcessing)),
82 filtered_frame_diff_ms_(new rtc::ExpFilter(kWeightFactorFrameDiff)) {
asapersson@webrtc.orgce12f1f2014-03-24 21:59:16 +000083 Reset();
asapersson@webrtc.org9e5b0342013-12-04 13:47:44 +000084 }
Niels Möller83dbeac2017-12-14 16:39:44 +010085 virtual ~SendProcessingUsage1() {}
asapersson@webrtc.org9e5b0342013-12-04 13:47:44 +000086
Niels Möller904f8692017-12-07 11:22:39 +010087 void Reset() override {
Niels Möllere08cf3a2017-12-07 15:23:58 +010088 frame_timing_.clear();
Niels Möller7dc26b72017-12-06 10:27:48 +010089 count_ = 0;
Niels Möllere08cf3a2017-12-07 15:23:58 +010090 last_processed_capture_time_us_ = -1;
Niels Möller7dc26b72017-12-06 10:27:48 +010091 max_sample_diff_ms_ = kDefaultSampleDiffMs * kMaxSampleDiffMarginFactor;
92 filtered_frame_diff_ms_->Reset(kWeightFactorFrameDiff);
93 filtered_frame_diff_ms_->Apply(1.0f, kInitialSampleDiffMs);
94 filtered_processing_ms_->Reset(kWeightFactorProcessing);
95 filtered_processing_ms_->Apply(1.0f, InitialProcessingMs());
asapersson@webrtc.orgce12f1f2014-03-24 21:59:16 +000096 }
97
Niels Möller904f8692017-12-07 11:22:39 +010098 void SetMaxSampleDiffMs(float diff_ms) override {
99 max_sample_diff_ms_ = diff_ms;
100 }
sprangfda496a2017-06-15 04:21:07 -0700101
Niels Möllere08cf3a2017-12-07 15:23:58 +0100102 void FrameCaptured(const VideoFrame& frame,
103 int64_t time_when_first_seen_us,
104 int64_t last_capture_time_us) override {
105 if (last_capture_time_us != -1)
106 AddCaptureSample(1e-3 * (time_when_first_seen_us - last_capture_time_us));
107
108 frame_timing_.push_back(FrameTiming(frame.timestamp_us(), frame.timestamp(),
109 time_when_first_seen_us));
Niels Möller7dc26b72017-12-06 10:27:48 +0100110 }
111
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200112 absl::optional<int> FrameSent(
Niels Möller83dbeac2017-12-14 16:39:44 +0100113 uint32_t timestamp,
114 int64_t time_sent_in_us,
115 int64_t /* capture_time_us */,
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200116 absl::optional<int> /* encode_duration_us */) override {
117 absl::optional<int> encode_duration_us;
Niels Möllere08cf3a2017-12-07 15:23:58 +0100118 // Delay before reporting actual encoding time, used to have the ability to
119 // detect total encoding time when encoding more than one layer. Encoding is
120 // here assumed to finish within a second (or that we get enough long-time
121 // samples before one second to trigger an overuse even when this is not the
122 // case).
123 static const int64_t kEncodingTimeMeasureWindowMs = 1000;
124 for (auto& it : frame_timing_) {
125 if (it.timestamp == timestamp) {
126 it.last_send_us = time_sent_in_us;
127 break;
128 }
129 }
130 // TODO(pbos): Handle the case/log errors when not finding the corresponding
131 // frame (either very slow encoding or incorrect wrong timestamps returned
132 // from the encoder).
133 // This is currently the case for all frames on ChromeOS, so logging them
134 // would be spammy, and triggering overuse would be wrong.
135 // https://crbug.com/350106
136 while (!frame_timing_.empty()) {
137 FrameTiming timing = frame_timing_.front();
138 if (time_sent_in_us - timing.capture_us <
139 kEncodingTimeMeasureWindowMs * rtc::kNumMicrosecsPerMillisec) {
140 break;
141 }
142 if (timing.last_send_us != -1) {
143 encode_duration_us.emplace(
144 static_cast<int>(timing.last_send_us - timing.capture_us));
Niels Möller6b642f72017-12-08 14:11:14 +0100145
Niels Möllere08cf3a2017-12-07 15:23:58 +0100146 if (last_processed_capture_time_us_ != -1) {
147 int64_t diff_us = timing.capture_us - last_processed_capture_time_us_;
148 AddSample(1e-3 * (*encode_duration_us), 1e-3 * diff_us);
149 }
150 last_processed_capture_time_us_ = timing.capture_us;
151 }
152 frame_timing_.pop_front();
153 }
154 return encode_duration_us;
Niels Möller7dc26b72017-12-06 10:27:48 +0100155 }
156
Niels Möller904f8692017-12-07 11:22:39 +0100157 int Value() override {
Niels Möller7dc26b72017-12-06 10:27:48 +0100158 if (count_ < static_cast<uint32_t>(options_.min_frame_samples)) {
159 return static_cast<int>(InitialUsageInPercent() + 0.5f);
asapersson@webrtc.orgce12f1f2014-03-24 21:59:16 +0000160 }
Niels Möller7dc26b72017-12-06 10:27:48 +0100161 float frame_diff_ms = std::max(filtered_frame_diff_ms_->filtered(), 1.0f);
162 frame_diff_ms = std::min(frame_diff_ms, max_sample_diff_ms_);
163 float encode_usage_percent =
164 100.0f * filtered_processing_ms_->filtered() / frame_diff_ms;
165 return static_cast<int>(encode_usage_percent + 0.5);
asapersson@webrtc.org9e5b0342013-12-04 13:47:44 +0000166 }
167
asapersson@webrtc.org2881ab12014-06-12 08:46:46 +0000168 private:
Niels Möllere08cf3a2017-12-07 15:23:58 +0100169 struct FrameTiming {
170 FrameTiming(int64_t capture_time_us, uint32_t timestamp, int64_t now)
171 : capture_time_us(capture_time_us),
172 timestamp(timestamp),
173 capture_us(now),
174 last_send_us(-1) {}
175 int64_t capture_time_us;
176 uint32_t timestamp;
177 int64_t capture_us;
178 int64_t last_send_us;
179 };
180
181 void AddCaptureSample(float sample_ms) {
182 float exp = sample_ms / kDefaultSampleDiffMs;
183 exp = std::min(exp, kMaxExp);
184 filtered_frame_diff_ms_->Apply(exp, sample_ms);
185 }
186
187 void AddSample(float processing_ms, int64_t diff_last_sample_ms) {
188 ++count_;
189 float exp = diff_last_sample_ms / kDefaultSampleDiffMs;
190 exp = std::min(exp, kMaxExp);
191 filtered_processing_ms_->Apply(exp, processing_ms);
192 }
193
Niels Möller7dc26b72017-12-06 10:27:48 +0100194 float InitialUsageInPercent() const {
195 // Start in between the underuse and overuse threshold.
196 return (options_.low_encode_usage_threshold_percent +
Yves Gerey665174f2018-06-19 15:03:05 +0200197 options_.high_encode_usage_threshold_percent) /
198 2.0f;
Niels Möller7dc26b72017-12-06 10:27:48 +0100199 }
200
201 float InitialProcessingMs() const {
202 return InitialUsageInPercent() * kInitialSampleDiffMs / 100;
203 }
204
205 const float kWeightFactorFrameDiff;
206 const float kWeightFactorProcessing;
207 const float kInitialSampleDiffMs;
Niels Möllere08cf3a2017-12-07 15:23:58 +0100208
Peter Boström4b91bd02015-06-26 06:58:16 +0200209 const CpuOveruseOptions options_;
Niels Möllere08cf3a2017-12-07 15:23:58 +0100210 std::list<FrameTiming> frame_timing_;
211 uint64_t count_;
212 int64_t last_processed_capture_time_us_;
Niels Möller7dc26b72017-12-06 10:27:48 +0100213 float max_sample_diff_ms_;
214 std::unique_ptr<rtc::ExpFilter> filtered_processing_ms_;
215 std::unique_ptr<rtc::ExpFilter> filtered_frame_diff_ms_;
asapersson@webrtc.org9e5b0342013-12-04 13:47:44 +0000216};
217
Niels Möller83dbeac2017-12-14 16:39:44 +0100218// New cpu load estimator.
219// TODO(bugs.webrtc.org/8504): For some period of time, we need to
220// switch between the two versions of the estimator for experiments.
221// When problems are sorted out, the old estimator should be deleted.
222class SendProcessingUsage2 : public OveruseFrameDetector::ProcessingUsage {
sprangc5d62e22017-04-02 23:53:04 -0700223 public:
Niels Möller83dbeac2017-12-14 16:39:44 +0100224 explicit SendProcessingUsage2(const CpuOveruseOptions& options)
225 : options_(options) {
226 Reset();
227 }
228 virtual ~SendProcessingUsage2() = default;
229
230 void Reset() override {
231 prev_time_us_ = -1;
232 // Start in between the underuse and overuse threshold.
233 load_estimate_ = (options_.low_encode_usage_threshold_percent +
234 options_.high_encode_usage_threshold_percent) /
235 200.0;
236 }
237
238 void SetMaxSampleDiffMs(float /* diff_ms */) override {}
239
240 void FrameCaptured(const VideoFrame& frame,
241 int64_t time_when_first_seen_us,
242 int64_t last_capture_time_us) override {}
243
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200244 absl::optional<int> FrameSent(
Niels Möllerf5033ad2018-08-14 17:00:46 +0200245 uint32_t /* timestamp */,
246 int64_t /* time_sent_in_us */,
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200247 int64_t capture_time_us,
248 absl::optional<int> encode_duration_us) override {
Niels Möller83dbeac2017-12-14 16:39:44 +0100249 if (encode_duration_us) {
Niels Möllerf5033ad2018-08-14 17:00:46 +0200250 int duration_per_frame_us =
251 DurationPerInputFrame(capture_time_us, *encode_duration_us);
Niels Möller83dbeac2017-12-14 16:39:44 +0100252 if (prev_time_us_ != -1) {
Niels Möller58d2a5e2018-08-07 16:37:18 +0200253 if (capture_time_us < prev_time_us_) {
254 // The weighting in AddSample assumes that samples are processed with
255 // non-decreasing measurement timestamps. We could implement
256 // appropriate weights for samples arriving late, but since it is a
257 // rare case, keep things simple, by just pushing those measurements a
258 // bit forward in time.
259 capture_time_us = prev_time_us_;
260 }
Niels Möllerf5033ad2018-08-14 17:00:46 +0200261 AddSample(1e-6 * duration_per_frame_us,
Niels Möller83dbeac2017-12-14 16:39:44 +0100262 1e-6 * (capture_time_us - prev_time_us_));
263 }
264 }
265 prev_time_us_ = capture_time_us;
266
267 return encode_duration_us;
268 }
269
270 private:
271 void AddSample(double encode_time, double diff_time) {
272 RTC_CHECK_GE(diff_time, 0.0);
273
274 // Use the filter update
275 //
276 // load <-- x/d (1-exp (-d/T)) + exp (-d/T) load
277 //
278 // where we must take care for small d, using the proper limit
279 // (1 - exp(-d/tau)) / d = 1/tau - d/2tau^2 + O(d^2)
280 double tau = (1e-3 * options_.filter_time_ms);
281 double e = diff_time / tau;
282 double c;
283 if (e < 0.0001) {
284 c = (1 - e / 2) / tau;
285 } else {
286 c = -expm1(-e) / diff_time;
287 }
288 load_estimate_ = c * encode_time + exp(-e) * load_estimate_;
289 }
290
Niels Möllerf5033ad2018-08-14 17:00:46 +0200291 int64_t DurationPerInputFrame(int64_t capture_time_us,
292 int64_t encode_time_us) {
293 // Discard data on old frames; limit 2 seconds.
294 static constexpr int64_t kMaxAge = 2 * rtc::kNumMicrosecsPerSec;
295 for (auto it = max_encode_time_per_input_frame_.begin();
296 it != max_encode_time_per_input_frame_.end() &&
297 it->first < capture_time_us - kMaxAge;) {
298 it = max_encode_time_per_input_frame_.erase(it);
299 }
300
301 std::map<int64_t, int>::iterator it;
302 bool inserted;
303 std::tie(it, inserted) = max_encode_time_per_input_frame_.emplace(
304 capture_time_us, encode_time_us);
305 if (inserted) {
306 // First encoded frame for this input frame.
307 return encode_time_us;
308 }
309 if (encode_time_us <= it->second) {
310 // Shorter encode time than previous frame (unlikely). Count it as being
311 // done in parallel.
312 return 0;
313 }
314 // Record new maximum encode time, and return increase from previous max.
315 int increase = encode_time_us - it->second;
316 it->second = encode_time_us;
317 return increase;
318 }
319
Niels Möller83dbeac2017-12-14 16:39:44 +0100320 int Value() override {
321 return static_cast<int>(100.0 * load_estimate_ + 0.5);
322 }
323
Niels Möller83dbeac2017-12-14 16:39:44 +0100324 const CpuOveruseOptions options_;
Niels Möllerf5033ad2018-08-14 17:00:46 +0200325 // Indexed by the capture timestamp, used as frame id.
326 std::map<int64_t, int> max_encode_time_per_input_frame_;
327
Niels Möller83dbeac2017-12-14 16:39:44 +0100328 int64_t prev_time_us_ = -1;
329 double load_estimate_;
330};
331
332// Class used for manual testing of overuse, enabled via field trial flag.
333class OverdoseInjector : public OveruseFrameDetector::ProcessingUsage {
334 public:
335 OverdoseInjector(std::unique_ptr<OveruseFrameDetector::ProcessingUsage> usage,
sprangc5d62e22017-04-02 23:53:04 -0700336 int64_t normal_period_ms,
337 int64_t overuse_period_ms,
338 int64_t underuse_period_ms)
Niels Möller83dbeac2017-12-14 16:39:44 +0100339 : usage_(std::move(usage)),
sprangc5d62e22017-04-02 23:53:04 -0700340 normal_period_ms_(normal_period_ms),
341 overuse_period_ms_(overuse_period_ms),
342 underuse_period_ms_(underuse_period_ms),
343 state_(State::kNormal),
344 last_toggling_ms_(-1) {
345 RTC_DCHECK_GT(overuse_period_ms, 0);
346 RTC_DCHECK_GT(normal_period_ms, 0);
Mirko Bonadei675513b2017-11-09 11:09:25 +0100347 RTC_LOG(LS_INFO) << "Simulating overuse with intervals " << normal_period_ms
348 << "ms normal mode, " << overuse_period_ms
349 << "ms overuse mode.";
sprangc5d62e22017-04-02 23:53:04 -0700350 }
351
352 ~OverdoseInjector() override {}
353
Niels Möller83dbeac2017-12-14 16:39:44 +0100354 void Reset() override { usage_->Reset(); }
355
356 void SetMaxSampleDiffMs(float diff_ms) override {
357 usage_->SetMaxSampleDiffMs(diff_ms);
358 }
359
360 void FrameCaptured(const VideoFrame& frame,
361 int64_t time_when_first_seen_us,
362 int64_t last_capture_time_us) override {
363 usage_->FrameCaptured(frame, time_when_first_seen_us, last_capture_time_us);
364 }
365
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200366 absl::optional<int> FrameSent(
Niels Möller83dbeac2017-12-14 16:39:44 +0100367 // These two argument used by old estimator.
368 uint32_t timestamp,
369 int64_t time_sent_in_us,
370 // And these two by the new estimator.
371 int64_t capture_time_us,
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200372 absl::optional<int> encode_duration_us) override {
Niels Möller83dbeac2017-12-14 16:39:44 +0100373 return usage_->FrameSent(timestamp, time_sent_in_us, capture_time_us,
374 encode_duration_us);
375 }
376
sprangc5d62e22017-04-02 23:53:04 -0700377 int Value() override {
378 int64_t now_ms = rtc::TimeMillis();
379 if (last_toggling_ms_ == -1) {
380 last_toggling_ms_ = now_ms;
381 } else {
382 switch (state_) {
383 case State::kNormal:
384 if (now_ms > last_toggling_ms_ + normal_period_ms_) {
385 state_ = State::kOveruse;
386 last_toggling_ms_ = now_ms;
Mirko Bonadei675513b2017-11-09 11:09:25 +0100387 RTC_LOG(LS_INFO) << "Simulating CPU overuse.";
sprangc5d62e22017-04-02 23:53:04 -0700388 }
389 break;
390 case State::kOveruse:
391 if (now_ms > last_toggling_ms_ + overuse_period_ms_) {
392 state_ = State::kUnderuse;
393 last_toggling_ms_ = now_ms;
Mirko Bonadei675513b2017-11-09 11:09:25 +0100394 RTC_LOG(LS_INFO) << "Simulating CPU underuse.";
sprangc5d62e22017-04-02 23:53:04 -0700395 }
396 break;
397 case State::kUnderuse:
398 if (now_ms > last_toggling_ms_ + underuse_period_ms_) {
399 state_ = State::kNormal;
400 last_toggling_ms_ = now_ms;
Mirko Bonadei675513b2017-11-09 11:09:25 +0100401 RTC_LOG(LS_INFO) << "Actual CPU overuse measurements in effect.";
sprangc5d62e22017-04-02 23:53:04 -0700402 }
403 break;
404 }
405 }
406
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200407 absl::optional<int> overried_usage_value;
sprangc5d62e22017-04-02 23:53:04 -0700408 switch (state_) {
409 case State::kNormal:
410 break;
411 case State::kOveruse:
412 overried_usage_value.emplace(250);
413 break;
414 case State::kUnderuse:
415 overried_usage_value.emplace(5);
416 break;
417 }
Niels Möller7dc26b72017-12-06 10:27:48 +0100418
Niels Möller83dbeac2017-12-14 16:39:44 +0100419 return overried_usage_value.value_or(usage_->Value());
sprangc5d62e22017-04-02 23:53:04 -0700420 }
421
422 private:
Niels Möller83dbeac2017-12-14 16:39:44 +0100423 const std::unique_ptr<OveruseFrameDetector::ProcessingUsage> usage_;
sprangc5d62e22017-04-02 23:53:04 -0700424 const int64_t normal_period_ms_;
425 const int64_t overuse_period_ms_;
426 const int64_t underuse_period_ms_;
427 enum class State { kNormal, kOveruse, kUnderuse } state_;
428 int64_t last_toggling_ms_;
429};
430
Niels Möller904f8692017-12-07 11:22:39 +0100431} // namespace
432
433CpuOveruseOptions::CpuOveruseOptions()
434 : high_encode_usage_threshold_percent(85),
435 frame_timeout_interval_ms(1500),
436 min_frame_samples(120),
437 min_process_count(3),
Niels Möller83dbeac2017-12-14 16:39:44 +0100438 high_threshold_consecutive_count(2),
439 // Disabled by default.
440 filter_time_ms(0) {
Niels Möller904f8692017-12-07 11:22:39 +0100441#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
442 // This is proof-of-concept code for letting the physical core count affect
443 // the interval into which we attempt to scale. For now, the code is Mac OS
444 // specific, since that's the platform were we saw most problems.
445 // TODO(torbjorng): Enhance SystemInfo to return this metric.
446
447 mach_port_t mach_host = mach_host_self();
448 host_basic_info hbi = {};
449 mach_msg_type_number_t info_count = HOST_BASIC_INFO_COUNT;
450 kern_return_t kr =
451 host_info(mach_host, HOST_BASIC_INFO, reinterpret_cast<host_info_t>(&hbi),
452 &info_count);
453 mach_port_deallocate(mach_task_self(), mach_host);
454
455 int n_physical_cores;
456 if (kr != KERN_SUCCESS) {
457 // If we couldn't get # of physical CPUs, don't panic. Assume we have 1.
458 n_physical_cores = 1;
459 RTC_LOG(LS_ERROR)
460 << "Failed to determine number of physical cores, assuming 1";
461 } else {
462 n_physical_cores = hbi.physical_cpu;
463 RTC_LOG(LS_INFO) << "Number of physical cores:" << n_physical_cores;
464 }
465
466 // Change init list default for few core systems. The assumption here is that
467 // encoding, which we measure here, takes about 1/4 of the processing of a
468 // two-way call. This is roughly true for x86 using both vp8 and vp9 without
469 // hardware encoding. Since we don't affect the incoming stream here, we only
470 // control about 1/2 of the total processing needs, but this is not taken into
471 // account.
472 if (n_physical_cores == 1)
473 high_encode_usage_threshold_percent = 20; // Roughly 1/4 of 100%.
474 else if (n_physical_cores == 2)
475 high_encode_usage_threshold_percent = 40; // Roughly 1/4 of 200%.
476#endif // defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
477
478 // Note that we make the interval 2x+epsilon wide, since libyuv scaling steps
479 // are close to that (when squared). This wide interval makes sure that
480 // scaling up or down does not jump all the way across the interval.
481 low_encode_usage_threshold_percent =
482 (high_encode_usage_threshold_percent - 1) / 2;
483}
484
485std::unique_ptr<OveruseFrameDetector::ProcessingUsage>
Yves Gerey665174f2018-06-19 15:03:05 +0200486OveruseFrameDetector::CreateProcessingUsage(const CpuOveruseOptions& options) {
Niels Möller904f8692017-12-07 11:22:39 +0100487 std::unique_ptr<ProcessingUsage> instance;
Niels Möller83dbeac2017-12-14 16:39:44 +0100488 if (options.filter_time_ms > 0) {
Karl Wiberg918f50c2018-07-05 11:40:33 +0200489 instance = absl::make_unique<SendProcessingUsage2>(options);
Niels Möller83dbeac2017-12-14 16:39:44 +0100490 } else {
Karl Wiberg918f50c2018-07-05 11:40:33 +0200491 instance = absl::make_unique<SendProcessingUsage1>(options);
Niels Möller83dbeac2017-12-14 16:39:44 +0100492 }
sprangc5d62e22017-04-02 23:53:04 -0700493 std::string toggling_interval =
494 field_trial::FindFullName("WebRTC-ForceSimulatedOveruseIntervalMs");
495 if (!toggling_interval.empty()) {
496 int normal_period_ms = 0;
497 int overuse_period_ms = 0;
498 int underuse_period_ms = 0;
499 if (sscanf(toggling_interval.c_str(), "%d-%d-%d", &normal_period_ms,
500 &overuse_period_ms, &underuse_period_ms) == 3) {
501 if (normal_period_ms > 0 && overuse_period_ms > 0 &&
502 underuse_period_ms > 0) {
Karl Wiberg918f50c2018-07-05 11:40:33 +0200503 instance = absl::make_unique<OverdoseInjector>(
Yves Gerey665174f2018-06-19 15:03:05 +0200504 std::move(instance), normal_period_ms, overuse_period_ms,
505 underuse_period_ms);
sprangc5d62e22017-04-02 23:53:04 -0700506 } else {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100507 RTC_LOG(LS_WARNING)
sprangc5d62e22017-04-02 23:53:04 -0700508 << "Invalid (non-positive) normal/overuse/underuse periods: "
509 << normal_period_ms << " / " << overuse_period_ms << " / "
510 << underuse_period_ms;
511 }
512 } else {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100513 RTC_LOG(LS_WARNING) << "Malformed toggling interval: "
514 << toggling_interval;
sprangc5d62e22017-04-02 23:53:04 -0700515 }
516 }
sprangc5d62e22017-04-02 23:53:04 -0700517 return instance;
518}
519
perkjd52063f2016-09-07 06:32:18 -0700520class OveruseFrameDetector::CheckOveruseTask : public rtc::QueuedTask {
521 public:
Niels Möller73f29cb2018-01-31 16:09:31 +0100522 CheckOveruseTask(OveruseFrameDetector* overuse_detector,
523 AdaptationObserverInterface* observer)
524 : overuse_detector_(overuse_detector), observer_(observer) {
perkjd52063f2016-09-07 06:32:18 -0700525 rtc::TaskQueue::Current()->PostDelayedTask(
526 std::unique_ptr<rtc::QueuedTask>(this), kTimeToFirstCheckForOveruseMs);
527 }
528
529 void Stop() {
530 RTC_CHECK(task_checker_.CalledSequentially());
531 overuse_detector_ = nullptr;
532 }
533
534 private:
535 bool Run() override {
536 RTC_CHECK(task_checker_.CalledSequentially());
537 if (!overuse_detector_)
538 return true; // This will make the task queue delete this task.
Niels Möller73f29cb2018-01-31 16:09:31 +0100539 overuse_detector_->CheckForOveruse(observer_);
perkjd52063f2016-09-07 06:32:18 -0700540
541 rtc::TaskQueue::Current()->PostDelayedTask(
542 std::unique_ptr<rtc::QueuedTask>(this), kCheckForOveruseIntervalMs);
543 // Return false to prevent this task from being deleted. Ownership has been
544 // transferred to the task queue when PostDelayedTask was called.
545 return false;
546 }
547 rtc::SequencedTaskChecker task_checker_;
548 OveruseFrameDetector* overuse_detector_;
Niels Möller73f29cb2018-01-31 16:09:31 +0100549 // Observer getting overuse reports.
550 AdaptationObserverInterface* observer_;
perkjd52063f2016-09-07 06:32:18 -0700551};
552
pbos@webrtc.org3e6e2712015-02-26 12:19:31 +0000553OveruseFrameDetector::OveruseFrameDetector(
pbos@webrtc.org3e6e2712015-02-26 12:19:31 +0000554 CpuOveruseMetricsObserver* metrics_observer)
perkjd52063f2016-09-07 06:32:18 -0700555 : check_overuse_task_(nullptr),
pbos@webrtc.org3e6e2712015-02-26 12:19:31 +0000556 metrics_observer_(metrics_observer),
asapersson@webrtc.orgb60346e2014-02-17 19:02:15 +0000557 num_process_times_(0),
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200558 // TODO(nisse): Use absl::optional
nissee0e3bdf2017-01-18 02:16:20 -0800559 last_capture_time_us_(-1),
asapersson74d85e12015-09-24 00:53:32 -0700560 num_pixels_(0),
Niels Möller7dc26b72017-12-06 10:27:48 +0100561 max_framerate_(kDefaultFrameRate),
Peter Boströme4499152016-02-05 11:13:28 +0100562 last_overuse_time_ms_(-1),
pbos@webrtc.orga9575702013-08-30 17:16:32 +0000563 checks_above_threshold_(0),
asapersson@webrtc.orgd9803072014-06-16 14:27:19 +0000564 num_overuse_detections_(0),
Peter Boströme4499152016-02-05 11:13:28 +0100565 last_rampup_time_ms_(-1),
pbos@webrtc.orga9575702013-08-30 17:16:32 +0000566 in_quick_rampup_(false),
Niels Möllerd1f7eb62018-03-28 16:40:58 +0200567 current_rampup_delay_ms_(kStandardRampUpDelayMs) {
perkjd52063f2016-09-07 06:32:18 -0700568 task_checker_.Detach();
asapersson@webrtc.org9e5b0342013-12-04 13:47:44 +0000569}
mflodman@webrtc.orge6168f52013-06-26 11:23:01 +0000570
571OveruseFrameDetector::~OveruseFrameDetector() {
perkjd52063f2016-09-07 06:32:18 -0700572 RTC_DCHECK(!check_overuse_task_) << "StopCheckForOverUse must be called.";
573}
574
Niels Möller73f29cb2018-01-31 16:09:31 +0100575void OveruseFrameDetector::StartCheckForOveruse(
Niels Möllerd1f7eb62018-03-28 16:40:58 +0200576 const CpuOveruseOptions& options,
Niels Möller73f29cb2018-01-31 16:09:31 +0100577 AdaptationObserverInterface* overuse_observer) {
perkjd52063f2016-09-07 06:32:18 -0700578 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
579 RTC_DCHECK(!check_overuse_task_);
Niels Möller73f29cb2018-01-31 16:09:31 +0100580 RTC_DCHECK(overuse_observer != nullptr);
Niels Möllerd1f7eb62018-03-28 16:40:58 +0200581
582 SetOptions(options);
Niels Möller73f29cb2018-01-31 16:09:31 +0100583 check_overuse_task_ = new CheckOveruseTask(this, overuse_observer);
perkjd52063f2016-09-07 06:32:18 -0700584}
585void OveruseFrameDetector::StopCheckForOveruse() {
586 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
Niels Möller4db138e2018-04-19 09:04:13 +0200587 if (check_overuse_task_) {
588 check_overuse_task_->Stop();
589 check_overuse_task_ = nullptr;
590 }
mflodman@webrtc.orge6168f52013-06-26 11:23:01 +0000591}
592
Peter Boströme4499152016-02-05 11:13:28 +0100593void OveruseFrameDetector::EncodedFrameTimeMeasured(int encode_duration_ms) {
perkjd52063f2016-09-07 06:32:18 -0700594 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
Niels Möller213618e2018-07-24 09:29:58 +0200595 encode_usage_percent_ = usage_->Value();
asapersson@webrtc.org9aed0022014-10-16 06:57:12 +0000596
Niels Möller213618e2018-07-24 09:29:58 +0200597 metrics_observer_->OnEncodedFrameTimeMeasured(encode_duration_ms,
598 *encode_usage_percent_);
asapersson@webrtc.orgab6bf4f2014-05-27 07:43:15 +0000599}
600
asapersson@webrtc.org8a8c3ef2014-03-20 13:15:01 +0000601bool OveruseFrameDetector::FrameSizeChanged(int num_pixels) const {
perkjd52063f2016-09-07 06:32:18 -0700602 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
asapersson@webrtc.org8a8c3ef2014-03-20 13:15:01 +0000603 if (num_pixels != num_pixels_) {
604 return true;
605 }
606 return false;
607}
608
nissee0e3bdf2017-01-18 02:16:20 -0800609bool OveruseFrameDetector::FrameTimeoutDetected(int64_t now_us) const {
perkjd52063f2016-09-07 06:32:18 -0700610 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
nissee0e3bdf2017-01-18 02:16:20 -0800611 if (last_capture_time_us_ == -1)
asapersson@webrtc.orgb60346e2014-02-17 19:02:15 +0000612 return false;
nissee0e3bdf2017-01-18 02:16:20 -0800613 return (now_us - last_capture_time_us_) >
Yves Gerey665174f2018-06-19 15:03:05 +0200614 options_.frame_timeout_interval_ms * rtc::kNumMicrosecsPerMillisec;
asapersson@webrtc.org8a8c3ef2014-03-20 13:15:01 +0000615}
616
Niels Möller7dc26b72017-12-06 10:27:48 +0100617void OveruseFrameDetector::ResetAll(int num_pixels) {
618 // Reset state, as a result resolution being changed. Do not however change
619 // the current frame rate back to the default.
perkjd52063f2016-09-07 06:32:18 -0700620 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
Niels Möller7dc26b72017-12-06 10:27:48 +0100621 num_pixels_ = num_pixels;
asapersson@webrtc.org9aed0022014-10-16 06:57:12 +0000622 usage_->Reset();
nissee0e3bdf2017-01-18 02:16:20 -0800623 last_capture_time_us_ = -1;
asapersson@webrtc.org8a8c3ef2014-03-20 13:15:01 +0000624 num_process_times_ = 0;
Niels Möller213618e2018-07-24 09:29:58 +0200625 encode_usage_percent_ = absl::nullopt;
Niels Möller7dc26b72017-12-06 10:27:48 +0100626 OnTargetFramerateUpdated(max_framerate_);
sprangfda496a2017-06-15 04:21:07 -0700627}
628
Niels Möller7dc26b72017-12-06 10:27:48 +0100629void OveruseFrameDetector::OnTargetFramerateUpdated(int framerate_fps) {
perkjd52063f2016-09-07 06:32:18 -0700630 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
Niels Möller7dc26b72017-12-06 10:27:48 +0100631 RTC_DCHECK_GE(framerate_fps, 0);
632 max_framerate_ = std::min(kMaxFramerate, framerate_fps);
633 usage_->SetMaxSampleDiffMs((1000 / std::max(kMinFramerate, max_framerate_)) *
634 kMaxSampleDiffMarginFactor);
asapersson@webrtc.org9aed0022014-10-16 06:57:12 +0000635}
636
Niels Möller7dc26b72017-12-06 10:27:48 +0100637void OveruseFrameDetector::FrameCaptured(const VideoFrame& frame,
638 int64_t time_when_first_seen_us) {
perkjd52063f2016-09-07 06:32:18 -0700639 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
Niels Möllereee7ced2017-12-01 11:25:01 +0100640
Niels Möller7dc26b72017-12-06 10:27:48 +0100641 if (FrameSizeChanged(frame.width() * frame.height()) ||
642 FrameTimeoutDetected(time_when_first_seen_us)) {
643 ResetAll(frame.width() * frame.height());
644 }
645
Niels Möllere08cf3a2017-12-07 15:23:58 +0100646 usage_->FrameCaptured(frame, time_when_first_seen_us, last_capture_time_us_);
Niels Möller7dc26b72017-12-06 10:27:48 +0100647 last_capture_time_us_ = time_when_first_seen_us;
Niels Möller7dc26b72017-12-06 10:27:48 +0100648}
649
650void OveruseFrameDetector::FrameSent(uint32_t timestamp,
Niels Möller83dbeac2017-12-14 16:39:44 +0100651 int64_t time_sent_in_us,
652 int64_t capture_time_us,
Danil Chapovalovb9b146c2018-06-15 12:28:07 +0200653 absl::optional<int> encode_duration_us) {
Niels Möller7dc26b72017-12-06 10:27:48 +0100654 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
Niels Möller83dbeac2017-12-14 16:39:44 +0100655 encode_duration_us = usage_->FrameSent(timestamp, time_sent_in_us,
656 capture_time_us, encode_duration_us);
Niels Möllere08cf3a2017-12-07 15:23:58 +0100657
658 if (encode_duration_us) {
659 EncodedFrameTimeMeasured(*encode_duration_us /
660 rtc::kNumMicrosecsPerMillisec);
Peter Boströme4499152016-02-05 11:13:28 +0100661 }
asapersson@webrtc.orgc7ff8f92013-11-26 11:12:33 +0000662}
663
Niels Möller73f29cb2018-01-31 16:09:31 +0100664void OveruseFrameDetector::CheckForOveruse(
665 AdaptationObserverInterface* observer) {
perkjd52063f2016-09-07 06:32:18 -0700666 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
Niels Möller73f29cb2018-01-31 16:09:31 +0100667 RTC_DCHECK(observer);
perkjd52063f2016-09-07 06:32:18 -0700668 ++num_process_times_;
Niels Möller213618e2018-07-24 09:29:58 +0200669 if (num_process_times_ <= options_.min_process_count ||
670 !encode_usage_percent_)
perkjd52063f2016-09-07 06:32:18 -0700671 return;
pbos@webrtc.orga9575702013-08-30 17:16:32 +0000672
nissee0e3bdf2017-01-18 02:16:20 -0800673 int64_t now_ms = rtc::TimeMillis();
pbos@webrtc.orga9575702013-08-30 17:16:32 +0000674
Niels Möller213618e2018-07-24 09:29:58 +0200675 if (IsOverusing(*encode_usage_percent_)) {
pbos@webrtc.orga9575702013-08-30 17:16:32 +0000676 // If the last thing we did was going up, and now have to back down, we need
677 // to check if this peak was short. If so we should back off to avoid going
678 // back and forth between this load, the system doesn't seem to handle it.
Peter Boströme4499152016-02-05 11:13:28 +0100679 bool check_for_backoff = last_rampup_time_ms_ > last_overuse_time_ms_;
pbos@webrtc.orga9575702013-08-30 17:16:32 +0000680 if (check_for_backoff) {
nissee0e3bdf2017-01-18 02:16:20 -0800681 if (now_ms - last_rampup_time_ms_ < kStandardRampUpDelayMs ||
asapersson@webrtc.orgd9803072014-06-16 14:27:19 +0000682 num_overuse_detections_ > kMaxOverusesBeforeApplyRampupDelay) {
pbos@webrtc.orga9575702013-08-30 17:16:32 +0000683 // Going up was not ok for very long, back off.
684 current_rampup_delay_ms_ *= kRampUpBackoffFactor;
685 if (current_rampup_delay_ms_ > kMaxRampUpDelayMs)
686 current_rampup_delay_ms_ = kMaxRampUpDelayMs;
687 } else {
688 // Not currently backing off, reset rampup delay.
689 current_rampup_delay_ms_ = kStandardRampUpDelayMs;
690 }
691 }
692
nissee0e3bdf2017-01-18 02:16:20 -0800693 last_overuse_time_ms_ = now_ms;
pbos@webrtc.orga9575702013-08-30 17:16:32 +0000694 in_quick_rampup_ = false;
695 checks_above_threshold_ = 0;
asapersson@webrtc.orgd9803072014-06-16 14:27:19 +0000696 ++num_overuse_detections_;
pbos@webrtc.orga9575702013-08-30 17:16:32 +0000697
Niels Möller73f29cb2018-01-31 16:09:31 +0100698 observer->AdaptDown(kScaleReasonCpu);
Niels Möller213618e2018-07-24 09:29:58 +0200699 } else if (IsUnderusing(*encode_usage_percent_, now_ms)) {
nissee0e3bdf2017-01-18 02:16:20 -0800700 last_rampup_time_ms_ = now_ms;
pbos@webrtc.orga9575702013-08-30 17:16:32 +0000701 in_quick_rampup_ = true;
702
Niels Möller73f29cb2018-01-31 16:09:31 +0100703 observer->AdaptUp(kScaleReasonCpu);
mflodman@webrtc.orge6168f52013-06-26 11:23:01 +0000704 }
pbos@webrtc.orga9575702013-08-30 17:16:32 +0000705
mflodman@webrtc.org5574dac2014-04-07 10:56:31 +0000706 int rampup_delay =
707 in_quick_rampup_ ? kQuickRampUpDelayMs : current_rampup_delay_ms_;
asapersson74d85e12015-09-24 00:53:32 -0700708
Mirko Bonadei675513b2017-11-09 11:09:25 +0100709 RTC_LOG(LS_VERBOSE) << " Frame stats: "
Niels Möller213618e2018-07-24 09:29:58 +0200710 << " encode usage " << *encode_usage_percent_
Mirko Bonadei675513b2017-11-09 11:09:25 +0100711 << " overuse detections " << num_overuse_detections_
712 << " rampup delay " << rampup_delay;
asapersson@webrtc.orge2af6222013-09-23 20:05:39 +0000713}
714
Niels Möllerd1f7eb62018-03-28 16:40:58 +0200715void OveruseFrameDetector::SetOptions(const CpuOveruseOptions& options) {
716 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
717 options_ = options;
718 // Force reset with next frame.
719 num_pixels_ = 0;
720 usage_ = CreateProcessingUsage(options);
721}
722
Niels Möller213618e2018-07-24 09:29:58 +0200723bool OveruseFrameDetector::IsOverusing(int usage_percent) {
perkjd52063f2016-09-07 06:32:18 -0700724 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
sprangc5d62e22017-04-02 23:53:04 -0700725
Niels Möller213618e2018-07-24 09:29:58 +0200726 if (usage_percent >= options_.high_encode_usage_threshold_percent) {
pbos@webrtc.orga9575702013-08-30 17:16:32 +0000727 ++checks_above_threshold_;
728 } else {
729 checks_above_threshold_ = 0;
730 }
asapersson@webrtc.org8a8c3ef2014-03-20 13:15:01 +0000731 return checks_above_threshold_ >= options_.high_threshold_consecutive_count;
mflodman@webrtc.orgd4412fe2013-07-31 16:42:21 +0000732}
733
Niels Möller213618e2018-07-24 09:29:58 +0200734bool OveruseFrameDetector::IsUnderusing(int usage_percent, int64_t time_now) {
perkjd52063f2016-09-07 06:32:18 -0700735 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
pbos@webrtc.orga9575702013-08-30 17:16:32 +0000736 int delay = in_quick_rampup_ ? kQuickRampUpDelayMs : current_rampup_delay_ms_;
Peter Boströme4499152016-02-05 11:13:28 +0100737 if (time_now < last_rampup_time_ms_ + delay)
mflodman@webrtc.orgd4412fe2013-07-31 16:42:21 +0000738 return false;
mflodman@webrtc.orgd4412fe2013-07-31 16:42:21 +0000739
Niels Möller213618e2018-07-24 09:29:58 +0200740 return usage_percent < options_.low_encode_usage_threshold_percent;
mflodman@webrtc.orgd4412fe2013-07-31 16:42:21 +0000741}
mflodman@webrtc.orge6168f52013-06-26 11:23:01 +0000742} // namespace webrtc