blob: 151a0a2c91a7d761f25504fac4d85d2e4891e242 [file] [log] [blame]
/*
* 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 <assert.h>
#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<EncodeTime>::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<float>(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<int, int64_t>::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