diff options
Diffstat (limited to 'chromium/third_party/webrtc/video_engine/overuse_frame_detector.cc')
-rw-r--r-- | chromium/third_party/webrtc/video_engine/overuse_frame_detector.cc | 342 |
1 files changed, 264 insertions, 78 deletions
diff --git a/chromium/third_party/webrtc/video_engine/overuse_frame_detector.cc b/chromium/third_party/webrtc/video_engine/overuse_frame_detector.cc index a5e2d6f5de7..764c2584c3b 100644 --- a/chromium/third_party/webrtc/video_engine/overuse_frame_detector.cc +++ b/chromium/third_party/webrtc/video_engine/overuse_frame_detector.cc @@ -15,12 +15,12 @@ #include <algorithm> #include <list> +#include <map> #include "webrtc/modules/video_coding/utility/include/exp_filter.h" #include "webrtc/system_wrappers/interface/clock.h" #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" -#include "webrtc/system_wrappers/interface/trace.h" -#include "webrtc/video_engine/include/vie_base.h" +#include "webrtc/system_wrappers/interface/logging.h" namespace webrtc { @@ -29,15 +29,8 @@ namespace webrtc { namespace { const int64_t kProcessIntervalMs = 5000; -// Consecutive checks above threshold to trigger overuse. -const int kConsecutiveChecksAboveThreshold = 2; - -// Minimum samples required to perform a check. -const size_t kMinFrameSampleCount = 15; - // Weight factor to apply to the standard deviation. const float kWeightFactor = 0.997f; - // Weight factor to apply to the average. const float kWeightFactorMean = 0.98f; @@ -45,12 +38,12 @@ const float kWeightFactorMean = 0.98f; const int kQuickRampUpDelayMs = 10 * 1000; // Delay between rampup attempts. Initially uses standard, scales up to max. const int kStandardRampUpDelayMs = 30 * 1000; -const int kMaxRampUpDelayMs = 120 * 1000; +const int kMaxRampUpDelayMs = 240 * 1000; // Expontential back-off factor, to prevent annoying up-down behaviour. const double kRampUpBackoffFactor = 2.0; -// The initial average encode time (set to a fairly small value). -const float kInitialAvgEncodeTimeMs = 5.0f; +// Max number of overuses detected before always applying the rampup delay. +const int kMaxOverusesBeforeApplyRampupDelay = 7; // The maximum exponent to use in VCMExpFilter. const float kSampleDiffMs = 33.0f; @@ -63,23 +56,28 @@ Statistics::Statistics() : count_(0), filtered_samples_(new VCMExpFilter(kWeightFactorMean)), filtered_variance_(new VCMExpFilter(kWeightFactor)) { + Reset(); +} + +void Statistics::SetOptions(const CpuOveruseOptions& options) { + options_ = options; } void Statistics::Reset() { sum_ = 0.0; count_ = 0; + filtered_variance_->Reset(kWeightFactor); + filtered_variance_->Apply(1.0f, InitialVariance()); } void Statistics::AddSample(float sample_ms) { sum_ += sample_ms; ++count_; - if (count_ < kMinFrameSampleCount) { + if (count_ < static_cast<uint32_t>(options_.min_frame_samples)) { // Initialize filtered samples. filtered_samples_->Reset(kWeightFactorMean); filtered_samples_->Apply(1.0f, InitialMean()); - filtered_variance_->Reset(kWeightFactor); - filtered_variance_->Apply(1.0f, InitialVariance()); return; } @@ -98,7 +96,8 @@ float Statistics::InitialMean() const { float Statistics::InitialVariance() const { // Start in between the underuse and overuse threshold. - float average_stddev = (kNormalUseStdDevMs + kOveruseStdDevMs)/2.0f; + float average_stddev = (options_.low_capture_jitter_threshold_ms + + options_.high_capture_jitter_threshold_ms) / 2.0f; return average_stddev * average_stddev; } @@ -116,6 +115,7 @@ class OveruseFrameDetector::EncodeTimeAvg { public: EncodeTimeAvg() : kWeightFactor(0.5f), + kInitialAvgEncodeTimeMs(5.0f), filtered_encode_time_ms_(new VCMExpFilter(kWeightFactor)) { filtered_encode_time_ms_->Apply(1.0f, kInitialAvgEncodeTimeMs); } @@ -127,12 +127,13 @@ class OveruseFrameDetector::EncodeTimeAvg { filtered_encode_time_ms_->Apply(exp, encode_time_ms); } - int filtered_encode_time_ms() const { + int Value() const { return static_cast<int>(filtered_encode_time_ms_->Value() + 0.5); } private: const float kWeightFactor; + const float kInitialAvgEncodeTimeMs; scoped_ptr<VCMExpFilter> filtered_encode_time_ms_; }; @@ -142,13 +143,27 @@ class OveruseFrameDetector::EncodeUsage { EncodeUsage() : kWeightFactorFrameDiff(0.998f), kWeightFactorEncodeTime(0.995f), + kInitialSampleDiffMs(40.0f), + kMaxSampleDiffMs(45.0f), + count_(0), filtered_encode_time_ms_(new VCMExpFilter(kWeightFactorEncodeTime)), filtered_frame_diff_ms_(new VCMExpFilter(kWeightFactorFrameDiff)) { - filtered_encode_time_ms_->Apply(1.0f, kInitialAvgEncodeTimeMs); - filtered_frame_diff_ms_->Apply(1.0f, kSampleDiffMs); + Reset(); } ~EncodeUsage() {} + void SetOptions(const CpuOveruseOptions& options) { + options_ = options; + } + + void Reset() { + count_ = 0; + filtered_frame_diff_ms_->Reset(kWeightFactorFrameDiff); + filtered_frame_diff_ms_->Apply(1.0f, kInitialSampleDiffMs); + filtered_encode_time_ms_->Reset(kWeightFactorEncodeTime); + filtered_encode_time_ms_->Apply(1.0f, InitialEncodeTimeMs()); + } + void AddSample(float sample_ms) { float exp = sample_ms / kSampleDiffMs; exp = std::min(exp, kMaxExp); @@ -156,25 +171,149 @@ class OveruseFrameDetector::EncodeUsage { } void AddEncodeSample(float encode_time_ms, int64_t diff_last_sample_ms) { + ++count_; float exp = diff_last_sample_ms / kSampleDiffMs; exp = std::min(exp, kMaxExp); filtered_encode_time_ms_->Apply(exp, encode_time_ms); } - int UsageInPercent() const { + int Value() const { + if (count_ < static_cast<uint32_t>(options_.min_frame_samples)) { + return static_cast<int>(InitialUsageInPercent() + 0.5f); + } float frame_diff_ms = std::max(filtered_frame_diff_ms_->Value(), 1.0f); + frame_diff_ms = std::min(frame_diff_ms, kMaxSampleDiffMs); float encode_usage_percent = 100.0f * filtered_encode_time_ms_->Value() / frame_diff_ms; return static_cast<int>(encode_usage_percent + 0.5); } private: + float InitialUsageInPercent() const { + // Start in between the underuse and overuse threshold. + return (options_.low_encode_usage_threshold_percent + + options_.high_encode_usage_threshold_percent) / 2.0f; + } + + float InitialEncodeTimeMs() const { + return InitialUsageInPercent() * kInitialSampleDiffMs / 100; + } + const float kWeightFactorFrameDiff; const float kWeightFactorEncodeTime; + const float kInitialSampleDiffMs; + const float kMaxSampleDiffMs; + uint64_t count_; + CpuOveruseOptions options_; scoped_ptr<VCMExpFilter> filtered_encode_time_ms_; scoped_ptr<VCMExpFilter> filtered_frame_diff_ms_; }; +// Class for calculating the relative standard deviation of encode times. +class OveruseFrameDetector::EncodeTimeRsd { + public: + EncodeTimeRsd(Clock* clock) + : kWeightFactor(0.6f), + count_(0), + filtered_rsd_(new VCMExpFilter(kWeightFactor)), + hist_samples_(0), + hist_sum_(0.0f), + last_process_time_ms_(clock->TimeInMilliseconds()) { + Reset(); + } + ~EncodeTimeRsd() {} + + void SetOptions(const CpuOveruseOptions& options) { + options_ = options; + } + + void Reset() { + count_ = 0; + filtered_rsd_->Reset(kWeightFactor); + filtered_rsd_->Apply(1.0f, InitialValue()); + hist_.clear(); + hist_samples_ = 0; + hist_sum_ = 0.0f; + } + + void AddEncodeSample(float encode_time_ms) { + int bin = static_cast<int>(encode_time_ms + 0.5f); + if (bin <= 0) { + // The frame was probably not encoded, skip possible dropped frame. + return; + } + ++count_; + ++hist_[bin]; + ++hist_samples_; + hist_sum_ += bin; + } + + void Process(int64_t now) { + if (count_ < static_cast<uint32_t>(options_.min_frame_samples)) { + // Have not received min number of frames since last reset. + return; + } + const int kMinHistSamples = 20; + if (hist_samples_ < kMinHistSamples) { + return; + } + const int64_t kMinDiffSinceLastProcessMs = 1000; + int64_t diff_last_process_ms = now - last_process_time_ms_; + if (now - last_process_time_ms_ <= kMinDiffSinceLastProcessMs) { + return; + } + last_process_time_ms_ = now; + + // Calculate variance (using samples above the mean). + // Checks for a larger encode time of some frames while there is a small + // increase in the average time. + int mean = hist_sum_ / hist_samples_; + float variance = 0.0f; + int total_count = 0; + for (std::map<int,int>::iterator it = hist_.begin(); + it != hist_.end(); ++it) { + int time = it->first; + int count = it->second; + if (time > mean) { + total_count += count; + for (int i = 0; i < count; ++i) { + variance += ((time - mean) * (time - mean)); + } + } + } + variance /= std::max(total_count, 1); + float cov = sqrt(variance) / mean; + + hist_.clear(); + hist_samples_ = 0; + hist_sum_ = 0.0f; + + float exp = static_cast<float>(diff_last_process_ms) / kProcessIntervalMs; + exp = std::min(exp, kMaxExp); + filtered_rsd_->Apply(exp, 100.0f * cov); + } + + int Value() const { + return static_cast<int>(filtered_rsd_->Value() + 0.5); + } + + private: + float InitialValue() const { + // Start in between the underuse and overuse threshold. + return std::max(((options_.low_encode_time_rsd_threshold + + options_.high_encode_time_rsd_threshold) / 2.0f), 0.0f); + } + + const float kWeightFactor; + uint32_t count_; // Number of encode samples since last reset. + CpuOveruseOptions options_; + scoped_ptr<VCMExpFilter> filtered_rsd_; + int hist_samples_; + float hist_sum_; + std::map<int,int> hist_; // Histogram of encode time of frames. + int64_t last_process_time_ms_; +}; + // Class for calculating the capture queue delay change. class OveruseFrameDetector::CaptureQueueDelay { public: @@ -221,7 +360,7 @@ class OveruseFrameDetector::CaptureQueueDelay { return delay_ms_; } - int filtered_delay_ms_per_s() const { + int Value() const { return static_cast<int>(filtered_delay_ms_per_s_->Value() + 0.5); } @@ -232,25 +371,23 @@ class OveruseFrameDetector::CaptureQueueDelay { scoped_ptr<VCMExpFilter> filtered_delay_ms_per_s_; }; -OveruseFrameDetector::OveruseFrameDetector(Clock* clock, - float normaluse_stddev_ms, - float overuse_stddev_ms) +OveruseFrameDetector::OveruseFrameDetector(Clock* clock) : crit_(CriticalSectionWrapper::CreateCriticalSection()), - normaluse_stddev_ms_(normaluse_stddev_ms), - overuse_stddev_ms_(overuse_stddev_ms), observer_(NULL), clock_(clock), next_process_time_(clock_->TimeInMilliseconds()), + num_process_times_(0), last_capture_time_(0), last_overuse_time_(0), checks_above_threshold_(0), + num_overuse_detections_(0), last_rampup_time_(0), in_quick_rampup_(false), current_rampup_delay_ms_(kStandardRampUpDelayMs), num_pixels_(0), - last_capture_jitter_ms_(-1), last_encode_sample_ms_(0), encode_time_(new EncodeTimeAvg()), + encode_rsd_(new EncodeTimeRsd(clock)), encode_usage_(new EncodeUsage()), capture_queue_delay_(new CaptureQueueDelay()) { } @@ -263,24 +400,32 @@ void OveruseFrameDetector::SetObserver(CpuOveruseObserver* observer) { observer_ = observer; } -int OveruseFrameDetector::AvgEncodeTimeMs() const { +void OveruseFrameDetector::SetOptions(const CpuOveruseOptions& options) { + assert(options.min_frame_samples > 0); CriticalSectionScoped cs(crit_.get()); - return encode_time_->filtered_encode_time_ms(); -} - -int OveruseFrameDetector::EncodeUsagePercent() const { - CriticalSectionScoped cs(crit_.get()); - return encode_usage_->UsageInPercent(); + if (options_.Equals(options)) { + return; + } + options_ = options; + capture_deltas_.SetOptions(options); + encode_usage_->SetOptions(options); + encode_rsd_->SetOptions(options); + ResetAll(num_pixels_); } -int OveruseFrameDetector::AvgCaptureQueueDelayMsPerS() const { +int OveruseFrameDetector::CaptureQueueDelayMsPerS() const { CriticalSectionScoped cs(crit_.get()); - return capture_queue_delay_->filtered_delay_ms_per_s(); + return capture_queue_delay_->delay_ms(); } -int OveruseFrameDetector::CaptureQueueDelayMsPerS() const { +void OveruseFrameDetector::GetCpuOveruseMetrics( + CpuOveruseMetrics* metrics) const { CriticalSectionScoped cs(crit_.get()); - return capture_queue_delay_->delay_ms(); + metrics->capture_jitter_ms = static_cast<int>(capture_deltas_.StdDev() + 0.5); + metrics->avg_encode_time_ms = encode_time_->Value(); + metrics->encode_rsd = encode_rsd_->Value(); + metrics->encode_usage_percent = encode_usage_->Value(); + metrics->capture_queue_delay_ms_per_s = capture_queue_delay_->Value(); } int32_t OveruseFrameDetector::TimeUntilNextProcess() { @@ -288,26 +433,45 @@ int32_t OveruseFrameDetector::TimeUntilNextProcess() { return next_process_time_ - clock_->TimeInMilliseconds(); } +bool OveruseFrameDetector::FrameSizeChanged(int num_pixels) const { + if (num_pixels != num_pixels_) { + return true; + } + return false; +} + +bool OveruseFrameDetector::FrameTimeoutDetected(int64_t now) const { + if (last_capture_time_ == 0) { + return false; + } + return (now - last_capture_time_) > options_.frame_timeout_interval_ms; +} + +void OveruseFrameDetector::ResetAll(int num_pixels) { + num_pixels_ = num_pixels; + capture_deltas_.Reset(); + encode_usage_->Reset(); + encode_rsd_->Reset(); + capture_queue_delay_->ClearFrames(); + last_capture_time_ = 0; + num_process_times_ = 0; +} + void OveruseFrameDetector::FrameCaptured(int width, int height) { CriticalSectionScoped cs(crit_.get()); - int num_pixels = width * height; - if (num_pixels != num_pixels_) { - // Frame size changed, reset statistics. - num_pixels_ = num_pixels; - capture_deltas_.Reset(); - last_capture_time_ = 0; - capture_queue_delay_->ClearFrames(); + int64_t now = clock_->TimeInMilliseconds(); + if (FrameSizeChanged(width * height) || FrameTimeoutDetected(now)) { + ResetAll(width * height); } - int64_t time = clock_->TimeInMilliseconds(); if (last_capture_time_ != 0) { - capture_deltas_.AddSample(time - last_capture_time_); - encode_usage_->AddSample(time - last_capture_time_); + capture_deltas_.AddSample(now - last_capture_time_); + encode_usage_->AddSample(now - last_capture_time_); } - last_capture_time_ = time; + last_capture_time_ = now; - capture_queue_delay_->FrameCaptured(time); + capture_queue_delay_->FrameCaptured(now); } void OveruseFrameDetector::FrameProcessingStarted() { @@ -322,15 +486,11 @@ void OveruseFrameDetector::FrameEncoded(int encode_time_ms) { int64_t diff_ms = time - last_encode_sample_ms_; encode_time_->AddEncodeSample(encode_time_ms, diff_ms); encode_usage_->AddEncodeSample(encode_time_ms, diff_ms); + encode_rsd_->AddEncodeSample(encode_time_ms); } last_encode_sample_ms_ = time; } -int OveruseFrameDetector::last_capture_jitter_ms() const { - CriticalSectionScoped cs(crit_.get()); - return last_capture_jitter_ms_; -} - int32_t OveruseFrameDetector::Process() { CriticalSectionScoped cs(crit_.get()); @@ -342,20 +502,23 @@ int32_t OveruseFrameDetector::Process() { int64_t diff_ms = now - next_process_time_ + kProcessIntervalMs; next_process_time_ = now + kProcessIntervalMs; + ++num_process_times_; - // Don't trigger overuse unless we've seen a certain number of frames. - if (capture_deltas_.Count() < kMinFrameSampleCount) - return 0; - + encode_rsd_->Process(now); capture_queue_delay_->CalculateDelayChange(diff_ms); + if (num_process_times_ <= options_.min_process_count) { + return 0; + } + if (IsOverusing()) { // If the last thing we did was going up, and now have to back down, we need // to check if this peak was short. If so we should back off to avoid going // back and forth between this load, the system doesn't seem to handle it. bool check_for_backoff = last_rampup_time_ > last_overuse_time_; if (check_for_backoff) { - if (now - last_rampup_time_ < kStandardRampUpDelayMs) { + if (now - last_rampup_time_ < kStandardRampUpDelayMs || + num_overuse_detections_ > kMaxOverusesBeforeApplyRampupDelay) { // Going up was not ok for very long, back off. current_rampup_delay_ms_ *= kRampUpBackoffFactor; if (current_rampup_delay_ms_ > kMaxRampUpDelayMs) @@ -369,6 +532,7 @@ int32_t OveruseFrameDetector::Process() { last_overuse_time_ = now; in_quick_rampup_ = false; checks_above_threshold_ = 0; + ++num_overuse_detections_; if (observer_ != NULL) observer_->OveruseDetected(); @@ -380,31 +544,39 @@ int32_t OveruseFrameDetector::Process() { observer_->NormalUsage(); } - WEBRTC_TRACE( - webrtc::kTraceInfo, - webrtc::kTraceVideo, - -1, - "Capture input stats: avg: %.2fms, std_dev: %.2fms (rampup delay: " - "%dms, overuse: >=%.2fms, " - "underuse: <%.2fms)", - capture_deltas_.Mean(), - capture_deltas_.StdDev(), - in_quick_rampup_ ? kQuickRampUpDelayMs : current_rampup_delay_ms_, - overuse_stddev_ms_, - normaluse_stddev_ms_); - - last_capture_jitter_ms_ = static_cast<int>(capture_deltas_.StdDev() + 0.5); + int rampup_delay = + in_quick_rampup_ ? kQuickRampUpDelayMs : current_rampup_delay_ms_; + LOG(LS_VERBOSE) << " Frame stats: capture avg: " << capture_deltas_.Mean() + << " capture stddev " << capture_deltas_.StdDev() + << " encode usage " << encode_usage_->Value() + << " encode rsd " << encode_rsd_->Value() + << " overuse detections " << num_overuse_detections_ + << " rampup delay " << rampup_delay; return 0; } bool OveruseFrameDetector::IsOverusing() { - if (capture_deltas_.StdDev() >= overuse_stddev_ms_) { + bool overusing = false; + if (options_.enable_capture_jitter_method) { + overusing = capture_deltas_.StdDev() >= + options_.high_capture_jitter_threshold_ms; + } else if (options_.enable_encode_usage_method) { + bool encode_usage_overuse = + encode_usage_->Value() >= options_.high_encode_usage_threshold_percent; + bool encode_rsd_overuse = false; + if (options_.high_encode_time_rsd_threshold > 0) { + encode_rsd_overuse = + (encode_rsd_->Value() >= options_.high_encode_time_rsd_threshold); + } + overusing = encode_usage_overuse || encode_rsd_overuse; + } + + if (overusing) { ++checks_above_threshold_; } else { checks_above_threshold_ = 0; } - - return checks_above_threshold_ >= kConsecutiveChecksAboveThreshold; + return checks_above_threshold_ >= options_.high_threshold_consecutive_count; } bool OveruseFrameDetector::IsUnderusing(int64_t time_now) { @@ -412,6 +584,20 @@ bool OveruseFrameDetector::IsUnderusing(int64_t time_now) { if (time_now < last_rampup_time_ + delay) return false; - return capture_deltas_.StdDev() < normaluse_stddev_ms_; + bool underusing = false; + if (options_.enable_capture_jitter_method) { + underusing = capture_deltas_.StdDev() < + options_.low_capture_jitter_threshold_ms; + } else if (options_.enable_encode_usage_method) { + bool encode_usage_underuse = + encode_usage_->Value() < options_.low_encode_usage_threshold_percent; + bool encode_rsd_underuse = true; + if (options_.low_encode_time_rsd_threshold > 0) { + encode_rsd_underuse = + (encode_rsd_->Value() < options_.low_encode_time_rsd_threshold); + } + underusing = encode_usage_underuse && encode_rsd_underuse; + } + return underusing; } } // namespace webrtc |