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/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 with the goal of providing a smooth playback experience. I.e., the | |
21 // selection process results in the best possible uniformity for displayed frame | |
22 // 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 30fps 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.974fps 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 the | |
56 // frame duration is shorter than the render interval; e.g. 120fps in 60Hz. In | |
57 // this case, the first frame in each group of N frames is displayed once, while | |
58 // the next N - 1 frames are dropped; where N is the fractional cadence of the | |
59 // frame group. Using the previous example N = 120/60 = 2. See implementations | |
60 // of CalculateTimeUntilGlitch() and UpdateFrameStatistics() for more details. | |
61 // | |
62 // In cases of non-integer cadence, the algorithm will fall back to choosing the | |
63 // frame which covers the most of the current render interval. If no frame | |
64 // covers the current interval, the least bad frame will be chosen based on its | |
65 // drift from the start of the interval. | |
66 // | |
67 // Combined these three approaches enforce optimal smoothness in many cases. | |
68 class MEDIA_EXPORT VideoRendererAlgorithm { | |
69 public: | |
70 // Used to convert a media timestamp into wall clock time. | |
71 using TimeConverterCB = base::Callback<base::TimeTicks(base::TimeDelta)>; | |
72 | |
73 explicit VideoRendererAlgorithm(const TimeConverterCB& time_converter_cb); | |
74 ~VideoRendererAlgorithm(); | |
75 | |
76 // Chooses the best frame for the interval [deadline_min, deadline_max] based | |
77 // on available and previously rendered frames. | |
78 // | |
79 // Under ideal circumstances the deadline interval provided to a Render() call | |
80 // should be directly adjacent to the deadline given to the previous Render() | |
81 // call with no overlap or gaps. In practice, |deadline_max| is an estimated | |
82 // value, which means the next |deadline_min| may overlap it slightly or have | |
83 // a slight gap. Gaps which exceed the length of the deadline interval are | |
84 // assumed to be repeated frames for the purposes of cadence detection. | |
85 // | |
86 // If provided, |frames_dropped| will be set to the number of frames which | |
87 // were removed from |frame_queue_|, during this call, which were never | |
88 // returned during a previous Render() call and are no longer suitable for | |
89 // rendering since their wall clock time is too far in the past. | |
90 scoped_refptr<VideoFrame> Render(base::TimeTicks deadline_min, | |
91 base::TimeTicks deadline_max, | |
92 size_t* frames_dropped); | |
93 | |
94 // Removes all video frames which are unusable since their ideal render | |
95 // interval [timestamp, timestamp + duration] is too far away from | |
96 // |deadline_min| than is allowed by drift constraints. | |
97 // | |
98 // At least one frame will always remain after this call so that subsequent | |
99 // Render() calls have a frame to return if no new frames are enqueued before | |
100 // then. Returns the number of frames removed. | |
101 size_t RemoveExpiredFrames(base::TimeTicks deadline_min); | |
102 | |
103 // Clients should call this if the last frame provided by Render() was never | |
104 // rendered; it ensures the presented cadence matches internal models. This | |
105 // must be called before the next Render() call. | |
106 void OnLastFrameDropped(); | |
107 | |
108 // Adds a frame to |frame_queue_| for consideration by Render(). Out of order | |
109 // timestamps will be sorted into appropriate order. Do not enqueue end of | |
110 // stream frames. Frames inserted prior to the last rendered frame will not | |
111 // be used. They will be discarded on the next call to Render(), counting as | |
112 // dropped frames, or by RemoveExpiredFrames(), counting as expired frames. | |
113 void EnqueueFrame(const scoped_refptr<VideoFrame>& frame); | |
114 | |
115 // Removes all frames from the |frame_queue_| and clears predictors. The | |
116 // algorithm will be as if freshly constructed after this call. | |
117 void Reset(); | |
118 | |
119 // Returns the number of frames currently buffered which could be rendered | |
120 // assuming current Render() interval trends. Before Render() is called or if | |
121 // no cadence pattern is detected, this will be the same as the number of | |
122 // frames given to EnqueueFrame(). | |
123 // | |
124 // If a cadence has been identified, this will return the number of frames | |
125 // which have a non-zero ideal render count. | |
126 size_t EffectiveFramesQueued() const; | |
127 | |
128 size_t frames_queued() const { return frame_queue_.size(); } | |
129 | |
130 // Returns the average of the duration of all frames in |frame_queue_| | |
131 // as measured in wall clock (not media) time. | |
132 base::TimeDelta average_frame_duration() const { | |
133 return average_frame_duration_; | |
134 } | |
135 | |
136 private: | |
137 friend class VideoRendererAlgorithmTest; | |
138 | |
139 // The determination of whether to clamp to a given cadence is based on the | |
140 // number of seconds before a frame would have to be dropped or repeated to | |
141 // compensate for reaching the maximum acceptable drift. | |
142 // | |
143 // We've chosen 8 seconds based on practical observations and the fact that it | |
144 // allows 29.9fps and 59.94fps in 60Hz and vice versa. | |
145 // | |
146 // Most users will not be able to see a single frame repeated or dropped every | |
147 // 8 seconds and certainly should notice it less than the randomly variable | |
148 // frame durations. | |
149 static const int kMinimumAcceptableTimeBetweenGlitchesSecs = 8; | |
150 | |
151 // Updates the render count for the last rendered frame based on the number | |
152 // of missing intervals between Render() calls. | |
153 void AccountForMissedIntervals(base::TimeTicks deadline_min, | |
154 base::TimeTicks deadline_max); | |
155 | |
156 // Calculates how long until |max_acceptable_drift_| would be exhausted by | |
157 // showing a frame for |clamped_cadence| render intervals instead of for the | |
158 // ideal |perfect_cadence| intervals. | |
159 // | |
160 // As mentioned in the introduction, |perfect_cadence| is the ratio of the | |
161 // frame duration to render interval length; while |clamped_cadence| is the | |
162 // nearest integer value to |perfect_cadence|. When computing a fractional | |
163 // cadence (1/|perfect_cadence|), |fractional| must be set to true to ensure | |
164 // the rendered and actual frame durations are computed correctly. | |
165 // | |
166 // In practice this works out to the following for common setups if the | |
167 // |clamped_cadence| is used for rendering: | |
168 // | |
169 // 29.5fps in 60Hz, ~17ms max drift => exhausted in ~1 second. | |
170 // 29.9fps in 60Hz, ~17ms max drift => exhausted in ~16.4 seconds. | |
171 // 24fps in 60Hz, ~21ms max drift => exhausted in ~0.15 seconds. | |
172 // 25fps in 60Hz, 20ms max drift => exhausted in ~4.0 seconds. | |
173 // 59.9fps in 60Hz, ~8.3ms max drift => exhausted in ~8.2 seconds. | |
174 // 24.9fps in 50Hz, ~20ms max drift => exhausted in ~20.5 seconds. | |
175 // 120fps in 59.9Hz, ~8.3ms max drift => exhausted in ~8.2 seconds. | |
xhwang
2015/04/22 06:11:38
Can we add UMA for the real fps/vsync combinations
DaleCurtis
2015/04/23 21:45:41
Absolutely.
| |
176 // | |
177 base::TimeDelta CalculateTimeUntilGlitch(double perfect_cadence, | |
178 double clamped_cadence, | |
179 bool fractional); | |
180 | |
181 // Updates the render count and wall clock timestamps for all frames in | |
182 // |frame_queue_|. Returns false if statistics can't be updated at this time; | |
183 // which can occur if media time has stopped. Sets |ideal_cadence_| to a non | |
184 // zero value if an integer cadence was detected. | |
185 bool UpdateFrameStatistics(); | |
186 | |
187 // Updates the ideal render count for all frames in |frame_queue_| based on | |
188 // the given |fractional_cadence|. The first of every |fractional_cadence| | |
189 // frames is given a render count of one, the rest are given zero. The first | |
190 // frame is determined by |last_frame_index_|. | |
191 void UpdateFractionalCadenceForFrames(int fractional_cadence); | |
192 | |
193 // If |ideal_cadence_| is non-zero and handles cases where the last frame is | |
194 // under cadence or exactly on cadence. Returns -1 if not enough frames are | |
miu
2015/04/21 07:04:22
nit: The first sentence of this comment is incompl
DaleCurtis
2015/04/23 21:45:41
Edited.
| |
195 // available for cadence selection or there is no |ideal_cadence_|. Will | |
196 // adjust the selected frame's ideal render count if the last rendered frame | |
197 // has been over selected. | |
198 int FindBestFrameByCadence(); | |
199 | |
200 // Similar to FindBestFrameByCadence(), but instead of adjusting the last | |
201 // rendered frame's ideal render count in the case over selection, optionally | |
miu
2015/04/21 07:04:22
nit: s/in the case over/in the case of over/
DaleCurtis
2015/04/23 21:45:41
Done.
| |
202 // returns the new ideal render count via |adjusted_ideal_render_count|. | |
203 int FindBestFrameByCadenceInternal(int* adjusted_ideal_render_count) const; | |
204 | |
205 // Iterates over |frame_queue_| and finds the frame which covers the most of | |
206 // the deadline interval. If multiple frames have coverage of the interval, | |
207 // |second_best| will be set to the index of the frame with the next highest | |
208 // coverage. Returns -1 if no frame has any coverage of the current interval. | |
209 // | |
210 // Prefers the earliest frame if multiple frames have similar coverage (within | |
211 // a few percent of each other). | |
212 int FindBestFrameByCoverage(base::TimeTicks deadline_min, | |
213 base::TimeTicks deadline_max, | |
214 int* second_best); | |
215 | |
216 // Iterates over |frame_queue_| and find the frame which drifts the least from | |
217 // |deadline_min|. There's always a best frame by drift, so the return value | |
218 // is always a valid frame index. | |
219 int FindBestFrameByDrift(base::TimeTicks deadline_min); | |
220 | |
221 // Calculates the drift from |deadline_min| for the given |frame_index|. If | |
222 // the [wall_clock_time, wall_clock_time + average_frame_duration_] lies | |
223 // before |deadline_min| the drift is the delta between |deadline_min| and | |
224 // |wall_clock_time + average_frame_duration_|. If the frame overlaps | |
225 // |deadline_min| the drift is zero. If the frame lies after |deadline_min| | |
226 // the drift is the delta between |deadline_min| and |wall_clock_time|. | |
227 base::TimeDelta CalculateDriftForFrame(base::TimeTicks deadline_min, | |
miu
2015/04/21 07:04:22
Since the values returned are absolute drift (i.e.
DaleCurtis
2015/04/23 21:45:41
Done.
| |
228 int frame_index); | |
229 | |
230 struct ReadyFrame { | |
miu
2015/04/21 07:04:22
style nit: Types should be defined at the top of t
DaleCurtis
2015/04/23 21:45:41
Done.
| |
231 ReadyFrame(const scoped_refptr<VideoFrame>& frame); | |
232 ~ReadyFrame(); | |
233 | |
234 scoped_refptr<VideoFrame> frame; | |
235 | |
236 base::TimeDelta media_timestamp; | |
miu
2015/04/21 07:04:22
Please remove this duplicate field. It doesn't se
DaleCurtis
2015/04/23 21:45:41
Done.
| |
237 base::TimeTicks wall_clock_time; | |
238 int ideal_render_count; | |
239 int render_count; | |
240 | |
241 // For use with std::lower_bound. | |
242 bool operator<(const ReadyFrame& other) const; | |
xhwang
2015/04/22 06:11:38
style nit: move to before member variables?
DaleCurtis
2015/04/23 21:45:41
Done.
| |
243 }; | |
244 | |
245 // Queue of incoming frames waiting for rendering. | |
246 using VideoFrameQueue = std::deque<ReadyFrame>; | |
247 VideoFrameQueue frame_queue_; | |
248 | |
249 // The index of the last frame rendered; presumed to be the first frame if no | |
250 // frame has been rendered yet. Updated by Render() and EnqueueFrame() if any | |
251 // frames are added or removed. | |
252 // | |
253 // In most cases this value is zero, but when out of order timestamps are | |
254 // present, the last rendered frame may be moved. | |
255 size_t last_frame_index_; | |
256 | |
257 // The idealized cadence for all frames seen thus far; updated based upon the | |
258 // |average_frame_duration_| relative to the deadline interval provided to | |
259 // Render(). Zero if no integer cadence could be detected. | |
260 // | |
261 // Fractional cadences are handled by strongly preferring the first frame in | |
262 // a series if it fits within acceptable drift. E.g., with 120fps content on | |
263 // a 60Hz monitor we'll strongly prefer the first frame of every 2 frames. | |
264 // | |
265 // |fractional_cadence_| is the number of frames per render interval; the | |
266 // first of which would be rendered and the rest dropped. | |
267 int ideal_cadence_; | |
268 int fractional_cadence_; | |
269 | |
270 // Used as hysteresis to prevent oscillation between cadence and coverage | |
271 // based rendering methods. | |
272 int last_detected_cadence_; | |
273 int render_intervals_cadence_held_; | |
274 bool cadence_hysteresis_enabled_; | |
275 | |
276 // Indicates if any calls to Render() have successfully yielded a frame yet. | |
277 bool have_rendered_frames_; | |
278 | |
279 // Callback used to convert media timestamps into wall clock timestamps. | |
280 TimeConverterCB time_converter_cb_; | |
miu
2015/04/21 07:04:22
nit: Type should be "const TimeConverterCB" since
DaleCurtis
2015/04/23 21:45:41
Done.
| |
281 | |
282 // The last |deadline_max| provided to Render(), used to predict whether | |
283 // frames were rendered over cadence between Render() calls. | |
284 base::TimeTicks last_deadline_max_; | |
285 | |
286 // The average of the duration of all frames in |frame_queue_| as measured in | |
287 // wall clock (not media) time at the time of the last Render(). | |
288 base::TimeDelta average_frame_duration_; | |
289 | |
290 // The length of the last deadline interval given to Render(), updated at the | |
291 // start of Render(). | |
292 base::TimeDelta render_interval_; | |
293 | |
294 // The maximum acceptable drift before a frame can no longer be considered for | |
295 // rendering within a given interval. | |
296 base::TimeDelta max_acceptable_drift_; | |
297 | |
298 // Indicates that the last call to Render() experienced a rendering glitch; it | |
299 // may have: under-rendered a frame, over-rendered a frame, dropped one or | |
300 // more frames, or chosen a frame which exceeded acceptable drift. | |
301 bool last_render_had_glitch_; | |
miu
2015/04/21 07:04:22
This doesn't need to be a member variable. It can
DaleCurtis
2015/04/23 21:45:41
It does if I don't want to pollute the Render() in
| |
302 | |
303 DISALLOW_COPY_AND_ASSIGN(VideoRendererAlgorithm); | |
304 }; | |
305 | |
306 } // namespace media | |
307 | |
308 #endif // MEDIA_FILTERS_VIDEO_RENDERER_ALGORITHM_H_ | |
OLD | NEW |