diff --git a/webrtc/video_engine/include/vie_base.h b/webrtc/video_engine/include/vie_base.h index 47fc0a791..486eca63d 100644 --- a/webrtc/video_engine/include/vie_base.h +++ b/webrtc/video_engine/include/vie_base.h @@ -63,16 +63,18 @@ const float kNormalUseStdDevMs = 20.0f; struct CpuOveruseOptions { CpuOveruseOptions() - : enable_capture_jitter_method(true), - low_capture_jitter_threshold_ms(kNormalUseStdDevMs), - high_capture_jitter_threshold_ms(kOveruseStdDevMs), - enable_encode_usage_method(false), - low_encode_usage_threshold_percent(60), - high_encode_usage_threshold_percent(90), - frame_timeout_interval_ms(1500), - min_frame_samples(120), - min_process_count(3), - high_threshold_consecutive_count(2) {} + : enable_capture_jitter_method(true), + low_capture_jitter_threshold_ms(kNormalUseStdDevMs), + high_capture_jitter_threshold_ms(kOveruseStdDevMs), + enable_encode_usage_method(false), + low_encode_usage_threshold_percent(60), + high_encode_usage_threshold_percent(90), + low_encode_time_rsd_threshold(0), + high_encode_time_rsd_threshold(0), + frame_timeout_interval_ms(1500), + min_frame_samples(120), + min_process_count(3), + high_threshold_consecutive_count(2) {} // Method based on inter-arrival jitter of captured frames. bool enable_capture_jitter_method; @@ -82,6 +84,8 @@ struct CpuOveruseOptions { bool enable_encode_usage_method; int low_encode_usage_threshold_percent; // Threshold for triggering underuse. int high_encode_usage_threshold_percent; // Threshold for triggering overuse. + int low_encode_time_rsd_threshold; // Threshold for triggering underuse. + int high_encode_time_rsd_threshold; // Threshold for triggering overuse. // General settings. int frame_timeout_interval_ms; // The maximum allowed interval between two // frames before resetting estimations. @@ -102,6 +106,8 @@ struct CpuOveruseOptions { o.low_encode_usage_threshold_percent && high_encode_usage_threshold_percent == o.high_encode_usage_threshold_percent && + low_encode_time_rsd_threshold == o.low_encode_time_rsd_threshold && + high_encode_time_rsd_threshold == o.high_encode_time_rsd_threshold && frame_timeout_interval_ms == o.frame_timeout_interval_ms && min_frame_samples == o.min_frame_samples && min_process_count == o.min_process_count && @@ -111,16 +117,18 @@ struct CpuOveruseOptions { struct CpuOveruseMetrics { CpuOveruseMetrics() - : capture_jitter_ms(-1), - avg_encode_time_ms(-1), - encode_usage_percent(-1), - capture_queue_delay_ms_per_s(-1) {} + : capture_jitter_ms(-1), + avg_encode_time_ms(-1), + encode_usage_percent(-1), + encode_rsd(-1), + capture_queue_delay_ms_per_s(-1) {} int capture_jitter_ms; // The current estimated jitter in ms based on // incoming captured frames. int avg_encode_time_ms; // The average encode time in ms. int encode_usage_percent; // The average encode time divided by the average // time difference between incoming captured frames. + int encode_rsd; // The relative std dev of encode time of frames. int capture_queue_delay_ms_per_s; // The current time delay between an // incoming captured frame until the frame // is being processed. The delay is diff --git a/webrtc/video_engine/overuse_frame_detector.cc b/webrtc/video_engine/overuse_frame_detector.cc index c136130b9..4a9510337 100644 --- a/webrtc/video_engine/overuse_frame_detector.cc +++ b/webrtc/video_engine/overuse_frame_detector.cc @@ -15,6 +15,7 @@ #include #include +#include #include "webrtc/modules/video_coding/utility/include/exp_filter.h" #include "webrtc/system_wrappers/interface/clock.h" @@ -206,6 +207,111 @@ class OveruseFrameDetector::EncodeUsage { scoped_ptr 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(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(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::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(diff_last_process_ms) / kProcessIntervalMs; + exp = std::min(exp, kMaxExp); + filtered_rsd_->Apply(exp, 100.0f * cov); + } + + int Value() const { + return static_cast(filtered_rsd_->Value() + 0.5); + } + + float InitialValue() const { + // Start in between the underuse and overuse threshold. + return (options_.low_encode_time_rsd_threshold + + options_.high_encode_time_rsd_threshold) / 2.0f; + } + + private: + const float kWeightFactor; + uint32_t count_; // Number of encode samples since last reset. + CpuOveruseOptions options_; + scoped_ptr filtered_rsd_; + int hist_samples_; + float hist_sum_; + std::map 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: @@ -278,6 +384,7 @@ OveruseFrameDetector::OveruseFrameDetector(Clock* clock) num_pixels_(0), last_encode_sample_ms_(0), encode_time_(new EncodeTimeAvg()), + encode_rsd_(new EncodeTimeRsd(clock)), encode_usage_(new EncodeUsage()), capture_queue_delay_(new CaptureQueueDelay()) { } @@ -299,6 +406,7 @@ void OveruseFrameDetector::SetOptions(const CpuOveruseOptions& options) { options_ = options; capture_deltas_.SetOptions(options); encode_usage_->SetOptions(options); + encode_rsd_->SetOptions(options); ResetAll(num_pixels_); } @@ -312,6 +420,7 @@ void OveruseFrameDetector::GetCpuOveruseMetrics( CriticalSectionScoped cs(crit_.get()); metrics->capture_jitter_ms = static_cast(capture_deltas_.StdDev() + 0.5); metrics->avg_encode_time_ms = encode_time_->filtered_encode_time_ms(); + metrics->encode_rsd = encode_rsd_->Value(); metrics->encode_usage_percent = encode_usage_->UsageInPercent(); metrics->capture_queue_delay_ms_per_s = capture_queue_delay_->filtered_delay_ms_per_s(); @@ -340,6 +449,7 @@ 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; @@ -374,6 +484,7 @@ 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; } @@ -391,6 +502,7 @@ int32_t OveruseFrameDetector::Process() { next_process_time_ = now + kProcessIntervalMs; ++num_process_times_; + encode_rsd_->Process(now); capture_queue_delay_->CalculateDelayChange(diff_ms); if (num_process_times_ <= options_.min_process_count) { @@ -430,11 +542,11 @@ int32_t OveruseFrameDetector::Process() { int rampup_delay = in_quick_rampup_ ? kQuickRampUpDelayMs : current_rampup_delay_ms_; - LOG(LS_VERBOSE) << "Capture input stats: avg: " << capture_deltas_.Mean() - << " std_dev " << capture_deltas_.StdDev() - << " rampup delay " << rampup_delay - << " overuse >= " << options_.high_capture_jitter_threshold_ms - << " underuse < " << options_.low_capture_jitter_threshold_ms; + LOG(LS_INFO) << " Frame stats: capture avg: " << capture_deltas_.Mean() + << " capture stddev " << capture_deltas_.StdDev() + << " encode usage " << encode_usage_->UsageInPercent() + << " encode rsd " << encode_rsd_->Value() + << " rampup delay " << rampup_delay; return 0; } diff --git a/webrtc/video_engine/overuse_frame_detector.h b/webrtc/video_engine/overuse_frame_detector.h index 38b927bae..ef6d6aba2 100644 --- a/webrtc/video_engine/overuse_frame_detector.h +++ b/webrtc/video_engine/overuse_frame_detector.h @@ -95,6 +95,7 @@ class OveruseFrameDetector : public Module { private: class EncodeTimeAvg; + class EncodeTimeRsd; class EncodeUsage; class CaptureQueueDelay; @@ -133,6 +134,7 @@ class OveruseFrameDetector : public Module { int64_t last_encode_sample_ms_; scoped_ptr encode_time_; + scoped_ptr encode_rsd_; scoped_ptr encode_usage_; scoped_ptr capture_queue_delay_;