/* * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "webrtc/video_engine/overuse_frame_detector.h" #include #include "webrtc/system_wrappers/interface/clock.h" #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" #include "webrtc/video_engine/include/vie_base.h" namespace webrtc { // TODO(mflodman) Test different values for all of these to trigger correctly, // avoid fluctuations etc. namespace { // Interval for 'Process' to be called. const int64_t kProcessIntervalMs = 2000; // Duration capture and encode samples are valid. const int kOveruseHistoryMs = 5000; // The minimum history to trigger an overuse or underuse. const int64_t kMinValidHistoryMs = kOveruseHistoryMs / 2; // Encode / capture ratio to decide an overuse. const float kMinEncodeRatio = 29 / 30.0f; // Minimum time between two callbacks. const int kMinCallbackDeltaMs = 30000; // Safety margin between encode time for different resolutions to decide if we // can trigger an underuse callback. // TODO(mflodman): This should be improved, e.g. test time per pixel? const float kIncreaseThreshold = 1.5f; } // namespace OveruseFrameDetector::OveruseFrameDetector(Clock* clock) : crit_(CriticalSectionWrapper::CreateCriticalSection()), observer_(NULL), clock_(clock), last_process_time_(clock->TimeInMilliseconds()), last_callback_time_(clock->TimeInMilliseconds()), underuse_encode_timing_enabled_(false), num_pixels_(0), max_num_pixels_(0) { } OveruseFrameDetector::~OveruseFrameDetector() { } void OveruseFrameDetector::SetObserver(CpuOveruseObserver* observer) { CriticalSectionScoped cs(crit_.get()); observer_ = observer; } void OveruseFrameDetector::set_underuse_encode_timing_enabled(bool enable) { CriticalSectionScoped cs(crit_.get()); underuse_encode_timing_enabled_ = enable; } void OveruseFrameDetector::FrameCaptured() { CriticalSectionScoped cs(crit_.get()); capture_times_.push_back(clock_->TimeInMilliseconds()); } void OveruseFrameDetector::FrameEncoded(int64_t encode_time, size_t width, size_t height) { assert(encode_time >= 0); CriticalSectionScoped cs(crit_.get()); // The frame is disregarded in case of a reset, to startup in a fresh state. if (MaybeResetResolution(width, height)) return; encode_times_.push_back(std::make_pair(clock_->TimeInMilliseconds(), encode_time)); } int32_t OveruseFrameDetector::TimeUntilNextProcess() { CriticalSectionScoped cs(crit_.get()); return last_process_time_ + kProcessIntervalMs - clock_->TimeInMilliseconds(); } int32_t OveruseFrameDetector::Process() { CriticalSectionScoped cs(crit_.get()); int64_t now = clock_->TimeInMilliseconds(); if (now < last_process_time_ + kProcessIntervalMs) return 0; last_process_time_ = now; RemoveOldSamples(); // Don't trigger an overuse unless we've encoded at least one frame. if (!observer_ || encode_times_.empty() || capture_times_.empty()) return 0; // At least half the maximum history should be filled before we trigger an // overuse. // TODO(mflodman) Shall the time difference between the first and the last // sample be checked instead? if (encode_times_.front().first > now - kMinValidHistoryMs) { return 0; } if (IsOverusing()) { // Overuse detected. // Remember the average encode time for this overuse, as a help to trigger // normal usage. encode_overuse_times_[num_pixels_] = CalculateAverageEncodeTime(); RemoveAllSamples(); observer_->OveruseDetected(); last_callback_time_ = now; } else if (IsUnderusing(now)) { RemoveAllSamples(); observer_->NormalUsage(); last_callback_time_ = now; } return 0; } void OveruseFrameDetector::RemoveOldSamples() { int64_t time_now = clock_->TimeInMilliseconds(); while (!capture_times_.empty() && capture_times_.front() < time_now - kOveruseHistoryMs) { capture_times_.pop_front(); } while (!encode_times_.empty() && encode_times_.front().first < time_now - kOveruseHistoryMs) { encode_times_.pop_front(); } } void OveruseFrameDetector::RemoveAllSamples() { capture_times_.clear(); encode_times_.clear(); } int64_t OveruseFrameDetector::CalculateAverageEncodeTime() const { if (encode_times_.empty()) return 0; int64_t total_encode_time = 0; for (std::list::const_iterator it = encode_times_.begin(); it != encode_times_.end(); ++it) { total_encode_time += it->second; } return total_encode_time / encode_times_.size(); } bool OveruseFrameDetector::MaybeResetResolution(size_t width, size_t height) { int num_pixels = width * height; if (num_pixels == num_pixels_) return false; RemoveAllSamples(); num_pixels_ = num_pixels; if (num_pixels > max_num_pixels_) max_num_pixels_ = num_pixels; return true; } bool OveruseFrameDetector::IsOverusing() { if (encode_times_.empty()) return false; float encode_ratio = encode_times_.size() / static_cast(capture_times_.size()); return encode_ratio < kMinEncodeRatio; } bool OveruseFrameDetector::IsUnderusing(int64_t time_now) { if (time_now < last_callback_time_ + kMinCallbackDeltaMs || num_pixels_ >= max_num_pixels_) { return false; } bool underusing = true; if (underuse_encode_timing_enabled_) { int prev_overuse_encode_time = 0; for (std::map::reverse_iterator rit = encode_overuse_times_.rbegin(); rit != encode_overuse_times_.rend() && rit->first > num_pixels_; ++rit) { prev_overuse_encode_time = rit->second; } // TODO(mflodman): This might happen now if the resolution is decreased // by the user before an overuse has been triggered. assert(prev_overuse_encode_time > 0); // TODO(mflodman) Use some other way to guess if an increased resolution // might work or not, e.g. encode time per pixel? if (CalculateAverageEncodeTime() * kIncreaseThreshold > prev_overuse_encode_time) { underusing = false; } } return underusing; } } // namespace webrtc