| Index: media/filters/video_renderer_base.cc
|
| diff --git a/media/filters/video_renderer_base.cc b/media/filters/video_renderer_base.cc
|
| index 166c99289deaaaaf3d9579e8c3c50edbed8df1c0..c9ede3ae12dd1a8e0912838e4ba649f2c3c00db1 100644
|
| --- a/media/filters/video_renderer_base.cc
|
| +++ b/media/filters/video_renderer_base.cc
|
| @@ -6,26 +6,12 @@
|
| #include "media/base/buffers.h"
|
| #include "media/base/callback.h"
|
| #include "media/base/filter_host.h"
|
| +#include "media/base/limits.h"
|
| #include "media/base/video_frame.h"
|
| #include "media/filters/video_renderer_base.h"
|
|
|
| namespace media {
|
|
|
| -// Limit our read ahead to at least 3 frames. One frame is typically in flux at
|
| -// all times, as in frame n is discarded at the top of ThreadMain() while frame
|
| -// (n + kMaxFrames) is being asynchronously fetched. The remaining two frames
|
| -// allow us to advance the current frame as well as read the timestamp of the
|
| -// following frame for more accurate timing.
|
| -//
|
| -// Increasing this number beyond 3 simply creates a larger buffer to work with
|
| -// at the expense of memory (~0.5MB and ~1.3MB per frame for 480p and 720p
|
| -// resolutions, respectively). This can help on lower-end systems if there are
|
| -// difficult sections in the movie and decoding slows down.
|
| -//
|
| -// Set to 4 because some vendor's driver doesn't allow buffer count to go below
|
| -// preset limit, e.g., EGLImage path.
|
| -static const size_t kMaxFrames = 4;
|
| -
|
| // This equates to ~16.67 fps, which is just slow enough to be tolerable when
|
| // our video renderer is ahead of the audio playback.
|
| //
|
| @@ -91,7 +77,7 @@ bool VideoRendererBase::ParseMediaFormat(
|
|
|
| void VideoRendererBase::Play(FilterCallback* callback) {
|
| AutoLock auto_lock(lock_);
|
| - DCHECK(kPaused == state_ || kFlushing == state_);
|
| + DCHECK_EQ(kPrerolled, state_);
|
| scoped_ptr<FilterCallback> c(callback);
|
| state_ = kPlaying;
|
| callback->Run();
|
| @@ -99,38 +85,29 @@ void VideoRendererBase::Play(FilterCallback* callback) {
|
|
|
| void VideoRendererBase::Pause(FilterCallback* callback) {
|
| AutoLock auto_lock(lock_);
|
| - DCHECK(state_ == kPlaying || state_ == kEnded);
|
| + DCHECK(state_ != kUninitialized || state_ == kError);
|
| AutoCallbackRunner done_runner(callback);
|
| state_ = kPaused;
|
| }
|
|
|
| void VideoRendererBase::Flush(FilterCallback* callback) {
|
| - DCHECK(state_ == kPaused);
|
| + DCHECK_EQ(state_, kPaused);
|
|
|
| AutoLock auto_lock(lock_);
|
| flush_callback_.reset(callback);
|
| state_ = kFlushing;
|
|
|
| - // Filter is considered paused when we've finished all pending reads, which
|
| - // implies all buffers are returned to owner in Decoder/Renderer. Renderer
|
| - // is considered paused with one more contingency that |pending_paint_| is
|
| - // false, such that no client of us is holding any reference to VideoFrame.
|
| - if (pending_reads_ == 0 && pending_paint_ == false) {
|
| - flush_callback_->Run();
|
| - flush_callback_.reset();
|
| + if (pending_paint_ == false)
|
| FlushBuffers();
|
| - }
|
| }
|
|
|
| void VideoRendererBase::Stop(FilterCallback* callback) {
|
| + DCHECK_EQ(pending_reads_, 0);
|
| +
|
| {
|
| AutoLock auto_lock(lock_);
|
| state_ = kStopped;
|
|
|
| - // TODO(jiesun): move this to flush.
|
| - // TODO(jiesun): we should wait until pending_paint_ is false;
|
| - FlushBuffers();
|
| -
|
| // Clean up our thread if present.
|
| if (thread_) {
|
| // Signal the thread since it's possible to get stopped with the video
|
| @@ -155,26 +132,25 @@ void VideoRendererBase::SetPlaybackRate(float playback_rate) {
|
|
|
| void VideoRendererBase::Seek(base::TimeDelta time, FilterCallback* callback) {
|
| AutoLock auto_lock(lock_);
|
| - DCHECK(kPaused == state_ || kFlushing == state_);
|
| - DCHECK_EQ(0u, pending_reads_) << "Pending reads should have completed";
|
| - state_ = kSeeking;
|
| - seek_callback_.reset(callback);
|
| - seek_timestamp_ = time;
|
| -
|
| - // Throw away everything and schedule our reads.
|
| - // TODO(jiesun): this should be guaranteed by pause/flush before seek happen.
|
| - frames_queue_ready_.clear();
|
| - frames_queue_done_.clear();
|
| - for (size_t i = 0; i < kMaxFrames; ++i) {
|
| - // TODO(jiesun): this is dummy read for ffmpeg path until we truely recycle
|
| - // in that path.
|
| - scoped_refptr<VideoFrame> null_frame;
|
| - frames_queue_done_.push_back(null_frame);
|
| + // There is a race condition between filters to receive SeekTask().
|
| + // It turns out we could receive buffer from decoder before seek()
|
| + // is called on us. so we do the following:
|
| + // kFlushed => ( Receive first buffer or Seek() ) => kSeeking and
|
| + // kSeeking => ( Receive enough buffers) => kPrerolled. )
|
| + DCHECK(kPrerolled == state_ || kFlushed == state_ || kSeeking == state_);
|
| +
|
| + if (state_ == kPrerolled) {
|
| + // Already get enough buffers from decoder.
|
| + callback->Run();
|
| + delete callback;
|
| + } else {
|
| + // Otherwise we are either kFlushed or kSeeking, but without enough buffers;
|
| + // we should save the callback function and call it later.
|
| + state_ = kSeeking;
|
| + seek_callback_.reset(callback);
|
| }
|
|
|
| - // TODO(jiesun): if EGL image path make sure those video frames are already in
|
| - // frames_queue_done_, we could remove FillThisBuffer call from derived class.
|
| - // But currently that is trigger by first paint(), which is bad.
|
| + seek_timestamp_ = time;
|
| ScheduleRead_Locked();
|
| }
|
|
|
| @@ -185,7 +161,7 @@ void VideoRendererBase::Initialize(VideoDecoder* decoder,
|
| DCHECK(callback);
|
| DCHECK_EQ(kUninitialized, state_);
|
| decoder_ = decoder;
|
| - scoped_ptr<FilterCallback> c(callback);
|
| + AutoCallbackRunner done_runner(callback);
|
|
|
| decoder_->set_fill_buffer_done_callback(
|
| NewCallback(this, &VideoRendererBase::OnFillBufferDone));
|
| @@ -195,7 +171,7 @@ void VideoRendererBase::Initialize(VideoDecoder* decoder,
|
| &surface_format_,
|
| &width_, &height_)) {
|
| host()->SetError(PIPELINE_ERROR_INITIALIZATION_FAILED);
|
| - callback->Run();
|
| + state_ = kError;
|
| return;
|
| }
|
| host()->SetVideoSize(width_, height_);
|
| @@ -205,19 +181,21 @@ void VideoRendererBase::Initialize(VideoDecoder* decoder,
|
| // we're holding the lock?
|
| if (!OnInitialize(decoder)) {
|
| host()->SetError(PIPELINE_ERROR_INITIALIZATION_FAILED);
|
| - callback->Run();
|
| + state_ = kError;
|
| return;
|
| }
|
|
|
| - // We're all good! Consider ourselves paused (ThreadMain() should never
|
| + // We're all good! Consider ourselves flushed. (ThreadMain() should never
|
| // see us in the kUninitialized state).
|
| - state_ = kPaused;
|
| + // Since we had an initial Seek, we consider ourself flushed, because we
|
| + // have not populated any buffers yet.
|
| + state_ = kFlushed;
|
|
|
| // Create our video thread.
|
| if (!PlatformThread::Create(0, this, &thread_)) {
|
| NOTREACHED() << "Video thread creation failed";
|
| host()->SetError(PIPELINE_ERROR_INITIALIZATION_FAILED);
|
| - callback->Run();
|
| + state_ = kError;
|
| return;
|
| }
|
|
|
| @@ -227,8 +205,6 @@ void VideoRendererBase::Initialize(VideoDecoder* decoder,
|
| ::SetThreadPriority(thread_, THREAD_PRIORITY_ABOVE_NORMAL);
|
| #endif // defined(OS_WIN)
|
|
|
| - // Finally, execute the start callback.
|
| - callback->Run();
|
| }
|
|
|
| bool VideoRendererBase::HasEnded() {
|
| @@ -266,8 +242,20 @@ void VideoRendererBase::ThreadMain() {
|
| remaining_time = CalculateSleepDuration(next_frame, playback_rate_);
|
| }
|
|
|
| + // TODO(jiesun): I do not think we should wake up every 10ms.
|
| + // We should only wait up when following is true:
|
| + // 1. frame arrival (use event);
|
| + // 2. state_ change (use event);
|
| + // 3. playback_rate_ change (use event);
|
| + // 4. next frame's pts (use timeout);
|
| if (remaining_time > kIdleTimeDelta)
|
| remaining_time = kIdleTimeDelta;
|
| +
|
| + // We can not do anything about this until next frame arrival.
|
| + // We do not want to spin in this case though.
|
| + if (remaining_time.InMicroseconds() < 0 && frames_queue_ready_.empty())
|
| + remaining_time = kIdleTimeDelta;
|
| +
|
| if (remaining_time.InMicroseconds() > 0)
|
| frame_available_.TimedWait(remaining_time);
|
|
|
| @@ -323,18 +311,8 @@ void VideoRendererBase::ThreadMain() {
|
| }
|
| }
|
| if (timeout_frame.get()) {
|
| - // TODO(jiesun): we should really merge the following branch. That way
|
| - // we will remove the last EGLImage hack in this class. but the
|
| - // |pending_reads_| prevents use to do so (until we had implemented
|
| - // flush logic and get rid of pending reads.)
|
| - if (uses_egl_image() &&
|
| - media::VideoFrame::TYPE_EGL_IMAGE == timeout_frame->type()) {
|
| - decoder_->FillThisBuffer(timeout_frame);
|
| - } else {
|
| - // TODO(jiesun): could this be merged with EGLimage path?
|
| - frames_queue_done_.push_back(timeout_frame);
|
| - ScheduleRead_Locked();
|
| - }
|
| + frames_queue_done_.push_back(timeout_frame);
|
| + ScheduleRead_Locked();
|
| }
|
| if (new_frame_available) {
|
| AutoUnlock auto_unlock(lock_);
|
| @@ -349,15 +327,13 @@ void VideoRendererBase::GetCurrentFrame(scoped_refptr<VideoFrame>* frame_out) {
|
| AutoLock auto_lock(lock_);
|
| DCHECK(!pending_paint_);
|
|
|
| - if (state_ == kStopped || !current_frame_.get() ||
|
| - current_frame_->IsEndOfStream()) {
|
| + if (!current_frame_.get() || current_frame_->IsEndOfStream()) {
|
| *frame_out = NULL;
|
| return;
|
| }
|
|
|
| // We should have initialized and have the current frame.
|
| - DCHECK(state_ == kPaused || state_ == kSeeking || state_ == kPlaying ||
|
| - state_ == kFlushing || state_ == kEnded);
|
| + DCHECK(state_ != kUninitialized && state_ != kStopped && state_ != kError);
|
| *frame_out = current_frame_;
|
| pending_paint_ = true;
|
| }
|
| @@ -375,29 +351,41 @@ void VideoRendererBase::PutCurrentFrame(scoped_refptr<VideoFrame> frame) {
|
| // 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_ == kFlushing && pending_reads_ == 0 && flush_callback_.get()) {
|
| - // No more pending reads! We're now officially "paused".
|
| + if (state_ == kFlushing)
|
| FlushBuffers();
|
| - flush_callback_->Run();
|
| - flush_callback_.reset();
|
| - }
|
| }
|
|
|
| void VideoRendererBase::OnFillBufferDone(scoped_refptr<VideoFrame> frame) {
|
| AutoLock auto_lock(lock_);
|
|
|
| - // TODO(ajwong): Work around cause we don't synchronize on stop. Correct
|
| - // fix is to resolve http://crbug.com/16059.
|
| - if (state_ == kStopped) {
|
| - // TODO(jiesun): Remove this when flush before stop landed!
|
| + // Decoder could reach seek state before our Seek() get called.
|
| + // We will enter kSeeking
|
| + if (kFlushed == state_)
|
| + state_ = kSeeking;
|
| +
|
| + // Synchronous flush between filters should prevent this from happening.
|
| + DCHECK_NE(state_, kStopped);
|
| + if (frame.get() && !frame->IsEndOfStream())
|
| + --pending_reads_;
|
| +
|
| + DCHECK(state_ != kUninitialized && state_ != kStopped && state_ != kError);
|
| +
|
| + if (state_ == kPaused || state_ == kFlushing) {
|
| + // Decoder are flushing rubbish video frame, we will not display them.
|
| + if (frame.get() && !frame->IsEndOfStream())
|
| + frames_queue_done_.push_back(frame);
|
| + DCHECK_LE(frames_queue_done_.size(),
|
| + static_cast<size_t>(Limits::kMaxVideoFrames));
|
| +
|
| + // Excluding kPause here, because in pause state, we will never
|
| + // transfer out-bounding buffer. We do not flush buffer when Compositor
|
| + // hold reference to our video frame either.
|
| + if (state_ == kFlushing && pending_paint_ == false)
|
| + FlushBuffers();
|
| +
|
| return;
|
| }
|
|
|
| - DCHECK(state_ == kPaused || state_ == kSeeking || state_ == kPlaying ||
|
| - state_ == kFlushing || state_ == kEnded);
|
| - DCHECK_GT(pending_reads_, 0u);
|
| - --pending_reads_;
|
| -
|
| // Discard frames until we reach our desired seek timestamp.
|
| if (state_ == kSeeking && !frame->IsEndOfStream() &&
|
| (frame->GetTimestamp() + frame->GetDuration()) < seek_timestamp_) {
|
| @@ -405,17 +393,18 @@ void VideoRendererBase::OnFillBufferDone(scoped_refptr<VideoFrame> frame) {
|
| ScheduleRead_Locked();
|
| } else {
|
| frames_queue_ready_.push_back(frame);
|
| - DCHECK_LE(frames_queue_ready_.size(), kMaxFrames);
|
| + DCHECK_LE(frames_queue_ready_.size(),
|
| + static_cast<size_t>(Limits::kMaxVideoFrames));
|
| frame_available_.Signal();
|
| }
|
|
|
| // Check for our preroll complete condition.
|
| if (state_ == kSeeking) {
|
| - DCHECK(seek_callback_.get());
|
| - if (frames_queue_ready_.size() == kMaxFrames || frame->IsEndOfStream()) {
|
| + if (frames_queue_ready_.size() == Limits::kMaxVideoFrames ||
|
| + frame->IsEndOfStream()) {
|
| // We're paused, so make sure we update |current_frame_| to represent
|
| // our new location.
|
| - state_ = kPaused;
|
| + state_ = kPrerolled;
|
|
|
| // Because we might remain paused (i.e., we were not playing before we
|
| // received a seek), we can't rely on ThreadMain() to notify the subclass
|
| @@ -428,18 +417,27 @@ void VideoRendererBase::OnFillBufferDone(scoped_refptr<VideoFrame> frame) {
|
| }
|
| OnFrameAvailable();
|
|
|
| - seek_callback_->Run();
|
| - seek_callback_.reset();
|
| + // If we reach prerolled state before Seek() is called by pipeline,
|
| + // |seek_callback_| is not set, we will return immediately during
|
| + // when Seek() is eventually called.
|
| + if ((seek_callback_.get())) {
|
| + seek_callback_->Run();
|
| + seek_callback_.reset();
|
| + }
|
| }
|
| } else if (state_ == kFlushing && pending_reads_ == 0 && !pending_paint_) {
|
| - // No more pending reads! We're now officially "paused".
|
| - if (flush_callback_.get()) {
|
| - flush_callback_->Run();
|
| - flush_callback_.reset();
|
| - }
|
| + OnFlushDone();
|
| }
|
| }
|
|
|
| +void VideoRendererBase::ReadInput(scoped_refptr<VideoFrame> frame) {
|
| + // We should never return empty frames or EOS frame.
|
| + DCHECK(frame.get() && !frame->IsEndOfStream());
|
| +
|
| + decoder_->FillThisBuffer(frame);
|
| + ++pending_reads_;
|
| +}
|
| +
|
| void VideoRendererBase::ScheduleRead_Locked() {
|
| lock_.AssertAcquired();
|
| DCHECK_NE(kEnded, state_);
|
| @@ -449,9 +447,7 @@ void VideoRendererBase::ScheduleRead_Locked() {
|
| while (!frames_queue_done_.empty()) {
|
| scoped_refptr<VideoFrame> video_frame = frames_queue_done_.front();
|
| frames_queue_done_.pop_front();
|
| - decoder_->FillThisBuffer(video_frame);
|
| - DCHECK_LT(pending_reads_, kMaxFrames);
|
| - ++pending_reads_;
|
| + ReadInput(video_frame);
|
| }
|
| }
|
|
|
| @@ -470,6 +466,32 @@ void VideoRendererBase::FlushBuffers() {
|
| frames_queue_done_.push_back(current_frame_);
|
| }
|
| current_frame_ = NULL;
|
| +
|
| + if (decoder_->ProvidesBuffer()) {
|
| + // Flush all buffers out to decoder;
|
| + ScheduleRead_Locked();
|
| + }
|
| +
|
| + if (pending_reads_ == 0)
|
| + OnFlushDone();
|
| +}
|
| +
|
| +void VideoRendererBase::OnFlushDone() {
|
| + // Check all buffers are return to owners.
|
| + if (decoder_->ProvidesBuffer()) {
|
| + DCHECK_EQ(frames_queue_done_.size(), 0u);
|
| + } else {
|
| + DCHECK_EQ(frames_queue_done_.size(),
|
| + static_cast<size_t>(Limits::kMaxVideoFrames));
|
| + }
|
| + DCHECK(!current_frame_.get());
|
| + DCHECK(frames_queue_ready_.empty());
|
| +
|
| + if (flush_callback_.get()) { // This ensures callback is invoked once.
|
| + flush_callback_->Run();
|
| + flush_callback_.reset();
|
| + }
|
| + state_ = kFlushed;
|
| }
|
|
|
| base::TimeDelta VideoRendererBase::CalculateSleepDuration(
|
|
|