| Index: media/filters/video_renderer_base.cc
|
| ===================================================================
|
| --- media/filters/video_renderer_base.cc (revision 21798)
|
| +++ media/filters/video_renderer_base.cc (working copy)
|
| @@ -4,6 +4,7 @@
|
|
|
| #include "media/base/buffers.h"
|
| #include "media/base/filter_host.h"
|
| +#include "media/base/video_frame_impl.h"
|
| #include "media/filters/video_renderer_base.h"
|
|
|
| namespace media {
|
| @@ -28,15 +29,18 @@
|
| static const int64 kMaxSleepMilliseconds = 75;
|
|
|
| VideoRendererBase::VideoRendererBase()
|
| - : frame_available_(&lock_),
|
| - state_(UNINITIALIZED),
|
| + : width_(0),
|
| + height_(0),
|
| + frame_available_(&lock_),
|
| + state_(kUninitialized),
|
| thread_(NULL),
|
| + pending_reads_(0),
|
| playback_rate_(0) {
|
| }
|
|
|
| VideoRendererBase::~VideoRendererBase() {
|
| AutoLock auto_lock(lock_);
|
| - DCHECK(state_ == UNINITIALIZED || state_ == STOPPED);
|
| + DCHECK(state_ == kUninitialized || state_ == kStopped);
|
| }
|
|
|
| // static
|
| @@ -54,9 +58,32 @@
|
| return true;
|
| }
|
|
|
| +void VideoRendererBase::Play(FilterCallback* callback) {
|
| + AutoLock auto_lock(lock_);
|
| + DCHECK_EQ(kPaused, state_);
|
| + scoped_ptr<FilterCallback> c(callback);
|
| + state_ = kPlaying;
|
| + callback->Run();
|
| +}
|
| +
|
| +void VideoRendererBase::Pause(FilterCallback* callback) {
|
| + AutoLock auto_lock(lock_);
|
| + DCHECK_EQ(kPlaying, state_);
|
| + pause_callback_.reset(callback);
|
| + state_ = kPaused;
|
| +
|
| + // We'll only pause when we've finished all pending reads.
|
| + if (pending_reads_ == 0) {
|
| + pause_callback_->Run();
|
| + pause_callback_.reset();
|
| + } else {
|
| + state_ = kPaused;
|
| + }
|
| +}
|
| +
|
| void VideoRendererBase::Stop() {
|
| AutoLock auto_lock(lock_);
|
| - state_ = STOPPED;
|
| + state_ = kStopped;
|
|
|
| // Signal the subclass we're stopping.
|
| // TODO(scherkus): do we trust subclasses not to do something silly while
|
| @@ -83,13 +110,15 @@
|
|
|
| void VideoRendererBase::Seek(base::TimeDelta time, FilterCallback* callback) {
|
| AutoLock auto_lock(lock_);
|
| - // We need the first frame in |frames_| to run the VideoRendererBase main
|
| - // loop, but we don't need decoded frames after the first frame since we are
|
| - // at a new time. We should get some new frames so issue reads to compensate
|
| - // for those discarded.
|
| - while (frames_.size() > 1) {
|
| - frames_.pop_back();
|
| - ScheduleRead();
|
| + DCHECK_EQ(kPaused, state_);
|
| + DCHECK_EQ(0u, pending_reads_) << "Pending reads should have completed";
|
| + state_ = kSeeking;
|
| + seek_callback_.reset(callback);
|
| +
|
| + // Throw away everything and schedule our reads.
|
| + frames_.clear();
|
| + for (size_t i = 0; i < kMaxFrames; ++i) {
|
| + ScheduleRead_Locked();
|
| }
|
| }
|
|
|
| @@ -98,38 +127,40 @@
|
| AutoLock auto_lock(lock_);
|
| DCHECK(decoder);
|
| DCHECK(callback);
|
| - DCHECK_EQ(state_, UNINITIALIZED);
|
| - state_ = INITIALIZING;
|
| + DCHECK_EQ(kUninitialized, state_);
|
| decoder_ = decoder;
|
| - initialize_callback_.reset(callback);
|
| + scoped_ptr<FilterCallback> c(callback);
|
|
|
| // Notify the pipeline of the video dimensions.
|
| - int width = 0;
|
| - int height = 0;
|
| - if (!ParseMediaFormat(decoder->media_format(), &width, &height)) {
|
| + if (!ParseMediaFormat(decoder->media_format(), &width_, &height_)) {
|
| host()->SetError(PIPELINE_ERROR_INITIALIZATION_FAILED);
|
| - initialize_callback_->Run();
|
| - initialize_callback_.reset();
|
| + callback->Run();
|
| return;
|
| }
|
| - host()->SetVideoSize(width, height);
|
| + host()->SetVideoSize(width_, height_);
|
|
|
| // Initialize the subclass.
|
| // TODO(scherkus): do we trust subclasses not to do something silly while
|
| // we're holding the lock?
|
| if (!OnInitialize(decoder)) {
|
| host()->SetError(PIPELINE_ERROR_INITIALIZATION_FAILED);
|
| - initialize_callback_->Run();
|
| - initialize_callback_.reset();
|
| + callback->Run();
|
| return;
|
| }
|
|
|
| + // Create a black frame so clients have something to render before we finish
|
| + // prerolling.
|
| + CreateBlackFrame(¤t_frame_);
|
| +
|
| + // We're all good! Consider ourselves paused (ThreadMain() should never
|
| + // see us in the kUninitialized state).
|
| + state_ = kPaused;
|
| +
|
| // Create our video thread.
|
| if (!PlatformThread::Create(0, this, &thread_)) {
|
| NOTREACHED() << "Video thread creation failed";
|
| host()->SetError(PIPELINE_ERROR_INITIALIZATION_FAILED);
|
| - initialize_callback_->Run();
|
| - initialize_callback_.reset();
|
| + callback->Run();
|
| return;
|
| }
|
|
|
| @@ -139,21 +170,13 @@
|
| ::SetThreadPriority(thread_, THREAD_PRIORITY_ABOVE_NORMAL);
|
| #endif // defined(OS_WIN)
|
|
|
| - // Queue initial reads.
|
| - for (size_t i = 0; i < kMaxFrames; ++i) {
|
| - ScheduleRead();
|
| - }
|
| + // Finally, execute the start callback.
|
| + callback->Run();
|
| }
|
|
|
| // PlatformThread::Delegate implementation.
|
| void VideoRendererBase::ThreadMain() {
|
| PlatformThread::SetName("VideoThread");
|
| -
|
| - // Wait to be initialized so we can notify the first frame is available.
|
| - if (!WaitForInitialized())
|
| - return;
|
| - OnFrameAvailable();
|
| -
|
| for (;;) {
|
| // State and playback rate to assume for this iteration of the loop.
|
| State state;
|
| @@ -163,34 +186,50 @@
|
| state = state_;
|
| playback_rate = playback_rate_;
|
| }
|
| - if (state == STOPPED) {
|
| + if (state == kStopped) {
|
| return;
|
| }
|
| - DCHECK_EQ(state, INITIALIZED);
|
|
|
| - // Sleep for 10 milliseconds while paused.
|
| - if (playback_rate == 0) {
|
| + // Sleep for 10 milliseconds while paused. Nothing special about the value,
|
| + // other than we're being more OS-friendly than sleeping for 1 millisecond.
|
| + if (state == kPaused || state == kSeeking || playback_rate == 0) {
|
| PlatformThread::Sleep(10);
|
| continue;
|
| }
|
|
|
| - // Advance |current_frame_| and try to determine |next_frame|.
|
| + // Advance |current_frame_| and try to determine |next_frame|. Note that
|
| + // this loop executes our "playing" logic.
|
| + DCHECK_EQ(kPlaying, state);
|
| scoped_refptr<VideoFrame> next_frame;
|
| {
|
| AutoLock auto_lock(lock_);
|
| - DCHECK(!frames_.empty());
|
| - DCHECK_EQ(current_frame_, frames_.front());
|
| - frames_.pop_front();
|
| - ScheduleRead();
|
| - while (frames_.empty()) {
|
| + // Check the actual state to see if we're trying to stop playing.
|
| + if (state_ != kPlaying) {
|
| + continue;
|
| + }
|
| +
|
| + // Otherwise we're playing, so advance the frame and keep reading from the
|
| + // decoder. |frames_| might be empty if we seeked to the very end of the
|
| + // media where no frames were available.
|
| + if (!frames_.empty()) {
|
| + DCHECK_EQ(current_frame_, frames_.front());
|
| + frames_.pop_front();
|
| + ScheduleRead_Locked();
|
| + }
|
| +
|
| + // While playing, we'll wait until a new frame arrives before updating
|
| + // |current_frame_|.
|
| + while (frames_.empty() && state_ == kPlaying) {
|
| frame_available_.Wait();
|
| + }
|
|
|
| - // We have the lock again, check the actual state to see if we're trying
|
| - // to stop.
|
| - if (state_ == STOPPED) {
|
| - return;
|
| - }
|
| + // If we ended up transitioning out of playing while waiting for a new
|
| + // frame, restart the iteration.
|
| + if (state_ != kPlaying) {
|
| + continue;
|
| }
|
| +
|
| + // Update our current frame and attempt to grab the next frame.
|
| current_frame_ = frames_.front();
|
| if (frames_.size() >= 2) {
|
| next_frame = frames_[1];
|
| @@ -200,36 +239,14 @@
|
| // Notify subclass that |current_frame_| has been updated.
|
| OnFrameAvailable();
|
|
|
| - // Determine the current and next presentation timestamps.
|
| - base::TimeDelta now = host()->GetTime();
|
| - base::TimeDelta this_pts = current_frame_->GetTimestamp();
|
| - base::TimeDelta next_pts;
|
| - if (next_frame) {
|
| - next_pts = next_frame->GetTimestamp();
|
| - } else {
|
| - next_pts = this_pts + current_frame_->GetDuration();
|
| - }
|
| + // Calculate our sleep duration.
|
| + base::TimeDelta sleep = CalculateSleepDuration(next_frame, playback_rate);
|
|
|
| - // Determine our sleep duration based on whether time advanced.
|
| - base::TimeDelta sleep;
|
| - if (now == previous_time_) {
|
| - // Time has not changed, assume we sleep for the frame's duration.
|
| - sleep = next_pts - this_pts;
|
| - } else {
|
| - // Time has changed, figure out real sleep duration.
|
| - sleep = next_pts - now;
|
| - previous_time_ = now;
|
| - }
|
| -
|
| - // Scale our sleep based on the playback rate.
|
| - // TODO(scherkus): floating point badness and degrade gracefully.
|
| - int sleep_ms = static_cast<int>(sleep.InMicroseconds() / playback_rate /
|
| - base::Time::kMicrosecondsPerMillisecond);
|
| -
|
| // To be safe, limit our sleep duration.
|
| // TODO(scherkus): handle seeking gracefully.. right now a seek backwards
|
| // will hit kMinSleepMilliseconds whereas a seek forward will hit
|
| // kMaxSleepMilliseconds.
|
| + int sleep_ms = static_cast<int>(sleep.InMilliseconds());
|
| if (sleep_ms < kMinSleepMilliseconds)
|
| sleep_ms = kMinSleepMilliseconds;
|
| else if (sleep_ms > kMaxSleepMilliseconds)
|
| @@ -241,13 +258,18 @@
|
|
|
| void VideoRendererBase::GetCurrentFrame(scoped_refptr<VideoFrame>* frame_out) {
|
| AutoLock auto_lock(lock_);
|
| - // Either we have initialized or we have the current frame.
|
| - DCHECK(state_ != INITIALIZED || current_frame_);
|
| + // We should have initialized and have the current frame.
|
| + DCHECK(state_ == kPaused || state_ == kSeeking || state_ == kPlaying);
|
| + DCHECK(current_frame_);
|
| *frame_out = current_frame_;
|
| }
|
|
|
| void VideoRendererBase::OnReadComplete(VideoFrame* frame) {
|
| AutoLock auto_lock(lock_);
|
| + DCHECK(state_ == kPaused || state_ == kSeeking || state_ == kPlaying);
|
| + DCHECK_GT(pending_reads_, 0u);
|
| + --pending_reads_;
|
| +
|
| // If this is an end of stream frame, don't enqueue it since it has no data.
|
| if (!frame->IsEndOfStream()) {
|
| frames_.push_back(frame);
|
| @@ -255,43 +277,110 @@
|
| frame_available_.Signal();
|
| }
|
|
|
| - // Check for our initialization condition.
|
| - if (state_ == INITIALIZING &&
|
| - (frames_.size() == kMaxFrames || frame->IsEndOfStream())) {
|
| - if (frames_.empty()) {
|
| - // We should have initialized but there's no decoded frames in the queue.
|
| - // Raise an error.
|
| - state_ = ERRORED;
|
| - host()->SetError(PIPELINE_ERROR_NO_DATA);
|
| - initialize_callback_->Run();
|
| - initialize_callback_.reset();
|
| - } else {
|
| - state_ = INITIALIZED;
|
| - current_frame_ = frames_.front();
|
| - initialize_callback_->Run();
|
| - initialize_callback_.reset();
|
| + // Check for our preroll complete condition.
|
| + if (state_ == kSeeking) {
|
| + DCHECK(seek_callback_.get());
|
| + if (frames_.size() == kMaxFrames || frame->IsEndOfStream()) {
|
| + if (frames_.empty()) {
|
| + // Eeep.. we seeked to somewhere where there's no video data (most
|
| + // likely the very end of the file). For user-friendliness, we'll
|
| + // create a black frame just in case |current_frame_| is old or garbage.
|
| + CreateBlackFrame(¤t_frame_);
|
| + } else {
|
| + // Update our current frame.
|
| + current_frame_ = frames_.front();
|
| + }
|
| + // Because we might remain paused, we can't rely on ThreadMain() to
|
| + // notify the subclass the frame has been updated.
|
| + DCHECK(current_frame_);
|
| + state_ = kPaused;
|
| + OnFrameAvailable();
|
| +
|
| + seek_callback_->Run();
|
| + seek_callback_.reset();
|
| }
|
| + } else if (state_ == kPaused && pending_reads_ == 0) {
|
| + // No more pending reads! We're now officially "paused".
|
| + if (pause_callback_.get()) {
|
| + pause_callback_->Run();
|
| + pause_callback_.reset();
|
| + }
|
| }
|
| }
|
|
|
| -void VideoRendererBase::ScheduleRead() {
|
| +void VideoRendererBase::ScheduleRead_Locked() {
|
| + lock_.AssertAcquired();
|
| + DCHECK_LT(pending_reads_, kMaxFrames);
|
| + ++pending_reads_;
|
| decoder_->Read(NewCallback(this, &VideoRendererBase::OnReadComplete));
|
| }
|
|
|
| -bool VideoRendererBase::WaitForInitialized() {
|
| - // This loop essentially handles preroll. We wait until we've been fully
|
| - // initialized so we can call OnFrameAvailable() to provide subclasses with
|
| - // the first frame.
|
| - AutoLock auto_lock(lock_);
|
| - while (state_ == INITIALIZING) {
|
| - frame_available_.Wait();
|
| +base::TimeDelta VideoRendererBase::CalculateSleepDuration(
|
| + VideoFrame* next_frame, float playback_rate) {
|
| + // Determine the current and next presentation timestamps.
|
| + base::TimeDelta now = host()->GetTime();
|
| + base::TimeDelta this_pts = current_frame_->GetTimestamp();
|
| + base::TimeDelta next_pts;
|
| + if (next_frame) {
|
| + next_pts = next_frame->GetTimestamp();
|
| + } else {
|
| + next_pts = this_pts + current_frame_->GetDuration();
|
| }
|
| - if (state_ == STOPPED || state_ == ERRORED) {
|
| - return false;
|
| +
|
| + // Determine our sleep duration based on whether time advanced.
|
| + base::TimeDelta sleep;
|
| + if (now == previous_time_) {
|
| + // Time has not changed, assume we sleep for the frame's duration.
|
| + sleep = next_pts - this_pts;
|
| + } else {
|
| + // Time has changed, figure out real sleep duration.
|
| + sleep = next_pts - now;
|
| + previous_time_ = now;
|
| }
|
| - DCHECK_EQ(state_, INITIALIZED);
|
| - DCHECK(current_frame_);
|
| - return true;
|
| +
|
| + // Scale our sleep based on the playback rate.
|
| + // TODO(scherkus): floating point badness and degrade gracefully.
|
| + return base::TimeDelta::FromMicroseconds(
|
| + static_cast<int64>(sleep.InMicroseconds() / playback_rate));
|
| }
|
|
|
| +void VideoRendererBase::CreateBlackFrame(scoped_refptr<VideoFrame>* frame_out) {
|
| + DCHECK_GT(width_, 0);
|
| + DCHECK_GT(height_, 0);
|
| + *frame_out = NULL;
|
| +
|
| + // Create our frame.
|
| + scoped_refptr<VideoFrame> frame;
|
| + const base::TimeDelta kZero;
|
| + VideoFrameImpl::CreateFrame(VideoSurface::YV12, width_, height_, kZero, kZero,
|
| + &frame);
|
| + DCHECK(frame);
|
| +
|
| + // Now set the data to YUV(0,128,128).
|
| + VideoSurface surface;
|
| + frame->Lock(&surface);
|
| + DCHECK_EQ(VideoSurface::YV12, surface.format) << "Expected YV12 surface";
|
| +
|
| + // Fill the Y plane.
|
| + for (size_t i = 0; i < surface.height; ++i) {
|
| + memset(surface.data[VideoSurface::kYPlane], 0x00, surface.width);
|
| + surface.data[VideoSurface::kYPlane]
|
| + += surface.strides[VideoSurface::kYPlane];
|
| + }
|
| +
|
| + // Fill the U and V planes.
|
| + for (size_t i = 0; i < (surface.height / 2); ++i) {
|
| + memset(surface.data[VideoSurface::kUPlane], 0x80, surface.width / 2);
|
| + memset(surface.data[VideoSurface::kVPlane], 0x80, surface.width / 2);
|
| + surface.data[VideoSurface::kUPlane]
|
| + += surface.strides[VideoSurface::kUPlane];
|
| + surface.data[VideoSurface::kVPlane]
|
| + += surface.strides[VideoSurface::kVPlane];
|
| + }
|
| + frame->Unlock();
|
| +
|
| + // Success!
|
| + *frame_out = frame;
|
| +}
|
| +
|
| } // namespace media
|
|
|
| Property changes on: media\filters\video_renderer_base.cc
|
| ___________________________________________________________________
|
| Added: svn:mergeinfo
|
| Merged /branches/chrome_webkit_merge_branch/media/filters/video_renderer_base.cc:r69-2775
|
| Merged /trunk/src/media/filters/video_renderer_base.cc:r21611
|
|
|
|
|