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
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210
  211
  212
  213
  214
  215
  216
  217
  218
  219
  220
  221
  222
  223
  224
  225
  226
  227
  228
  229
  230
  231
  232
  233
  234
  235
  236
  237
  238
  239
  240
  241
  242
  243
  244
  245
  246
  247
  248
  249
  250
  251
  252
  253
  254
  255
  256
  257
  258
  259
  260
  261
  262
  263
  264
  265
  266
  267
  268
  269
  270
  271
  272
  273
  274
  275
  276
  277
  278
  279
  280
  281
  282
  283
  284
  285
  286
  287
  288
  289
  290
  291
  292
  293
  294
  295
  296
  297
  298
  299
  300
  301
  302
  303
  304
  305
  306
  307
  308
  309
  310
  311
  312
  313
  314
  315
  316
  317
  318
  319
  320
  321
  322
  323
  324
  325
  326
  327
  328
  329
  330
  331
  332
  333
  334
  335
  336
  337
  338
  339
  340
  341
  342
  343
  344
  345
  346
  347
  348
  349
  350
  351
  352
  353
  354
  355
  356

media / filters / video_renderer_algorithm.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_RENDERER_ALGORITHM_H_
#define MEDIA_FILTERS_VIDEO_RENDERER_ALGORITHM_H_

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

#include "base/containers/circular_deque.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/moving_window.h"
#include "base/time/time.h"
#include "media/base/media_export.h"
#include "media/base/video_frame.h"
#include "media/base/video_renderer.h"
#include "media/filters/video_cadence_estimator.h"

namespace media {

class MediaLog;

// VideoRendererAlgorithm manages a queue of VideoFrames from which it chooses
// frames with the goal of providing a smooth playback experience.  I.e., the
// selection process results in the best possible uniformity for displayed frame
// durations over time.
//
// Clients will provide frames to VRA via EnqueueFrame() and then VRA will yield
// one of those frames in response to a future Render() call.  Each Render()
// call takes a render interval which is used to compute the best frame for
// display during that interval.
//
// Render() calls are expected to happen on a regular basis.  Failure to do so
// will result in suboptimal rendering experiences.  If a client knows that
// Render() callbacks are stalled for any reason, it should tell VRA to expire
// frames which are unusable via RemoveExpiredFrames(); this prevents useless
// accumulation of stale VideoFrame objects (which are frequently quite large).
//
// The primary means of smooth frame selection is via forced integer cadence,
// see VideoCadenceEstimator for details on this process.  In cases of non-
// integer cadence, the algorithm will fall back to choosing the frame which
// covers the most of the current render interval.  If no frame covers the
// current interval, the least bad frame will be chosen based on its drift from
// the start of the interval.
//
// Combined these three approaches enforce optimal smoothness in many cases.
class MEDIA_EXPORT VideoRendererAlgorithm {
 public:
  VideoRendererAlgorithm(const TimeSource::WallClockTimeCB& wall_clock_time_cb,
                         MediaLog* media_log);

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

  ~VideoRendererAlgorithm();

  // Chooses the best frame for the interval [deadline_min, deadline_max] based
  // on available and previously rendered frames.
  //
  // Under ideal circumstances the deadline interval provided to a Render() call
  // should be directly adjacent to the deadline given to the previous Render()
  // call with no overlap or gaps.  In practice, |deadline_max| is an estimated
  // value, which means the next |deadline_min| may overlap it slightly or have
  // a slight gap.  Gaps which exceed the length of the deadline interval are
  // assumed to be repeated frames for the purposes of cadence detection.
  //
  // If provided, |frames_dropped| will be set to the number of frames which
  // were removed from |frame_queue_|, during this call, which were never
  // returned during a previous Render() call and are no longer suitable for
  // rendering since their wall clock time is too far in the past.
  scoped_refptr<VideoFrame> Render(base::TimeTicks deadline_min,
                                   base::TimeTicks deadline_max,
                                   size_t* frames_dropped);

