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

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: rebase over split out CLs 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 7d99dbf14a4bbfda6a7b5c68b8e253ddb46dd4f8..3bd282201f3753f5c95671c2055ee40948086b19 100644
--- a/media/filters/video_renderer_impl.cc
+++ b/media/filters/video_renderer_impl.cc
@@ -5,74 +5,71 @@
#include "media/filters/video_renderer_impl.h"
#include "base/bind.h"
-#include "base/callback.h"
#include "base/callback_helpers.h"
-#include "base/debug/trace_event.h"
-#include "base/location.h"
#include "base/single_thread_task_runner.h"
-#include "base/threading/platform_thread.h"
-#include "media/base/buffers.h"
+#include "base/time/default_tick_clock.h"
#include "media/base/limits.h"
-#include "media/base/pipeline.h"
#include "media/base/video_frame.h"
namespace media {
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()),
+ tick_clock_(new base::DefaultTickClock()),
video_frame_stream_(task_runner, decoders.Pass(), set_decryptor_ready_cb),
+ next_scheduled_frame_id_(0),
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());
}
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;
+
+ ScheduleAllFrames();
+ AttemptRead();
+
+ // 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);
+
state_ = kPaused;
+ ResetScheduler();
+
callback.Run();
}
void VideoRendererImpl::Flush(const base::Closure& callback) {
DCHECK(task_runner_->BelongsToCurrentThread());
- base::AutoLock auto_lock(lock_);
DCHECK_EQ(state_, kPaused);
+ DCHECK_EQ(scheduled_frames_.size(), 0u);
flush_cb_ = callback;
state_ = kFlushing;
- // 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();
+ unscheduled_frames_.clear();
+
received_end_of_stream_ = false;
video_frame_stream_.Reset(
base::Bind(&VideoRendererImpl::OnVideoFrameStreamResetDone,
@@ -81,7 +78,6 @@ 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;
@@ -94,44 +90,40 @@ 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();
+ scheduled_frames_.clear();
+ scheduler_->Reset();
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;
+ ResetScheduler();
+
+ if (state_ == kPlaying)
+ ScheduleAllFrames();
}
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_;
+ DCHECK(state_ == kFlushed || state_ == kPaused) << "state_ " << state_;
if (state_ == kFlushed) {
DCHECK(time != kNoTimestamp());
DCHECK(!pending_read_);
- DCHECK(ready_frames_.empty());
+ DCHECK(unscheduled_frames_.empty());
+ DCHECK(scheduled_frames_.empty());
} else {
+ // Prerolling without a timestamp is used for rebuffering.
DCHECK(time == kNoTimestamp());
}
@@ -139,12 +131,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,
@@ -156,7 +148,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());
@@ -185,7 +176,6 @@ void VideoRendererImpl::Initialize(DemuxerStream* stream,
void VideoRendererImpl::OnVideoFrameStreamInitialized(bool success) {
DCHECK(task_runner_->BelongsToCurrentThread());
- base::AutoLock auto_lock(lock_);
if (state_ == kStopped)
return;
@@ -198,143 +188,14 @@ void VideoRendererImpl::OnVideoFrameStreamInitialized(bool success) {
return;
}
- // We're all good! Consider ourselves flushed. (ThreadMain() should never
- // see us in the kUninitialized state).
- // Since we had an initial Preroll(), we consider ourself flushed, because we
- // have not populated any buffers yet.
+ // Consider ourselves flushed as no frames have been received 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_);
DCHECK_NE(state_, kUninitialized);
DCHECK_NE(state_, kFlushed);
@@ -367,7 +228,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;
}
@@ -378,7 +239,10 @@ void VideoRendererImpl::FrameReady(VideoFrameStream::Status status,
max_time_cb_.Run(get_duration_cb_.Run());
if (state_ == kPrerolling)
- TransitionToPrerolled_Locked();
+ TransitionToPrerolled();
+
+ if (ShouldTransitionToEnded())
+ TransitionToEnded();
return;
}
@@ -387,32 +251,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
@@ -423,28 +264,49 @@ 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();
+ ScheduleAllFrames();
+
+ 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() {
+ return state_ == kPlaying && unscheduled_frames_.empty() &&
+ scheduled_frames_.empty() && received_end_of_stream_;
}
-void VideoRendererImpl::AttemptRead_Locked() {
+void VideoRendererImpl::TransitionToEnded() {
+ DCHECK(ShouldTransitionToEnded());
+ state_ = kEnded;
+ ended_cb_.Run();
+}
+
+void VideoRendererImpl::AttemptRead() {
DCHECK(task_runner_->BelongsToCurrentThread());
- lock_.AssertAcquired();
if (pending_read_ || received_end_of_stream_ ||
- ready_frames_.size() == static_cast<size_t>(limits::kMaxVideoFrames)) {
+ video_frame_count() == limits::kMaxVideoFrames) {
return;
}
@@ -470,41 +332,20 @@ void VideoRendererImpl::AttemptRead_Locked() {
}
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;
@@ -512,29 +353,113 @@ 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();
+ 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::ResetScheduler() {
+ for (VideoFrameIDMap::iterator iter = scheduled_frames_.begin();
+ iter != scheduled_frames_.end();
+ ++iter) {
+ unscheduled_frames_.push_back(iter->second);
+ }
+ scheduled_frames_.clear();
+ scheduler_->Reset();
+}
+
+void VideoRendererImpl::ScheduleAllFrames() {
+ DCHECK_EQ(state_, kPlaying);
+
+ if (playback_rate_ == 0)
+ return;
+
+ base::TimeDelta current_media_time = get_time_cb_.Run();
+ base::TimeTicks current_wall_ticks = tick_clock_->NowTicks();
+
+ while (!unscheduled_frames_.empty()) {
+ // Negative deltas are permitted as it's up to the scheduler to decide
+ // what it wants to do with late frames.
+ base::TimeDelta delta =
+ unscheduled_frames_.front()->timestamp() - current_media_time;
+ base::TimeDelta scaled_delta = base::TimeDelta::FromMicroseconds(
+ static_cast<int64>(delta.InMicroseconds() / playback_rate_));
+ base::TimeTicks target_wall_ticks = current_wall_ticks + scaled_delta;
+
+ scoped_refptr<VideoFrame> frame = unscheduled_frames_.front();
+ int frame_id = next_scheduled_frame_id_++;
+ scheduled_frames_[frame_id] = frame;
+ unscheduled_frames_.pop_front();
+
+ scheduler_->ScheduleVideoFrame(
+ frame,
+ target_wall_ticks,
+ base::Bind(&VideoRendererImpl::OnVideoFrameFinished,
+ weak_factory_.GetWeakPtr(),
+ frame_id));
+ }
+}
+
+void VideoRendererImpl::ScheduleFirstFrameForImmediateDisplay() {
+ DCHECK_EQ(state_, kPrerolled);
+
+ scoped_refptr<VideoFrame> frame = unscheduled_frames_.front();
+ int frame_id = next_scheduled_frame_id_++;
+ scheduled_frames_[frame_id] = frame;
+ unscheduled_frames_.pop_front();
+
+ scheduler_->ScheduleVideoFrame(
+ frame,
+ tick_clock_->NowTicks(),
+ base::Bind(&VideoRendererImpl::OnVideoFrameFinished,
+ weak_factory_.GetWeakPtr(),
+ frame_id));
+}
+
+void VideoRendererImpl::OnVideoFrameFinished(
+ int frame_id,
+ const scoped_refptr<VideoFrame>& frame,
+ VideoFrameScheduler::Reason reason) {
+ VideoFrameIDMap::iterator iter = scheduled_frames_.find(frame_id);
+ if (iter == scheduled_frames_.end()) {
+ LOG(ERROR) << "Ignoring frame_id=" << frame_id
+ << ", timestamp=" << frame->timestamp().InMicroseconds();
+ return;
+ }
+
+ CHECK_EQ(iter->second, frame);
+ scheduled_frames_.erase(iter);
- if (frames_decoded_) {
- PipelineStatistics statistics;
- statistics.video_frames_decoded = frames_decoded_;
- statistics.video_frames_dropped = frames_dropped_;
- statistics_cb_.Run(statistics);
+ switch (reason) {
+ case VideoFrameScheduler::DISPLAYED:
+ ++frames_decoded_;
+ break;
- frames_decoded_ = 0;
- frames_dropped_ = 0;
+ case VideoFrameScheduler::DROPPED:
+ ++frames_decoded_;
+ ++frames_dropped_;
+ break;
}
- frame_available_.TimedWait(wait_duration);
+ PipelineStatistics stats;
+ stats.video_frames_decoded = frames_decoded_;
+ stats.video_frames_dropped = frames_dropped_;
+ statistics_cb_.Run(stats);
+
+ if (ShouldTransitionToEnded()) {
+ TransitionToEnded();
+ return;
+ }
+
+ AttemptRead();
+}
+
+void VideoRendererImpl::SetTickClockForTesting(
+ scoped_ptr<base::TickClock> tick_clock) {
+ tick_clock_.swap(tick_clock);
}
} // 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