| Index: media/filters/video_cadence_estimator.cc
|
| diff --git a/media/filters/video_cadence_estimator.cc b/media/filters/video_cadence_estimator.cc
|
| index 54146598459b196dd63f3a89d633bdad0747c43d..a328abd491a634d9b0d79efa462e8ac68758e670 100644
|
| --- a/media/filters/video_cadence_estimator.cc
|
| +++ b/media/filters/video_cadence_estimator.cc
|
| @@ -5,7 +5,9 @@
|
| #include "media/filters/video_cadence_estimator.h"
|
|
|
| #include <algorithm>
|
| +#include <iterator>
|
| #include <limits>
|
| +#include <string>
|
|
|
| #include "base/metrics/histogram_macros.h"
|
|
|
| @@ -24,10 +26,10 @@ static void HistogramCadenceChangeCount(int cadence_changes) {
|
| }
|
|
|
| VideoCadenceEstimator::VideoCadenceEstimator(
|
| - base::TimeDelta minimum_time_until_glitch)
|
| + base::TimeDelta minimum_time_until_max_drift)
|
| : cadence_hysteresis_threshold_(
|
| base::TimeDelta::FromMilliseconds(kMinimumCadenceDurationMs)),
|
| - minimum_time_until_glitch_(minimum_time_until_glitch) {
|
| + minimum_time_until_max_drift_(minimum_time_until_max_drift) {
|
| Reset();
|
| }
|
|
|
| @@ -35,8 +37,8 @@ VideoCadenceEstimator::~VideoCadenceEstimator() {
|
| }
|
|
|
| void VideoCadenceEstimator::Reset() {
|
| - cadence_ = fractional_cadence_ = 0;
|
| - pending_cadence_ = pending_fractional_cadence_ = 0;
|
| + cadence_.clear();
|
| + pending_cadence_.clear();
|
| cadence_changes_ = render_intervals_cadence_held_ = 0;
|
| first_update_call_ = true;
|
| }
|
| @@ -48,21 +50,12 @@ bool VideoCadenceEstimator::UpdateCadenceEstimate(
|
| DCHECK_GT(render_interval, base::TimeDelta());
|
| DCHECK_GT(frame_duration, base::TimeDelta());
|
|
|
| - base::TimeDelta time_until_cadence_glitch;
|
| - base::TimeDelta time_until_fractional_cadence_glitch;
|
| -
|
| - // See if the clamped cadence fits acceptable thresholds for exhausting drift.
|
| - int new_cadence = 0, new_fractional_cadence = 0;
|
| - if (CalculateCadence(render_interval, frame_duration, max_acceptable_drift,
|
| - false, &new_cadence, &time_until_cadence_glitch)) {
|
| - DCHECK(new_cadence);
|
| - } else if (CalculateCadence(render_interval, frame_duration,
|
| - max_acceptable_drift, true,
|
| - &new_fractional_cadence,
|
| - &time_until_fractional_cadence_glitch)) {
|
| - new_cadence = 1;
|
| - DCHECK(new_fractional_cadence);
|
| - }
|
| + base::TimeDelta time_until_max_drift;
|
| +
|
| + // See if we can find a cadence which fits the data.
|
| + Cadence new_cadence =
|
| + CalculateCadence(render_interval, frame_duration, max_acceptable_drift,
|
| + &time_until_max_drift);
|
|
|
| // If this is the first time UpdateCadenceEstimate() has been called,
|
| // initialize the histogram with a zero count for cadence changes; this
|
| @@ -73,9 +66,8 @@ bool VideoCadenceEstimator::UpdateCadenceEstimate(
|
| HistogramCadenceChangeCount(0);
|
| }
|
|
|
| - // Nothing changed, so do nothing.
|
| - if (new_cadence == cadence_ &&
|
| - new_fractional_cadence == fractional_cadence_) {
|
| + // If nothing changed, do nothing.
|
| + if (new_cadence == cadence_) {
|
| // Clear cadence hold to pending values from accumulating incorrectly.
|
| render_intervals_cadence_held_ = 0;
|
| return false;
|
| @@ -84,18 +76,14 @@ bool VideoCadenceEstimator::UpdateCadenceEstimate(
|
| // Wait until enough render intervals have elapsed before accepting the
|
| // cadence change. Prevents oscillation of the cadence selection.
|
| bool update_pending_cadence = true;
|
| - if ((new_cadence == pending_cadence_ &&
|
| - new_fractional_cadence == pending_fractional_cadence_) ||
|
| + if (new_cadence == pending_cadence_ ||
|
| cadence_hysteresis_threshold_ <= render_interval) {
|
| if (++render_intervals_cadence_held_ * render_interval >=
|
| cadence_hysteresis_threshold_) {
|
| - DVLOG(1) << "Cadence switch: (" << cadence_ << ", " << fractional_cadence_
|
| - << ") -> (" << new_cadence << ", " << new_fractional_cadence
|
| - << ") :: (" << time_until_cadence_glitch << ", "
|
| - << time_until_fractional_cadence_glitch << ")";
|
| -
|
| - cadence_ = new_cadence;
|
| - fractional_cadence_ = new_fractional_cadence;
|
| + DVLOG(1) << "Cadence switch: " << CadenceToString(cadence_) << " -> "
|
| + << CadenceToString(new_cadence)
|
| + << " :: Time until drift exceeded: " << time_until_max_drift;
|
| + cadence_.swap(new_cadence);
|
|
|
| // Note: Because this class is transitively owned by a garbage collected
|
| // object, WebMediaPlayer, we log cadence changes as they are encountered.
|
| @@ -106,86 +94,161 @@ bool VideoCadenceEstimator::UpdateCadenceEstimate(
|
| update_pending_cadence = false;
|
| }
|
|
|
| - DVLOG(2) << "Hysteresis prevented cadence switch: (" << cadence_ << ", "
|
| - << fractional_cadence_ << ") -> (" << new_cadence << ", "
|
| - << new_fractional_cadence << ") :: (" << time_until_cadence_glitch
|
| - << ", " << time_until_fractional_cadence_glitch << ")";
|
| + DVLOG(2) << "Hysteresis prevented cadence switch: "
|
| + << CadenceToString(cadence_) << " -> "
|
| + << CadenceToString(new_cadence);
|
|
|
| if (update_pending_cadence) {
|
| - pending_cadence_ = new_cadence;
|
| - pending_fractional_cadence_ = new_fractional_cadence;
|
| + pending_cadence_.swap(new_cadence);
|
| render_intervals_cadence_held_ = 1;
|
| }
|
|
|
| return false;
|
| }
|
|
|
| -bool VideoCadenceEstimator::CalculateCadence(
|
| +int VideoCadenceEstimator::GetCadenceForFrame(uint64_t frame_number) const {
|
| + DCHECK(has_cadence());
|
| + return cadence_[frame_number % cadence_.size()];
|
| +}
|
| +
|
| +VideoCadenceEstimator::Cadence VideoCadenceEstimator::CalculateCadence(
|
| + base::TimeDelta render_interval,
|
| + base::TimeDelta frame_duration,
|
| + base::TimeDelta max_acceptable_drift,
|
| + base::TimeDelta* time_until_max_drift) const {
|
| + // See if we can find a cadence which fits the data.
|
| + Cadence result;
|
| + if (CalculateOneFrameCadence(render_interval, frame_duration,
|
| + max_acceptable_drift, &result,
|
| + time_until_max_drift)) {
|
| + DCHECK_EQ(1u, result.size());
|
| + } else if (CalculateFractionalCadence(render_interval, frame_duration,
|
| + max_acceptable_drift, &result,
|
| + time_until_max_drift)) {
|
| + DCHECK(!result.empty());
|
| + } else if (CalculateOneFrameCadence(render_interval, frame_duration * 2,
|
| + max_acceptable_drift, &result,
|
| + time_until_max_drift)) {
|
| + // By finding cadence for double the frame duration, we're saying there
|
| + // exist two integers a and b, where a > b and a + b = |result|; this
|
| + // matches all patterns which regularly have half a frame per render
|
| + // interval; i.e. 24fps in 60hz.
|
| + DCHECK_EQ(1u, result.size());
|
| +
|
| + // Two pattern cadence is always an odd number.
|
| + DCHECK(result[0] & 1);
|
| +
|
| + result[0] = std::ceil(result[0] / 2.0);
|
| + result.push_back(result[0] - 1);
|
| + }
|
| + return result;
|
| +}
|
| +
|
| +bool VideoCadenceEstimator::CalculateOneFrameCadence(
|
| base::TimeDelta render_interval,
|
| base::TimeDelta frame_duration,
|
| base::TimeDelta max_acceptable_drift,
|
| - bool fractional,
|
| - int* cadence,
|
| - base::TimeDelta* time_until_glitch) {
|
| + Cadence* cadence,
|
| + base::TimeDelta* time_until_max_drift) const {
|
| + DCHECK(cadence->empty());
|
| +
|
| // The perfect cadence is the number of render intervals per frame, while the
|
| - // clamped cadence is the nearest matching integer cadence.
|
| - //
|
| - // Fractional cadence is checked to see if we have a cadence which would look
|
| - // best if we consistently drop the same frames.
|
| + // clamped cadence is the nearest matching integer value.
|
| //
|
| // As mentioned in the introduction, |perfect_cadence| is the ratio of the
|
| // frame duration to render interval length; while |clamped_cadence| is the
|
| - // nearest integer value to |perfect_cadence|. When computing a fractional
|
| - // cadence (1/|perfect_cadence|), |fractional| must be set to true to ensure
|
| - // the rendered and actual frame durations are computed correctly.
|
| + // nearest integer value to |perfect_cadence|.
|
| const double perfect_cadence =
|
| - fractional ? render_interval.InSecondsF() / frame_duration.InSecondsF()
|
| - : frame_duration.InSecondsF() / render_interval.InSecondsF();
|
| + frame_duration.InSecondsF() / render_interval.InSecondsF();
|
| const int clamped_cadence = perfect_cadence + 0.5;
|
| if (!clamped_cadence)
|
| return false;
|
|
|
| - // Calculate the drift in microseconds for each frame we render at cadence
|
| - // instead of for its real duration.
|
| + // For cadence based rendering the actual frame duration is just the frame
|
| + // duration, while the |rendered_frame_duration| is how long the frame would
|
| + // be displayed for if we rendered it |clamped_cadence| times.
|
| const base::TimeDelta rendered_frame_duration =
|
| - fractional ? render_interval : clamped_cadence * render_interval;
|
| + clamped_cadence * render_interval;
|
| + if (!IsAcceptableCadence(rendered_frame_duration, frame_duration,
|
| + max_acceptable_drift, time_until_max_drift)) {
|
| + return false;
|
| + }
|
| +
|
| + cadence->push_back(clamped_cadence);
|
| + return true;
|
| +}
|
| +
|
| +bool VideoCadenceEstimator::CalculateFractionalCadence(
|
| + base::TimeDelta render_interval,
|
| + base::TimeDelta frame_duration,
|
| + base::TimeDelta max_acceptable_drift,
|
| + Cadence* cadence,
|
| + base::TimeDelta* time_until_max_drift) const {
|
| + DCHECK(cadence->empty());
|
| +
|
| + // Fractional cadence allows us to see if we have a cadence which would look
|
| + // best if we consistently drop the same frames.
|
| + //
|
| + // In this case, the perfect cadence is the number of frames per render
|
| + // interval, while the clamped cadence is the nearest integer value.
|
| + const double perfect_cadence =
|
| + render_interval.InSecondsF() / frame_duration.InSecondsF();
|
| + const int clamped_cadence = perfect_cadence + 0.5;
|
| + if (!clamped_cadence)
|
| + return false;
|
|
|
| - // When computing a fractional drift, we render the first of |clamped_cadence|
|
| - // frames and drop |clamped_cadence| - 1 frames. To make the calculations
|
| - // below work we need to project out the timestamp of the frame which would be
|
| - // rendered after accounting for those |clamped_cadence| frames.
|
| + // For fractional cadence, the rendered duration of each frame is just the
|
| + // render interval. While the actual frame duration is the total duration of
|
| + // all the frames we would end up dropping during that time.
|
| const base::TimeDelta actual_frame_duration =
|
| - fractional ? clamped_cadence * frame_duration : frame_duration;
|
| - if (rendered_frame_duration == actual_frame_duration) {
|
| - *cadence = clamped_cadence;
|
| - return true;
|
| + clamped_cadence * frame_duration;
|
| + if (!IsAcceptableCadence(render_interval, actual_frame_duration,
|
| + max_acceptable_drift, time_until_max_drift)) {
|
| + return false;
|
| }
|
|
|
| - // Compute how long it'll take to exhaust the drift using |clamped_cadence|.
|
| + // Fractional cadence means we render the first of |clamped_cadence| frames
|
| + // and drop |clamped_cadence| - 1 frames.
|
| + cadence->insert(cadence->begin(), clamped_cadence, 0);
|
| + (*cadence)[0] = 1;
|
| + return true;
|
| +}
|
| +
|
| +std::string VideoCadenceEstimator::CadenceToString(
|
| + const Cadence& cadence) const {
|
| + if (cadence.empty())
|
| + return std::string("[]");
|
| +
|
| + std::ostringstream os;
|
| + os << "[";
|
| + std::copy(cadence.begin(), cadence.end() - 1,
|
| + std::ostream_iterator<int>(os, ":"));
|
| + os << cadence.back() << "]";
|
| + return os.str();
|
| +}
|
| +
|
| +bool VideoCadenceEstimator::IsAcceptableCadence(
|
| + base::TimeDelta rendered_frame_duration,
|
| + base::TimeDelta actual_frame_duration,
|
| + base::TimeDelta max_acceptable_drift,
|
| + base::TimeDelta* time_until_max_drift) const {
|
| + if (rendered_frame_duration == actual_frame_duration)
|
| + return true;
|
| +
|
| + // Compute how long it'll take to exhaust the drift if frames are rendered for
|
| + // |rendered_frame_duration| instead of |actual_frame_duration|.
|
| const double duration_delta =
|
| (rendered_frame_duration - actual_frame_duration)
|
| .magnitude()
|
| .InMicroseconds();
|
| const int64 frames_until_drift_exhausted =
|
| std::ceil(max_acceptable_drift.InMicroseconds() / duration_delta);
|
| - *time_until_glitch = rendered_frame_duration * frames_until_drift_exhausted;
|
| -
|
| - if (*time_until_glitch >= minimum_time_until_glitch_) {
|
| - *cadence = clamped_cadence;
|
| - return true;
|
| - }
|
| -
|
| - return false;
|
| -}
|
| -
|
| -int VideoCadenceEstimator::GetCadenceForFrame(int index) const {
|
| - DCHECK(has_cadence());
|
| - DCHECK_GE(index, 0);
|
| -
|
| - if (fractional_cadence_)
|
| - return index % fractional_cadence_ == 0 ? 1 : 0;
|
|
|
| - return cadence_;
|
| + // If the time until a frame would be repeated or dropped is greater than our
|
| + // limit of acceptability, the cadence is acceptable.
|
| + *time_until_max_drift =
|
| + rendered_frame_duration * frames_until_drift_exhausted;
|
| + return *time_until_max_drift >= minimum_time_until_max_drift_;
|
| }
|
|
|
| } // namespace media
|
|
|