1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187

media / filters / video_cadence_estimator.h [blame]

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef MEDIA_FILTERS_VIDEO_CADENCE_ESTIMATOR_H_
#define MEDIA_FILTERS_VIDEO_CADENCE_ESTIMATOR_H_

#include <stddef.h>
#include <stdint.h>

#include <optional>
#include <string>
#include <vector>

#include "base/time/time.h"
#include "media/base/media_export.h"

namespace media {

// Estimates whether a given frame duration and render interval length have a
// render cadence which would allow for optimal uniformity of displayed frame
// durations over time.
//
// Cadence is the ideal repeating frame pattern for a group of frames; currently
// VideoCadenceEstimator supports N-frame ([a1:a2:..:aN]) cadences where N <= 5.
// Details on what this means are below.
//
// The perfect cadence of a set of frames is the ratio of the frame duration to
// render interval length.  I.e. for 30fps in 60Hz the cadence would be (1/30) /
// (1/60) = 60 / 30 = 2.  It's common that this is not an exact integer, e.g.,
// 29.974fps in 60Hz which would have a cadence of (1/29.974) / (1/60) =
// ~2.0029.
//
// The perfect cadence is always a real number.  All N-cadences [a1:a2:..:aN]
// where aK is an integer are an approximation of the perfect cadence; i.e. the
// average of [a1:..:aN] will approximately equal the perfect cadence.  When N=1
// we have a 1-frame cadence, when N=2, we have a 2-frame cadence, etc.
//
// For single frame cadence we just round the perfect cadence (~2.0029 in the
// previous example) to the nearest integer value (2 in this case; which is
// denoted as a cadence of [2]).  If the delta between those values is small we
// can choose to render frames for the integer number of render intervals;
// shortening or lengthening the actual rendered frame duration.  Doing so
// ensures each frame gets an optimal amount of display time.
//
// For N-frame cadence, the idea is similar, we just round the perfect cadence
// to some K/N, where K is an integer, and distribute [floor(K/N), floor(K/N)+1]
// into the cadence vector as evenly as possible.  For example, 23.97fps in
// 60Hz, the perfect cadence is 2.50313, we can round it to 2.5 = 5/2, and we
// can then construct the cadence vector as [2:3].
//
// The delta between the perfect cadence and the rounded cadence leads to drift
// over time of the actual VideoFrame timestamp relative to its rendered time,
// so we perform some calculations to ensure we only use a cadence when it will
// take some time to drift an undesirable amount; see CalculateCadence() for
// details on how this calculation is made.
//
// In practice this works out to the following for common setups if we use
// cadence based selection:
//
//    29.5fps in 60Hz,    ~17ms max drift => exhausted in ~1 second.
//    29.9fps in 60Hz,    ~17ms max drift => exhausted in ~16.4 seconds.
//    24fps   in 59.9Hz,  ~21ms max drift => exhausted in ~12.6 seconds.
//    24.9fps in 60Hz,    ~20ms max drift => exhausted in ~4.0 seconds.
//    59.9fps in 60Hz,    ~8.3ms max drift => exhausted in ~8.2 seconds.
//    24.9fps in 50Hz,    ~20ms max drift => exhausted in ~20.5 seconds.
//    120fps  in 59.9Hz,  ~8.3ms max drift => exhausted in ~8.2 seconds.
//
class MEDIA_EXPORT VideoCadenceEstimator {
 public:
  using Cadence = std::vector<int>;

  // As mentioned in the introduction, the determination of whether to clamp to
  // a given cadence is based on how long it takes before a frame would have to
  // be dropped or repeated to compensate for reaching the maximum acceptable
  // drift; this time length is controlled by |minimum_time_until_max_drift|.
  explicit VideoCadenceEstimator(base::TimeDelta minimum_time_until_max_drift);

  VideoCadenceEstimator(const VideoCadenceEstimator&) = delete;
  VideoCadenceEstimator& operator=(const VideoCadenceEstimator&) = delete;

  ~VideoCadenceEstimator();

  // Clears stored cadence information.
  void Reset();

