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..b773f40facbb9b594a8ec6dbe4f98ecd208bf309 100644 |
--- a/content/browser/media/capture/animated_content_sampler.cc |
+++ b/content/browser/media/capture/animated_content_sampler.cc |
@@ -41,41 +41,93 @@ 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; |
+ } |
+ |
+ sampling_period_ = ComputeSamplingPeriod(detected_period_, |
hubbe
2015/05/06 19:53:18
Do we need to do this here?
Can we do it after the
miu
2015/05/09 20:57:38
Added a comment explaining this. It could be move
|
+ 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_; |
+ } |
+ } 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_) { |
hubbe
2015/05/06 19:53:18
Correct me if I'm wrong, but it seems like this to
miu
2015/05/09 20:57:38
It doesn't. Both real-world testing and the unit
hubbe
2015/05/12 19:29:03
I think you're right. I was thinking that the prob
miu
2015/05/13 00:01:01
Ah, yes. I little extra shouldn't hurt and should
|
+ 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 +227,52 @@ 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. |
- // |
- // 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); |
+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. |
+ 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; |
hubbe
2015/05/06 19:53:18
Wonder if it would make sense to break out ClockSm
miu
2015/05/09 20:57:38
As discussed face-to-face, I'll consider this for
hubbe
2015/05/12 19:29:03
Optional: Add a TODO?
miu
2015/05/13 00:01:01
Done.
|
+} |
+ |
+// 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 to achieve the target sampling |
+ // period. If so, compute the sampling period such that the sampling rate is |
+ // the closest integer multiple of the animation frame rate. |
+ base::TimeDelta sampling_period; |
+ if (animation_period < target_sampling_period) { |
+ const double target_fps = 1.0 / target_sampling_period.InSecondsF(); |
+ const double animation_fps = 1.0 / animation_period.InSecondsF(); |
+ const int64 ratio = target_sampling_period / animation_period; |
+ if (std::abs(animation_fps / ratio - target_fps) < |
hubbe
2015/05/06 19:53:18
I don't think you need this if statement.
I think
miu
2015/05/09 20:57:38
|ratio| is an int64, so there's an implicit floor'
hubbe
2015/05/12 19:29:03
Ok, I'm officially an idiot, now I've read up on f
miu
2015/05/13 00:01:01
As discussed, our mad algebra and spreadsheet skil
|
+ 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 |