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

Unified Diff: media/filters/video_renderer_impl.cc

Issue 237353007: Refactor VideoRendererImpl to use VideoFrameScheduler. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 6 years, 8 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_impl.h ('k') | media/filters/video_renderer_impl_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: media/filters/video_renderer_impl.cc
diff --git a/media/filters/video_renderer_impl.cc b/media/filters/video_renderer_impl.cc
index 27cd9f375f032e8a024fb75a8fe08d4ce21d3f1d..d76ab4eeb88fe7a2cbd8a347d9e151c24bd28dc9 100644
--- a/media/filters/video_renderer_impl.cc
+++ b/media/filters/video_renderer_impl.cc
@@ -24,59 +24,60 @@ base::TimeDelta VideoRendererImpl::kMaxLastFrameDuration() {
VideoRendererImpl::VideoRendererImpl(
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
+ scoped_ptr<VideoFrameScheduler> scheduler,
ScopedVector<VideoDecoder> decoders,
- const SetDecryptorReadyCB& set_decryptor_ready_cb,
- const PaintCB& paint_cb,
- bool drop_frames)
+ const SetDecryptorReadyCB& set_decryptor_ready_cb)
: task_runner_(task_runner),
+ scheduler_(scheduler.Pass()),
video_frame_stream_(task_runner, decoders.Pass(), set_decryptor_ready_cb),
received_end_of_stream_(false),
- frame_available_(&lock_),
state_(kUninitialized),
- thread_(),
pending_read_(false),
- drop_frames_(drop_frames),
playback_rate_(0),
- paint_cb_(paint_cb),
- last_timestamp_(kNoTimestamp()),
frames_decoded_(0),
frames_dropped_(0),
- weak_factory_(this) {
- DCHECK(!paint_cb_.is_null());
-}
+ weak_factory_(this) {}
VideoRendererImpl::~VideoRendererImpl() {
- base::AutoLock auto_lock(lock_);
CHECK(state_ == kStopped || state_ == kUninitialized) << state_;
- CHECK(thread_.is_null());
}
void VideoRendererImpl::Play(const base::Closure& callback) {
DCHECK(task_runner_->BelongsToCurrentThread());
- base::AutoLock auto_lock(lock_);
DCHECK_EQ(kPrerolled, state_);
state_ = kPlaying;
+
+ ScheduleVideoFrames();
+
+ // Changing from non-playing to playing state can trigger ended.
+ if (ShouldTransitionToEnded())
+ TransitionToEnded();
+
callback.Run();
}
void VideoRendererImpl::Pause(const base::Closure& callback) {
DCHECK(task_runner_->BelongsToCurrentThread());
- base::AutoLock auto_lock(lock_);
DCHECK(state_ != kUninitialized || state_ == kError);
+ scheduler_->Reset();
state_ = kPaused;
callback.Run();
}
void VideoRendererImpl::Flush(const base::Closure& callback) {
DCHECK(task_runner_->BelongsToCurrentThread());
- base::AutoLock auto_lock(lock_);
DCHECK_EQ(state_, kPaused);
flush_cb_ = callback;
state_ = kFlushing;
+ scheduler_->Reset();
+
// This is necessary if the |video_frame_stream_| has already seen an end of
// stream and needs to drain it before flushing it.
- ready_frames_.clear();
+ // ready_frames_.clear();
+ unscheduled_frames_.clear();
+ // XXX what about scheduled_frames_?
scherkus (not reviewing) 2014/04/16 23:45:02 I'm pretty sure we may have to wait for all frames
+
received_end_of_stream_ = false;
video_frame_stream_.Reset(
base::Bind(&VideoRendererImpl::OnVideoFrameStreamResetDone,
@@ -85,12 +86,13 @@ void VideoRendererImpl::Flush(const base::Closure& callback) {
void VideoRendererImpl::Stop(const base::Closure& callback) {
DCHECK(task_runner_->BelongsToCurrentThread());
- base::AutoLock auto_lock(lock_);
if (state_ == kUninitialized || state_ == kStopped) {
callback.Run();
return;
}
+ scheduler_->Reset();
+
// TODO(scherkus): Consider invalidating |weak_factory_| and replacing
// task-running guards that check |state_| with DCHECK().
@@ -98,35 +100,32 @@ void VideoRendererImpl::Stop(const base::Closure& callback) {
statistics_cb_.Reset();
max_time_cb_.Reset();
- DoStopOrError_Locked();
-
- // Clean up our thread if present.
- base::PlatformThreadHandle thread_to_join = base::PlatformThreadHandle();
- if (!thread_.is_null()) {
- // 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);
- }
- if (!thread_to_join.is_null()) {
- base::AutoUnlock auto_unlock(lock_);
- base::PlatformThread::Join(thread_to_join);
- }
+ unscheduled_frames_.clear();
+ // XXX what about scheduled_frames_?
+ // ready_frames_.clear();
video_frame_stream_.Stop(callback);
}
void VideoRendererImpl::SetPlaybackRate(float playback_rate) {
DCHECK(task_runner_->BelongsToCurrentThread());
- base::AutoLock auto_lock(lock_);
+ if (playback_rate_ == playback_rate)
+ return;
+
playback_rate_ = playback_rate;
+
+ scheduler_->Reset();
+ ScheduleVideoFrames();
+
+ // Changing from a zero to a non-zero playback rate can trigger ended.
scherkus (not reviewing) 2014/04/16 23:45:02 old code would have us fire ended even if playback
+ if (ShouldTransitionToEnded())
+ TransitionToEnded();
}
void VideoRendererImpl::Preroll(base::TimeDelta time,
const PipelineStatusCB& cb) {
DCHECK(task_runner_->BelongsToCurrentThread());
- base::AutoLock auto_lock(lock_);
DCHECK(!cb.is_null());
DCHECK(preroll_cb_.is_null());
DCHECK(state_ == kFlushed || state_== kPaused) << "state_ " << state_;
@@ -134,7 +133,8 @@ void VideoRendererImpl::Preroll(base::TimeDelta time,
if (state_ == kFlushed) {
DCHECK(time != kNoTimestamp());
DCHECK(!pending_read_);
- DCHECK(ready_frames_.empty());
+ DCHECK(unscheduled_frames_.empty());
+ DCHECK(scheduled_frames_.empty());
} else {
DCHECK(time == kNoTimestamp());
}
@@ -143,12 +143,12 @@ void VideoRendererImpl::Preroll(base::TimeDelta time,
preroll_cb_ = cb;
preroll_timestamp_ = time;
- if (ShouldTransitionToPrerolled_Locked()) {
- TransitionToPrerolled_Locked();
+ if (ShouldTransitionToPrerolled()) {
+ TransitionToPrerolled();
return;
}
- AttemptRead_Locked();
+ AttemptRead();
}
void VideoRendererImpl::Initialize(DemuxerStream* stream,
@@ -160,7 +160,6 @@ void VideoRendererImpl::Initialize(DemuxerStream* stream,
const TimeDeltaCB& get_time_cb,
const TimeDeltaCB& get_duration_cb) {
DCHECK(task_runner_->BelongsToCurrentThread());
- base::AutoLock auto_lock(lock_);
DCHECK(stream);
DCHECK_EQ(stream->type(), DemuxerStream::VIDEO);
DCHECK(!init_cb.is_null());
@@ -189,7 +188,6 @@ void VideoRendererImpl::Initialize(DemuxerStream* stream,
void VideoRendererImpl::OnVideoFrameStreamInitialized(bool success) {
DCHECK(task_runner_->BelongsToCurrentThread());
- base::AutoLock auto_lock(lock_);
if (state_ == kStopped)
return;
@@ -208,137 +206,12 @@ void VideoRendererImpl::OnVideoFrameStreamInitialized(bool success) {
// have not populated any buffers yet.
state_ = kFlushed;
- // Create our video thread.
- if (!base::PlatformThread::Create(0, this, &thread_)) {
- NOTREACHED() << "Video thread creation failed";
- state_ = kError;
- base::ResetAndReturn(&init_cb_).Run(PIPELINE_ERROR_INITIALIZATION_FAILED);
- return;
- }
-
-#if defined(OS_WIN)
- // Bump up our priority so our sleeping is more accurate.
- // TODO(scherkus): find out if this is necessary, but it seems to help.
- ::SetThreadPriority(thread_.platform_handle(), THREAD_PRIORITY_ABOVE_NORMAL);
-#endif // defined(OS_WIN)
base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK);
}
-// PlatformThread::Delegate implementation.
-void VideoRendererImpl::ThreadMain() {
- base::PlatformThread::SetName("CrVideoRenderer");
-
- // The number of milliseconds to idle when we do not have anything to do.
- // Nothing special about the value, other than we're being more OS-friendly
- // than sleeping for 1 millisecond.
- //
- // TODO(scherkus): switch to pure event-driven frame timing instead of this
- // kIdleTimeDelta business http://crbug.com/106874
- const base::TimeDelta kIdleTimeDelta =
- base::TimeDelta::FromMilliseconds(10);
-
- for (;;) {
- base::AutoLock auto_lock(lock_);
-
- // Thread exit condition.
- if (state_ == kStopped)
- return;
-
- // Remain idle as long as we're not playing.
- if (state_ != kPlaying || playback_rate_ == 0) {
- UpdateStatsAndWait_Locked(kIdleTimeDelta);
- continue;
- }
-
- // Remain idle until we have the next frame ready for rendering.
- if (ready_frames_.empty()) {
- if (received_end_of_stream_) {
- state_ = kEnded;
- ended_cb_.Run();
-
- // No need to sleep here as we idle when |state_ != kPlaying|.
- continue;
- }
-
- UpdateStatsAndWait_Locked(kIdleTimeDelta);
- continue;
- }
-
- base::TimeDelta remaining_time =
- CalculateSleepDuration(ready_frames_.front(), playback_rate_);
-
- // Sleep up to a maximum of our idle time until we're within the time to
- // render the next frame.
- if (remaining_time.InMicroseconds() > 0) {
- remaining_time = std::min(remaining_time, kIdleTimeDelta);
- UpdateStatsAndWait_Locked(remaining_time);
- 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.
- //
- // 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.
- //
- // 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()->timestamp() +
- (ready_frames_.front()->timestamp() - last_timestamp_) / 2;
-
- if (now > deadline) {
- DropNextReadyFrame_Locked();
- 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();
- }
-}
-
-void VideoRendererImpl::PaintNextReadyFrame_Locked() {
- lock_.AssertAcquired();
-
- scoped_refptr<VideoFrame> next_frame = ready_frames_.front();
- ready_frames_.pop_front();
- frames_decoded_++;
-
- last_timestamp_ = next_frame->timestamp();
-
- paint_cb_.Run(next_frame);
-
- task_runner_->PostTask(
- FROM_HERE,
- base::Bind(&VideoRendererImpl::AttemptRead, weak_factory_.GetWeakPtr()));
-}
-
-void VideoRendererImpl::DropNextReadyFrame_Locked() {
- TRACE_EVENT0("media", "VideoRendererImpl:frameDropped");
-
- lock_.AssertAcquired();
-
- last_timestamp_ = ready_frames_.front()->timestamp();
- ready_frames_.pop_front();
- frames_decoded_++;
- frames_dropped_++;
-
- task_runner_->PostTask(
- FROM_HERE,
- base::Bind(&VideoRendererImpl::AttemptRead, weak_factory_.GetWeakPtr()));
-}
-
void VideoRendererImpl::FrameReady(VideoFrameStream::Status status,
const scoped_refptr<VideoFrame>& frame) {
- base::AutoLock auto_lock(lock_);
+ LOG(ERROR) << __FUNCTION__ << " eos=" << (frame && frame->end_of_stream());
DCHECK_NE(state_, kUninitialized);
DCHECK_NE(state_, kFlushed);
@@ -371,7 +244,7 @@ void VideoRendererImpl::FrameReady(VideoFrameStream::Status status,
// A new preroll will be requested after this one completes so there is no
// point trying to collect more frames.
if (state_ == kPrerolling)
- TransitionToPrerolled_Locked();
+ TransitionToPrerolled();
return;
}
@@ -382,7 +255,7 @@ void VideoRendererImpl::FrameReady(VideoFrameStream::Status status,
max_time_cb_.Run(get_duration_cb_.Run());
if (state_ == kPrerolling)
- TransitionToPrerolled_Locked();
+ TransitionToPrerolled();
return;
}
@@ -391,32 +264,9 @@ void VideoRendererImpl::FrameReady(VideoFrameStream::Status status,
// prerolling has completed.
if (state_ == kPrerolling && preroll_timestamp_ != kNoTimestamp() &&
frame->timestamp() <= preroll_timestamp_) {
- ready_frames_.clear();
+ unscheduled_frames_.clear();
}
- AddReadyFrame_Locked(frame);
-
- if (ShouldTransitionToPrerolled_Locked())
- TransitionToPrerolled_Locked();
-
- // Always request more decoded video if we have capacity. This serves two
- // purposes:
- // 1) Prerolling while paused
- // 2) Keeps decoding going if video rendering thread starts falling behind
- AttemptRead_Locked();
-}
-
-bool VideoRendererImpl::ShouldTransitionToPrerolled_Locked() {
- return state_ == kPrerolling &&
- (!video_frame_stream_.CanReadWithoutStalling() ||
- ready_frames_.size() >= static_cast<size_t>(limits::kMaxVideoFrames));
-}
-
-void VideoRendererImpl::AddReadyFrame_Locked(
- const scoped_refptr<VideoFrame>& frame) {
- lock_.AssertAcquired();
- DCHECK(!frame->end_of_stream());
-
// Adjust the incoming frame if its rendering stop time is past the duration
// of the video itself. This is typically the last frame of the video and
// occurs if the container specifies a duration that isn't a multiple of the
@@ -427,35 +277,70 @@ void VideoRendererImpl::AddReadyFrame_Locked(
frame->set_timestamp(duration);
}
- ready_frames_.push_back(frame);
- DCHECK_LE(ready_frames_.size(),
- static_cast<size_t>(limits::kMaxVideoFrames));
+ unscheduled_frames_.push_back(frame);
+ DCHECK_LE(video_frame_count(), limits::kMaxVideoFrames);
max_time_cb_.Run(frame->timestamp());
- // Avoid needlessly waking up |thread_| unless playing.
+ if (ShouldTransitionToPrerolled())
+ TransitionToPrerolled();
+
if (state_ == kPlaying)
- frame_available_.Signal();
+ ScheduleVideoFrames();
+
+ if (ShouldTransitionToEnded())
+ TransitionToEnded();
+
+ // Always request more decoded video if we have capacity. This serves two
+ // purposes:
+ // 1) Prerolling while paused
+ // 2) Keeps decoding going if video rendering thread starts falling behind
+ AttemptRead();
}
-void VideoRendererImpl::AttemptRead() {
- base::AutoLock auto_lock(lock_);
- AttemptRead_Locked();
+bool VideoRendererImpl::ShouldTransitionToPrerolled() {
+ return state_ == kPrerolling &&
+ (!video_frame_stream_.CanReadWithoutStalling() ||
+ video_frame_count() >= limits::kMaxVideoFrames);
+}
+
+bool VideoRendererImpl::ShouldTransitionToEnded() {
+ LOG(ERROR) << __FUNCTION__ << " state_=" << state_
+ << " rate=" << playback_rate_
+ << " unsched=" << unscheduled_frames_.size()
+ << " sched=" << scheduled_frames_.size()
+ << " eos=" << received_end_of_stream_;
+ return state_ == kPlaying && playback_rate_ != 0.0f &&
+ unscheduled_frames_.empty() && scheduled_frames_.empty() &&
+ received_end_of_stream_;
+}
+
+void VideoRendererImpl::TransitionToEnded() {
+ DCHECK_EQ(state_, kPlaying);
+ state_ = kEnded;
+ ended_cb_.Run();
}
-void VideoRendererImpl::AttemptRead_Locked() {
+void VideoRendererImpl::AttemptRead() {
DCHECK(task_runner_->BelongsToCurrentThread());
- lock_.AssertAcquired();
+
+ LOG(ERROR) << __FUNCTION__ << " pending_read_=" << pending_read_
+ << " received_eos_=" << received_end_of_stream_
+ << " count=" << video_frame_count() << "/"
+ << limits::kMaxVideoFrames << " state_=" << state_;
if (pending_read_ || received_end_of_stream_ ||
- ready_frames_.size() == static_cast<size_t>(limits::kMaxVideoFrames)) {
+ video_frame_count() == limits::kMaxVideoFrames) {
+ LOG(ERROR) << __FUNCTION__ << " -> did not read";
return;
}
switch (state_) {
case kPaused:
case kPrerolling:
+ case kPrerolled:
scherkus (not reviewing) 2014/04/16 23:45:02 it also turns out that after hitting kPrerolled an
case kPlaying:
+ LOG(ERROR) << __FUNCTION__ << " -> did read";
pending_read_ = true;
video_frame_stream_.Read(base::Bind(&VideoRendererImpl::FrameReady,
weak_factory_.GetWeakPtr()));
@@ -463,52 +348,31 @@ void VideoRendererImpl::AttemptRead_Locked() {
case kUninitialized:
case kInitializing:
- case kPrerolled:
case kFlushing:
case kFlushed:
case kEnded:
case kStopped:
case kError:
+ LOG(ERROR) << __FUNCTION__ << " -> did not read";
return;
}
}
void VideoRendererImpl::OnVideoFrameStreamResetDone() {
- base::AutoLock auto_lock(lock_);
if (state_ == kStopped)
return;
DCHECK_EQ(kFlushing, state_);
DCHECK(!pending_read_);
- DCHECK(ready_frames_.empty());
+ DCHECK(unscheduled_frames_.empty());
+ DCHECK(scheduled_frames_.empty());
DCHECK(!received_end_of_stream_);
state_ = kFlushed;
- last_timestamp_ = kNoTimestamp();
base::ResetAndReturn(&flush_cb_).Run();
}
-base::TimeDelta VideoRendererImpl::CalculateSleepDuration(
- const scoped_refptr<VideoFrame>& next_frame,
- float playback_rate) {
- // Determine the current and next presentation timestamps.
- base::TimeDelta now = get_time_cb_.Run();
- base::TimeDelta next_pts = next_frame->timestamp();
-
- // Scale our sleep based on the playback rate.
- base::TimeDelta sleep = next_pts - now;
- return base::TimeDelta::FromMicroseconds(
- static_cast<int64>(sleep.InMicroseconds() / playback_rate));
-}
-
-void VideoRendererImpl::DoStopOrError_Locked() {
- lock_.AssertAcquired();
- last_timestamp_ = kNoTimestamp();
- ready_frames_.clear();
-}
-
-void VideoRendererImpl::TransitionToPrerolled_Locked() {
- lock_.AssertAcquired();
+void VideoRendererImpl::TransitionToPrerolled() {
DCHECK_EQ(state_, kPrerolling);
state_ = kPrerolled;
@@ -516,29 +380,81 @@ void VideoRendererImpl::TransitionToPrerolled_Locked() {
// 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_.empty())
- PaintNextReadyFrame_Locked();
+ if (!unscheduled_frames_.empty()) {
+ ScheduleFirstFrameForImmediateDisplay();
scherkus (not reviewing) 2014/04/16 23:45:02 FYI here's the bit where we only display that firs
+ AttemptRead();
+ }
base::ResetAndReturn(&preroll_cb_).Run(PIPELINE_OK);
}
-void VideoRendererImpl::UpdateStatsAndWait_Locked(
- base::TimeDelta wait_duration) {
- lock_.AssertAcquired();
- DCHECK_GE(frames_decoded_, 0);
- DCHECK_LE(frames_dropped_, frames_decoded_);
+void VideoRendererImpl::ScheduleVideoFrames() {
+ if (playback_rate_ == 0)
+ return;
+
+ base::TimeDelta current_media_time = get_time_cb_.Run();
+ base::TimeTicks current_wall_ticks = base::TimeTicks::Now();
- if (frames_decoded_) {
- PipelineStatistics statistics;
- statistics.video_frames_decoded = frames_decoded_;
- statistics.video_frames_dropped = frames_dropped_;
- statistics_cb_.Run(statistics);
+ while (!unscheduled_frames_.empty()) {
+ base::TimeDelta delta =
+ (unscheduled_frames_.front()->timestamp() - current_media_time) /
+ playback_rate_;
+ base::TimeTicks target_wall_ticks = current_wall_ticks + delta;
- frames_decoded_ = 0;
- frames_dropped_ = 0;
+ scheduled_frames_.push_back(unscheduled_frames_.front());
+ unscheduled_frames_.pop_front();
+
+ scheduler_->ScheduleVideoFrame(
+ scheduled_frames_.back(),
+ target_wall_ticks,
+ base::Bind(&VideoRendererImpl::OnVideoFrameFinished,
+ weak_factory_.GetWeakPtr()));
+ }
+}
+
+void VideoRendererImpl::ScheduleFirstFrameForImmediateDisplay() {
+ scheduled_frames_.push_back(unscheduled_frames_.front());
+ unscheduled_frames_.pop_front();
+
+ scheduler_->ScheduleVideoFrame(
+ scheduled_frames_.back(),
+ base::TimeTicks::Now(),
+ base::Bind(&VideoRendererImpl::OnVideoFrameFinished,
+ weak_factory_.GetWeakPtr()));
+}
+
+void VideoRendererImpl::OnVideoFrameFinished(
+ const scoped_refptr<VideoFrame>& frame,
+ VideoFrameScheduler::Reason reason) {
+ VideoFrameQueue::iterator iter =
+ std::find(scheduled_frames_.begin(), scheduled_frames_.end(), frame);
+ CHECK(iter != scheduled_frames_.end());
+
+ switch (reason) {
+ case VideoFrameScheduler::DISPLAYED:
+ ++frames_decoded_;
+ scheduled_frames_.erase(iter);
+ break;
+
+ case VideoFrameScheduler::DROPPED:
+ ++frames_decoded_;
+ ++frames_dropped_;
+ scheduled_frames_.erase(iter);
+ break;
+
+ case VideoFrameScheduler::RESET:
+ unscheduled_frames_.push_back(*iter);
+ scheduled_frames_.erase(iter);
+ break;
+ }
+
+ if (unscheduled_frames_.empty() && received_end_of_stream_) {
scherkus (not reviewing) 2014/04/16 23:45:02 I'm pretty sure this will fire ended too soon. We
+ state_ = kEnded;
+ ended_cb_.Run();
+ return;
}
- frame_available_.TimedWait(wait_duration);
+ AttemptRead();
}
} // namespace media
« no previous file with comments | « media/filters/video_renderer_impl.h ('k') | media/filters/video_renderer_impl_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698