Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(136)

Side by Side Diff: media/filters/video_renderer_algorithm.h

Issue 1021943002: Introduce cadence based VideoRendererAlgorithm. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Comments. Created 5 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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/callback.h"
11 #include "base/memory/ref_counted.h"
12 #include "base/time/time.h"
13 #include "media/base/media_export.h"
14 #include "media/base/video_frame.h"
15 #include "media/base/video_renderer.h"
16
17 namespace media {
18
19 // VideoRendererAlgorithm manages a queue of VideoFrames from which it chooses
20 // frames from with the goal of providing a smooth playback experience. I.e.,
xhwang 2015/04/16 22:30:45 s/from//
DaleCurtis 2015/04/18 01:29:21 Done.
21 // the selection process results in the best possible uniformity for displayed
22 // frame durations over time.
23 //
24 // Clients will provide frames to VRA via EnqueueFrame() and then VRA will yield
25 // one of those frames in response to a future Render() call. Each Render()
26 // call takes a render interval which is used to compute the best frame for
27 // display during that interval.
28 //
29 // Render() calls are expected to happen on a regular basis. Failure to do so
30 // will result in suboptimal rendering experiences. If a client knows that
31 // Render() callbacks are stalled for any reason, it should tell VRA to expire
32 // frames which are unusable via RemoveExpiredFrames(); this prevents useless
33 // accumulation of stale VideoFrame objects (which are frequently quite large).
34 //
35 // Some definitions are necessary to understand how VRA selects frames: the
36 // primary means of smooth frame selection is via forced integer cadence.
37 // Cadence is the ratio of the frame duration to render interval length. I.e.
38 // for 30 fps in 60Hz the cadence would be (1/30) / (1/60) == 60 / 30 == 2. It's
39 // common that this is not an exact integer, e.g., 29.974 fps in 60Hz which
40 // would have a cadence of (1/29.974) / (1/60) == ~2.0029.
41 //
42 // Forced integer cadence means we round the actual cadence (~2.0029 in the
43 // previous example) to the nearest integer value (2 in this case). If the delta
44 // between those values is small, we can choose to render frames for the integer
45 // number of render intervals; shortening or lengthening the actual rendered
46 // frame duration. Doing so ensures each frame gets an optimal amount of
47 // display time.
48 //
49 // Obviously forcing cadence like that leads to drift over time of the actual
50 // VideoFrame timestamp relative to its rendered time, so we perform some
51 // calculations to ensure we only force cadence when it will take many seconds
52 // to drift an undesirable amount; see CalculateTimeUntilGlitch() for details on
53 // how this calculation is made.
54 //
55 // Notably, this concept can be extended to include "fractional cadence" when
56 // the frame duration is shorter than the render interval; see implementations
57 // of CalculateTimeUntilGlitch() and UpdateFrameStatistics() for details.
58 //
59 // In cases of non-integer cadence, the algorithm will fall back to choosing the
60 // frame which covers the most of the current render interval. If no frame
61 // covers the current interval, the least bad frame will be chosen based on its
62 // drift from the start of the interval.
63 //
64 // Combined these three approaches enforce optimal smoothness in many cases.
65 class MEDIA_EXPORT VideoRendererAlgorithm {
66 public:
67 // Used to convert a media timestamp into wall clock time.
68 using TimeConverterCB = base::Callback<base::TimeTicks(base::TimeDelta)>;
69
70 explicit VideoRendererAlgorithm(const TimeConverterCB& time_converter_cb);
71 ~VideoRendererAlgorithm();
72
73 // Chooses the best frame for the interval [deadline_min, deadline_max] based
74 // on available and previously rendered frames.
75 //
76 // The deadline interval provided to a Render() call should be adjacent to the
xhwang 2015/04/16 22:30:45 tiny nit: Since this is part of the API, it would
DaleCurtis 2015/04/17 02:38:12 Actually, I forgot Brian told me the other day tha
DaleCurtis 2015/04/18 01:29:21 Done.
77 // deadline given to the previous Render() call. Gaps which exceed the length
78 // of the deadline interval are assumed to be repeated frames for the purposes
79 // of cadence detection.
80 //
81 // If provided, |frames_dropped| will be set to the number of frames which
82 // were removed from |frame_queue_|, during this call, which were never
xhwang 2015/04/16 22:30:45 Does this include the frame dropped during Enqueue
DaleCurtis 2015/04/17 02:38:12 There are no frames dropped during EnqueueFrame. D
xhwang 2015/04/17 04:02:14 I was referring to the comment at l.104-105: "Fram
DaleCurtis 2015/04/17 04:15:55 Ah, my bad! That is confusing. They are counted he
DaleCurtis 2015/04/18 01:29:21 Done.
83 // returned during a previous Render() call and are no longer suitable for
84 // rendering since their wall clock display time is too far in the past.
85 scoped_refptr<VideoFrame> Render(base::TimeTicks deadline_min,
86 base::TimeTicks deadline_max,
xhwang 2015/04/16 22:30:45 We can iterate on this in a later CL if you prefer
DaleCurtis 2015/04/18 01:29:21 Yeah, lets handle this in another CL. I'm starting
87 int* frames_dropped);
88
89 // Removes all video frames which are unusable since their display interval
90 // [timestamp, timestamp + duration] is too far away from |deadline_min| than
91 // allowed by drift constraints.
92 //
93 // At least one frame will always remain after this call so that subsequent
94 // Render() calls have a frame to return if no new frames are enqueued before
95 // then. Returns the number of frames expired.
xhwang 2015/04/16 22:30:45 nit: s/expired/removed
DaleCurtis 2015/04/18 01:29:21 Done.
96 int RemoveExpiredFrames(base::TimeTicks deadline_min);
xhwang 2015/04/16 22:30:45 nit: How about just return a size_t to be consiste
DaleCurtis 2015/04/18 01:29:21 This spirals out into a couple of of size_t conver
97
98 // Clients should call this if the last frame provided by Render() was never
99 // rendered; it ensures the presented cadence matches internal models. This
100 // must be called before the next Render() call.
101 void OnLastFrameDropped();
102
103 // Adds a frame to |frame_queue_| for consideration by Render(). Out of order
104 // timestamp will be sorted into appropriate order. Frames inserted prior to
105 // the last rendered frame will be dropped. Do not enqueue end of stream
106 // frames.
107 void EnqueueFrame(const scoped_refptr<VideoFrame>& frame);
108
109 // Removes all frames from the |frame_queue_| and clears predictors. The
110 // algorithm will be as if freshly constructed after this call.
111 void Reset();
112
113 // Returns the number of frames currently buffered which could be rendered
114 // assuming current Render() interval trends. Before Render() is called or if
115 // no cadence pattern is detected, this will be the same as the number of
116 // frames given to EnqueueFrame().
xhwang 2015/04/16 22:30:45 What about a super old frame was enqueued and ther
DaleCurtis 2015/04/17 02:38:12 It does not count, in the implementation you can s
117 //
118 // If a cadence has been identified, this will return the number of frames
119 // which have a non-zero ideal render count.
xhwang 2015/04/16 22:30:45 What is "non-zero ideal render count"?
DaleCurtis 2015/04/17 02:38:12 A positive render count :) I guess I need to expla
xhwang 2015/04/17 06:18:32 Thinking wildly... I wonder why we need to treat
DaleCurtis 2015/04/17 16:47:31 Very astute observation! You're correct and that's
120 size_t EffectiveFramesQueued() const;
xhwang 2015/04/16 22:30:45 nit: Add an empty line here.
DaleCurtis 2015/04/18 01:29:21 Done.
121 size_t frames_queued() const { return frame_queue_.size(); }
122
123 // Returns the average of the display duration of all frames in |frame_queue_|
124 // as measured in wall clock (not media) time.
125 base::TimeDelta average_frame_duration() const { return frame_duration_; }
126
127 private:
128 friend class VideoRendererAlgorithmTest;
129
130 enum {
131 // The determination of whether to clamp to a given cadence is based on the
132 // number of seconds before a frame would have to be dropped or repeated to
133 // compensate for reaching the maximum acceptable drift.
134 //
135 // We've chosen 8 seconds based on practical observations and the fact that
136 // it allows 29.9fps and 59.94fps in 60Hz and vice versa.
137 //
138 // Most users will not be able to see a single frame repeated or dropped
139 // every 8 seconds and certainly should notice it less than the randomly
140 // variable frame durations.
141 kMinimumAcceptableTimeBetweenGlitchesSecs = 8
xhwang 2015/04/16 22:30:45 See comment in the previous PS.
142 };
143
144 // Updates the display count for the last rendered frame based on the number
145 // of missing intervals between Render() calls.
146 void AccountForMissedIntervals(base::TimeTicks deadline_min,
147 base::TimeTicks deadline_max);
148
149 // Calculates how long until |max_acceptable_drift_| would be exhausted by
150 // showing a frame for |clamped_cadence| render intervals instead of for the
151 // ideal |perfect_cadence| intervals.
152 //
153 // As mentioned in the introduction, |perfect_cadence| is the ratio of the
154 // frame duration to render interval length; while |clamped_cadence| is the
155 // nearest integer value to |perfect_cadence|. When computing a fractional
xhwang 2015/04/16 22:30:45 In this case, do we need to pass in |clamped_caden
DaleCurtis 2015/04/17 02:38:12 This function is first passed |frame_duration| / |
156 // cadence (1/|perfect_cadence|), |fractional| must be set to true to ensure
157 // the rendered and actual frame durations are computed correctly.
xhwang 2015/04/16 22:30:45 So when |fractional| is true, we must have |perfec
DaleCurtis 2015/04/17 02:38:12 No, this isn't true, this function powers two calc
158 //
159 // In practice this works out to the following for common setups if the
160 // |clamped_cadence| is used for rendering:
161 //
162 // 29.5fps in 60Hz, ~17ms max drift => exhausted in ~1 second.
163 // 29.9fps in 60Hz, ~17ms max drift => exhausted in ~16.4 seconds.
164 // 24fps in 60Hz, ~21ms max drift => exhausted in ~0.15 seconds.
165 // 25fps in 60Hz, 20ms max drift => exhausted in ~4.0 seconds.
166 // 59.9fps in 60Hz, ~8.3ms max drift => exhausted in ~8.2 seconds.
167 // 24.9fps in 50Hz, ~20ms max drift => exhausted in ~20.5 seconds.
168 // 120fps in 59.9Hz, ~8.3ms max drift => exhausted in ~8.2 seconds.
169 //
170 base::TimeDelta CalculateTimeUntilGlitch(double perfect_cadence,
171 double clamped_cadence,
172 bool fractional);
173
174 // Updates the display count and wall clock timestamps for all frames in
175 // |frame_queue_|. Returns false if statistics can't be updated at this time;
176 // which can occur if media time has stopped. Sets |ideal_cadence_| to a non
177 // zero value if an integer cadence was detected.
178 bool UpdateFrameStatistics();
179
180 // Updates the ideal display count for all frames in |frame_queue_| based on
181 // the given |fractional_cadence|. The first of every |fractional_cadence|
182 // frames is given a display count of one, the rest are given zero. The first
183 // frame is determined by |last_frame_index_|.
184 void UpdateFractionalCadenceForFrames(int fractional_cadence);
185
186 // If |ideal_cadence_| is non-zero and handles cases where the last frame is
187 // under cadence or exactly on cadence. Returns -1 if the last frame is above
188 // cadence or there is no |ideal_cadence_|.
189 int FindBestFrameByCadence();
190
191 // Iterates over |frame_queue_| and finds the frame which covers the most of
192 // the deadline interval. If multiple frames have coverage of the interval,
193 // |second_best| will be set to the index of the frame with the next highest
194 // coverage. Returns -1 if no frame has any coverage of the current interval.
195 //
196 // Prefers the earliest frame if multiple frames have similar coverage (within
197 // a few percent of each other).
198 int FindBestFrameByCoverage(base::TimeTicks deadline_min,
199 base::TimeTicks deadline_max,
200 int* second_best);
201
202 // Iterates over |frame_queue_| and find the frame which drifts the least from
203 // |deadline_min|. There's always a best frame by drift, so the return value
204 // is always a valid frame index.
205 int FindBestFrameByDrift(base::TimeTicks deadline_min);
206
207 // Calculates the drift from |deadline_min| for the given |frame_index|. If
208 // the [wall_clock_time, wall_clock_time + frame_duration_] lies before
209 // |deadline_min| the drift is the delta between |deadline_min| and
210 // |wall_clock_time + frame_duration_|. If the frame overlaps |deadline_min|
211 // the drift is zero. If the frame lies after |deadline_min| the drift is the
212 // delta between |deadline_min| and |wall_clock_time|.
213 base::TimeDelta CalculateDriftForFrame(base::TimeTicks deadline_min,
214 int frame_index);
215
216 struct ReadyFrame {
217 ReadyFrame(const scoped_refptr<VideoFrame>& frame);
218 ~ReadyFrame();
219
220 scoped_refptr<VideoFrame> frame;
221
222 base::TimeDelta media_timestamp;
223 base::TimeTicks wall_clock_time;
224 int ideal_render_count;
225 int render_count;
226
227 // For use with std::lower_bound.
228 bool operator<(const ReadyFrame& other) const;
229 };
230
231 // Queue of incoming frames waiting for rendering.
232 using VideoFrameQueue = std::deque<ReadyFrame>;
233 VideoFrameQueue frame_queue_;
234
235 // The index of the last frame rendered; presumed to be the first frame if no
236 // frame has been rendered yet. Updated by Render() and EnqueueFrame() if any
237 // frames are added or removed.
238 //
239 // In most cases this value is zero, but when out of order timestamps are
240 // present, the last displayed frame may be moved.
241 int last_frame_index_;
242
243 // The idealized cadence for all frames seen thus far; updated based upon the
244 // |frame_duration_| relative to the deadline interval provided to Render().
245 // Zero if no integer cadence could be detected.
246 //
247 // Fractional cadences are handled by strongly preferring the first frame in
248 // a series if it fits within acceptable drift. E.g., with 120fps content on
249 // a 60Hz monitor we'll strongly prefer the first frame of every 2 frames.
250 //
251 // |fractional_cadence_| is the number of frames per render interval; the
252 // first of which would be displayed and the rest dropped.
253 int ideal_cadence_;
254 int fractional_cadence_;
255
256 // Used as hysteresis to prevent oscillation between cadence and coverage
257 // based rendering methods.
258 int last_detected_cadence_;
259 int render_intervals_cadence_held_;
260 bool cadence_hysteresis_enabled_;
261
262 // Indicates if any calls to Render() have successfully yielded a frame yet.
263 bool have_rendered_frames_;
264
265 // Callback used to convert media timestamps into wall clock timestamps.
266 TimeConverterCB time_converter_cb_;
267
268 // The last |deadline_max| provided to Render(), used to predict whether
269 // frames were displayed over cadence between Render() calls.
270 base::TimeTicks last_deadline_max_;
271
272 // The average of the display duration of all frames in |frame_queue_| as
273 // measured in wall clock (not media) time at the time of the last Render().
274 base::TimeDelta frame_duration_;
xhwang 2015/04/16 22:30:45 nit: s/frame_duration_/average_frame_duration/
DaleCurtis 2015/04/18 01:29:21 Done.
275
276 // The length of the last deadline interval given to Render(), updated at the
277 // start of Render().
278 base::TimeDelta render_interval_;
279
280 // The maximum acceptable drift before a frame can no longer be considered for
281 // rendering within a given interval.
282 base::TimeDelta max_acceptable_drift_;
283
284 // Indicates that the last call to Render() experienced a rendering glitch; it
285 // may have: under-displayed a frame, over-displayed a frame, dropped one or
286 // more frames, or chosen a frame which exceeded acceptable drift.
287 bool last_render_had_glitch_;
288
289 DISALLOW_COPY_AND_ASSIGN(VideoRendererAlgorithm);
290 };
291
292 } // namespace media
293
294 #endif // MEDIA_FILTERS_VIDEO_RENDERER_ALGORITHM_H_
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698