  // Updates the estimates for |cadence_| based on the given values as described
  // in the introduction above.
  //
  // Clients should call this and then update the cadence for all frames via the
  // GetCadenceForFrame() method if the cadence changes.
  //
  // Cadence changes will not take affect until enough render intervals have
  // elapsed.  For the purposes of hysteresis, each UpdateCadenceEstimate() call
  // is assumed to elapse one |render_interval| worth of time.
  //
  // Returns true if the cadence has changed since the last call.
  bool UpdateCadenceEstimate(base::TimeDelta render_interval,
                             base::TimeDelta frame_duration,
                             base::TimeDelta frame_duration_deviation,
                             base::TimeDelta max_acceptable_drift);

  // Returns true if a useful cadence was found.
  bool has_cadence() const { return !cadence_.empty(); }

  // Given a |frame_number|, where zero is the most recently rendered frame,
  // returns the ideal cadence for that frame.
  //
  // Note: Callers must track the base |frame_number| relative to all frames
  // rendered or removed after the first frame for which cadence is detected.
  // The first frame after cadence is detected has a |frame_number| of 0.
  //
  // Frames which come in before the last rendered frame should be ignored in
  // terms of impact to the base |frame_number|.
  int GetCadenceForFrame(uint64_t frame_number) const;

  void set_cadence_hysteresis_threshold_for_testing(base::TimeDelta threshold) {
    cadence_hysteresis_threshold_ = threshold;
  }

  double avg_cadence_for_testing() const;
  size_t cadence_size_for_testing() const { return cadence_.size(); }
  std::string GetCadenceForTesting() const { return CadenceToString(cadence_); }

  // Determines whether a simple (single-valued) integer cadence exists for
  // |render_interval| and |frame_duration| that won't drift more than
  // |render_interval| within |minimum_time_until_max_drift|.
  static bool HasSimpleCadence(
      base::TimeDelta render_interval,
      base::TimeDelta frame_duration,
      base::TimeDelta minimum_time_until_max_drift = base::Seconds(8));

 private:
  // Attempts to find an N-frame cadence.  Returns the cadence vector if cadence
  // is found and sets |time_until_max_drift| for the computed cadence. If
  // multiple cadences satisfying the max drift constraint exist, we are going
  // to return the one with largest |time_until_max_drift|.
  // For details on the math and algorithm, see https://goo.gl/QK0vbz
  Cadence CalculateCadence(base::TimeDelta render_interval,
                           base::TimeDelta frame_duration,
                           base::TimeDelta max_acceptable_drift,
                           base::TimeDelta* time_until_max_drift) const;

  // Converts a cadence vector into a human readable string of the form
  // "[a: b: ...: z]".
  std::string CadenceToString(const Cadence& cadence) const;

  bool UpdateBresenhamCadenceEstimate(base::TimeDelta render_interval,
                                      base::TimeDelta frame_duration);

  void UpdateCadenceInternal(Cadence new_cadence,
                             base::TimeDelta time_until_max_drift);

  // The approximate best N-frame cadence for all frames seen thus far; updated
  // by UpdateCadenceEstimate().  Empty when no cadence has been detected.
  Cadence cadence_;

  // Used as hysteresis to prevent oscillation between cadence approximations
  // for spurious blips in the render interval or frame duration.
  //
  // Once a new cadence is detected, |render_intervals_cadence_held_| is
  // incremented for each UpdateCadenceEstimate() call where |cadence_| matches
  // |pending_cadence_|.  |render_intervals_cadence_held_| is cleared when a
  // "new" cadence matches |cadence_| or |pending_cadence_|.
  //
  // Once |kMinimumCadenceDurationMs| is exceeded in render intervals, the
  // detected cadence is set in |cadence_|.
  Cadence pending_cadence_;
  int render_intervals_cadence_held_;
  base::TimeDelta cadence_hysteresis_threshold_;

  // Tracks how many times cadence has switched during a given playback, used to
  // histogram the number of cadence changes in a playback.
  bool first_update_call_;
  int cadence_changes_;

  // The minimum amount of time allowed before a glitch occurs before confirming
  // cadence for a given render interval and frame duration.
  const base::TimeDelta minimum_time_until_max_drift_;

  bool is_variable_frame_rate_;
  base::TimeDelta last_render_interval_;
};

}  // namespace media

#endif  // MEDIA_FILTERS_VIDEO_CADENCE_ESTIMATOR_H_