Adding possibility to use encoding time when trigger underuse for frame based overuse detection.
BUG=
TEST=Added unittest.
R=asapersson@webrtc.org
Review URL: https://webrtc-codereview.appspot.com/1885004
git-svn-id: http://webrtc.googlecode.com/svn/trunk@4452 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/webrtc/video_engine/overuse_frame_detector.cc b/webrtc/video_engine/overuse_frame_detector.cc
index 61d1506..151a0a2 100644
--- a/webrtc/video_engine/overuse_frame_detector.cc
+++ b/webrtc/video_engine/overuse_frame_detector.cc
@@ -10,24 +10,48 @@
#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 thresholds.
+// 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()) {
+ last_callback_time_(clock->TimeInMilliseconds()),
+ underuse_encode_timing_enabled_(false),
+ num_pixels_(0),
+ max_num_pixels_(0) {
}
OveruseFrameDetector::~OveruseFrameDetector() {
@@ -38,18 +62,30 @@
observer_ = observer;
}
-void OveruseFrameDetector::CapturedFrame() {
+void OveruseFrameDetector::set_underuse_encode_timing_enabled(bool enable) {
CriticalSectionScoped cs(crit_.get());
- CleanOldSamples();
+ underuse_encode_timing_enabled_ = enable;
+}
+
+void OveruseFrameDetector::FrameCaptured() {
+ CriticalSectionScoped cs(crit_.get());
capture_times_.push_back(clock_->TimeInMilliseconds());
}
-void OveruseFrameDetector::EncodedFrame() {
+void OveruseFrameDetector::FrameEncoded(int64_t encode_time, size_t width,
+ size_t height) {
+ assert(encode_time >= 0);
CriticalSectionScoped cs(crit_.get());
- encode_times_.push_back(clock_->TimeInMilliseconds());
+ // 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();
}
@@ -60,40 +96,112 @@
return 0;
last_process_time_ = now;
- if (!observer_ || encode_times_.size() == 0 || capture_times_.size() == 0)
+ RemoveOldSamples();
+
+ // Don't trigger an overuse unless we've encoded at least one frame.
+ if (!observer_ || encode_times_.empty() || capture_times_.empty())
return 0;
- CleanOldSamples();
- if (encode_times_.front() > now - kOveruseHistoryMs / 2) {
+ // 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;
}
- float encode_ratio = encode_times_.size() /
- static_cast<float>(capture_times_.size());
- if (encode_ratio < kMinEncodeRatio) {
+
+ 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();
- capture_times_.clear();
- encode_times_.clear();
last_callback_time_ = now;
- } else if (last_callback_time_ < now - kMinCallbackDeltaMs) {
- // TODO(mflodman) This should only be triggered if we have a good reason to
- // believe we can increase the resolution again.
+ } else if (IsUnderusing(now)) {
+ RemoveAllSamples();
observer_->NormalUsage();
last_callback_time_ = now;
- capture_times_.clear();
- encode_times_.clear();
}
return 0;
}
-void OveruseFrameDetector::CleanOldSamples() {
+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() < time_now - kOveruseHistoryMs) {
+ 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
diff --git a/webrtc/video_engine/overuse_frame_detector.h b/webrtc/video_engine/overuse_frame_detector.h
index e382c7d..7772266 100644
--- a/webrtc/video_engine/overuse_frame_detector.h
+++ b/webrtc/video_engine/overuse_frame_detector.h
@@ -12,6 +12,8 @@
#define WEBRTC_VIDEO_ENGINE_OVERUSE_FRAME_DETECTOR_H_
#include <list>
+#include <map>
+#include <utility>
#include "webrtc/modules/interface/module.h"
#include "webrtc/system_wrappers/interface/constructor_magic.h"
@@ -30,20 +32,39 @@
explicit OveruseFrameDetector(Clock* clock);
~OveruseFrameDetector();
+ // Registers an observer receiving overuse and underuse callbacks. Set
+ // 'observer' to NULL to disable callbacks.
void SetObserver(CpuOveruseObserver* observer);
- // Called for each new captured frame.
- void CapturedFrame();
+ // TODO(mflodman): Move to another API?
+ // Enables usage of encode time to trigger normal usage after an overuse,
+ // default false.
+ void set_underuse_encode_timing_enabled(bool enable);
+
+ // Called for each captured frame.
+ void FrameCaptured();
// Called for every encoded frame.
- void EncodedFrame();
+ void FrameEncoded(int64_t encode_time, size_t width, size_t height);
// Implements Module.
virtual int32_t TimeUntilNextProcess();
virtual int32_t Process();
private:
- void CleanOldSamples();
+ // All private functions are assumed to be critical section protected.
+ // Clear samples older than the overuse history.
+ void RemoveOldSamples();
+ // Clears the entire history, including samples still affecting the
+ // calculations.
+ void RemoveAllSamples();
+ int64_t CalculateAverageEncodeTime() const;
+ // Returns true and resets calculations and history if a new resolution is
+ // discovered, false otherwise.
+ bool MaybeResetResolution(size_t width, size_t height);
+
+ bool IsOverusing();
+ bool IsUnderusing(int64_t time_now);
// Protecting all members.
scoped_ptr<CriticalSectionWrapper> crit_;
@@ -55,11 +76,21 @@
int64_t last_process_time_;
int64_t last_callback_time_;
- // Capture time for frames.
+ // Sorted list of times captured frames were delivered, oldest frame first.
std::list<int64_t> capture_times_;
+ // <Encode report time, time spent encoding the frame>.
+ typedef std::pair<int64_t, int64_t> EncodeTime;
+ // Sorted list with oldest frame first.
+ std::list<EncodeTime> encode_times_;
- // Start encode time for a frame.
- std::list<int64_t> encode_times_;
+ // True if encode time should be considered to trigger an underuse.
+ bool underuse_encode_timing_enabled_;
+ // Number of pixels in the currently encoded resolution.
+ int num_pixels_;
+ // Maximum resolution encoded.
+ int max_num_pixels_;
+ // <number of pixels, average encode time triggering an overuse>.
+ std::map<int, int64_t> encode_overuse_times_;
DISALLOW_COPY_AND_ASSIGN(OveruseFrameDetector);
};
diff --git a/webrtc/video_engine/overuse_frame_detector_unittest.cc b/webrtc/video_engine/overuse_frame_detector_unittest.cc
index 864f1a2..c5398fe 100644
--- a/webrtc/video_engine/overuse_frame_detector_unittest.cc
+++ b/webrtc/video_engine/overuse_frame_detector_unittest.cc
@@ -23,6 +23,9 @@
namespace webrtc {
const int kProcessIntervalMs = 2000;
+const int kOveruseHistoryMs = 5000;
+const int kMinCallbackDeltaMs = 30000;
+const int64_t kMinValidHistoryMs = kOveruseHistoryMs / 2;
class MockCpuOveruseObserver : public CpuOveruseObserver {
public:
@@ -41,6 +44,32 @@
overuse_detector_.reset(new OveruseFrameDetector(clock_.get()));
overuse_detector_->SetObserver(observer_.get());
}
+
+ void CaptureAndEncodeFrames(int num_frames, int64_t frame_interval_ms,
+ int encode_time_ms, size_t width, size_t height) {
+ for (int frame = 0; frame < num_frames; ++frame) {
+ overuse_detector_->FrameCaptured();
+ overuse_detector_->FrameEncoded(encode_time_ms, width, height);
+ clock_->AdvanceTimeMilliseconds(frame_interval_ms);
+ }
+ }
+
+ void CaptureAndEncodeWithOveruse(int overuse_time_ms,
+ int64_t frame_interval_ms,
+ int64_t encode_time_ms, size_t width,
+ size_t height) {
+ // 'encodes_before_dropping' is derived from 'kMinEncodeRatio' in
+ // 'overuse_frame_detector.h'.
+ const int encodes_before_dropping = 14;
+ for (int time_ms = 0; time_ms < overuse_time_ms;
+ time_ms += frame_interval_ms * (1 + encodes_before_dropping)) {
+ CaptureAndEncodeFrames(encodes_before_dropping, frame_interval_ms,
+ encode_time_ms, width, height);
+ overuse_detector_->FrameCaptured();
+ clock_->AdvanceTimeMilliseconds(frame_interval_ms);
+ }
+ }
+
scoped_ptr<SimulatedClock> clock_;
scoped_ptr<MockCpuOveruseObserver> observer_;
scoped_ptr<OveruseFrameDetector> overuse_detector_;
@@ -48,21 +77,139 @@
TEST_F(OveruseFrameDetectorTest, TriggerOveruse) {
EXPECT_EQ(overuse_detector_->TimeUntilNextProcess(), kProcessIntervalMs);
- overuse_detector_->CapturedFrame();
- overuse_detector_->EncodedFrame();
- clock_->AdvanceTimeMilliseconds(kProcessIntervalMs);
+
+ // Enough history to trigger an overuse, but life is good so far.
+ int frame_interval_ms = 33;
+ int num_frames = kMinValidHistoryMs / frame_interval_ms + 1;
+ CaptureAndEncodeFrames(num_frames, frame_interval_ms, 2, 2, 2);
EXPECT_CALL(*(observer_.get()), OveruseDetected()).Times(0);
overuse_detector_->Process();
- overuse_detector_->CapturedFrame();
- clock_->AdvanceTimeMilliseconds(kProcessIntervalMs);
+ // Trigger an overuse.
+ CaptureAndEncodeWithOveruse(kOveruseHistoryMs, frame_interval_ms, 2, 2, 2);
+ EXPECT_CALL(*(observer_.get()), OveruseDetected()).Times(1);
+ overuse_detector_->Process();
+}
+
+TEST_F(OveruseFrameDetectorTest, OveruseAndRecover) {
+ overuse_detector_->set_underuse_encode_timing_enabled(true);
+ // Start with triggering an overuse.
+ // A new resolution will trigger a reset, so add one frame to get going.
+ int frame_interval_ms = 33;
+ CaptureAndEncodeWithOveruse(kMinValidHistoryMs, frame_interval_ms, 2, 2, 2);
EXPECT_CALL(*(observer_.get()), OveruseDetected()).Times(1);
overuse_detector_->Process();
- clock_->AdvanceTimeMilliseconds(5000);
- overuse_detector_->CapturedFrame();
- overuse_detector_->EncodedFrame();
+ // Make everything good again, but don't advance time long enough to trigger
+ // an underuse.
+ int num_frames = kOveruseHistoryMs / frame_interval_ms;
+ CaptureAndEncodeFrames(num_frames, frame_interval_ms, 1, 1, 1);
EXPECT_CALL(*(observer_.get()), OveruseDetected()).Times(0);
overuse_detector_->Process();
+
+ // Advance time long enough to trigger an increase callback.
+ num_frames = (kMinCallbackDeltaMs - kOveruseHistoryMs + 1) /
+ (frame_interval_ms - 0.5f);
+ CaptureAndEncodeFrames(num_frames, frame_interval_ms, 1, 1, 1);
+ EXPECT_CALL(*(observer_.get()), NormalUsage()).Times(1);
+ overuse_detector_->Process();
+}
+
+TEST_F(OveruseFrameDetectorTest, DoubleOveruseAndRecover) {
+ overuse_detector_->set_underuse_encode_timing_enabled(true);
+ // Start with triggering an overuse.
+ int frame_interval_ms = 33;
+ CaptureAndEncodeWithOveruse(kMinValidHistoryMs, frame_interval_ms, 16, 4, 4);
+ EXPECT_CALL(*(observer_.get()), OveruseDetected()).Times(1);
+ overuse_detector_->Process();
+
+ CaptureAndEncodeWithOveruse(kOveruseHistoryMs, frame_interval_ms, 4, 2, 2);
+ EXPECT_CALL(*(observer_.get()), OveruseDetected()).Times(1);
+ overuse_detector_->Process();
+
+ // Let life be good again and wait for an underuse callback.
+ int num_frames = kMinCallbackDeltaMs / (frame_interval_ms - 0.5f);
+ CaptureAndEncodeFrames(num_frames, frame_interval_ms, 1, 1, 1);
+ EXPECT_CALL(*(observer_.get()), NormalUsage()).Times(1);
+ overuse_detector_->Process();
+
+ // And one more.
+ CaptureAndEncodeFrames(num_frames, frame_interval_ms, 4, 2, 2);
+ EXPECT_CALL(*(observer_.get()), NormalUsage()).Times(1);
+ overuse_detector_->Process();
+
+ // But no more since we're at the max resolution.
+ CaptureAndEncodeFrames(num_frames, frame_interval_ms, 4, 4, 4);
+ EXPECT_CALL(*(observer_.get()), NormalUsage()).Times(0);
+ overuse_detector_->Process();
+}
+
+TEST_F(OveruseFrameDetectorTest, OveruseAndNoRecovery) {
+ overuse_detector_->set_underuse_encode_timing_enabled(true);
+ // Start with triggering an overuse.
+ int frame_interval_ms = 33;
+ CaptureAndEncodeWithOveruse(kMinValidHistoryMs, frame_interval_ms, 4, 2, 2);
+ EXPECT_CALL(*(observer_.get()), OveruseDetected()).Times(1);
+ overuse_detector_->Process();
+
+ // Everything is fine, but we haven't waited long enough to trigger an
+ // increase callback.
+ CaptureAndEncodeFrames(30, 33, 3, 1, 1);
+ EXPECT_CALL(*(observer_.get()), OveruseDetected()).Times(0);
+ overuse_detector_->Process();
+
+ // Advance time enough to trigger an increase callback, but encode time
+ // shouldn't have decreased enough to try an increase.
+ int num_frames = kMinCallbackDeltaMs / (frame_interval_ms - 0.5f);
+ CaptureAndEncodeFrames(num_frames, frame_interval_ms, 3, 1, 1);
+ EXPECT_CALL(*(observer_.get()), NormalUsage()).Times(0);
+ overuse_detector_->Process();
+}
+
+TEST_F(OveruseFrameDetectorTest, NoEncodeTimeForUnderuse) {
+ overuse_detector_->set_underuse_encode_timing_enabled(false);
+ // Start with triggering an overuse.
+ int frame_interval_ms = 33;
+ CaptureAndEncodeWithOveruse(kMinValidHistoryMs, frame_interval_ms, 4, 2, 2);
+ EXPECT_CALL(*(observer_.get()), OveruseDetected()).Times(1);
+ overuse_detector_->Process();
+
+ // Everything is fine, but we haven't waited long enough to trigger an
+ // increase callback.
+ int num_frames = 1000 / (frame_interval_ms - 0.5f);
+ CaptureAndEncodeFrames(num_frames, frame_interval_ms, 3, 1, 1);
+ EXPECT_CALL(*(observer_.get()), OveruseDetected()).Times(0);
+ overuse_detector_->Process();
+
+ // Advance time enough to allow underuse, but keep encode time too high to
+ // trigger an underuse if accounted for, see 'OveruseAndNoRecovery' test case.
+ num_frames = kMinCallbackDeltaMs / (frame_interval_ms - 0.5f);
+ CaptureAndEncodeFrames(num_frames, frame_interval_ms, 3, 1, 1);
+ EXPECT_CALL(*(observer_.get()), NormalUsage()).Times(1);
+ overuse_detector_->Process();
+}
+
+TEST_F(OveruseFrameDetectorTest, ResolutionChange) {
+ overuse_detector_->set_underuse_encode_timing_enabled(true);
+ int frame_interval_ms = 33;
+ CaptureAndEncodeWithOveruse(kMinValidHistoryMs / 2, frame_interval_ms, 3, 1,
+ 1);
+
+ // Keep overusing, but with a new resolution.
+ CaptureAndEncodeWithOveruse(kMinValidHistoryMs - frame_interval_ms,
+ frame_interval_ms, 4, 2, 2);
+
+ // Enough samples and time to trigger an overuse, but resolution reset should
+ // prevent this.
+ EXPECT_CALL(*(observer_.get()), OveruseDetected()).Times(0);
+ overuse_detector_->Process();
+
+ // Fill the history.
+ CaptureAndEncodeFrames(2, kOveruseHistoryMs / 2, 3, 1, 1);
+
+ // Capture a frame without finish encoding to trigger an overuse.
+ overuse_detector_->FrameCaptured();
+ EXPECT_CALL(*(observer_.get()), OveruseDetected()).Times(1);
+ overuse_detector_->Process();
}
} // namespace webrtc
diff --git a/webrtc/video_engine/vie_capturer.cc b/webrtc/video_engine/vie_capturer.cc
index 5364a8f..66f3eec 100644
--- a/webrtc/video_engine/vie_capturer.cc
+++ b/webrtc/video_engine/vie_capturer.cc
@@ -16,6 +16,7 @@
#include "webrtc/modules/video_capture/include/video_capture_factory.h"
#include "webrtc/modules/video_processing/main/interface/video_processing.h"
#include "webrtc/modules/video_render/include/video_render_defines.h"
+#include "webrtc/system_wrappers/interface/clock.h"
#include "webrtc/system_wrappers/interface/critical_section_wrapper.h"
#include "webrtc/system_wrappers/interface/event_wrapper.h"
#include "webrtc/system_wrappers/interface/thread_wrapper.h"
@@ -349,7 +350,7 @@
captured_frame_.SwapFrame(&video_frame);
capture_event_.Set();
- overuse_detector_->CapturedFrame();
+ overuse_detector_->FrameCaptured();
return;
}
@@ -513,12 +514,19 @@
if (!captured_frame_.IsZeroSize()) {
// New I420 frame.
capture_cs_->Enter();
- // The frame sent for encoding, update the overuse detector.
- overuse_detector_->EncodedFrame();
deliver_frame_.SwapFrame(&captured_frame_);
captured_frame_.ResetSize();
capture_cs_->Leave();
+
+ int64_t encode_start_time =
+ Clock::GetRealTimeClock()->TimeInMilliseconds();
DeliverI420Frame(&deliver_frame_);
+
+ // The frame has been encoded, update the overuse detector with the
+ // duration.
+ overuse_detector_->FrameEncoded(
+ Clock::GetRealTimeClock()->TimeInMilliseconds() - encode_start_time,
+ deliver_frame_.width(), deliver_frame_.height());
}
deliver_cs_->Leave();
if (current_brightness_level_ != reported_brightness_level_) {