1 // Copyright (c) 2012 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.
5 #include "media/filters/video_renderer_base.h"
8 #include "base/callback.h"
9 #include "base/callback_helpers.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/threading/platform_thread.h"
12 #include "media/base/buffers.h"
13 #include "media/base/limits.h"
14 #include "media/base/pipeline.h"
15 #include "media/base/video_frame.h"
19 base::TimeDelta
VideoRendererBase::kMaxLastFrameDuration() {
20 return base::TimeDelta::FromMilliseconds(250);
23 VideoRendererBase::VideoRendererBase(
24 const scoped_refptr
<base::MessageLoopProxy
>& message_loop
,
25 ScopedVector
<VideoDecoder
> decoders
,
26 const SetDecryptorReadyCB
& set_decryptor_ready_cb
,
27 const PaintCB
& paint_cb
,
28 const SetOpaqueCB
& set_opaque_cb
,
30 : message_loop_(message_loop
),
33 message_loop
, decoders
.Pass(), set_decryptor_ready_cb
),
34 received_end_of_stream_(false),
35 frame_available_(&lock_
),
36 state_(kUninitialized
),
39 drop_frames_(drop_frames
),
42 set_opaque_cb_(set_opaque_cb
),
43 last_timestamp_(kNoTimestamp()) {
44 DCHECK(!paint_cb_
.is_null());
47 VideoRendererBase::~VideoRendererBase() {
48 base::AutoLock
auto_lock(lock_
);
49 CHECK(state_
== kStopped
|| state_
== kUninitialized
) << state_
;
50 CHECK(thread_
.is_null());
53 void VideoRendererBase::Play(const base::Closure
& callback
) {
54 DCHECK(message_loop_
->BelongsToCurrentThread());
55 base::AutoLock
auto_lock(lock_
);
56 DCHECK_EQ(kPrerolled
, state_
);
61 void VideoRendererBase::Pause(const base::Closure
& callback
) {
62 DCHECK(message_loop_
->BelongsToCurrentThread());
63 base::AutoLock
auto_lock(lock_
);
64 DCHECK(state_
!= kUninitialized
|| state_
== kError
);
69 void VideoRendererBase::Flush(const base::Closure
& callback
) {
70 DCHECK(message_loop_
->BelongsToCurrentThread());
71 base::AutoLock
auto_lock(lock_
);
72 DCHECK_EQ(state_
, kPaused
);
76 // This is necessary if the |video_frame_stream_| has already seen an end of
77 // stream and needs to drain it before flushing it.
78 ready_frames_
.clear();
79 received_end_of_stream_
= false;
80 video_frame_stream_
.Reset(base::Bind(
81 &VideoRendererBase::OnVideoFrameStreamResetDone
, weak_this_
));
84 void VideoRendererBase::Stop(const base::Closure
& callback
) {
85 DCHECK(message_loop_
->BelongsToCurrentThread());
86 base::AutoLock
auto_lock(lock_
);
87 if (state_
== kUninitialized
|| state_
== kStopped
) {
92 // TODO(scherkus): Consider invalidating |weak_factory_| and replacing
93 // task-running guards that check |state_| with DCHECK().
97 statistics_cb_
.Reset();
99 DoStopOrError_Locked();
101 // Clean up our thread if present.
102 base::PlatformThreadHandle thread_to_join
= base::PlatformThreadHandle();
103 if (!thread_
.is_null()) {
104 // Signal the thread since it's possible to get stopped with the video
105 // thread waiting for a read to complete.
106 frame_available_
.Signal();
107 std::swap(thread_
, thread_to_join
);
110 if (!thread_to_join
.is_null()) {
111 base::AutoUnlock
auto_unlock(lock_
);
112 base::PlatformThread::Join(thread_to_join
);
115 video_frame_stream_
.Stop(callback
);
118 void VideoRendererBase::SetPlaybackRate(float playback_rate
) {
119 DCHECK(message_loop_
->BelongsToCurrentThread());
120 base::AutoLock
auto_lock(lock_
);
121 playback_rate_
= playback_rate
;
124 void VideoRendererBase::Preroll(base::TimeDelta time
,
125 const PipelineStatusCB
& cb
) {
126 DCHECK(message_loop_
->BelongsToCurrentThread());
127 base::AutoLock
auto_lock(lock_
);
128 DCHECK_EQ(state_
, kFlushed
) << "Must flush prior to prerolling.";
129 DCHECK(!cb
.is_null());
130 DCHECK(preroll_cb_
.is_null());
132 state_
= kPrerolling
;
134 preroll_timestamp_
= time
;
135 AttemptRead_Locked();
138 void VideoRendererBase::Initialize(DemuxerStream
* stream
,
139 const PipelineStatusCB
& init_cb
,
140 const StatisticsCB
& statistics_cb
,
141 const TimeCB
& max_time_cb
,
142 const NaturalSizeChangedCB
& size_changed_cb
,
143 const base::Closure
& ended_cb
,
144 const PipelineStatusCB
& error_cb
,
145 const TimeDeltaCB
& get_time_cb
,
146 const TimeDeltaCB
& get_duration_cb
) {
147 DCHECK(message_loop_
->BelongsToCurrentThread());
148 base::AutoLock
auto_lock(lock_
);
150 DCHECK_EQ(stream
->type(), DemuxerStream::VIDEO
);
151 DCHECK(!init_cb
.is_null());
152 DCHECK(!statistics_cb
.is_null());
153 DCHECK(!max_time_cb
.is_null());
154 DCHECK(!size_changed_cb
.is_null());
155 DCHECK(!ended_cb
.is_null());
156 DCHECK(!get_time_cb
.is_null());
157 DCHECK(!get_duration_cb
.is_null());
158 DCHECK_EQ(kUninitialized
, state_
);
160 weak_this_
= weak_factory_
.GetWeakPtr();
162 statistics_cb_
= statistics_cb
;
163 max_time_cb_
= max_time_cb
;
164 size_changed_cb_
= size_changed_cb
;
165 ended_cb_
= ended_cb
;
166 error_cb_
= error_cb
;
167 get_time_cb_
= get_time_cb
;
168 get_duration_cb_
= get_duration_cb
;
169 state_
= kInitializing
;
171 video_frame_stream_
.Initialize(
174 base::Bind(&VideoRendererBase::OnVideoFrameStreamInitialized
,
178 void VideoRendererBase::OnVideoFrameStreamInitialized(bool success
,
180 DCHECK(message_loop_
->BelongsToCurrentThread());
181 base::AutoLock
auto_lock(lock_
);
183 if (state_
== kStopped
)
186 DCHECK_EQ(state_
, kInitializing
);
189 state_
= kUninitialized
;
190 base::ResetAndReturn(&init_cb_
).Run(DECODER_ERROR_NOT_SUPPORTED
);
194 // We're all good! Consider ourselves flushed. (ThreadMain() should never
195 // see us in the kUninitialized state).
196 // Since we had an initial Preroll(), we consider ourself flushed, because we
197 // have not populated any buffers yet.
200 set_opaque_cb_
.Run(!has_alpha
);
201 set_opaque_cb_
.Reset();
203 // Create our video thread.
204 if (!base::PlatformThread::Create(0, this, &thread_
)) {
205 NOTREACHED() << "Video thread creation failed";
207 base::ResetAndReturn(&init_cb_
).Run(PIPELINE_ERROR_INITIALIZATION_FAILED
);
212 // Bump up our priority so our sleeping is more accurate.
213 // TODO(scherkus): find out if this is necessary, but it seems to help.
214 ::SetThreadPriority(thread_
.platform_handle(), THREAD_PRIORITY_ABOVE_NORMAL
);
215 #endif // defined(OS_WIN)
216 base::ResetAndReturn(&init_cb_
).Run(PIPELINE_OK
);
219 // PlatformThread::Delegate implementation.
220 void VideoRendererBase::ThreadMain() {
221 base::PlatformThread::SetName("CrVideoRenderer");
223 // The number of milliseconds to idle when we do not have anything to do.
224 // Nothing special about the value, other than we're being more OS-friendly
225 // than sleeping for 1 millisecond.
227 // TODO(scherkus): switch to pure event-driven frame timing instead of this
228 // kIdleTimeDelta business http://crbug.com/106874
229 const base::TimeDelta kIdleTimeDelta
=
230 base::TimeDelta::FromMilliseconds(10);
233 base::AutoLock
auto_lock(lock_
);
235 // Thread exit condition.
236 if (state_
== kStopped
)
239 // Remain idle as long as we're not playing.
240 if (state_
!= kPlaying
|| playback_rate_
== 0) {
241 frame_available_
.TimedWait(kIdleTimeDelta
);
245 // Remain idle until we have the next frame ready for rendering.
246 if (ready_frames_
.empty()) {
247 if (received_end_of_stream_
) {
251 // No need to sleep here as we idle when |state_ != kPlaying|.
255 frame_available_
.TimedWait(kIdleTimeDelta
);
259 base::TimeDelta remaining_time
=
260 CalculateSleepDuration(ready_frames_
.front(), playback_rate_
);
262 // Sleep up to a maximum of our idle time until we're within the time to
263 // render the next frame.
264 if (remaining_time
.InMicroseconds() > 0) {
265 remaining_time
= std::min(remaining_time
, kIdleTimeDelta
);
266 frame_available_
.TimedWait(remaining_time
);
270 // Deadline is defined as the midpoint between this frame and the next
271 // frame, using the delta between this frame and the previous frame as the
272 // assumption for frame duration.
274 // TODO(scherkus): An improvement over midpoint might be selecting the
275 // minimum and/or maximum between the midpoint and some constants. As a
276 // thought experiment, consider what would be better than the midpoint
277 // for both the 1fps case and 120fps case.
279 // TODO(scherkus): This can be vastly improved. Use a histogram to measure
280 // the accuracy of our frame timing code. http://crbug.com/149829
281 if (drop_frames_
&& last_timestamp_
!= kNoTimestamp()) {
282 base::TimeDelta now
= get_time_cb_
.Run();
283 base::TimeDelta deadline
= ready_frames_
.front()->GetTimestamp() +
284 (ready_frames_
.front()->GetTimestamp() - last_timestamp_
) / 2;
286 if (now
> deadline
) {
287 DropNextReadyFrame_Locked();
292 // Congratulations! You've made it past the video frame timing gauntlet.
294 // At this point enough time has passed that the next frame that ready for
296 PaintNextReadyFrame_Locked();
300 void VideoRendererBase::PaintNextReadyFrame_Locked() {
301 lock_
.AssertAcquired();
303 scoped_refptr
<VideoFrame
> next_frame
= ready_frames_
.front();
304 ready_frames_
.pop_front();
306 last_timestamp_
= next_frame
->GetTimestamp();
308 const gfx::Size
& natural_size
= next_frame
->natural_size();
309 if (natural_size
!= last_natural_size_
) {
310 last_natural_size_
= natural_size
;
311 size_changed_cb_
.Run(natural_size
);
314 paint_cb_
.Run(next_frame
);
316 message_loop_
->PostTask(FROM_HERE
, base::Bind(
317 &VideoRendererBase::AttemptRead
, weak_this_
));
320 void VideoRendererBase::DropNextReadyFrame_Locked() {
321 lock_
.AssertAcquired();
323 last_timestamp_
= ready_frames_
.front()->GetTimestamp();
324 ready_frames_
.pop_front();
326 PipelineStatistics statistics
;
327 statistics
.video_frames_dropped
= 1;
328 statistics_cb_
.Run(statistics
);
330 message_loop_
->PostTask(FROM_HERE
, base::Bind(
331 &VideoRendererBase::AttemptRead
, weak_this_
));
334 void VideoRendererBase::FrameReady(VideoFrameStream::Status status
,
335 const scoped_refptr
<VideoFrame
>& frame
) {
336 base::AutoLock
auto_lock(lock_
);
337 DCHECK_NE(state_
, kUninitialized
);
338 DCHECK_NE(state_
, kFlushed
);
340 CHECK(pending_read_
);
341 pending_read_
= false;
343 if (status
== VideoFrameStream::DECODE_ERROR
||
344 status
== VideoFrameStream::DECRYPT_ERROR
) {
345 DCHECK(!frame
.get());
346 PipelineStatus error
= PIPELINE_ERROR_DECODE
;
347 if (status
== VideoFrameStream::DECRYPT_ERROR
)
348 error
= PIPELINE_ERROR_DECRYPT
;
350 if (!preroll_cb_
.is_null()) {
351 base::ResetAndReturn(&preroll_cb_
).Run(error
);
355 error_cb_
.Run(error
);
359 // Already-queued VideoFrameStream ReadCB's can fire after various state
360 // transitions have happened; in that case just drop those frames immediately.
361 if (state_
== kStopped
|| state_
== kError
|| state_
== kFlushing
)
365 // Abort preroll early for a NULL frame because we won't get more frames.
366 // A new preroll will be requested after this one completes so there is no
367 // point trying to collect more frames.
368 if (state_
== kPrerolling
)
369 TransitionToPrerolled_Locked();
374 if (frame
->IsEndOfStream()) {
375 DCHECK(!received_end_of_stream_
);
376 received_end_of_stream_
= true;
377 max_time_cb_
.Run(get_duration_cb_
.Run());
379 if (state_
== kPrerolling
)
380 TransitionToPrerolled_Locked();
385 // Maintain the latest frame decoded so the correct frame is displayed after
386 // prerolling has completed.
387 if (state_
== kPrerolling
&& frame
->GetTimestamp() <= preroll_timestamp_
) {
388 ready_frames_
.clear();
391 AddReadyFrame_Locked(frame
);
393 if (state_
== kPrerolling
) {
394 if (!video_frame_stream_
.CanReadWithoutStalling() ||
395 ready_frames_
.size() >= static_cast<size_t>(limits::kMaxVideoFrames
)) {
396 TransitionToPrerolled_Locked();
399 // We only count frames decoded during normal playback.
400 PipelineStatistics statistics
;
401 statistics
.video_frames_decoded
= 1;
402 statistics_cb_
.Run(statistics
);
405 // Always request more decoded video if we have capacity. This serves two
407 // 1) Prerolling while paused
408 // 2) Keeps decoding going if video rendering thread starts falling behind
409 AttemptRead_Locked();
412 void VideoRendererBase::AddReadyFrame_Locked(
413 const scoped_refptr
<VideoFrame
>& frame
) {
414 lock_
.AssertAcquired();
415 DCHECK(!frame
->IsEndOfStream());
417 // Adjust the incoming frame if its rendering stop time is past the duration
418 // of the video itself. This is typically the last frame of the video and
419 // occurs if the container specifies a duration that isn't a multiple of the
420 // frame rate. Another way for this to happen is for the container to state
421 // a smaller duration than the largest packet timestamp.
422 base::TimeDelta duration
= get_duration_cb_
.Run();
423 if (frame
->GetTimestamp() > duration
) {
424 frame
->SetTimestamp(duration
);
427 ready_frames_
.push_back(frame
);
428 DCHECK_LE(ready_frames_
.size(),
429 static_cast<size_t>(limits::kMaxVideoFrames
));
431 max_time_cb_
.Run(frame
->GetTimestamp());
433 // Avoid needlessly waking up |thread_| unless playing.
434 if (state_
== kPlaying
)
435 frame_available_
.Signal();
438 void VideoRendererBase::AttemptRead() {
439 base::AutoLock
auto_lock(lock_
);
440 AttemptRead_Locked();
443 void VideoRendererBase::AttemptRead_Locked() {
444 DCHECK(message_loop_
->BelongsToCurrentThread());
445 lock_
.AssertAcquired();
447 if (pending_read_
|| received_end_of_stream_
||
448 ready_frames_
.size() == static_cast<size_t>(limits::kMaxVideoFrames
)) {
456 pending_read_
= true;
457 video_frame_stream_
.Read(base::Bind(&VideoRendererBase::FrameReady
,
473 void VideoRendererBase::OnVideoFrameStreamResetDone() {
474 base::AutoLock
auto_lock(lock_
);
475 if (state_
== kStopped
)
478 DCHECK_EQ(kFlushing
, state_
);
479 DCHECK(!pending_read_
);
480 DCHECK(ready_frames_
.empty());
481 DCHECK(!received_end_of_stream_
);
484 last_timestamp_
= kNoTimestamp();
485 base::ResetAndReturn(&flush_cb_
).Run();
488 base::TimeDelta
VideoRendererBase::CalculateSleepDuration(
489 const scoped_refptr
<VideoFrame
>& next_frame
,
490 float playback_rate
) {
491 // Determine the current and next presentation timestamps.
492 base::TimeDelta now
= get_time_cb_
.Run();
493 base::TimeDelta next_pts
= next_frame
->GetTimestamp();
495 // Scale our sleep based on the playback rate.
496 base::TimeDelta sleep
= next_pts
- now
;
497 return base::TimeDelta::FromMicroseconds(
498 static_cast<int64
>(sleep
.InMicroseconds() / playback_rate
));
501 void VideoRendererBase::DoStopOrError_Locked() {
502 lock_
.AssertAcquired();
503 last_timestamp_
= kNoTimestamp();
504 ready_frames_
.clear();
507 void VideoRendererBase::TransitionToPrerolled_Locked() {
508 lock_
.AssertAcquired();
509 DCHECK_EQ(state_
, kPrerolling
);
513 // Because we might remain in the prerolled state for an undetermined amount
514 // of time (e.g., we seeked while paused), we'll paint the first prerolled
516 if (!ready_frames_
.empty())
517 PaintNextReadyFrame_Locked();
519 base::ResetAndReturn(&preroll_cb_
).Run(PIPELINE_OK
);