diff options
Diffstat (limited to 'content/browser/media/capture/video_capture_oracle.cc')
-rw-r--r-- | content/browser/media/capture/video_capture_oracle.cc | 170 |
1 files changed, 170 insertions, 0 deletions
diff --git a/content/browser/media/capture/video_capture_oracle.cc b/content/browser/media/capture/video_capture_oracle.cc new file mode 100644 index 0000000..dfe86f4 --- /dev/null +++ b/content/browser/media/capture/video_capture_oracle.cc @@ -0,0 +1,170 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/media/capture/video_capture_oracle.h" + +#include "base/debug/trace_event.h" + +namespace content { + +namespace { + +// This value controls how many redundant, timer-base captures occur when the +// content is static. Redundantly capturing the same frame allows iterative +// quality enhancement, and also allows the buffer to fill in "buffered mode". +// +// TODO(nick): Controlling this here is a hack and a layering violation, since +// it's a strategy specific to the WebRTC consumer, and probably just papers +// over some frame dropping and quality bugs. It should either be controlled at +// a higher level, or else redundant frame generation should be pushed down +// further into the WebRTC encoding stack. +const int kNumRedundantCapturesOfStaticContent = 200; + +} // anonymous namespace + +VideoCaptureOracle::VideoCaptureOracle(base::TimeDelta capture_period, + bool events_are_reliable) + : capture_period_(capture_period), + frame_number_(0), + last_delivered_frame_number_(0), + sampler_(capture_period_, + events_are_reliable, + kNumRedundantCapturesOfStaticContent) {} + +bool VideoCaptureOracle::ObserveEventAndDecideCapture( + Event event, + base::TimeTicks event_time) { + // Record |event| and decide whether it's a good time to capture. + const bool content_is_dirty = (event == kCompositorUpdate || + event == kSoftwarePaint); + bool should_sample; + if (content_is_dirty) { + frame_number_++; + should_sample = sampler_.AddEventAndConsiderSampling(event_time); + } else { + should_sample = sampler_.IsOverdueForSamplingAt(event_time); + } + return should_sample; +} + +int VideoCaptureOracle::RecordCapture() { + sampler_.RecordSample(); + return frame_number_; +} + +bool VideoCaptureOracle::CompleteCapture(int frame_number, + base::TimeTicks timestamp) { + // Drop frame if previous frame number is higher or we're trying to deliver + // a frame with the same timestamp. + if (last_delivered_frame_number_ > frame_number || + last_delivered_frame_timestamp_ == timestamp) { + LOG(ERROR) << "Frame with same timestamp or out of order delivery. " + << "Dropping frame."; + return false; + } + + if (last_delivered_frame_timestamp_ > timestamp) { + // We should not get here unless time was adjusted backwards. + LOG(ERROR) << "Frame with past timestamp (" << timestamp.ToInternalValue() + << ") was delivered"; + } + + last_delivered_frame_number_ = frame_number; + last_delivered_frame_timestamp_ = timestamp; + + return true; +} + +SmoothEventSampler::SmoothEventSampler(base::TimeDelta capture_period, + bool events_are_reliable, + int redundant_capture_goal) + : events_are_reliable_(events_are_reliable), + capture_period_(capture_period), + redundant_capture_goal_(redundant_capture_goal), + token_bucket_capacity_(capture_period + capture_period / 2), + overdue_sample_count_(0), + token_bucket_(token_bucket_capacity_) { + DCHECK_GT(capture_period_.InMicroseconds(), 0); +} + +bool SmoothEventSampler::AddEventAndConsiderSampling( + base::TimeTicks event_time) { + DCHECK(!event_time.is_null()); + + // Add tokens to the bucket based on advancement in time. Then, re-bound the + // number of tokens in the bucket. Overflow occurs when there is too much + // time between events (a common case), or when RecordSample() is not being + // called often enough (a bug). On the other hand, if RecordSample() is being + // called too often (e.g., as a reaction to IsOverdueForSamplingAt()), the + // bucket will underflow. + if (!current_event_.is_null()) { + if (current_event_ < event_time) { + token_bucket_ += event_time - current_event_; + if (token_bucket_ > token_bucket_capacity_) + token_bucket_ = token_bucket_capacity_; + } + // Side note: If the system clock is reset, causing |current_event_| to be + // greater than |event_time|, everything here will simply gracefully adjust. + if (token_bucket_ < base::TimeDelta()) + token_bucket_ = base::TimeDelta(); + TRACE_COUNTER1("mirroring", + "MirroringTokenBucketUsec", token_bucket_.InMicroseconds()); + } + current_event_ = event_time; + + // Return true if one capture period's worth of tokens are in the bucket. + return token_bucket_ >= capture_period_; +} + +void SmoothEventSampler::RecordSample() { + token_bucket_ -= capture_period_; + TRACE_COUNTER1("mirroring", + "MirroringTokenBucketUsec", token_bucket_.InMicroseconds()); + + bool was_paused = overdue_sample_count_ == redundant_capture_goal_; + if (HasUnrecordedEvent()) { + last_sample_ = current_event_; + overdue_sample_count_ = 0; + } else { + ++overdue_sample_count_; + } + bool is_paused = overdue_sample_count_ == redundant_capture_goal_; + + VLOG_IF(0, !was_paused && is_paused) + << "Tab content unchanged for " << redundant_capture_goal_ + << " frames; capture will halt until content changes."; + VLOG_IF(0, was_paused && !is_paused) + << "Content changed; capture will resume."; +} + +bool SmoothEventSampler::IsOverdueForSamplingAt(base::TimeTicks event_time) + const { + DCHECK(!event_time.is_null()); + + // If we don't get events on compositor updates on this platform, then we + // don't reliably know whether we're dirty. + if (events_are_reliable_) { + if (!HasUnrecordedEvent() && + overdue_sample_count_ >= redundant_capture_goal_) { + return false; // Not dirty. + } + } + + if (last_sample_.is_null()) + return true; + + // If we're dirty but not yet old, then we've recently gotten updates, so we + // won't request a sample just yet. + base::TimeDelta dirty_interval = event_time - last_sample_; + if (dirty_interval < capture_period_ * 4) + return false; + else + return true; +} + +bool SmoothEventSampler::HasUnrecordedEvent() const { + return !current_event_.is_null() && current_event_ != last_sample_; +} + +} // namespace content |