Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(17)

Unified Diff: media/filters/video_renderer_base.cc

Issue 159476: Merge 21611 - Implemented proper pausethenseek behaviour for the media pipeli... (Closed) Base URL: svn://chrome-svn/chrome/branches/195/src/
Patch Set: Created 11 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « media/filters/video_renderer_base.h ('k') | media/filters/video_renderer_base_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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(&current_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(&current_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
« no previous file with comments | « media/filters/video_renderer_base.h ('k') | media/filters/video_renderer_base_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698