Home | History | Annotate | Download | only in video_engine
      1 /*
      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 
     13 #include <assert.h>
     14 #include <math.h>
     15 
     16 #include <algorithm>
     17 #include <list>
     18 #include <map>
     19 
     20 #include "webrtc/base/exp_filter.h"
     21 #include "webrtc/system_wrappers/interface/clock.h"
     22 #include "webrtc/system_wrappers/interface/critical_section_wrapper.h"
     23 #include "webrtc/system_wrappers/interface/logging.h"
     24 
     25 namespace webrtc {
     26 
     27 // TODO(mflodman) Test different values for all of these to trigger correctly,
     28 // avoid fluctuations etc.
     29 namespace {
     30 const int64_t kProcessIntervalMs = 5000;
     31 
     32 // Weight factor to apply to the standard deviation.
     33 const float kWeightFactor = 0.997f;
     34 // Weight factor to apply to the average.
     35 const float kWeightFactorMean = 0.98f;
     36 
     37 // Delay between consecutive rampups. (Used for quick recovery.)
     38 const int kQuickRampUpDelayMs = 10 * 1000;
     39 // Delay between rampup attempts. Initially uses standard, scales up to max.
     40 const int kStandardRampUpDelayMs = 40 * 1000;
     41 const int kMaxRampUpDelayMs = 240 * 1000;
     42 // Expontential back-off factor, to prevent annoying up-down behaviour.
     43 const double kRampUpBackoffFactor = 2.0;
     44 
     45 // Max number of overuses detected before always applying the rampup delay.
     46 const int kMaxOverusesBeforeApplyRampupDelay = 4;
     47 
     48 // The maximum exponent to use in VCMExpFilter.
     49 const float kSampleDiffMs = 33.0f;
     50 const float kMaxExp = 7.0f;
     51 
     52 }  // namespace
     53 
     54 Statistics::Statistics() :
     55     sum_(0.0),
     56     count_(0),
     57     filtered_samples_(new rtc::ExpFilter(kWeightFactorMean)),
     58     filtered_variance_(new rtc::ExpFilter(kWeightFactor)) {
     59   Reset();
     60 }
     61 
     62 void Statistics::SetOptions(const CpuOveruseOptions& options) {
     63   options_ = options;
     64 }
     65 
     66 void Statistics::Reset() {
     67   sum_ =  0.0;
     68   count_ = 0;
     69   filtered_variance_->Reset(kWeightFactor);
     70   filtered_variance_->Apply(1.0f, InitialVariance());
     71 }
     72 
     73 void Statistics::AddSample(float sample_ms) {
     74   sum_ += sample_ms;
     75   ++count_;
     76 
     77   if (count_ < static_cast<uint32_t>(options_.min_frame_samples)) {
     78     // Initialize filtered samples.
     79     filtered_samples_->Reset(kWeightFactorMean);
     80     filtered_samples_->Apply(1.0f, InitialMean());
     81     return;
     82   }
     83 
     84   float exp = sample_ms / kSampleDiffMs;
     85   exp = std::min(exp, kMaxExp);
     86   filtered_samples_->Apply(exp, sample_ms);
     87   filtered_variance_->Apply(exp, (sample_ms - filtered_samples_->filtered()) *
     88                                  (sample_ms - filtered_samples_->filtered()));
     89 }
     90 
     91 float Statistics::InitialMean() const {
     92   if (count_ == 0)
     93     return 0.0;
     94   return sum_ / count_;
     95 }
     96 
     97 float Statistics::InitialVariance() const {
     98   // Start in between the underuse and overuse threshold.
     99   float average_stddev = (options_.low_capture_jitter_threshold_ms +
    100                           options_.high_capture_jitter_threshold_ms) / 2.0f;
    101   return average_stddev * average_stddev;
    102 }
    103 
    104 float Statistics::Mean() const { return filtered_samples_->filtered(); }
    105 
    106 float Statistics::StdDev() const {
    107   return sqrt(std::max(filtered_variance_->filtered(), 0.0f));
    108 }
    109 
    110 uint64_t Statistics::Count() const { return count_; }
    111 
    112 
    113 // Class for calculating the average encode time.
    114 class OveruseFrameDetector::EncodeTimeAvg {
    115  public:
    116   EncodeTimeAvg()
    117       : kWeightFactor(0.5f),
    118         kInitialAvgEncodeTimeMs(5.0f),
    119         filtered_encode_time_ms_(new rtc::ExpFilter(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 Value() const {
    131     return static_cast<int>(filtered_encode_time_ms_->filtered() + 0.5);
    132   }
    133 
    134  private:
    135   const float kWeightFactor;
    136   const float kInitialAvgEncodeTimeMs;
    137   scoped_ptr<rtc::ExpFilter> filtered_encode_time_ms_;
    138 };
    139 
    140 // Class for calculating the encode usage.
    141 class OveruseFrameDetector::EncodeUsage {
    142  public:
    143   EncodeUsage()
    144       : kWeightFactorFrameDiff(0.998f),
    145         kWeightFactorEncodeTime(0.995f),
    146         kInitialSampleDiffMs(40.0f),
    147         kMaxSampleDiffMs(45.0f),
    148         count_(0),
    149         filtered_encode_time_ms_(new rtc::ExpFilter(kWeightFactorEncodeTime)),
    150         filtered_frame_diff_ms_(new rtc::ExpFilter(kWeightFactorFrameDiff)) {
    151     Reset();
    152   }
    153   ~EncodeUsage() {}
    154 
    155   void SetOptions(const CpuOveruseOptions& options) {
    156     options_ = options;
    157   }
    158 
    159   void Reset() {
    160     count_ = 0;
    161     filtered_frame_diff_ms_->Reset(kWeightFactorFrameDiff);
    162     filtered_frame_diff_ms_->Apply(1.0f, kInitialSampleDiffMs);
    163     filtered_encode_time_ms_->Reset(kWeightFactorEncodeTime);
    164     filtered_encode_time_ms_->Apply(1.0f, InitialEncodeTimeMs());
    165   }
    166 
    167   void AddSample(float sample_ms) {
    168     float exp = sample_ms / kSampleDiffMs;
    169     exp = std::min(exp, kMaxExp);
    170     filtered_frame_diff_ms_->Apply(exp, sample_ms);
    171   }
    172 
    173   void AddEncodeSample(float encode_time_ms, int64_t diff_last_sample_ms) {
    174     ++count_;
    175     float exp = diff_last_sample_ms / kSampleDiffMs;
    176     exp = std::min(exp, kMaxExp);
    177     filtered_encode_time_ms_->Apply(exp, encode_time_ms);
    178   }
    179 
    180   int Value() const {
    181     if (count_ < static_cast<uint32_t>(options_.min_frame_samples)) {
    182       return static_cast<int>(InitialUsageInPercent() + 0.5f);
    183     }
    184     float frame_diff_ms = std::max(filtered_frame_diff_ms_->filtered(), 1.0f);
    185     frame_diff_ms = std::min(frame_diff_ms, kMaxSampleDiffMs);
    186     float encode_usage_percent =
    187         100.0f * filtered_encode_time_ms_->filtered() / frame_diff_ms;
    188     return static_cast<int>(encode_usage_percent + 0.5);
    189   }
    190 
    191  private:
    192   float InitialUsageInPercent() const {
    193     // Start in between the underuse and overuse threshold.
    194     return (options_.low_encode_usage_threshold_percent +
    195             options_.high_encode_usage_threshold_percent) / 2.0f;
    196   }
    197 
    198   float InitialEncodeTimeMs() const {
    199     return InitialUsageInPercent() * kInitialSampleDiffMs / 100;
    200   }
    201 
    202   const float kWeightFactorFrameDiff;
    203   const float kWeightFactorEncodeTime;
    204   const float kInitialSampleDiffMs;
    205   const float kMaxSampleDiffMs;
    206   uint64_t count_;
    207   CpuOveruseOptions options_;
    208   scoped_ptr<rtc::ExpFilter> filtered_encode_time_ms_;
    209   scoped_ptr<rtc::ExpFilter> filtered_frame_diff_ms_;
    210 };
    211 
    212 // Class for calculating the relative standard deviation of encode times.
    213 class OveruseFrameDetector::EncodeTimeRsd {
    214  public:
    215   EncodeTimeRsd(Clock* clock)
    216       : kWeightFactor(0.6f),
    217         count_(0),
    218         filtered_rsd_(new rtc::ExpFilter(kWeightFactor)),
    219         hist_samples_(0),
    220         hist_sum_(0.0f),
    221         last_process_time_ms_(clock->TimeInMilliseconds()) {
    222     Reset();
    223   }
    224   ~EncodeTimeRsd() {}
    225 
    226   void SetOptions(const CpuOveruseOptions& options) {
    227     options_ = options;
    228   }
    229 
    230   void Reset() {
    231     count_ = 0;
    232     filtered_rsd_->Reset(kWeightFactor);
    233     filtered_rsd_->Apply(1.0f, InitialValue());
    234     hist_.clear();
    235     hist_samples_ = 0;
    236     hist_sum_ = 0.0f;
    237   }
    238 
    239   void AddEncodeSample(float encode_time_ms) {
    240     int bin = static_cast<int>(encode_time_ms + 0.5f);
    241     if (bin <= 0) {
    242       // The frame was probably not encoded, skip possible dropped frame.
    243       return;
    244     }
    245     ++count_;
    246     ++hist_[bin];
    247     ++hist_samples_;
    248     hist_sum_ += bin;
    249   }
    250 
    251   void Process(int64_t now) {
    252     if (count_ < static_cast<uint32_t>(options_.min_frame_samples)) {
    253       // Have not received min number of frames since last reset.
    254       return;
    255     }
    256     const int kMinHistSamples = 20;
    257     if (hist_samples_ < kMinHistSamples) {
    258       return;
    259     }
    260     const int64_t kMinDiffSinceLastProcessMs = 1000;
    261     int64_t diff_last_process_ms = now - last_process_time_ms_;
    262     if (now - last_process_time_ms_ <= kMinDiffSinceLastProcessMs) {
    263       return;
    264     }
    265     last_process_time_ms_ = now;
    266 
    267     // Calculate variance (using samples above the mean).
    268     // Checks for a larger encode time of some frames while there is a small
    269     // increase in the average time.
    270     int mean = hist_sum_ / hist_samples_;
    271     float variance = 0.0f;
    272     int total_count = 0;
    273     for (std::map<int,int>::iterator it = hist_.begin();
    274          it != hist_.end(); ++it) {
    275       int time = it->first;
    276       int count = it->second;
    277       if (time > mean) {
    278         total_count += count;
    279         for (int i = 0; i < count; ++i) {
    280           variance += ((time - mean) * (time - mean));
    281         }
    282       }
    283     }
    284     variance /= std::max(total_count, 1);
    285     float cov = sqrt(variance) / mean;
    286 
    287     hist_.clear();
    288     hist_samples_ = 0;
    289     hist_sum_ = 0.0f;
    290 
    291     float exp = static_cast<float>(diff_last_process_ms) / kProcessIntervalMs;
    292     exp = std::min(exp, kMaxExp);
    293     filtered_rsd_->Apply(exp, 100.0f * cov);
    294   }
    295 
    296   int Value() const {
    297     return static_cast<int>(filtered_rsd_->filtered() + 0.5);
    298   }
    299 
    300  private:
    301   float InitialValue() const {
    302     // Start in between the underuse and overuse threshold.
    303     return std::max(((options_.low_encode_time_rsd_threshold +
    304                       options_.high_encode_time_rsd_threshold) / 2.0f), 0.0f);
    305   }
    306 
    307   const float kWeightFactor;
    308   uint32_t count_;  // Number of encode samples since last reset.
    309   CpuOveruseOptions options_;
    310   scoped_ptr<rtc::ExpFilter> filtered_rsd_;
    311   int hist_samples_;
    312   float hist_sum_;
    313   std::map<int,int> hist_;  // Histogram of encode time of frames.
    314   int64_t last_process_time_ms_;
    315 };
    316 
    317 // Class for calculating the capture queue delay change.
    318 class OveruseFrameDetector::CaptureQueueDelay {
    319  public:
    320   CaptureQueueDelay()
    321       : kWeightFactor(0.5f),
    322         delay_ms_(0),
    323         filtered_delay_ms_per_s_(new rtc::ExpFilter(kWeightFactor)) {
    324     filtered_delay_ms_per_s_->Apply(1.0f, 0.0f);
    325   }
    326   ~CaptureQueueDelay() {}
    327 
    328   void FrameCaptured(int64_t now) {
    329     const size_t kMaxSize = 200;
    330     if (frames_.size() > kMaxSize) {
    331       frames_.pop_front();
    332     }
    333     frames_.push_back(now);
    334   }
    335 
    336   void FrameProcessingStarted(int64_t now) {
    337     if (frames_.empty()) {
    338       return;
    339     }
    340     delay_ms_ = now - frames_.front();
    341     frames_.pop_front();
    342   }
    343 
    344   void CalculateDelayChange(int64_t diff_last_sample_ms) {
    345     if (diff_last_sample_ms <= 0) {
    346       return;
    347     }
    348     float exp = static_cast<float>(diff_last_sample_ms) / kProcessIntervalMs;
    349     exp = std::min(exp, kMaxExp);
    350     filtered_delay_ms_per_s_->Apply(exp,
    351                                     delay_ms_ * 1000.0f / diff_last_sample_ms);
    352     ClearFrames();
    353   }
    354 
    355   void ClearFrames() {
    356     frames_.clear();
    357   }
    358 
    359   int delay_ms() const {
    360     return delay_ms_;
    361   }
    362 
    363   int Value() const {
    364     return static_cast<int>(filtered_delay_ms_per_s_->filtered() + 0.5);
    365   }
    366 
    367  private:
    368   const float kWeightFactor;
    369   std::list<int64_t> frames_;
    370   int delay_ms_;
    371   scoped_ptr<rtc::ExpFilter> filtered_delay_ms_per_s_;
    372 };
    373 
    374 OveruseFrameDetector::OveruseFrameDetector(Clock* clock)
    375     : crit_(CriticalSectionWrapper::CreateCriticalSection()),
    376       observer_(NULL),
    377       clock_(clock),
    378       next_process_time_(clock_->TimeInMilliseconds()),
    379       num_process_times_(0),
    380       last_capture_time_(0),
    381       last_overuse_time_(0),
    382       checks_above_threshold_(0),
    383       num_overuse_detections_(0),
    384       last_rampup_time_(0),
    385       in_quick_rampup_(false),
    386       current_rampup_delay_ms_(kStandardRampUpDelayMs),
    387       num_pixels_(0),
    388       last_encode_sample_ms_(0),
    389       encode_time_(new EncodeTimeAvg()),
    390       encode_rsd_(new EncodeTimeRsd(clock)),
    391       encode_usage_(new EncodeUsage()),
    392       capture_queue_delay_(new CaptureQueueDelay()) {
    393 }
    394 
    395 OveruseFrameDetector::~OveruseFrameDetector() {
    396 }
    397 
    398 void OveruseFrameDetector::SetObserver(CpuOveruseObserver* observer) {
    399   CriticalSectionScoped cs(crit_.get());
    400   observer_ = observer;
    401 }
    402 
    403 void OveruseFrameDetector::SetOptions(const CpuOveruseOptions& options) {
    404   assert(options.min_frame_samples > 0);
    405   CriticalSectionScoped cs(crit_.get());
    406   if (options_.Equals(options)) {
    407     return;
    408   }
    409   options_ = options;
    410   capture_deltas_.SetOptions(options);
    411   encode_usage_->SetOptions(options);
    412   encode_rsd_->SetOptions(options);
    413   ResetAll(num_pixels_);
    414 }
    415 
    416 int OveruseFrameDetector::CaptureQueueDelayMsPerS() const {
    417   CriticalSectionScoped cs(crit_.get());
    418   return capture_queue_delay_->delay_ms();
    419 }
    420 
    421 void OveruseFrameDetector::GetCpuOveruseMetrics(
    422     CpuOveruseMetrics* metrics) const {
    423   CriticalSectionScoped cs(crit_.get());
    424   metrics->capture_jitter_ms = static_cast<int>(capture_deltas_.StdDev() + 0.5);
    425   metrics->avg_encode_time_ms = encode_time_->Value();
    426   metrics->encode_rsd = encode_rsd_->Value();
    427   metrics->encode_usage_percent = encode_usage_->Value();
    428   metrics->capture_queue_delay_ms_per_s = capture_queue_delay_->Value();
    429 }
    430 
    431 int32_t OveruseFrameDetector::TimeUntilNextProcess() {
    432   CriticalSectionScoped cs(crit_.get());
    433   return next_process_time_ - clock_->TimeInMilliseconds();
    434 }
    435 
    436 bool OveruseFrameDetector::FrameSizeChanged(int num_pixels) const {
    437   if (num_pixels != num_pixels_) {
    438     return true;
    439   }
    440   return false;
    441 }
    442 
    443 bool OveruseFrameDetector::FrameTimeoutDetected(int64_t now) const {
    444   if (last_capture_time_ == 0) {
    445     return false;
    446   }
    447   return (now - last_capture_time_) > options_.frame_timeout_interval_ms;
    448 }
    449 
    450 void OveruseFrameDetector::ResetAll(int num_pixels) {
    451   num_pixels_ = num_pixels;
    452   capture_deltas_.Reset();
    453   encode_usage_->Reset();
    454   encode_rsd_->Reset();
    455   capture_queue_delay_->ClearFrames();
    456   last_capture_time_ = 0;
    457   num_process_times_ = 0;
    458 }
    459 
    460 void OveruseFrameDetector::FrameCaptured(int width, int height) {
    461   CriticalSectionScoped cs(crit_.get());
    462 
    463   int64_t now = clock_->TimeInMilliseconds();
    464   if (FrameSizeChanged(width * height) || FrameTimeoutDetected(now)) {
    465     ResetAll(width * height);
    466   }
    467 
    468   if (last_capture_time_ != 0) {
    469     capture_deltas_.AddSample(now - last_capture_time_);
    470     encode_usage_->AddSample(now - last_capture_time_);
    471   }
    472   last_capture_time_ = now;
    473 
    474   capture_queue_delay_->FrameCaptured(now);
    475 }
    476 
    477 void OveruseFrameDetector::FrameProcessingStarted() {
    478   CriticalSectionScoped cs(crit_.get());
    479   capture_queue_delay_->FrameProcessingStarted(clock_->TimeInMilliseconds());
    480 }
    481 
    482 void OveruseFrameDetector::FrameEncoded(int encode_time_ms) {
    483   CriticalSectionScoped cs(crit_.get());
    484   int64_t time = clock_->TimeInMilliseconds();
    485   if (last_encode_sample_ms_ != 0) {
    486     int64_t diff_ms = time - last_encode_sample_ms_;
    487     encode_time_->AddEncodeSample(encode_time_ms, diff_ms);
    488     encode_usage_->AddEncodeSample(encode_time_ms, diff_ms);
    489     encode_rsd_->AddEncodeSample(encode_time_ms);
    490   }
    491   last_encode_sample_ms_ = time;
    492 }
    493 
    494 int32_t OveruseFrameDetector::Process() {
    495   CriticalSectionScoped cs(crit_.get());
    496 
    497   int64_t now = clock_->TimeInMilliseconds();
    498 
    499   // Used to protect against Process() being called too often.
    500   if (now < next_process_time_)
    501     return 0;
    502 
    503   int64_t diff_ms = now - next_process_time_ + kProcessIntervalMs;
    504   next_process_time_ = now + kProcessIntervalMs;
    505   ++num_process_times_;
    506 
    507   encode_rsd_->Process(now);
    508   capture_queue_delay_->CalculateDelayChange(diff_ms);
    509 
    510   if (num_process_times_ <= options_.min_process_count) {
    511     return 0;
    512   }
    513 
    514   if (IsOverusing()) {
    515     // If the last thing we did was going up, and now have to back down, we need
    516     // to check if this peak was short. If so we should back off to avoid going
    517     // back and forth between this load, the system doesn't seem to handle it.
    518     bool check_for_backoff = last_rampup_time_ > last_overuse_time_;
    519     if (check_for_backoff) {
    520       if (now - last_rampup_time_ < kStandardRampUpDelayMs ||
    521           num_overuse_detections_ > kMaxOverusesBeforeApplyRampupDelay) {
    522         // Going up was not ok for very long, back off.
    523         current_rampup_delay_ms_ *= kRampUpBackoffFactor;
    524         if (current_rampup_delay_ms_ > kMaxRampUpDelayMs)
    525           current_rampup_delay_ms_ = kMaxRampUpDelayMs;
    526       } else {
    527         // Not currently backing off, reset rampup delay.
    528         current_rampup_delay_ms_ = kStandardRampUpDelayMs;
    529       }
    530     }
    531 
    532     last_overuse_time_ = now;
    533     in_quick_rampup_ = false;
    534     checks_above_threshold_ = 0;
    535     ++num_overuse_detections_;
    536 
    537     if (observer_ != NULL)
    538       observer_->OveruseDetected();
    539   } else if (IsUnderusing(now)) {
    540     last_rampup_time_ = now;
    541     in_quick_rampup_ = true;
    542 
    543     if (observer_ != NULL)
    544       observer_->NormalUsage();
    545   }
    546 
    547   int rampup_delay =
    548       in_quick_rampup_ ? kQuickRampUpDelayMs : current_rampup_delay_ms_;
    549   LOG(LS_VERBOSE) << " Frame stats: capture avg: " << capture_deltas_.Mean()
    550                   << " capture stddev " << capture_deltas_.StdDev()
    551                   << " encode usage " << encode_usage_->Value()
    552                   << " encode rsd " << encode_rsd_->Value()
    553                   << " overuse detections " << num_overuse_detections_
    554                   << " rampup delay " << rampup_delay;
    555   return 0;
    556 }
    557 
    558 bool OveruseFrameDetector::IsOverusing() {
    559   bool overusing = false;
    560   if (options_.enable_capture_jitter_method) {
    561     overusing = capture_deltas_.StdDev() >=
    562         options_.high_capture_jitter_threshold_ms;
    563   } else if (options_.enable_encode_usage_method) {
    564     bool encode_usage_overuse =
    565         encode_usage_->Value() >= options_.high_encode_usage_threshold_percent;
    566     bool encode_rsd_overuse = false;
    567     if (options_.high_encode_time_rsd_threshold > 0) {
    568       encode_rsd_overuse =
    569           (encode_rsd_->Value() >= options_.high_encode_time_rsd_threshold);
    570     }
    571     overusing = encode_usage_overuse || encode_rsd_overuse;
    572   }
    573 
    574   if (overusing) {
    575     ++checks_above_threshold_;
    576   } else {
    577     checks_above_threshold_ = 0;
    578   }
    579   return checks_above_threshold_ >= options_.high_threshold_consecutive_count;
    580 }
    581 
    582 bool OveruseFrameDetector::IsUnderusing(int64_t time_now) {
    583   int delay = in_quick_rampup_ ? kQuickRampUpDelayMs : current_rampup_delay_ms_;
    584   if (time_now < last_rampup_time_ + delay)
    585     return false;
    586 
    587   bool underusing = false;
    588   if (options_.enable_capture_jitter_method) {
    589     underusing = capture_deltas_.StdDev() <
    590         options_.low_capture_jitter_threshold_ms;
    591   } else if (options_.enable_encode_usage_method) {
    592     bool encode_usage_underuse =
    593         encode_usage_->Value() < options_.low_encode_usage_threshold_percent;
    594     bool encode_rsd_underuse = true;
    595     if (options_.low_encode_time_rsd_threshold > 0) {
    596       encode_rsd_underuse =
    597           (encode_rsd_->Value() < options_.low_encode_time_rsd_threshold);
    598     }
    599     underusing = encode_usage_underuse && encode_rsd_underuse;
    600   }
    601   return underusing;
    602 }
    603 }  // namespace webrtc
    604