  // Removes all video frames which are unusable since their ideal render
  // interval [timestamp, timestamp + duration] is too far away from
  // |deadline_min| than is allowed by drift constraints.
  //
  // At least one frame will always remain after this call so that subsequent
  // Render() calls have a frame to return if no new frames are enqueued before
  // then.  Returns the number of frames removed that were never rendered.
  //
  // Note: In cases where there is no known frame duration (i.e. perhaps a video
  // with only a single frame), the last frame can not be expired, regardless of
  // the given deadline.  Clients must handle this case externally.
  size_t RemoveExpiredFrames(base::TimeTicks deadline);

  // Clients should call this if the last frame provided by Render() was never
  // rendered; it ensures the presented cadence matches internal models.  This
  // must be called before the next Render() call.
  void OnLastFrameDropped();

  // Adds a frame to |frame_queue_| for consideration by Render().  Out of order
  // timestamps will be sorted into appropriate order.  Do not enqueue end of
  // stream frames.  Frames inserted prior to the last rendered frame will not
  // be used.  They will be discarded on the next call to Render(), counting as
  // dropped frames, or by RemoveExpiredFrames(), counting as expired frames.
  //
  // Attempting to enqueue a frame with the same timestamp as a previous frame
  // will result in the previous frame being replaced if it has not been
  // rendered yet.  If it has been rendered, the new frame will be dropped.
  //
  // EnqueueFrame() will compute the current start time and an estimated end
  // time of the frame based on previous frames or the value of
  // VideoFrameMetadata::FRAME_DURATION if no previous frames, so that
  // EffectiveFramesQueued() is relatively accurate immediately after this call.
  void EnqueueFrame(scoped_refptr<VideoFrame> frame);

  // Removes all frames from the |frame_queue_| and clears predictors.  The
  // algorithm will be as if freshly constructed after this call.  By default
  // everything is reset, but if kPreserveNextFrameEstimates is specified, then
  // predictors for the start time of the next frame given to EnqueueFrame()
  // will be kept; allowing EffectiveFramesQueued() accuracy with one frame.
  enum class ResetFlag { kEverything, kPreserveNextFrameEstimates };
  void Reset(ResetFlag reset_flag = ResetFlag::kEverything);

  // Returns the number of frames currently buffered which could be rendered
  // assuming current Render() interval trends.
  //
  // If a cadence has been identified, this will return the number of frames
  // which have a non-zero ideal render count.
  //
  // If cadence has not been identified, this will return the number of frames
  // which have a frame end time greater than the end of the last render
  // interval passed to Render().  Note: If Render() callbacks become suspended
  // and the duration is unknown the last frame may never be stop counting as
  // effective.  Clients must handle this case externally.
  //
  // In either case, frames enqueued before the last displayed frame will not
  // be counted as effective.
  size_t effective_frames_queued() const { return effective_frames_queued_; }

  // Returns an estimate of the amount of memory (in bytes) used for frames.
  int64_t GetMemoryUsage() const;

  // Tells the algorithm that Render() callbacks have been suspended for a known
  // reason and such stoppage shouldn't be counted against future frames.
  void set_time_stopped() { was_time_moving_ = false; }

  size_t frames_queued() const { return frame_queue_.size(); }

  // Returns the average of the duration of all frames in |frame_queue_|
  // as measured in wall clock (not media) time.
  base::TimeDelta average_frame_duration() const {
    return average_frame_duration_;
  }

  // End time of the last frame.
  base::TimeTicks last_frame_end_time() const {
    return frame_queue_.back().end_time;
  }

  const VideoFrame& last_frame() const { return *frame_queue_.back().frame; }

  // Current render interval.
  base::TimeDelta render_interval() const { return render_interval_; }

  // Method used for testing which disables frame dropping, in this mode the
  // algorithm will never drop frames and instead always return every frame
  // for display at least once.
  void disable_frame_dropping() { frame_dropping_disabled_ = true; }

