| Index: content/browser/media/capture/animated_content_sampler.cc
|
| diff --git a/content/browser/media/capture/animated_content_sampler.cc b/content/browser/media/capture/animated_content_sampler.cc
|
| index a30364b0e97d215acfc836442698112180b452a0..8727e762ed78076d13eb74a952c8cdfbc2d35730 100644
|
| --- a/content/browser/media/capture/animated_content_sampler.cc
|
| +++ b/content/browser/media/capture/animated_content_sampler.cc
|
| @@ -41,41 +41,101 @@ const int kDriftCorrectionMillis = 2000;
|
|
|
| AnimatedContentSampler::AnimatedContentSampler(
|
| base::TimeDelta min_capture_period)
|
| - : min_capture_period_(min_capture_period) {}
|
| + : min_capture_period_(min_capture_period),
|
| + sampling_state_(NOT_SAMPLING) {
|
| + DCHECK_GT(min_capture_period_, base::TimeDelta());
|
| +}
|
|
|
| AnimatedContentSampler::~AnimatedContentSampler() {}
|
|
|
| +void AnimatedContentSampler::SetTargetSamplingPeriod(base::TimeDelta period) {
|
| + target_sampling_period_ = period;
|
| +}
|
| +
|
| void AnimatedContentSampler::ConsiderPresentationEvent(
|
| const gfx::Rect& damage_rect, base::TimeTicks event_time) {
|
| + // Analyze the current event and recent history to determine whether animating
|
| + // content is detected.
|
| AddObservation(damage_rect, event_time);
|
| -
|
| - if (AnalyzeObservations(event_time, &detected_region_, &detected_period_) &&
|
| - detected_period_ > base::TimeDelta() &&
|
| - detected_period_ <=
|
| + if (!AnalyzeObservations(event_time, &detected_region_, &detected_period_) ||
|
| + detected_period_ <= base::TimeDelta() ||
|
| + detected_period_ >
|
| base::TimeDelta::FromMicroseconds(kMaxLockInPeriodMicros)) {
|
| - if (damage_rect == detected_region_)
|
| - UpdateFrameTimestamp(event_time);
|
| - else
|
| - frame_timestamp_ = base::TimeTicks();
|
| - } else {
|
| + // Animated content not detected.
|
| detected_region_ = gfx::Rect();
|
| detected_period_ = base::TimeDelta();
|
| - frame_timestamp_ = base::TimeTicks();
|
| + sampling_state_ = NOT_SAMPLING;
|
| + return;
|
| + }
|
| +
|
| + // At this point, animation is being detected. Update the sampling period
|
| + // since the client may call the accessor method even if the heuristics below
|
| + // decide not to sample the current event.
|
| + sampling_period_ = ComputeSamplingPeriod(detected_period_,
|
| + target_sampling_period_,
|
| + min_capture_period_);
|
| +
|
| + // If this is the first event causing animating content to be detected,
|
| + // transition to the START_SAMPLING state.
|
| + if (sampling_state_ == NOT_SAMPLING)
|
| + sampling_state_ = START_SAMPLING;
|
| +
|
| + // If the current event does not represent a frame that is part of the
|
| + // animation, do not sample.
|
| + if (damage_rect != detected_region_) {
|
| + if (sampling_state_ == SHOULD_SAMPLE)
|
| + sampling_state_ = SHOULD_NOT_SAMPLE;
|
| + return;
|
| + }
|
| +
|
| + // When starting sampling, determine where to sync-up for sampling and frame
|
| + // timestamp rewriting. Otherwise, just add one animation period's worth of
|
| + // tokens to the token bucket.
|
| + if (sampling_state_ == START_SAMPLING) {
|
| + if (event_time - frame_timestamp_ > sampling_period_) {
|
| + // The frame timestamp sequence should start with the current event
|
| + // time.
|
| + frame_timestamp_ = event_time - sampling_period_;
|
| + token_bucket_ = sampling_period_;
|
| + } else {
|
| + // The frame timestamp sequence will continue from the last recorded
|
| + // frame timestamp.
|
| + token_bucket_ = event_time - frame_timestamp_;
|
| + }
|
| +
|
| + // Provide a little extra in the initial token bucket so that minor error in
|
| + // the detected period won't prevent a reasonably-timed event from being
|
| + // sampled.
|
| + token_bucket_ += detected_period_ / 2;
|
| + } else {
|
| + token_bucket_ += detected_period_;
|
| + }
|
| +
|
| + // If the token bucket is full enough, take tokens from it and propose
|
| + // sampling. Otherwise, do not sample.
|
| + DCHECK_LE(detected_period_, sampling_period_);
|
| + if (token_bucket_ >= sampling_period_) {
|
| + token_bucket_ -= sampling_period_;
|
| + frame_timestamp_ = ComputeNextFrameTimestamp(event_time);
|
| + sampling_state_ = SHOULD_SAMPLE;
|
| + } else {
|
| + sampling_state_ = SHOULD_NOT_SAMPLE;
|
| }
|
| }
|
|
|
| bool AnimatedContentSampler::HasProposal() const {
|
| - return detected_period_ > base::TimeDelta();
|
| + return sampling_state_ != NOT_SAMPLING;
|
| }
|
|
|
| bool AnimatedContentSampler::ShouldSample() const {
|
| - return !frame_timestamp_.is_null();
|
| + return sampling_state_ == SHOULD_SAMPLE;
|
| }
|
|
|
| void AnimatedContentSampler::RecordSample(base::TimeTicks frame_timestamp) {
|
| - recorded_frame_timestamp_ =
|
| - HasProposal() ? frame_timestamp : base::TimeTicks();
|
| - sequence_offset_ = base::TimeDelta();
|
| + if (sampling_state_ == NOT_SAMPLING)
|
| + frame_timestamp_ = frame_timestamp;
|
| + else if (sampling_state_ == SHOULD_SAMPLE)
|
| + sampling_state_ = SHOULD_NOT_SAMPLE;
|
| }
|
|
|
| void AnimatedContentSampler::AddObservation(const gfx::Rect& damage_rect,
|
| @@ -175,48 +235,59 @@ bool AnimatedContentSampler::AnalyzeObservations(
|
| return true;
|
| }
|
|
|
| -void AnimatedContentSampler::UpdateFrameTimestamp(base::TimeTicks event_time) {
|
| - // This is how much time to advance from the last frame timestamp. Never
|
| - // advance by less than |min_capture_period_| because the downstream consumer
|
| - // cannot handle the higher frame rate. If |detected_period_| is less than
|
| - // |min_capture_period_|, excess frames should be dropped.
|
| - const base::TimeDelta advancement =
|
| - std::max(detected_period_, min_capture_period_);
|
| -
|
| - // Compute the |timebase| upon which to determine the |frame_timestamp_|.
|
| - // Ideally, this would always equal the timestamp of the last recorded frame
|
| - // sampling. Determine how much drift from the ideal is present, then adjust
|
| - // the timebase by a small amount to spread out the entire correction over
|
| - // many frame timestamps.
|
| +base::TimeTicks AnimatedContentSampler::ComputeNextFrameTimestamp(
|
| + base::TimeTicks event_time) const {
|
| + // The ideal next frame timestamp one sampling period since the last one.
|
| + const base::TimeTicks ideal_timestamp = frame_timestamp_ + sampling_period_;
|
| +
|
| + // Account for two main sources of drift: 1) The clock drift of the system
|
| + // clock relative to the video hardware, which affects the event times; and
|
| + // 2) The small error introduced by this frame timestamp rewriting, as it is
|
| + // based on averaging over recent events.
|
| //
|
| - // This accounts for two main sources of drift: 1) The clock drift of the
|
| - // system clock relative to the video hardware, which affects the event times;
|
| - // and 2) The small error introduced by this frame timestamp rewriting, as it
|
| - // is based on averaging over recent events.
|
| - base::TimeTicks timebase = event_time - sequence_offset_ - advancement;
|
| - if (!recorded_frame_timestamp_.is_null()) {
|
| - const base::TimeDelta drift = recorded_frame_timestamp_ - timebase;
|
| - const int64 correct_over_num_frames =
|
| - base::TimeDelta::FromMilliseconds(kDriftCorrectionMillis) /
|
| - detected_period_;
|
| - DCHECK_GT(correct_over_num_frames, 0);
|
| - timebase = recorded_frame_timestamp_ - (drift / correct_over_num_frames);
|
| + // TODO(miu): This is similar to the ClockSmoother in
|
| + // media/base/audio_shifter.cc. Consider refactor-and-reuse here.
|
| + const base::TimeDelta drift = ideal_timestamp - event_time;
|
| + const int64 correct_over_num_frames =
|
| + base::TimeDelta::FromMilliseconds(kDriftCorrectionMillis) /
|
| + sampling_period_;
|
| + DCHECK_GT(correct_over_num_frames, 0);
|
| +
|
| + return ideal_timestamp - drift / correct_over_num_frames;
|
| +}
|
| +
|
| +// static
|
| +base::TimeDelta AnimatedContentSampler::ComputeSamplingPeriod(
|
| + base::TimeDelta animation_period,
|
| + base::TimeDelta target_sampling_period,
|
| + base::TimeDelta min_capture_period) {
|
| + // If the animation rate is unknown, return the ideal sampling period.
|
| + if (animation_period == base::TimeDelta()) {
|
| + return std::max(target_sampling_period, min_capture_period);
|
| }
|
|
|
| - // Compute |frame_timestamp_|. Whenever |detected_period_| is less than
|
| - // |min_capture_period_|, some extra time is "borrowed" to be able to advance
|
| - // by the full |min_capture_period_|. Then, whenever the total amount of
|
| - // borrowed time reaches a full |min_capture_period_|, drop a frame. Note
|
| - // that when |detected_period_| is greater or equal to |min_capture_period_|,
|
| - // this logic is effectively disabled.
|
| - borrowed_time_ += advancement - detected_period_;
|
| - if (borrowed_time_ >= min_capture_period_) {
|
| - borrowed_time_ -= min_capture_period_;
|
| - frame_timestamp_ = base::TimeTicks();
|
| + // Determine whether subsampling is needed. If so, compute the sampling
|
| + // period corresponding to the sampling rate is the closest integer division
|
| + // of the animation frame rate to the target sampling rate.
|
| + //
|
| + // For example, consider a target sampling rate of 30 FPS and an animation
|
| + // rate of 42 FPS. Possible sampling rates would be 42/1 = 42, 42/2 = 21,
|
| + // 42/3 = 14, and so on. Of these candidates, 21 FPS is closest to 30.
|
| + base::TimeDelta sampling_period;
|
| + if (animation_period < target_sampling_period) {
|
| + const int64 ratio = target_sampling_period / animation_period;
|
| + const double target_fps = 1.0 / target_sampling_period.InSecondsF();
|
| + const double animation_fps = 1.0 / animation_period.InSecondsF();
|
| + if (std::abs(animation_fps / ratio - target_fps) <
|
| + std::abs(animation_fps / (ratio + 1) - target_fps)) {
|
| + sampling_period = ratio * animation_period;
|
| + } else {
|
| + sampling_period = (ratio + 1) * animation_period;
|
| + }
|
| } else {
|
| - sequence_offset_ += advancement;
|
| - frame_timestamp_ = timebase + sequence_offset_;
|
| + sampling_period = animation_period;
|
| }
|
| + return std::max(sampling_period, min_capture_period);
|
| }
|
|
|
| } // namespace content
|
|
|