| Index: media/filters/video_renderer_base.cc
|
| ===================================================================
|
| --- media/filters/video_renderer_base.cc (revision 182591)
|
| +++ media/filters/video_renderer_base.cc (working copy)
|
| @@ -25,30 +25,24 @@
|
| VideoRendererBase::VideoRendererBase(
|
| const scoped_refptr<base::MessageLoopProxy>& message_loop,
|
| const SetDecryptorReadyCB& set_decryptor_ready_cb,
|
| - const PaintCB& paint_cb,
|
| + const base::Closure& paint_cb,
|
| const SetOpaqueCB& set_opaque_cb,
|
| bool drop_frames)
|
| : message_loop_(message_loop),
|
| - weak_factory_(this),
|
| set_decryptor_ready_cb_(set_decryptor_ready_cb),
|
| frame_available_(&lock_),
|
| state_(kUninitialized),
|
| thread_(base::kNullThreadHandle),
|
| pending_read_(false),
|
| + pending_paint_(false),
|
| + pending_paint_with_last_available_(false),
|
| drop_frames_(drop_frames),
|
| playback_rate_(0),
|
| paint_cb_(paint_cb),
|
| - set_opaque_cb_(set_opaque_cb),
|
| - last_timestamp_(kNoTimestamp()) {
|
| + set_opaque_cb_(set_opaque_cb) {
|
| DCHECK(!paint_cb_.is_null());
|
| }
|
|
|
| -VideoRendererBase::~VideoRendererBase() {
|
| - base::AutoLock auto_lock(lock_);
|
| - CHECK(state_ == kUninitialized || state_ == kStopped) << state_;
|
| - CHECK_EQ(thread_, base::kNullThreadHandle);
|
| -}
|
| -
|
| void VideoRendererBase::Play(const base::Closure& callback) {
|
| DCHECK(message_loop_->BelongsToCurrentThread());
|
| base::AutoLock auto_lock(lock_);
|
| @@ -74,55 +68,51 @@
|
|
|
| if (decrypting_demuxer_stream_) {
|
| decrypting_demuxer_stream_->Reset(base::Bind(
|
| - &VideoRendererBase::ResetDecoder, weak_this_));
|
| + &VideoRendererBase::ResetDecoder, this));
|
| return;
|
| }
|
|
|
| - decoder_->Reset(base::Bind(
|
| - &VideoRendererBase::OnDecoderResetDone, weak_this_));
|
| + decoder_->Reset(base::Bind(&VideoRendererBase::OnDecoderResetDone, this));
|
| }
|
|
|
| void VideoRendererBase::ResetDecoder() {
|
| DCHECK(message_loop_->BelongsToCurrentThread());
|
| base::AutoLock auto_lock(lock_);
|
| - decoder_->Reset(base::Bind(
|
| - &VideoRendererBase::OnDecoderResetDone, weak_this_));
|
| + decoder_->Reset(base::Bind(&VideoRendererBase::OnDecoderResetDone, this));
|
| }
|
|
|
| void VideoRendererBase::Stop(const base::Closure& callback) {
|
| DCHECK(message_loop_->BelongsToCurrentThread());
|
| - base::AutoLock auto_lock(lock_);
|
| if (state_ == kUninitialized || state_ == kStopped) {
|
| callback.Run();
|
| return;
|
| }
|
|
|
| - // TODO(scherkus): Consider invalidating |weak_factory_| and replacing
|
| - // task-running guards that check |state_| with DCHECK().
|
| + base::PlatformThreadHandle thread_to_join = base::kNullThreadHandle;
|
| + {
|
| + base::AutoLock auto_lock(lock_);
|
| + state_ = kStopped;
|
|
|
| - state_ = kStopped;
|
| + statistics_cb_.Reset();
|
| + max_time_cb_.Reset();
|
| + if (!pending_paint_ && !pending_paint_with_last_available_)
|
| + DoStopOrError_Locked();
|
|
|
| - statistics_cb_.Reset();
|
| - max_time_cb_.Reset();
|
| - DoStopOrError_Locked();
|
| -
|
| - // Clean up our thread if present.
|
| - base::PlatformThreadHandle thread_to_join = base::kNullThreadHandle;
|
| - if (thread_ != base::kNullThreadHandle) {
|
| - // Signal the thread since it's possible to get stopped with the video
|
| - // thread waiting for a read to complete.
|
| - frame_available_.Signal();
|
| - std::swap(thread_, thread_to_join);
|
| + // Clean up our thread if present.
|
| + if (thread_ != base::kNullThreadHandle) {
|
| + // Signal the thread since it's possible to get stopped with the video
|
| + // thread waiting for a read to complete.
|
| + frame_available_.Signal();
|
| + thread_to_join = thread_;
|
| + thread_ = base::kNullThreadHandle;
|
| + }
|
| }
|
| -
|
| - if (thread_to_join != base::kNullThreadHandle) {
|
| - base::AutoUnlock auto_unlock(lock_);
|
| + if (thread_to_join != base::kNullThreadHandle)
|
| base::PlatformThread::Join(thread_to_join);
|
| - }
|
|
|
| if (decrypting_demuxer_stream_) {
|
| decrypting_demuxer_stream_->Reset(base::Bind(
|
| - &VideoRendererBase::StopDecoder, weak_this_, callback));
|
| + &VideoRendererBase::StopDecoder, this, callback));
|
| return;
|
| }
|
|
|
| @@ -180,7 +170,6 @@
|
| DCHECK(!get_duration_cb.is_null());
|
| DCHECK_EQ(kUninitialized, state_);
|
|
|
| - weak_this_ = weak_factory_.GetWeakPtr();
|
| init_cb_ = init_cb;
|
| statistics_cb_ = statistics_cb;
|
| max_time_cb_ = max_time_cb;
|
| @@ -202,7 +191,7 @@
|
| decoder_selector_ptr->SelectVideoDecoder(
|
| stream,
|
| statistics_cb,
|
| - base::Bind(&VideoRendererBase::OnDecoderSelected, weak_this_,
|
| + base::Bind(&VideoRendererBase::OnDecoderSelected, this,
|
| base::Passed(&decoder_selector)));
|
| }
|
|
|
| @@ -263,6 +252,8 @@
|
| const base::TimeDelta kIdleTimeDelta =
|
| base::TimeDelta::FromMilliseconds(10);
|
|
|
| + uint32 frames_dropped = 0;
|
| +
|
| for (;;) {
|
| base::AutoLock auto_lock(lock_);
|
|
|
| @@ -270,6 +261,14 @@
|
| if (state_ == kStopped)
|
| return;
|
|
|
| + if (frames_dropped > 0) {
|
| + PipelineStatistics statistics;
|
| + statistics.video_frames_dropped = frames_dropped;
|
| + statistics_cb_.Run(statistics);
|
| +
|
| + frames_dropped = 0;
|
| + }
|
| +
|
| // Remain idle as long as we're not playing.
|
| if (state_ != kPlaying || playback_rate_ == 0) {
|
| frame_available_.TimedWait(kIdleTimeDelta);
|
| @@ -282,16 +281,25 @@
|
| continue;
|
| }
|
|
|
| - // We've reached the end!
|
| - if (ready_frames_.front()->IsEndOfStream()) {
|
| - state_ = kEnded;
|
| - ready_frames_.clear();
|
| - ended_cb_.Run();
|
| + // Remain idle until we've initialized |current_frame_| via prerolling.
|
| + if (!current_frame_) {
|
| + // This can happen if our preroll only contains end of stream frames.
|
| + if (ready_frames_.front()->IsEndOfStream()) {
|
| + state_ = kEnded;
|
| + ended_cb_.Run();
|
| + ready_frames_.clear();
|
|
|
| - // No need to sleep here as we idle when |state_ != kPlaying|.
|
| + // No need to sleep here as we idle when |state_ != kPlaying|.
|
| + continue;
|
| + }
|
| +
|
| + frame_available_.TimedWait(kIdleTimeDelta);
|
| continue;
|
| }
|
|
|
| + // Calculate how long until we should advance the frame, which is
|
| + // typically negative but for playback rates < 1.0f may be long enough
|
| + // that it makes more sense to idle and check again.
|
| base::TimeDelta remaining_time =
|
| CalculateSleepDuration(ready_frames_.front(), playback_rate_);
|
|
|
| @@ -303,70 +311,152 @@
|
| continue;
|
| }
|
|
|
| - // Deadline is defined as the midpoint between this frame and the next
|
| - // frame, using the delta between this frame and the previous frame as the
|
| - // assumption for frame duration.
|
| +
|
| + // We're almost there!
|
| //
|
| - // TODO(scherkus): An improvement over midpoint might be selecting the
|
| - // minimum and/or maximum between the midpoint and some constants. As a
|
| - // thought experiment, consider what would be better than the midpoint
|
| - // for both the 1fps case and 120fps case.
|
| + // At this point we've rendered |current_frame_| for the proper amount
|
| + // of time and also have the next frame that ready for rendering.
|
| +
|
| +
|
| + // If the next frame is end of stream then we are truly at the end of the
|
| + // video stream.
|
| //
|
| - // TODO(scherkus): This can be vastly improved. Use a histogram to measure
|
| - // the accuracy of our frame timing code. http://crbug.com/149829
|
| - if (drop_frames_ && last_timestamp_ != kNoTimestamp()) {
|
| - base::TimeDelta now = get_time_cb_.Run();
|
| - base::TimeDelta deadline = ready_frames_.front()->GetTimestamp() +
|
| - (ready_frames_.front()->GetTimestamp() - last_timestamp_) / 2;
|
| + // TODO(scherkus): deduplicate this end of stream check after we get rid of
|
| + // |current_frame_|.
|
| + if (ready_frames_.front()->IsEndOfStream()) {
|
| + state_ = kEnded;
|
| + ended_cb_.Run();
|
| + ready_frames_.clear();
|
|
|
| - if (now > deadline) {
|
| - DropNextReadyFrame_Locked();
|
| - continue;
|
| + // No need to sleep here as we idle when |state_ != kPlaying|.
|
| + continue;
|
| + }
|
| +
|
| + // We cannot update |current_frame_| until we've completed the pending
|
| + // paint. Furthermore, the pending paint might be really slow: check to
|
| + // see if we have any ready frames that we can drop if they've already
|
| + // expired.
|
| + if (pending_paint_) {
|
| + while (!ready_frames_.empty()) {
|
| + // Can't drop anything if we're at the end.
|
| + if (ready_frames_.front()->IsEndOfStream())
|
| + break;
|
| +
|
| + base::TimeDelta remaining_time =
|
| + ready_frames_.front()->GetTimestamp() - get_time_cb_.Run();
|
| +
|
| + // Still a chance we can render the frame!
|
| + if (remaining_time.InMicroseconds() > 0)
|
| + break;
|
| +
|
| + if (!drop_frames_)
|
| + break;
|
| +
|
| + // Frame dropped: read again.
|
| + ++frames_dropped;
|
| + ready_frames_.pop_front();
|
| + message_loop_->PostTask(FROM_HERE, base::Bind(
|
| + &VideoRendererBase::AttemptRead, this));
|
| }
|
| + // Continue waiting for the current paint to finish.
|
| + frame_available_.TimedWait(kIdleTimeDelta);
|
| + continue;
|
| }
|
|
|
| +
|
| // Congratulations! You've made it past the video frame timing gauntlet.
|
| //
|
| - // At this point enough time has passed that the next frame that ready for
|
| - // rendering.
|
| - PaintNextReadyFrame_Locked();
|
| + // We can now safely update the current frame, request another frame, and
|
| + // signal to the client that a new frame is available.
|
| + DCHECK(!pending_paint_);
|
| + DCHECK(!ready_frames_.empty());
|
| + SetCurrentFrameToNextReadyFrame();
|
| + message_loop_->PostTask(FROM_HERE, base::Bind(
|
| + &VideoRendererBase::AttemptRead, this));
|
| +
|
| + base::AutoUnlock auto_unlock(lock_);
|
| + paint_cb_.Run();
|
| }
|
| }
|
|
|
| -void VideoRendererBase::PaintNextReadyFrame_Locked() {
|
| - lock_.AssertAcquired();
|
| -
|
| - scoped_refptr<VideoFrame> next_frame = ready_frames_.front();
|
| +void VideoRendererBase::SetCurrentFrameToNextReadyFrame() {
|
| + current_frame_ = ready_frames_.front();
|
| ready_frames_.pop_front();
|
|
|
| - last_timestamp_ = next_frame->GetTimestamp();
|
| -
|
| - const gfx::Size& natural_size = next_frame->natural_size();
|
| + // Notify the pipeline of natural_size() changes.
|
| + const gfx::Size& natural_size = current_frame_->natural_size();
|
| if (natural_size != last_natural_size_) {
|
| + size_changed_cb_.Run(natural_size);
|
| last_natural_size_ = natural_size;
|
| - size_changed_cb_.Run(natural_size);
|
| }
|
| +}
|
|
|
| - paint_cb_.Run(next_frame);
|
| +void VideoRendererBase::GetCurrentFrame(scoped_refptr<VideoFrame>* frame_out) {
|
| + base::AutoLock auto_lock(lock_);
|
| + DCHECK(!pending_paint_ && !pending_paint_with_last_available_);
|
|
|
| - message_loop_->PostTask(FROM_HERE, base::Bind(
|
| - &VideoRendererBase::AttemptRead, weak_this_));
|
| + if ((!current_frame_ || current_frame_->IsEndOfStream()) &&
|
| + (!last_available_frame_ || last_available_frame_->IsEndOfStream())) {
|
| + *frame_out = NULL;
|
| + return;
|
| + }
|
| +
|
| + // We should have initialized and have the current frame.
|
| + DCHECK_NE(state_, kUninitialized);
|
| + DCHECK_NE(state_, kStopped);
|
| + DCHECK_NE(state_, kError);
|
| +
|
| + if (current_frame_) {
|
| + *frame_out = current_frame_;
|
| + last_available_frame_ = current_frame_;
|
| + pending_paint_ = true;
|
| + } else {
|
| + DCHECK(last_available_frame_);
|
| + *frame_out = last_available_frame_;
|
| + pending_paint_with_last_available_ = true;
|
| + }
|
| }
|
|
|
| -void VideoRendererBase::DropNextReadyFrame_Locked() {
|
| - lock_.AssertAcquired();
|
| +void VideoRendererBase::PutCurrentFrame(scoped_refptr<VideoFrame> frame) {
|
| + base::AutoLock auto_lock(lock_);
|
|
|
| - last_timestamp_ = ready_frames_.front()->GetTimestamp();
|
| - ready_frames_.pop_front();
|
| + // Note that we do not claim |pending_paint_| when we return NULL frame, in
|
| + // that case, |current_frame_| could be changed before PutCurrentFrame.
|
| + if (pending_paint_) {
|
| + DCHECK_EQ(current_frame_, frame);
|
| + DCHECK(!pending_paint_with_last_available_);
|
| + pending_paint_ = false;
|
| + } else if (pending_paint_with_last_available_) {
|
| + DCHECK_EQ(last_available_frame_, frame);
|
| + DCHECK(!pending_paint_);
|
| + pending_paint_with_last_available_ = false;
|
| + } else {
|
| + DCHECK(!frame);
|
| + }
|
|
|
| - PipelineStatistics statistics;
|
| - statistics.video_frames_dropped = 1;
|
| - statistics_cb_.Run(statistics);
|
| + // We had cleared the |pending_paint_| flag, there are chances that current
|
| + // frame is timed-out. We will wake up our main thread to advance the current
|
| + // frame when this is true.
|
| + frame_available_.Signal();
|
| + if (state_ == kFlushingDecoder)
|
| + return;
|
|
|
| - message_loop_->PostTask(FROM_HERE, base::Bind(
|
| - &VideoRendererBase::AttemptRead, weak_this_));
|
| + if (state_ == kFlushing) {
|
| + AttemptFlush_Locked();
|
| + return;
|
| + }
|
| +
|
| + if (state_ == kError || state_ == kStopped) {
|
| + DoStopOrError_Locked();
|
| + }
|
| }
|
|
|
| +VideoRendererBase::~VideoRendererBase() {
|
| + base::AutoLock auto_lock(lock_);
|
| + CHECK(state_ == kUninitialized || state_ == kStopped) << state_;
|
| + CHECK_EQ(thread_, base::kNullThreadHandle);
|
| +}
|
| +
|
| void VideoRendererBase::FrameReady(VideoDecoder::Status status,
|
| const scoped_refptr<VideoFrame>& frame) {
|
| base::AutoLock auto_lock(lock_);
|
| @@ -443,18 +533,23 @@
|
|
|
| // If we're at capacity or end of stream while prerolling we need to
|
| // transition to prerolled.
|
| - if (state_ != kPrerolling)
|
| - return;
|
| + if (state_ == kPrerolling) {
|
| + DCHECK(!current_frame_);
|
| + state_ = kPrerolled;
|
|
|
| - state_ = kPrerolled;
|
| + // Because we might remain in the prerolled state for an undetermined amount
|
| + // of time (i.e., we were not playing before we started prerolling), we'll
|
| + // manually update the current frame and notify the subclass below.
|
| + if (!ready_frames_.front()->IsEndOfStream())
|
| + SetCurrentFrameToNextReadyFrame();
|
|
|
| - // Because we might remain in the prerolled state for an undetermined amount
|
| - // of time (e.g., we seeked while paused), we'll paint the first prerolled
|
| - // frame.
|
| - if (!ready_frames_.front()->IsEndOfStream())
|
| - PaintNextReadyFrame_Locked();
|
| + // ...and we're done prerolling!
|
| + DCHECK(!preroll_cb_.is_null());
|
| + base::ResetAndReturn(&preroll_cb_).Run(PIPELINE_OK);
|
|
|
| - base::ResetAndReturn(&preroll_cb_).Run(PIPELINE_OK);
|
| + base::AutoUnlock ul(lock_);
|
| + paint_cb_.Run();
|
| + }
|
| }
|
|
|
| void VideoRendererBase::AddReadyFrame(const scoped_refptr<VideoFrame>& frame) {
|
| @@ -470,9 +565,10 @@
|
| end_timestamp = std::min(
|
| duration,
|
| ready_frames_.back()->GetTimestamp() + kMaxLastFrameDuration());
|
| - } else if (last_timestamp_ != kNoTimestamp()) {
|
| + } else if (current_frame_) {
|
| end_timestamp =
|
| - std::min(duration, last_timestamp_ + kMaxLastFrameDuration());
|
| + std::min(duration,
|
| + current_frame_->GetTimestamp() + kMaxLastFrameDuration());
|
| }
|
| frame->SetTimestamp(end_timestamp);
|
| } else if (frame->GetTimestamp() > duration) {
|
| @@ -511,7 +607,7 @@
|
| case kPrerolling:
|
| case kPlaying:
|
| pending_read_ = true;
|
| - decoder_->Read(base::Bind(&VideoRendererBase::FrameReady, weak_this_));
|
| + decoder_->Read(base::Bind(&VideoRendererBase::FrameReady, this));
|
| return;
|
|
|
| case kUninitialized:
|
| @@ -541,12 +637,11 @@
|
| prerolling_delayed_frame_ = NULL;
|
| ready_frames_.clear();
|
|
|
| - if (pending_read_)
|
| - return;
|
| -
|
| - state_ = kFlushed;
|
| - last_timestamp_ = kNoTimestamp();
|
| - base::ResetAndReturn(&flush_cb_).Run();
|
| + if (!pending_paint_ && !pending_read_) {
|
| + state_ = kFlushed;
|
| + current_frame_ = NULL;
|
| + base::ResetAndReturn(&flush_cb_).Run();
|
| + }
|
| }
|
|
|
| base::TimeDelta VideoRendererBase::CalculateSleepDuration(
|
| @@ -563,14 +658,20 @@
|
| }
|
|
|
| void VideoRendererBase::DoStopOrError_Locked() {
|
| + DCHECK(!pending_paint_);
|
| + DCHECK(!pending_paint_with_last_available_);
|
| lock_.AssertAcquired();
|
| - last_timestamp_ = kNoTimestamp();
|
| + current_frame_ = NULL;
|
| + last_available_frame_ = NULL;
|
| ready_frames_.clear();
|
| }
|
|
|
| int VideoRendererBase::NumFrames_Locked() const {
|
| lock_.AssertAcquired();
|
| - return ready_frames_.size();
|
| + int outstanding_frames =
|
| + (current_frame_ ? 1 : 0) + (last_available_frame_ ? 1 : 0) +
|
| + (current_frame_ && (current_frame_ == last_available_frame_) ? -1 : 0);
|
| + return ready_frames_.size() + outstanding_frames;
|
| }
|
|
|
| } // namespace media
|
|
|