  enum : int {
    // The number of frames to store for moving average calculations.  Value
    // picked after experimenting with playback of various local media and
    // YouTube clips.
    kMovingAverageSamples = 32
  };

 private:
  friend class VideoRendererAlgorithmTest;

  // The determination of whether to clamp to a given cadence is based on the
  // number of seconds before a frame would have to be dropped or repeated to
  // compensate for reaching the maximum acceptable drift.
  //
  // We've chosen 8 seconds based on practical observations and the fact that it
  // allows 29.9fps and 59.94fps in 60Hz and vice versa.
  //
  // Most users will not be able to see a single frame repeated or dropped every
  // 8 seconds and certainly should notice it less than the randomly variable
  // frame durations.
  static const int kMinimumAcceptableTimeBetweenGlitchesSecs = 8;

  // Metadata container for enqueued frames.  See |frame_queue_| below.
  struct ReadyFrame {
    ReadyFrame(scoped_refptr<VideoFrame> frame);
    ReadyFrame(const ReadyFrame& other);
    ~ReadyFrame();

    // For use with std::lower_bound.
    bool operator<(const ReadyFrame& other) const;

    scoped_refptr<VideoFrame> frame;

    // |start_time| is only available after UpdateFrameStatistics() has been
    // called and |end_time| only after we have more than one frame.
    base::TimeTicks start_time;
    base::TimeTicks end_time;

    // True if this frame's end time is based on the average frame duration and
    // not the time of the next frame.
    bool has_estimated_end_time;

    int ideal_render_count;
    int render_count;
    int drop_count;
  };

  // Updates the render count for the last rendered frame based on the number
  // of missing intervals between Render() calls.
  void AccountForMissedIntervals(base::TimeTicks deadline_min,
                                 base::TimeTicks deadline_max);

  // Updates the render count and wall clock timestamps for all frames in
  // |frame_queue_|.  Updates |was_time_stopped_|, |cadence_estimator_| and
  // |frame_duration_calculator_|.
  //
  // Note: Wall clock time is recomputed each Render() call because it's
  // expected that the TimeSource powering TimeSource::WallClockTimeCB will skew
  // slightly based on the audio clock.
  //
  // TODO(dalecurtis): Investigate how accurate we need the wall clock times to
  // be, so we can avoid recomputing every time (we would need to recompute when
  // playback rate changes occur though).
  void UpdateFrameStatistics();

  // Updates the ideal render count for all frames in |frame_queue_| based on
  // the cadence returned by |cadence_estimator_|.  Cadence is assigned based
  // on |frame_counter_|.
  void UpdateCadenceForFrames();

  // If |cadence_estimator_| has detected a valid cadence, attempts to find the
  // next frame which should be rendered.  Returns -1 if not enough frames are
  // available for cadence selection or there is no cadence.
  int FindBestFrameByCadence() const;

  // Iterates over |frame_queue_| and finds the frame which covers the most of
  // the deadline interval.  If multiple frames have coverage of the interval,
  // |second_best| will be set to the index of the frame with the next highest
  // coverage.  Returns -1 if no frame has any coverage of the current interval.
  //
  // Prefers the earliest frame if multiple frames have similar coverage (within
  // a few percent of each other).
  int FindBestFrameByCoverage(base::TimeTicks deadline_min,
                              base::TimeTicks deadline_max,
                              int* second_best) const;

  // Iterates over |frame_queue_| and find the frame which drifts the least from
  // |deadline_min|.  There's always a best frame by drift, so the return value
  // is always a valid frame index.  |selected_frame_drift| will be set to the
  // drift of the chosen frame.
  //
  // Note: Drift calculations assume contiguous frames in the time domain, so
  // it's not possible to have a case where a frame is -10ms from |deadline_min|
  // and another frame which is at some time after |deadline_min|.  The second
  // frame would be considered to start at -10ms before |deadline_min| and would
  // overlap |deadline_min|, so its drift would be zero.
  int FindBestFrameByDrift(base::TimeTicks deadline_min,
                           base::TimeDelta* selected_frame_drift) const;

