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