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

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: Moar tests! 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/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_
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698