  // Calculates the drift from |deadline_min| for the given |frame_index|.  If
  // the [start_time, end_time] lies before |deadline_min| the drift is
  // the delta between |deadline_min| and |end_time|. If the frame
  // overlaps |deadline_min| the drift is zero. If the frame lies after
  // |deadline_min| the drift is the delta between |deadline_min| and
  // |start_time|.
  base::TimeDelta CalculateAbsoluteDriftForFrame(base::TimeTicks deadline_min,
                                                 int frame_index) const;

  // Returns the index of the first usable frame or -1 if no usable frames.
  int FindFirstGoodFrame() const;

  // Updates |effective_frames_queued_| which is typically called far more
  // frequently (~4x) than the value changes.  This must be called whenever
  // frames are added or removed from the queue or when any property of a
  // ReadyFrame within the queue changes.
  void UpdateEffectiveFramesQueued();

  // Computes the unclamped count of effective frames.  Used by
  // UpdateEffectiveFramesQueued().
  size_t CountEffectiveFramesQueued() const;

  raw_ptr<MediaLog> media_log_;
  int out_of_order_frame_logs_ = 0;

  // Queue of incoming frames waiting for rendering.
  using VideoFrameQueue = base::circular_deque<ReadyFrame>;
  VideoFrameQueue frame_queue_;

  // Handles cadence detection and frame cadence assignments.
  VideoCadenceEstimator cadence_estimator_;

  // Indicates if any calls to Render() have successfully yielded a frame yet.
  bool have_rendered_frames_;

  // Callback used to convert media timestamps into wall clock timestamps.
  const TimeSource::WallClockTimeCB wall_clock_time_cb_;

  // The last |deadline_max| provided to Render(), used to predict whether
  // frames were rendered over cadence between Render() calls.
  base::TimeTicks last_deadline_max_;

  // The average of the duration of all frames in |frame_queue_| as measured in
  // wall clock (not media) time at the time of the last Render().
  base::MovingAverageDeviation<base::TimeDelta> frame_duration_calculator_;
  base::TimeDelta average_frame_duration_;

  // The length of the last deadline interval given to Render(), updated at the
  // start of Render().
  base::TimeDelta render_interval_;

  // The maximum acceptable drift before a frame can no longer be considered for
  // rendering within a given interval.
  base::TimeDelta max_acceptable_drift_;

  // Indicates that the last call to Render() experienced a rendering glitch; it
  // may have: under-rendered a frame, over-rendered a frame, dropped one or
  // more frames, or chosen a frame which exceeded acceptable drift.
  bool last_render_had_glitch_;

  // For testing functionality which enables clockless playback of all frames,
  // does not prevent frame dropping due to equivalent timestamps.
  bool frame_dropping_disabled_;

  // Tracks frames dropped during enqueue when identical timestamps are added
  // to the queue.  Callers are told about these frames during Render().
  size_t frames_dropped_during_enqueue_;

  // When cadence is present, we don't want to start counting against cadence
  // until the first frame has reached its presentation time.
  bool first_frame_;

  // The frame number of the last rendered frame; incremented for every frame
  // rendered and every frame dropped or expired since the last rendered frame.
  //
  // Given to |cadence_estimator_| when assigning cadence values to the
  // ReadyFrameQueue.  Cleared when a new cadence is detected.
  uint64_t cadence_frame_counter_;

  // Indicates if time was moving, set to the return value from
  // UpdateFrameStatistics() during Render() or externally by
  // set_time_stopped().
  bool was_time_moving_;

  // Current number of effective frames in the |frame_queue_|.  Updated by calls
  // to UpdateEffectiveFramesQueued() whenever the |frame_queue_| is changed.
  size_t effective_frames_queued_;
};

}  // namespace media

#endif  // MEDIA_FILTERS_VIDEO_RENDERER_ALGORITHM_H_