OLD | NEW |
---|---|
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "media/filters/video_renderer_impl.h" | 5 #include "media/filters/video_renderer_impl.h" |
6 | 6 |
7 #include "base/bind.h" | 7 #include "base/bind.h" |
8 #include "base/callback.h" | 8 #include "base/callback.h" |
9 #include "base/callback_helpers.h" | 9 #include "base/callback_helpers.h" |
10 #include "base/debug/trace_event.h" | 10 #include "base/debug/trace_event.h" |
11 #include "base/location.h" | 11 #include "base/location.h" |
12 #include "base/single_thread_task_runner.h" | 12 #include "base/single_thread_task_runner.h" |
13 #include "base/threading/platform_thread.h" | 13 #include "base/threading/platform_thread.h" |
14 #include "media/base/buffers.h" | 14 #include "media/base/buffers.h" |
15 #include "media/base/limits.h" | 15 #include "media/base/limits.h" |
16 #include "media/base/pipeline.h" | 16 #include "media/base/pipeline.h" |
17 #include "media/base/video_frame.h" | 17 #include "media/base/video_frame.h" |
18 | 18 |
19 namespace media { | 19 namespace media { |
20 | 20 |
21 base::TimeDelta VideoRendererImpl::kMaxLastFrameDuration() { | 21 base::TimeDelta VideoRendererImpl::kMaxLastFrameDuration() { |
22 return base::TimeDelta::FromMilliseconds(250); | 22 return base::TimeDelta::FromMilliseconds(250); |
23 } | 23 } |
24 | 24 |
25 VideoRendererImpl::VideoRendererImpl( | 25 VideoRendererImpl::VideoRendererImpl( |
26 const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, | 26 const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, |
27 scoped_ptr<VideoFrameScheduler> scheduler, | |
27 ScopedVector<VideoDecoder> decoders, | 28 ScopedVector<VideoDecoder> decoders, |
28 const SetDecryptorReadyCB& set_decryptor_ready_cb, | 29 const SetDecryptorReadyCB& set_decryptor_ready_cb) |
29 const PaintCB& paint_cb, | |
30 bool drop_frames) | |
31 : task_runner_(task_runner), | 30 : task_runner_(task_runner), |
31 scheduler_(scheduler.Pass()), | |
32 video_frame_stream_(task_runner, decoders.Pass(), set_decryptor_ready_cb), | 32 video_frame_stream_(task_runner, decoders.Pass(), set_decryptor_ready_cb), |
33 received_end_of_stream_(false), | 33 received_end_of_stream_(false), |
34 frame_available_(&lock_), | |
35 state_(kUninitialized), | 34 state_(kUninitialized), |
36 thread_(), | |
37 pending_read_(false), | 35 pending_read_(false), |
38 drop_frames_(drop_frames), | |
39 playback_rate_(0), | 36 playback_rate_(0), |
40 paint_cb_(paint_cb), | |
41 last_timestamp_(kNoTimestamp()), | |
42 frames_decoded_(0), | 37 frames_decoded_(0), |
43 frames_dropped_(0), | 38 frames_dropped_(0), |
44 weak_factory_(this) { | 39 weak_factory_(this) {} |
45 DCHECK(!paint_cb_.is_null()); | |
46 } | |
47 | 40 |
48 VideoRendererImpl::~VideoRendererImpl() { | 41 VideoRendererImpl::~VideoRendererImpl() { |
49 base::AutoLock auto_lock(lock_); | |
50 CHECK(state_ == kStopped || state_ == kUninitialized) << state_; | 42 CHECK(state_ == kStopped || state_ == kUninitialized) << state_; |
51 CHECK(thread_.is_null()); | |
52 } | 43 } |
53 | 44 |
54 void VideoRendererImpl::Play(const base::Closure& callback) { | 45 void VideoRendererImpl::Play(const base::Closure& callback) { |
55 DCHECK(task_runner_->BelongsToCurrentThread()); | 46 DCHECK(task_runner_->BelongsToCurrentThread()); |
56 base::AutoLock auto_lock(lock_); | |
57 DCHECK_EQ(kPrerolled, state_); | 47 DCHECK_EQ(kPrerolled, state_); |
58 state_ = kPlaying; | 48 state_ = kPlaying; |
49 | |
50 ScheduleVideoFrames(); | |
51 | |
52 // Changing from non-playing to playing state can trigger ended. | |
53 if (ShouldTransitionToEnded()) | |
54 TransitionToEnded(); | |
55 | |
59 callback.Run(); | 56 callback.Run(); |
60 } | 57 } |
61 | 58 |
62 void VideoRendererImpl::Pause(const base::Closure& callback) { | 59 void VideoRendererImpl::Pause(const base::Closure& callback) { |
63 DCHECK(task_runner_->BelongsToCurrentThread()); | 60 DCHECK(task_runner_->BelongsToCurrentThread()); |
64 base::AutoLock auto_lock(lock_); | |
65 DCHECK(state_ != kUninitialized || state_ == kError); | 61 DCHECK(state_ != kUninitialized || state_ == kError); |
62 scheduler_->Reset(); | |
66 state_ = kPaused; | 63 state_ = kPaused; |
67 callback.Run(); | 64 callback.Run(); |
68 } | 65 } |
69 | 66 |
70 void VideoRendererImpl::Flush(const base::Closure& callback) { | 67 void VideoRendererImpl::Flush(const base::Closure& callback) { |
71 DCHECK(task_runner_->BelongsToCurrentThread()); | 68 DCHECK(task_runner_->BelongsToCurrentThread()); |
72 base::AutoLock auto_lock(lock_); | |
73 DCHECK_EQ(state_, kPaused); | 69 DCHECK_EQ(state_, kPaused); |
74 flush_cb_ = callback; | 70 flush_cb_ = callback; |
75 state_ = kFlushing; | 71 state_ = kFlushing; |
76 | 72 |
73 scheduler_->Reset(); | |
74 | |
77 // This is necessary if the |video_frame_stream_| has already seen an end of | 75 // This is necessary if the |video_frame_stream_| has already seen an end of |
78 // stream and needs to drain it before flushing it. | 76 // stream and needs to drain it before flushing it. |
79 ready_frames_.clear(); | 77 // ready_frames_.clear(); |
78 unscheduled_frames_.clear(); | |
79 // XXX what about scheduled_frames_? | |
scherkus (not reviewing)
2014/04/16 23:45:02
I'm pretty sure we may have to wait for all frames
| |
80 | |
80 received_end_of_stream_ = false; | 81 received_end_of_stream_ = false; |
81 video_frame_stream_.Reset( | 82 video_frame_stream_.Reset( |
82 base::Bind(&VideoRendererImpl::OnVideoFrameStreamResetDone, | 83 base::Bind(&VideoRendererImpl::OnVideoFrameStreamResetDone, |
83 weak_factory_.GetWeakPtr())); | 84 weak_factory_.GetWeakPtr())); |
84 } | 85 } |
85 | 86 |
86 void VideoRendererImpl::Stop(const base::Closure& callback) { | 87 void VideoRendererImpl::Stop(const base::Closure& callback) { |
87 DCHECK(task_runner_->BelongsToCurrentThread()); | 88 DCHECK(task_runner_->BelongsToCurrentThread()); |
88 base::AutoLock auto_lock(lock_); | |
89 if (state_ == kUninitialized || state_ == kStopped) { | 89 if (state_ == kUninitialized || state_ == kStopped) { |
90 callback.Run(); | 90 callback.Run(); |
91 return; | 91 return; |
92 } | 92 } |
93 | 93 |
94 scheduler_->Reset(); | |
95 | |
94 // TODO(scherkus): Consider invalidating |weak_factory_| and replacing | 96 // TODO(scherkus): Consider invalidating |weak_factory_| and replacing |
95 // task-running guards that check |state_| with DCHECK(). | 97 // task-running guards that check |state_| with DCHECK(). |
96 | 98 |
97 state_ = kStopped; | 99 state_ = kStopped; |
98 | 100 |
99 statistics_cb_.Reset(); | 101 statistics_cb_.Reset(); |
100 max_time_cb_.Reset(); | 102 max_time_cb_.Reset(); |
101 DoStopOrError_Locked(); | |
102 | 103 |
103 // Clean up our thread if present. | 104 unscheduled_frames_.clear(); |
104 base::PlatformThreadHandle thread_to_join = base::PlatformThreadHandle(); | 105 // XXX what about scheduled_frames_? |
105 if (!thread_.is_null()) { | 106 // ready_frames_.clear(); |
106 // Signal the thread since it's possible to get stopped with the video | |
107 // thread waiting for a read to complete. | |
108 frame_available_.Signal(); | |
109 std::swap(thread_, thread_to_join); | |
110 } | |
111 | |
112 if (!thread_to_join.is_null()) { | |
113 base::AutoUnlock auto_unlock(lock_); | |
114 base::PlatformThread::Join(thread_to_join); | |
115 } | |
116 | 107 |
117 video_frame_stream_.Stop(callback); | 108 video_frame_stream_.Stop(callback); |
118 } | 109 } |
119 | 110 |
120 void VideoRendererImpl::SetPlaybackRate(float playback_rate) { | 111 void VideoRendererImpl::SetPlaybackRate(float playback_rate) { |
121 DCHECK(task_runner_->BelongsToCurrentThread()); | 112 DCHECK(task_runner_->BelongsToCurrentThread()); |
122 base::AutoLock auto_lock(lock_); | 113 if (playback_rate_ == playback_rate) |
114 return; | |
115 | |
123 playback_rate_ = playback_rate; | 116 playback_rate_ = playback_rate; |
117 | |
118 scheduler_->Reset(); | |
119 ScheduleVideoFrames(); | |
120 | |
121 // Changing from a zero to a non-zero playback rate can trigger ended. | |
scherkus (not reviewing)
2014/04/16 23:45:02
old code would have us fire ended even if playback
| |
122 if (ShouldTransitionToEnded()) | |
123 TransitionToEnded(); | |
124 } | 124 } |
125 | 125 |
126 void VideoRendererImpl::Preroll(base::TimeDelta time, | 126 void VideoRendererImpl::Preroll(base::TimeDelta time, |
127 const PipelineStatusCB& cb) { | 127 const PipelineStatusCB& cb) { |
128 DCHECK(task_runner_->BelongsToCurrentThread()); | 128 DCHECK(task_runner_->BelongsToCurrentThread()); |
129 base::AutoLock auto_lock(lock_); | |
130 DCHECK(!cb.is_null()); | 129 DCHECK(!cb.is_null()); |
131 DCHECK(preroll_cb_.is_null()); | 130 DCHECK(preroll_cb_.is_null()); |
132 DCHECK(state_ == kFlushed || state_== kPaused) << "state_ " << state_; | 131 DCHECK(state_ == kFlushed || state_== kPaused) << "state_ " << state_; |
133 | 132 |
134 if (state_ == kFlushed) { | 133 if (state_ == kFlushed) { |
135 DCHECK(time != kNoTimestamp()); | 134 DCHECK(time != kNoTimestamp()); |
136 DCHECK(!pending_read_); | 135 DCHECK(!pending_read_); |
137 DCHECK(ready_frames_.empty()); | 136 DCHECK(unscheduled_frames_.empty()); |
137 DCHECK(scheduled_frames_.empty()); | |
138 } else { | 138 } else { |
139 DCHECK(time == kNoTimestamp()); | 139 DCHECK(time == kNoTimestamp()); |
140 } | 140 } |
141 | 141 |
142 state_ = kPrerolling; | 142 state_ = kPrerolling; |
143 preroll_cb_ = cb; | 143 preroll_cb_ = cb; |
144 preroll_timestamp_ = time; | 144 preroll_timestamp_ = time; |
145 | 145 |
146 if (ShouldTransitionToPrerolled_Locked()) { | 146 if (ShouldTransitionToPrerolled()) { |
147 TransitionToPrerolled_Locked(); | 147 TransitionToPrerolled(); |
148 return; | 148 return; |
149 } | 149 } |
150 | 150 |
151 AttemptRead_Locked(); | 151 AttemptRead(); |
152 } | 152 } |
153 | 153 |
154 void VideoRendererImpl::Initialize(DemuxerStream* stream, | 154 void VideoRendererImpl::Initialize(DemuxerStream* stream, |
155 const PipelineStatusCB& init_cb, | 155 const PipelineStatusCB& init_cb, |
156 const StatisticsCB& statistics_cb, | 156 const StatisticsCB& statistics_cb, |
157 const TimeCB& max_time_cb, | 157 const TimeCB& max_time_cb, |
158 const base::Closure& ended_cb, | 158 const base::Closure& ended_cb, |
159 const PipelineStatusCB& error_cb, | 159 const PipelineStatusCB& error_cb, |
160 const TimeDeltaCB& get_time_cb, | 160 const TimeDeltaCB& get_time_cb, |
161 const TimeDeltaCB& get_duration_cb) { | 161 const TimeDeltaCB& get_duration_cb) { |
162 DCHECK(task_runner_->BelongsToCurrentThread()); | 162 DCHECK(task_runner_->BelongsToCurrentThread()); |
163 base::AutoLock auto_lock(lock_); | |
164 DCHECK(stream); | 163 DCHECK(stream); |
165 DCHECK_EQ(stream->type(), DemuxerStream::VIDEO); | 164 DCHECK_EQ(stream->type(), DemuxerStream::VIDEO); |
166 DCHECK(!init_cb.is_null()); | 165 DCHECK(!init_cb.is_null()); |
167 DCHECK(!statistics_cb.is_null()); | 166 DCHECK(!statistics_cb.is_null()); |
168 DCHECK(!max_time_cb.is_null()); | 167 DCHECK(!max_time_cb.is_null()); |
169 DCHECK(!ended_cb.is_null()); | 168 DCHECK(!ended_cb.is_null()); |
170 DCHECK(!get_time_cb.is_null()); | 169 DCHECK(!get_time_cb.is_null()); |
171 DCHECK(!get_duration_cb.is_null()); | 170 DCHECK(!get_duration_cb.is_null()); |
172 DCHECK_EQ(kUninitialized, state_); | 171 DCHECK_EQ(kUninitialized, state_); |
173 | 172 |
174 init_cb_ = init_cb; | 173 init_cb_ = init_cb; |
175 statistics_cb_ = statistics_cb; | 174 statistics_cb_ = statistics_cb; |
176 max_time_cb_ = max_time_cb; | 175 max_time_cb_ = max_time_cb; |
177 ended_cb_ = ended_cb; | 176 ended_cb_ = ended_cb; |
178 error_cb_ = error_cb; | 177 error_cb_ = error_cb; |
179 get_time_cb_ = get_time_cb; | 178 get_time_cb_ = get_time_cb; |
180 get_duration_cb_ = get_duration_cb; | 179 get_duration_cb_ = get_duration_cb; |
181 state_ = kInitializing; | 180 state_ = kInitializing; |
182 | 181 |
183 video_frame_stream_.Initialize( | 182 video_frame_stream_.Initialize( |
184 stream, | 183 stream, |
185 statistics_cb, | 184 statistics_cb, |
186 base::Bind(&VideoRendererImpl::OnVideoFrameStreamInitialized, | 185 base::Bind(&VideoRendererImpl::OnVideoFrameStreamInitialized, |
187 weak_factory_.GetWeakPtr())); | 186 weak_factory_.GetWeakPtr())); |
188 } | 187 } |
189 | 188 |
190 void VideoRendererImpl::OnVideoFrameStreamInitialized(bool success) { | 189 void VideoRendererImpl::OnVideoFrameStreamInitialized(bool success) { |
191 DCHECK(task_runner_->BelongsToCurrentThread()); | 190 DCHECK(task_runner_->BelongsToCurrentThread()); |
192 base::AutoLock auto_lock(lock_); | |
193 | 191 |
194 if (state_ == kStopped) | 192 if (state_ == kStopped) |
195 return; | 193 return; |
196 | 194 |
197 DCHECK_EQ(state_, kInitializing); | 195 DCHECK_EQ(state_, kInitializing); |
198 | 196 |
199 if (!success) { | 197 if (!success) { |
200 state_ = kUninitialized; | 198 state_ = kUninitialized; |
201 base::ResetAndReturn(&init_cb_).Run(DECODER_ERROR_NOT_SUPPORTED); | 199 base::ResetAndReturn(&init_cb_).Run(DECODER_ERROR_NOT_SUPPORTED); |
202 return; | 200 return; |
203 } | 201 } |
204 | 202 |
205 // We're all good! Consider ourselves flushed. (ThreadMain() should never | 203 // We're all good! Consider ourselves flushed. (ThreadMain() should never |
206 // see us in the kUninitialized state). | 204 // see us in the kUninitialized state). |
207 // Since we had an initial Preroll(), we consider ourself flushed, because we | 205 // Since we had an initial Preroll(), we consider ourself flushed, because we |
208 // have not populated any buffers yet. | 206 // have not populated any buffers yet. |
209 state_ = kFlushed; | 207 state_ = kFlushed; |
210 | 208 |
211 // Create our video thread. | |
212 if (!base::PlatformThread::Create(0, this, &thread_)) { | |
213 NOTREACHED() << "Video thread creation failed"; | |
214 state_ = kError; | |
215 base::ResetAndReturn(&init_cb_).Run(PIPELINE_ERROR_INITIALIZATION_FAILED); | |
216 return; | |
217 } | |
218 | |
219 #if defined(OS_WIN) | |
220 // Bump up our priority so our sleeping is more accurate. | |
221 // TODO(scherkus): find out if this is necessary, but it seems to help. | |
222 ::SetThreadPriority(thread_.platform_handle(), THREAD_PRIORITY_ABOVE_NORMAL); | |
223 #endif // defined(OS_WIN) | |
224 base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK); | 209 base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK); |
225 } | 210 } |
226 | 211 |
227 // PlatformThread::Delegate implementation. | |
228 void VideoRendererImpl::ThreadMain() { | |
229 base::PlatformThread::SetName("CrVideoRenderer"); | |
230 | |
231 // The number of milliseconds to idle when we do not have anything to do. | |
232 // Nothing special about the value, other than we're being more OS-friendly | |
233 // than sleeping for 1 millisecond. | |
234 // | |
235 // TODO(scherkus): switch to pure event-driven frame timing instead of this | |
236 // kIdleTimeDelta business http://crbug.com/106874 | |
237 const base::TimeDelta kIdleTimeDelta = | |
238 base::TimeDelta::FromMilliseconds(10); | |
239 | |
240 for (;;) { | |
241 base::AutoLock auto_lock(lock_); | |
242 | |
243 // Thread exit condition. | |
244 if (state_ == kStopped) | |
245 return; | |
246 | |
247 // Remain idle as long as we're not playing. | |
248 if (state_ != kPlaying || playback_rate_ == 0) { | |
249 UpdateStatsAndWait_Locked(kIdleTimeDelta); | |
250 continue; | |
251 } | |
252 | |
253 // Remain idle until we have the next frame ready for rendering. | |
254 if (ready_frames_.empty()) { | |
255 if (received_end_of_stream_) { | |
256 state_ = kEnded; | |
257 ended_cb_.Run(); | |
258 | |
259 // No need to sleep here as we idle when |state_ != kPlaying|. | |
260 continue; | |
261 } | |
262 | |
263 UpdateStatsAndWait_Locked(kIdleTimeDelta); | |
264 continue; | |
265 } | |
266 | |
267 base::TimeDelta remaining_time = | |
268 CalculateSleepDuration(ready_frames_.front(), playback_rate_); | |
269 | |
270 // Sleep up to a maximum of our idle time until we're within the time to | |
271 // render the next frame. | |
272 if (remaining_time.InMicroseconds() > 0) { | |
273 remaining_time = std::min(remaining_time, kIdleTimeDelta); | |
274 UpdateStatsAndWait_Locked(remaining_time); | |
275 continue; | |
276 } | |
277 | |
278 // Deadline is defined as the midpoint between this frame and the next | |
279 // frame, using the delta between this frame and the previous frame as the | |
280 // assumption for frame duration. | |
281 // | |
282 // TODO(scherkus): An improvement over midpoint might be selecting the | |
283 // minimum and/or maximum between the midpoint and some constants. As a | |
284 // thought experiment, consider what would be better than the midpoint | |
285 // for both the 1fps case and 120fps case. | |
286 // | |
287 // TODO(scherkus): This can be vastly improved. Use a histogram to measure | |
288 // the accuracy of our frame timing code. http://crbug.com/149829 | |
289 if (drop_frames_ && last_timestamp_ != kNoTimestamp()) { | |
290 base::TimeDelta now = get_time_cb_.Run(); | |
291 base::TimeDelta deadline = ready_frames_.front()->timestamp() + | |
292 (ready_frames_.front()->timestamp() - last_timestamp_) / 2; | |
293 | |
294 if (now > deadline) { | |
295 DropNextReadyFrame_Locked(); | |
296 continue; | |
297 } | |
298 } | |
299 | |
300 // Congratulations! You've made it past the video frame timing gauntlet. | |
301 // | |
302 // At this point enough time has passed that the next frame that ready for | |
303 // rendering. | |
304 PaintNextReadyFrame_Locked(); | |
305 } | |
306 } | |
307 | |
308 void VideoRendererImpl::PaintNextReadyFrame_Locked() { | |
309 lock_.AssertAcquired(); | |
310 | |
311 scoped_refptr<VideoFrame> next_frame = ready_frames_.front(); | |
312 ready_frames_.pop_front(); | |
313 frames_decoded_++; | |
314 | |
315 last_timestamp_ = next_frame->timestamp(); | |
316 | |
317 paint_cb_.Run(next_frame); | |
318 | |
319 task_runner_->PostTask( | |
320 FROM_HERE, | |
321 base::Bind(&VideoRendererImpl::AttemptRead, weak_factory_.GetWeakPtr())); | |
322 } | |
323 | |
324 void VideoRendererImpl::DropNextReadyFrame_Locked() { | |
325 TRACE_EVENT0("media", "VideoRendererImpl:frameDropped"); | |
326 | |
327 lock_.AssertAcquired(); | |
328 | |
329 last_timestamp_ = ready_frames_.front()->timestamp(); | |
330 ready_frames_.pop_front(); | |
331 frames_decoded_++; | |
332 frames_dropped_++; | |
333 | |
334 task_runner_->PostTask( | |
335 FROM_HERE, | |
336 base::Bind(&VideoRendererImpl::AttemptRead, weak_factory_.GetWeakPtr())); | |
337 } | |
338 | |
339 void VideoRendererImpl::FrameReady(VideoFrameStream::Status status, | 212 void VideoRendererImpl::FrameReady(VideoFrameStream::Status status, |
340 const scoped_refptr<VideoFrame>& frame) { | 213 const scoped_refptr<VideoFrame>& frame) { |
341 base::AutoLock auto_lock(lock_); | 214 LOG(ERROR) << __FUNCTION__ << " eos=" << (frame && frame->end_of_stream()); |
342 DCHECK_NE(state_, kUninitialized); | 215 DCHECK_NE(state_, kUninitialized); |
343 DCHECK_NE(state_, kFlushed); | 216 DCHECK_NE(state_, kFlushed); |
344 | 217 |
345 CHECK(pending_read_); | 218 CHECK(pending_read_); |
346 pending_read_ = false; | 219 pending_read_ = false; |
347 | 220 |
348 if (status == VideoFrameStream::DECODE_ERROR || | 221 if (status == VideoFrameStream::DECODE_ERROR || |
349 status == VideoFrameStream::DECRYPT_ERROR) { | 222 status == VideoFrameStream::DECRYPT_ERROR) { |
350 DCHECK(!frame.get()); | 223 DCHECK(!frame.get()); |
351 PipelineStatus error = PIPELINE_ERROR_DECODE; | 224 PipelineStatus error = PIPELINE_ERROR_DECODE; |
(...skipping 12 matching lines...) Expand all Loading... | |
364 // Already-queued VideoFrameStream ReadCB's can fire after various state | 237 // Already-queued VideoFrameStream ReadCB's can fire after various state |
365 // transitions have happened; in that case just drop those frames immediately. | 238 // transitions have happened; in that case just drop those frames immediately. |
366 if (state_ == kStopped || state_ == kError || state_ == kFlushing) | 239 if (state_ == kStopped || state_ == kError || state_ == kFlushing) |
367 return; | 240 return; |
368 | 241 |
369 if (!frame.get()) { | 242 if (!frame.get()) { |
370 // Abort preroll early for a NULL frame because we won't get more frames. | 243 // Abort preroll early for a NULL frame because we won't get more frames. |
371 // A new preroll will be requested after this one completes so there is no | 244 // A new preroll will be requested after this one completes so there is no |
372 // point trying to collect more frames. | 245 // point trying to collect more frames. |
373 if (state_ == kPrerolling) | 246 if (state_ == kPrerolling) |
374 TransitionToPrerolled_Locked(); | 247 TransitionToPrerolled(); |
375 | 248 |
376 return; | 249 return; |
377 } | 250 } |
378 | 251 |
379 if (frame->end_of_stream()) { | 252 if (frame->end_of_stream()) { |
380 DCHECK(!received_end_of_stream_); | 253 DCHECK(!received_end_of_stream_); |
381 received_end_of_stream_ = true; | 254 received_end_of_stream_ = true; |
382 max_time_cb_.Run(get_duration_cb_.Run()); | 255 max_time_cb_.Run(get_duration_cb_.Run()); |
383 | 256 |
384 if (state_ == kPrerolling) | 257 if (state_ == kPrerolling) |
385 TransitionToPrerolled_Locked(); | 258 TransitionToPrerolled(); |
386 | 259 |
387 return; | 260 return; |
388 } | 261 } |
389 | 262 |
390 // Maintain the latest frame decoded so the correct frame is displayed after | 263 // Maintain the latest frame decoded so the correct frame is displayed after |
391 // prerolling has completed. | 264 // prerolling has completed. |
392 if (state_ == kPrerolling && preroll_timestamp_ != kNoTimestamp() && | 265 if (state_ == kPrerolling && preroll_timestamp_ != kNoTimestamp() && |
393 frame->timestamp() <= preroll_timestamp_) { | 266 frame->timestamp() <= preroll_timestamp_) { |
394 ready_frames_.clear(); | 267 unscheduled_frames_.clear(); |
395 } | 268 } |
396 | 269 |
397 AddReadyFrame_Locked(frame); | |
398 | |
399 if (ShouldTransitionToPrerolled_Locked()) | |
400 TransitionToPrerolled_Locked(); | |
401 | |
402 // Always request more decoded video if we have capacity. This serves two | |
403 // purposes: | |
404 // 1) Prerolling while paused | |
405 // 2) Keeps decoding going if video rendering thread starts falling behind | |
406 AttemptRead_Locked(); | |
407 } | |
408 | |
409 bool VideoRendererImpl::ShouldTransitionToPrerolled_Locked() { | |
410 return state_ == kPrerolling && | |
411 (!video_frame_stream_.CanReadWithoutStalling() || | |
412 ready_frames_.size() >= static_cast<size_t>(limits::kMaxVideoFrames)); | |
413 } | |
414 | |
415 void VideoRendererImpl::AddReadyFrame_Locked( | |
416 const scoped_refptr<VideoFrame>& frame) { | |
417 lock_.AssertAcquired(); | |
418 DCHECK(!frame->end_of_stream()); | |
419 | |
420 // Adjust the incoming frame if its rendering stop time is past the duration | 270 // Adjust the incoming frame if its rendering stop time is past the duration |
421 // of the video itself. This is typically the last frame of the video and | 271 // of the video itself. This is typically the last frame of the video and |
422 // occurs if the container specifies a duration that isn't a multiple of the | 272 // occurs if the container specifies a duration that isn't a multiple of the |
423 // frame rate. Another way for this to happen is for the container to state | 273 // frame rate. Another way for this to happen is for the container to state |
424 // a smaller duration than the largest packet timestamp. | 274 // a smaller duration than the largest packet timestamp. |
425 base::TimeDelta duration = get_duration_cb_.Run(); | 275 base::TimeDelta duration = get_duration_cb_.Run(); |
426 if (frame->timestamp() > duration) { | 276 if (frame->timestamp() > duration) { |
427 frame->set_timestamp(duration); | 277 frame->set_timestamp(duration); |
428 } | 278 } |
429 | 279 |
430 ready_frames_.push_back(frame); | 280 unscheduled_frames_.push_back(frame); |
431 DCHECK_LE(ready_frames_.size(), | 281 DCHECK_LE(video_frame_count(), limits::kMaxVideoFrames); |
432 static_cast<size_t>(limits::kMaxVideoFrames)); | |
433 | 282 |
434 max_time_cb_.Run(frame->timestamp()); | 283 max_time_cb_.Run(frame->timestamp()); |
435 | 284 |
436 // Avoid needlessly waking up |thread_| unless playing. | 285 if (ShouldTransitionToPrerolled()) |
286 TransitionToPrerolled(); | |
287 | |
437 if (state_ == kPlaying) | 288 if (state_ == kPlaying) |
438 frame_available_.Signal(); | 289 ScheduleVideoFrames(); |
290 | |
291 if (ShouldTransitionToEnded()) | |
292 TransitionToEnded(); | |
293 | |
294 // Always request more decoded video if we have capacity. This serves two | |
295 // purposes: | |
296 // 1) Prerolling while paused | |
297 // 2) Keeps decoding going if video rendering thread starts falling behind | |
298 AttemptRead(); | |
299 } | |
300 | |
301 bool VideoRendererImpl::ShouldTransitionToPrerolled() { | |
302 return state_ == kPrerolling && | |
303 (!video_frame_stream_.CanReadWithoutStalling() || | |
304 video_frame_count() >= limits::kMaxVideoFrames); | |
305 } | |
306 | |
307 bool VideoRendererImpl::ShouldTransitionToEnded() { | |
308 LOG(ERROR) << __FUNCTION__ << " state_=" << state_ | |
309 << " rate=" << playback_rate_ | |
310 << " unsched=" << unscheduled_frames_.size() | |
311 << " sched=" << scheduled_frames_.size() | |
312 << " eos=" << received_end_of_stream_; | |
313 return state_ == kPlaying && playback_rate_ != 0.0f && | |
314 unscheduled_frames_.empty() && scheduled_frames_.empty() && | |
315 received_end_of_stream_; | |
316 } | |
317 | |
318 void VideoRendererImpl::TransitionToEnded() { | |
319 DCHECK_EQ(state_, kPlaying); | |
320 state_ = kEnded; | |
321 ended_cb_.Run(); | |
439 } | 322 } |
440 | 323 |
441 void VideoRendererImpl::AttemptRead() { | 324 void VideoRendererImpl::AttemptRead() { |
442 base::AutoLock auto_lock(lock_); | 325 DCHECK(task_runner_->BelongsToCurrentThread()); |
443 AttemptRead_Locked(); | |
444 } | |
445 | 326 |
446 void VideoRendererImpl::AttemptRead_Locked() { | 327 LOG(ERROR) << __FUNCTION__ << " pending_read_=" << pending_read_ |
447 DCHECK(task_runner_->BelongsToCurrentThread()); | 328 << " received_eos_=" << received_end_of_stream_ |
448 lock_.AssertAcquired(); | 329 << " count=" << video_frame_count() << "/" |
330 << limits::kMaxVideoFrames << " state_=" << state_; | |
449 | 331 |
450 if (pending_read_ || received_end_of_stream_ || | 332 if (pending_read_ || received_end_of_stream_ || |
451 ready_frames_.size() == static_cast<size_t>(limits::kMaxVideoFrames)) { | 333 video_frame_count() == limits::kMaxVideoFrames) { |
334 LOG(ERROR) << __FUNCTION__ << " -> did not read"; | |
452 return; | 335 return; |
453 } | 336 } |
454 | 337 |
455 switch (state_) { | 338 switch (state_) { |
456 case kPaused: | 339 case kPaused: |
457 case kPrerolling: | 340 case kPrerolling: |
341 case kPrerolled: | |
scherkus (not reviewing)
2014/04/16 23:45:02
it also turns out that after hitting kPrerolled an
| |
458 case kPlaying: | 342 case kPlaying: |
343 LOG(ERROR) << __FUNCTION__ << " -> did read"; | |
459 pending_read_ = true; | 344 pending_read_ = true; |
460 video_frame_stream_.Read(base::Bind(&VideoRendererImpl::FrameReady, | 345 video_frame_stream_.Read(base::Bind(&VideoRendererImpl::FrameReady, |
461 weak_factory_.GetWeakPtr())); | 346 weak_factory_.GetWeakPtr())); |
462 return; | 347 return; |
463 | 348 |
464 case kUninitialized: | 349 case kUninitialized: |
465 case kInitializing: | 350 case kInitializing: |
466 case kPrerolled: | |
467 case kFlushing: | 351 case kFlushing: |
468 case kFlushed: | 352 case kFlushed: |
469 case kEnded: | 353 case kEnded: |
470 case kStopped: | 354 case kStopped: |
471 case kError: | 355 case kError: |
356 LOG(ERROR) << __FUNCTION__ << " -> did not read"; | |
472 return; | 357 return; |
473 } | 358 } |
474 } | 359 } |
475 | 360 |
476 void VideoRendererImpl::OnVideoFrameStreamResetDone() { | 361 void VideoRendererImpl::OnVideoFrameStreamResetDone() { |
477 base::AutoLock auto_lock(lock_); | |
478 if (state_ == kStopped) | 362 if (state_ == kStopped) |
479 return; | 363 return; |
480 | 364 |
481 DCHECK_EQ(kFlushing, state_); | 365 DCHECK_EQ(kFlushing, state_); |
482 DCHECK(!pending_read_); | 366 DCHECK(!pending_read_); |
483 DCHECK(ready_frames_.empty()); | 367 DCHECK(unscheduled_frames_.empty()); |
368 DCHECK(scheduled_frames_.empty()); | |
484 DCHECK(!received_end_of_stream_); | 369 DCHECK(!received_end_of_stream_); |
485 | 370 |
486 state_ = kFlushed; | 371 state_ = kFlushed; |
487 last_timestamp_ = kNoTimestamp(); | |
488 base::ResetAndReturn(&flush_cb_).Run(); | 372 base::ResetAndReturn(&flush_cb_).Run(); |
489 } | 373 } |
490 | 374 |
491 base::TimeDelta VideoRendererImpl::CalculateSleepDuration( | 375 void VideoRendererImpl::TransitionToPrerolled() { |
492 const scoped_refptr<VideoFrame>& next_frame, | |
493 float playback_rate) { | |
494 // Determine the current and next presentation timestamps. | |
495 base::TimeDelta now = get_time_cb_.Run(); | |
496 base::TimeDelta next_pts = next_frame->timestamp(); | |
497 | |
498 // Scale our sleep based on the playback rate. | |
499 base::TimeDelta sleep = next_pts - now; | |
500 return base::TimeDelta::FromMicroseconds( | |
501 static_cast<int64>(sleep.InMicroseconds() / playback_rate)); | |
502 } | |
503 | |
504 void VideoRendererImpl::DoStopOrError_Locked() { | |
505 lock_.AssertAcquired(); | |
506 last_timestamp_ = kNoTimestamp(); | |
507 ready_frames_.clear(); | |
508 } | |
509 | |
510 void VideoRendererImpl::TransitionToPrerolled_Locked() { | |
511 lock_.AssertAcquired(); | |
512 DCHECK_EQ(state_, kPrerolling); | 376 DCHECK_EQ(state_, kPrerolling); |
513 | 377 |
514 state_ = kPrerolled; | 378 state_ = kPrerolled; |
515 | 379 |
516 // Because we might remain in the prerolled state for an undetermined amount | 380 // Because we might remain in the prerolled state for an undetermined amount |
517 // of time (e.g., we seeked while paused), we'll paint the first prerolled | 381 // of time (e.g., we seeked while paused), we'll paint the first prerolled |
518 // frame. | 382 // frame. |
519 if (!ready_frames_.empty()) | 383 if (!unscheduled_frames_.empty()) { |
520 PaintNextReadyFrame_Locked(); | 384 ScheduleFirstFrameForImmediateDisplay(); |
scherkus (not reviewing)
2014/04/16 23:45:02
FYI here's the bit where we only display that firs
| |
385 AttemptRead(); | |
386 } | |
521 | 387 |
522 base::ResetAndReturn(&preroll_cb_).Run(PIPELINE_OK); | 388 base::ResetAndReturn(&preroll_cb_).Run(PIPELINE_OK); |
523 } | 389 } |
524 | 390 |
525 void VideoRendererImpl::UpdateStatsAndWait_Locked( | 391 void VideoRendererImpl::ScheduleVideoFrames() { |
526 base::TimeDelta wait_duration) { | 392 if (playback_rate_ == 0) |
527 lock_.AssertAcquired(); | 393 return; |
528 DCHECK_GE(frames_decoded_, 0); | |
529 DCHECK_LE(frames_dropped_, frames_decoded_); | |
530 | 394 |
531 if (frames_decoded_) { | 395 base::TimeDelta current_media_time = get_time_cb_.Run(); |
532 PipelineStatistics statistics; | 396 base::TimeTicks current_wall_ticks = base::TimeTicks::Now(); |
533 statistics.video_frames_decoded = frames_decoded_; | |
534 statistics.video_frames_dropped = frames_dropped_; | |
535 statistics_cb_.Run(statistics); | |
536 | 397 |
537 frames_decoded_ = 0; | 398 while (!unscheduled_frames_.empty()) { |
538 frames_dropped_ = 0; | 399 base::TimeDelta delta = |
400 (unscheduled_frames_.front()->timestamp() - current_media_time) / | |
401 playback_rate_; | |
402 base::TimeTicks target_wall_ticks = current_wall_ticks + delta; | |
403 | |
404 scheduled_frames_.push_back(unscheduled_frames_.front()); | |
405 unscheduled_frames_.pop_front(); | |
406 | |
407 scheduler_->ScheduleVideoFrame( | |
408 scheduled_frames_.back(), | |
409 target_wall_ticks, | |
410 base::Bind(&VideoRendererImpl::OnVideoFrameFinished, | |
411 weak_factory_.GetWeakPtr())); | |
412 } | |
413 } | |
414 | |
415 void VideoRendererImpl::ScheduleFirstFrameForImmediateDisplay() { | |
416 scheduled_frames_.push_back(unscheduled_frames_.front()); | |
417 unscheduled_frames_.pop_front(); | |
418 | |
419 scheduler_->ScheduleVideoFrame( | |
420 scheduled_frames_.back(), | |
421 base::TimeTicks::Now(), | |
422 base::Bind(&VideoRendererImpl::OnVideoFrameFinished, | |
423 weak_factory_.GetWeakPtr())); | |
424 } | |
425 | |
426 void VideoRendererImpl::OnVideoFrameFinished( | |
427 const scoped_refptr<VideoFrame>& frame, | |
428 VideoFrameScheduler::Reason reason) { | |
429 VideoFrameQueue::iterator iter = | |
430 std::find(scheduled_frames_.begin(), scheduled_frames_.end(), frame); | |
431 CHECK(iter != scheduled_frames_.end()); | |
432 | |
433 switch (reason) { | |
434 case VideoFrameScheduler::DISPLAYED: | |
435 ++frames_decoded_; | |
436 scheduled_frames_.erase(iter); | |
437 break; | |
438 | |
439 case VideoFrameScheduler::DROPPED: | |
440 ++frames_decoded_; | |
441 ++frames_dropped_; | |
442 scheduled_frames_.erase(iter); | |
443 break; | |
444 | |
445 case VideoFrameScheduler::RESET: | |
446 unscheduled_frames_.push_back(*iter); | |
447 scheduled_frames_.erase(iter); | |
448 break; | |
539 } | 449 } |
540 | 450 |
541 frame_available_.TimedWait(wait_duration); | 451 if (unscheduled_frames_.empty() && received_end_of_stream_) { |
scherkus (not reviewing)
2014/04/16 23:45:02
I'm pretty sure this will fire ended too soon.
We
| |
452 state_ = kEnded; | |
453 ended_cb_.Run(); | |
454 return; | |
455 } | |
456 | |
457 AttemptRead(); | |
542 } | 458 } |
543 | 459 |
544 } // namespace media | 460 } // namespace media |
OLD | NEW |