Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #ifndef MEDIA_FILTERS_VIDEO_RENDERER_ALGORITHM_H_ | |
| 6 #define MEDIA_FILTERS_VIDEO_RENDERER_ALGORITHM_H_ | |
| 7 | |
| 8 #include <deque> | |
| 9 | |
| 10 #include "base/memory/ref_counted.h" | |
| 11 #include "base/time/time.h" | |
| 12 #include "media/base/media_export.h" | |
| 13 #include "media/base/video_frame.h" | |
| 14 #include "media/base/video_renderer.h" | |
| 15 | |
| 16 namespace media { | |
| 17 | |
| 18 // A cadence based video rendering algorithm with coverage based fallback for | |
| 19 // non-integer cadences. I.e., if the presentation interval is an integer | |
| 20 // multiple or divisor of the wall clock frame duration the algorithm will | |
| 21 // prefer to redisplay the current frame until a drift threshold is exceeded or | |
| 22 // the optimal cadence is reached. | |
| 23 // | |
| 24 // In cases of non-integer cadence, the algorithm will fallback to choosing the | |
| 25 // frame which covers the most of the current presentation interval. If no frame | |
| 26 // covers the current interval, the least bad frame will be chosen based on its | |
| 27 // drift from the interval. | |
|
xhwang
2015/04/15 18:05:28
This class is not trivial. Can you explain more ab
DaleCurtis
2015/04/15 19:10:53
Good idea. I'll add more here. Essentially a class
| |
| 28 class MEDIA_EXPORT VideoRendererAlgorithm { | |
| 29 public: | |
| 30 // Used to convert a media timestamp into wall clock time. | |
| 31 using TimeConverterCB = base::Callback<base::TimeTicks(base::TimeDelta)>; | |
|
xhwang
2015/04/15 18:05:28
add include for base::Callback
DaleCurtis
2015/04/16 01:48:01
Done.
| |
| 32 | |
| 33 VideoRendererAlgorithm(const TimeConverterCB& time_converter_cb); | |
|
xhwang
2015/04/15 18:05:28
explicit
DaleCurtis
2015/04/16 01:48:01
Done.
| |
| 34 ~VideoRendererAlgorithm(); | |
| 35 | |
| 36 // Chooses the best frame for the interval [deadline_min, deadline_max] based | |
| 37 // on available and previously rendered frames. | |
| 38 // | |
| 39 // The deadline interval provided to a Render() call should be adjacent to the | |
| 40 // deadline given to the previous Render() call. Gaps which exceed the length | |
| 41 // of the deadline interval are assumed to be repeated frames for the purposes | |
| 42 // of cadence detection. | |
|
xhwang
2015/04/15 20:51:39
Can you elaborate on this as well? By "adjacent",
DaleCurtis
2015/04/16 01:48:01
Yes, hence the "Gaps which exceed..." - do you sti
| |
| 43 // | |
| 44 // If provided, |frames_dropped| will be set to the number of frames which | |
| 45 // were removed from |frame_queue_|, during this call, which were never | |
| 46 // returned during a previous Render() call and are no longer suitable for | |
| 47 // rendering since their wall clock display time is too far in the past. | |
| 48 scoped_refptr<VideoFrame> Render(base::TimeTicks deadline_min, | |
| 49 base::TimeTicks deadline_max, | |
|
xhwang
2015/04/15 18:05:28
Bike shedding:
1. We have this deadline_min and d
DaleCurtis
2015/04/15 19:10:53
Hmm, I'll have to think about adding a class for t
xhwang
2015/04/15 20:51:39
FrameInterval can be confused with frame duration.
| |
| 50 int* frames_dropped); | |
| 51 | |
| 52 // Removes all video frames which are unusable since their display interval | |
| 53 // [timestamp, timestamp + duration] is too far away from |deadline_min| than | |
| 54 // allowed by drift constraints. | |
| 55 // | |
| 56 // At least one frame will always remain after this call so that subsequent | |
| 57 // Render() calls have a frame to return if no new frames are enqueued before | |
| 58 // then. Returns the number of frames expired. | |
| 59 int RemoveExpiredFrames(base::TimeTicks deadline_min); | |
|
xhwang
2015/04/15 18:05:28
Wondering shouldn't this be handled internally in
DaleCurtis
2015/04/15 19:10:53
The VRI will use this to expire frames via a count
| |
| 60 | |
| 61 // Clients should call this if the last frame provided by Render() was never | |
| 62 // rendered; it ensures the presented cadence matches internal models. This | |
| 63 // must be called before the next Render() call. | |
| 64 void OnLastFrameDropped(); | |
| 65 | |
| 66 // Adds a frame to |frame_queue_| for consideration by Render(). Out of order | |
| 67 // timestamp will be sorted into appropriate order. Frames inserted prior to | |
| 68 // the last rendered frame will be dropped. Do not enqueue end of stream | |
| 69 // frames. | |
| 70 void EnqueueFrame(const scoped_refptr<VideoFrame>& frame); | |
| 71 | |
| 72 // Removes all frames from the |frame_queue_| and clears predictors. | |
|
xhwang
2015/04/15 18:05:28
Just to double check, does this set |this| to a cl
DaleCurtis
2015/04/15 19:10:53
Yes. I use it extensively in the tests :)
| |
| 73 void Reset(); | |
| 74 | |
| 75 // Returns the number of frames currently buffered which could be rendered | |
| 76 // assuming current Render() interval trends. Before Render() is called or if | |
| 77 // no cadence pattern is detected, this will be the same as the number of | |
| 78 // frames given to EnqueueFrame(). | |
| 79 // | |
| 80 // If a cadence has been identified, this will return the number of frames | |
| 81 // which have a non-zero ideal render count. | |
| 82 size_t EffectiveFramesQueued() const; | |
| 83 size_t frames_queued() const { return frame_queue_.size(); } | |
| 84 | |
| 85 // Returns the average of the display duration of all frames in |frame_queue_| | |
| 86 // as measured in wall clock (not media) time. | |
| 87 base::TimeDelta average_frame_duration() const { return frame_duration_; } | |
| 88 | |
| 89 bool last_render_had_glitch() const { return last_render_had_glitch_; } | |
| 90 | |
| 91 void disable_cadence_hysteresis_for_testing() { | |
|
xhwang
2015/04/15 18:05:28
Move to private since you have the test class as a
DaleCurtis
2015/04/16 01:48:01
Removed, it's a relic of when I didn't have a bool
| |
| 92 cadence_hysteresis_enabled_ = false; | |
| 93 } | |
| 94 | |
| 95 private: | |
| 96 friend class VideoRendererAlgorithmTest; | |
| 97 | |
|
xhwang
2015/04/15 20:51:39
Maybe have a summary here on how the internal algo
DaleCurtis
2015/04/16 01:48:01
I've given a high level summary at the top of the
| |
| 98 enum { | |
|
xhwang
2015/04/15 18:05:28
why enum instead of just a static const?
http://s
DaleCurtis
2015/04/15 19:10:53
Hmm, I was under the impression this would result
xhwang
2015/04/15 20:51:39
Since this is an integer type, the value will be e
DaleCurtis
2015/04/16 01:48:01
http://stackoverflow.com/questions/92546/variable-
xhwang
2015/04/16 22:30:45
[I am just learning on this :) Would be happy to g
DaleCurtis
2015/04/18 01:29:20
Looked into this a bit more, it seems okay for int
| |
| 99 // The determination of whether to clamp to a given cadence is based on the | |
| 100 // number of seconds before a frame would have to be dropped or repeated to | |
| 101 // compensate for reaching the maximum acceptable drift. | |
| 102 // | |
| 103 // We've chosen 8 seconds based on practical observations and the fact that | |
| 104 // it allows 29.9fps and 59.94fps in 60Hz and vice versa. | |
|
xhwang
2015/04/15 18:05:28
I still don't follow how 8 seconds is calculated;
DaleCurtis
2015/04/15 19:10:53
Essentially I played some videos and looked at how
| |
| 105 // | |
| 106 // Most users will not be able to see a single frame repeated or dropped | |
| 107 // every 8 seconds and certainly should notice it less than the randomly | |
| 108 // variable frame durations. | |
| 109 kMinimumAcceptableTimeBetweenGlitchesSecs = 8 | |
| 110 }; | |
| 111 | |
| 112 // Updates the display count for the last rendered frame based on the number | |
| 113 // of missing intervals between Render() calls. | |
| 114 void AccountForMissedIntervals(base::TimeTicks deadline_min, | |
| 115 base::TimeTicks deadline_max); | |
| 116 | |
| 117 // Calculates how long until |max_acceptable_drift_| would be exhausted by | |
| 118 // showing a frame for |clamped_cadence| render intervals instead of for the | |
| 119 // ideal |perfect_cadence| intervals. | |
| 120 // | |
| 121 // In practice this works out to the following for common setups if the | |
| 122 // |clamped_cadence| is used for rendering: | |
| 123 // | |
| 124 // 29.5fps in 60Hz, ~17ms max drift => exhausted in ~1 second. | |
|
xhwang
2015/04/15 18:05:28
Is 29.5 the perfect cadence, and 60 the clamped ca
DaleCurtis
2015/04/15 19:10:53
29.5fps is the video frame rate, 60Hz is the displ
xhwang
2015/04/15 20:51:39
Thanks for the explanation. We definitely need mor
DaleCurtis
2015/04/16 01:48:01
Done.
| |
| 125 // 29.9fps in 60Hz, ~17ms max drift => exhausted in ~16.4 seconds. | |
| 126 // 24fps in 60Hz, ~21ms max drift => exhausted in ~0.15 seconds. | |
| 127 // 25fps in 60Hz, 20ms max drift => exhausted in ~4.0 seconds. | |
| 128 // 59.9fps in 60Hz, ~8.3ms max drift => exhausted in ~8.2 seconds. | |
| 129 // 24.9fps in 50Hz, ~20ms max drift => exhausted in ~20.5 seconds. | |
| 130 // 120fps in 59.9Hz, ~8.3ms max drift => exhausted in ~8.2 seconds. | |
| 131 // | |
| 132 base::TimeDelta CalculateTimeUntilGlitch(double perfect_cadence, | |
| 133 double clamped_cadence, | |
| 134 bool fractional); | |
| 135 | |
| 136 // Updates the display count and wall clock timestamps for all frames in | |
| 137 // |frame_queue_|. Returns false if statistics can't be updated at this time; | |
| 138 // which can occur if media time has stopped. Sets |ideal_cadence_| to a non | |
| 139 // zero value if an integer cadence was detected. | |
| 140 bool UpdateFrameStatistics(); | |
| 141 | |
| 142 // Updates the ideal display count for all frames in |frame_queue_| based on | |
| 143 // the given |fractional_cadence|. The first of every |fractional_cadence| | |
| 144 // frames is given a display count of one, the rest are given zero. The first | |
| 145 // frame is determined by |last_frame_index_|. | |
| 146 void UpdateFractionalCadenceForFrames(int fractional_cadence); | |
| 147 | |
| 148 // If |ideal_cadence_| is non-zero and handles cases where the last frame is | |
| 149 // under cadence or exactly on cadence. Returns -1 if the last frame is above | |
| 150 // cadence or there is no |ideal_cadence_|. | |
| 151 int FindBestFrameByCadence(); | |
| 152 | |
| 153 // Iterates over |frame_queue_| and finds the frame which covers the most of | |
| 154 // the deadline interval. If multiple frames have coverage of the interval, | |
| 155 // |second_best| will be set to the index of the frame with the next highest | |
| 156 // coverage. Returns -1 if no frame has any coverage of the current interval. | |
| 157 // | |
| 158 // Prefers the earliest frame if multiple frames have similar coverage (within | |
| 159 // a few percent of each other). | |
| 160 int FindBestFrameByCoverage(base::TimeTicks deadline_min, | |
| 161 base::TimeTicks deadline_max, | |
| 162 int* second_best); | |
| 163 | |
| 164 // Iterates over |frame_queue_| and find the frame which drifts the least from | |
| 165 // |deadline_min|. There's always a best frame by drift, so the return value | |
| 166 // is always a valid frame index. | |
| 167 int FindBestFrameByDrift(base::TimeTicks deadline_min); | |
| 168 | |
| 169 // Calculates the drift from |deadline_min| for the given |frame_index|. If | |
| 170 // the [wall_clock_time, wall_clock_time + frame_duration_] lies before | |
| 171 // |deadline_min| the drift is the delta between |deadline_min| and | |
| 172 // |wall_clock_time + frame_duration_|. If the frame overlaps |deadline_min| | |
| 173 // the drift is zero. If the frame lies after |deadline_min| the drift is the | |
| 174 // delta between |deadline_min| and |wall_clock_time|. | |
| 175 base::TimeDelta CalculateDriftForFrame(base::TimeTicks deadline_min, | |
| 176 int frame_index); | |
| 177 | |
| 178 struct ReadyFrame { | |
| 179 ReadyFrame(const scoped_refptr<VideoFrame>& frame); | |
| 180 ~ReadyFrame(); | |
| 181 | |
| 182 scoped_refptr<VideoFrame> frame; | |
| 183 | |
| 184 base::TimeDelta media_timestamp; | |
| 185 base::TimeTicks wall_clock_time; | |
| 186 int ideal_render_count; | |
| 187 int render_count; | |
| 188 | |
| 189 // For use with std::lower_bound. | |
| 190 bool operator<(const ReadyFrame& other) const; | |
| 191 }; | |
| 192 | |
| 193 // Queue of incoming frames waiting for rendering. | |
| 194 using VideoFrameQueue = std::deque<ReadyFrame>; | |
| 195 VideoFrameQueue frame_queue_; | |
| 196 | |
| 197 // The index of the last frame rendered; presumed to be the first frame if no | |
| 198 // frame has been rendered yet. Updated by Render() and EnqueueFrame() if any | |
| 199 // frames are added or removed. | |
| 200 // | |
| 201 // In most cases this value is zero, but when out of order timestamps are | |
| 202 // present, the last displayed frame may be moved. | |
| 203 int last_frame_index_; | |
| 204 | |
| 205 // The idealized cadence for all frames seen thus far; updated based upon the | |
| 206 // |frame_duration_| relative to the deadline interval provided to Render(). | |
| 207 // Zero if no integer cadence could be detected. | |
| 208 // | |
| 209 // Fractional cadences are handled by strongly preferring the first frame in | |
| 210 // a series if it fits within acceptable drift. E.g., with 120fps content on | |
| 211 // a 60Hz monitor we'll strongly prefer the first frame of every 2 frames. | |
| 212 // | |
| 213 // |fractional_cadence_| is the number of frames per render interval; the | |
| 214 // first of which would be displayed and the rest dropped. | |
| 215 int ideal_cadence_; | |
| 216 int fractional_cadence_; | |
| 217 | |
| 218 // Used as hysteresis to prevent oscillation between cadence and coverage | |
| 219 // based rendering methods. | |
| 220 int last_detected_cadence_; | |
| 221 int render_intervals_cadence_held_; | |
| 222 bool cadence_hysteresis_enabled_; | |
| 223 | |
| 224 // Indicates if any calls to Render() have successfully yielded a frame yet. | |
| 225 bool have_rendered_frames_; | |
| 226 | |
| 227 // Callback used to convert media timestamps into wall clock timestamps. | |
| 228 TimeConverterCB time_converter_cb_; | |
| 229 | |
| 230 // The last |deadline_max| provided to Render(), used to predict whether | |
| 231 // frames were displayed over cadence between Render() calls. | |
| 232 base::TimeTicks last_deadline_max_; | |
| 233 | |
| 234 // The average of the display duration of all frames in |frame_queue_| as | |
| 235 // measured in wall clock (not media) time at the time of the last Render(). | |
| 236 base::TimeDelta frame_duration_; | |
| 237 | |
| 238 // The length of the last deadline interval given to Render(), updated at the | |
| 239 // start of Render(). | |
| 240 base::TimeDelta render_interval_; | |
|
xhwang
2015/04/15 18:05:28
How about |last_interval_|? If you have a class fo
DaleCurtis
2015/04/16 01:48:01
Still thinking about this; it seems reasonable, bu
| |
| 241 | |
| 242 // The maximum acceptable drift before a frame can no longer be considered for | |
| 243 // rendering within a given interval. | |
| 244 base::TimeDelta max_acceptable_drift_; | |
| 245 | |
| 246 // Indicates that the last call to Render() experienced a rendering glitch; it | |
| 247 // may have: under-displayed a frame, over-displayed a frame, dropped one or | |
| 248 // more frames, or chosen a frame which exceeded acceptable drift. | |
| 249 bool last_render_had_glitch_; | |
| 250 | |
| 251 DISALLOW_COPY_AND_ASSIGN(VideoRendererAlgorithm); | |
| 252 }; | |
| 253 | |
| 254 } // namespace media | |
| 255 | |
| 256 #endif // MEDIA_FILTERS_VIDEO_RENDERER_ALGORITHM_H_ | |
| OLD | NEW |