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

Unified Diff: media/renderers/video_renderer_impl.cc

Issue 1083383005: Connect the new video rendering path to the compositor. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@vra
Patch Set: Update LoginCustomFlags histogram. Created 5 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/renderers/video_renderer_impl.h ('k') | media/renderers/video_renderer_impl_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: media/renderers/video_renderer_impl.cc
diff --git a/media/renderers/video_renderer_impl.cc b/media/renderers/video_renderer_impl.cc
index 3e0698f14266106940b6c6047f240b2698995b46..558c8e6dfe108ed279cd1fe95e45eea0682d87b7 100644
--- a/media/renderers/video_renderer_impl.cc
+++ b/media/renderers/video_renderer_impl.cc
@@ -7,6 +7,7 @@
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
+#include "base/command_line.h"
#include "base/location.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/platform_thread.h"
@@ -15,11 +16,16 @@
#include "media/base/bind_to_current_loop.h"
#include "media/base/buffers.h"
#include "media/base/limits.h"
+#include "media/base/media_switches.h"
#include "media/base/pipeline.h"
#include "media/base/video_frame.h"
namespace media {
+// Wait a quarter of a second for Render() callbacks before starting background
+// rendering to keep the decode pump moving.
+const int kBackgroundRenderingTimeoutMs = 250;
+
VideoRendererImpl::VideoRendererImpl(
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
VideoRendererSink* sink,
@@ -27,7 +33,11 @@ VideoRendererImpl::VideoRendererImpl(
bool drop_frames,
const scoped_refptr<MediaLog>& media_log)
: task_runner_(task_runner),
+ use_new_video_renderering_path_(
+ base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableNewVideoRenderer)),
sink_(sink),
+ sink_started_(false),
video_frame_stream_(
new VideoFrameStream(task_runner, decoders.Pass(), media_log)),
low_delay_(false),
@@ -43,13 +53,19 @@ VideoRendererImpl::VideoRendererImpl(
frames_dropped_(0),
is_shutting_down_(false),
tick_clock_(new base::DefaultTickClock()),
+ is_background_rendering_(false),
+ should_use_background_renderering_(true),
+ time_progressing_(false),
+ render_first_frame_and_stop_(false),
+ background_rendering_timeout_(
+ base::TimeDelta::FromMilliseconds(kBackgroundRenderingTimeoutMs)),
weak_factory_(this) {
}
VideoRendererImpl::~VideoRendererImpl() {
DCHECK(task_runner_->BelongsToCurrentThread());
- {
+ if (!use_new_video_renderering_path_) {
base::AutoLock auto_lock(lock_);
is_shutting_down_ = true;
frame_available_.Signal();
@@ -63,11 +79,18 @@ VideoRendererImpl::~VideoRendererImpl() {
if (!flush_cb_.is_null())
base::ResetAndReturn(&flush_cb_).Run();
+
+ if (use_new_video_renderering_path_ && sink_started_)
+ StopSink();
}
void VideoRendererImpl::Flush(const base::Closure& callback) {
DVLOG(1) << __FUNCTION__;
DCHECK(task_runner_->BelongsToCurrentThread());
+
+ if (use_new_video_renderering_path_ && sink_started_)
+ StopSink();
+
base::AutoLock auto_lock(lock_);
DCHECK_EQ(state_, kPlaying);
flush_cb_ = callback;
@@ -83,6 +106,9 @@ void VideoRendererImpl::Flush(const base::Closure& callback) {
received_end_of_stream_ = false;
rendered_end_of_stream_ = false;
+ if (use_new_video_renderering_path_)
+ algorithm_->Reset();
+
video_frame_stream_->Reset(
base::Bind(&VideoRendererImpl::OnVideoFrameStreamResetDone,
weak_factory_.GetWeakPtr()));
@@ -122,6 +148,9 @@ void VideoRendererImpl::Initialize(
DCHECK(!ended_cb.is_null());
DCHECK(!wall_clock_time_cb.is_null());
DCHECK_EQ(kUninitialized, state_);
+ DCHECK(!render_first_frame_and_stop_);
+ DCHECK(!is_background_rendering_);
+ DCHECK(!time_progressing_);
low_delay_ = (stream->liveness() == DemuxerStream::LIVENESS_LIVE);
@@ -147,14 +176,67 @@ void VideoRendererImpl::Initialize(
scoped_refptr<VideoFrame> VideoRendererImpl::Render(
base::TimeTicks deadline_min,
base::TimeTicks deadline_max) {
- // TODO(dalecurtis): Hook this up to the new VideoRendererAlgorithm.
- NOTIMPLEMENTED();
- return nullptr;
+ base::AutoLock auto_lock(lock_);
+ DCHECK(use_new_video_renderering_path_);
+ DCHECK_EQ(state_, kPlaying);
+ last_render_time_ = tick_clock_->NowTicks();
+
+ size_t frames_dropped = 0;
+ scoped_refptr<VideoFrame> result =
+ algorithm_->Render(deadline_min, deadline_max, &frames_dropped);
+
+ // Due to how the |algorithm_| holds frames, this should never be null if
+ // we've had a proper startup sequence.
+ DCHECK(result);
+
+ // See if it's time to fire the ended callback.
+ const size_t effective_frames = MaybeFireEndedCallback();
+
+ // Declare HAVE_NOTHING if we have no more effective frames.
+ if (!effective_frames && !rendered_end_of_stream_ &&
+ buffering_state_ == BUFFERING_HAVE_ENOUGH) {
+ buffering_state_ = BUFFERING_HAVE_NOTHING;
+ task_runner_->PostTask(
+ FROM_HERE, base::Bind(buffering_state_cb_, BUFFERING_HAVE_NOTHING));
+ }
+
+ // As we resume from background rendering, don't count the initial batch of
+ // dropped frames since they are likely just dropped due to being too old.
+ if (!is_background_rendering_) {
+ frames_dropped_ += frames_dropped;
+ UpdateStatsAndWait_Locked(base::TimeDelta());
+ }
+ is_background_rendering_ = false;
+
+ // After painting the first frame, if playback hasn't started, we request that
+ // the sink be stopped. OnTimeStateChanged() will clear this flag if time
+ // starts before we get here and MaybeStopSinkAfterFirstPaint() will ignore
+ // this request if time starts before the call executes.
+ if (render_first_frame_and_stop_) {
+ task_runner_->PostTask(
+ FROM_HERE, base::Bind(&VideoRendererImpl::MaybeStopSinkAfterFirstPaint,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ // Always post this task, it will acquire new frames if necessary, reset the
+ // background rendering timer, and more.
+ task_runner_->PostTask(FROM_HERE, base::Bind(&VideoRendererImpl::AttemptRead,
+ weak_factory_.GetWeakPtr()));
+
+ return result;
}
void VideoRendererImpl::OnFrameDropped() {
- // TODO(dalecurtis): Hook this up to the new VideoRendererAlgorithm.
- NOTIMPLEMENTED();
+ base::AutoLock auto_lock(lock_);
+ DCHECK(use_new_video_renderering_path_);
+ algorithm_->OnLastFrameDropped();
+}
+
+void VideoRendererImpl::SetBackgroundRenderingForTesting(
+ bool enabled,
+ base::TimeDelta timeout) {
+ should_use_background_renderering_ = enabled;
+ background_rendering_timeout_ = timeout;
}
void VideoRendererImpl::CreateVideoThread() {
@@ -186,13 +268,20 @@ void VideoRendererImpl::OnVideoFrameStreamInitialized(bool success) {
// have not populated any buffers yet.
state_ = kFlushed;
- CreateVideoThread();
+ if (use_new_video_renderering_path_) {
+ algorithm_.reset(new VideoRendererAlgorithm(wall_clock_time_cb_));
+ if (!drop_frames_)
+ algorithm_->disable_frame_dropping();
+ } else {
+ CreateVideoThread();
+ }
base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK);
}
// PlatformThread::Delegate implementation.
void VideoRendererImpl::ThreadMain() {
+ DCHECK(!use_new_video_renderering_path_);
base::PlatformThread::SetName("CrVideoRenderer");
// The number of milliseconds to idle when we do not have anything to do.
@@ -290,15 +379,27 @@ void VideoRendererImpl::SetTickClockForTesting(
}
void VideoRendererImpl::OnTimeStateChanged(bool time_progressing) {
- // TODO(dalecurtis): Wire up to the VideoRendererSink once it's implemented.
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ time_progressing_ = time_progressing;
+
+ // WARNING: Do not attempt to use |lock_| here as this may be a reentrant call
+ // in response to callbacks firing above.
+
+ if (!use_new_video_renderering_path_ || sink_started_ == time_progressing_)
+ return;
+
+ if (time_progressing_)
+ StartSink();
+ else
+ StopSink();
}
void VideoRendererImpl::PaintNextReadyFrame_Locked() {
+ DCHECK(!use_new_video_renderering_path_);
lock_.AssertAcquired();
scoped_refptr<VideoFrame> next_frame = ready_frames_.front();
ready_frames_.pop_front();
- frames_decoded_++;
last_media_time_ = wall_clock_time_cb_.Run(next_frame->timestamp());
@@ -310,6 +411,7 @@ void VideoRendererImpl::PaintNextReadyFrame_Locked() {
}
void VideoRendererImpl::DropNextReadyFrame_Locked() {
+ DCHECK(!use_new_video_renderering_path_);
TRACE_EVENT0("media", "VideoRendererImpl:frameDropped");
lock_.AssertAcquired();
@@ -318,7 +420,6 @@ void VideoRendererImpl::DropNextReadyFrame_Locked() {
wall_clock_time_cb_.Run(ready_frames_.front()->timestamp());
ready_frames_.pop_front();
- frames_decoded_++;
frames_dropped_++;
task_runner_->PostTask(
@@ -329,64 +430,109 @@ void VideoRendererImpl::DropNextReadyFrame_Locked() {
void VideoRendererImpl::FrameReady(VideoFrameStream::Status status,
const scoped_refptr<VideoFrame>& frame) {
DCHECK(task_runner_->BelongsToCurrentThread());
- base::AutoLock auto_lock(lock_);
- DCHECK_NE(state_, kUninitialized);
- DCHECK_NE(state_, kFlushed);
-
- CHECK(pending_read_);
- pending_read_ = false;
-
- if (status == VideoFrameStream::DECODE_ERROR ||
- status == VideoFrameStream::DECRYPT_ERROR) {
- DCHECK(!frame.get());
- PipelineStatus error = PIPELINE_ERROR_DECODE;
- if (status == VideoFrameStream::DECRYPT_ERROR)
- error = PIPELINE_ERROR_DECRYPT;
- task_runner_->PostTask(FROM_HERE, base::Bind(error_cb_, error));
- return;
- }
+ bool start_sink = false;
+ {
+ base::AutoLock auto_lock(lock_);
+ DCHECK_NE(state_, kUninitialized);
+ DCHECK_NE(state_, kFlushed);
+
+ CHECK(pending_read_);
+ pending_read_ = false;
+
+ if (status == VideoFrameStream::DECODE_ERROR ||
+ status == VideoFrameStream::DECRYPT_ERROR) {
+ DCHECK(!frame.get());
+ PipelineStatus error = PIPELINE_ERROR_DECODE;
+ if (status == VideoFrameStream::DECRYPT_ERROR)
+ error = PIPELINE_ERROR_DECRYPT;
+ task_runner_->PostTask(FROM_HERE, base::Bind(error_cb_, error));
+ return;
+ }
- // Already-queued VideoFrameStream ReadCB's can fire after various state
- // transitions have happened; in that case just drop those frames immediately.
- if (state_ == kFlushing)
- return;
+ // Already-queued VideoFrameStream ReadCB's can fire after various state
+ // transitions have happened; in that case just drop those frames
+ // immediately.
+ if (state_ == kFlushing)
+ return;
- DCHECK_EQ(state_, kPlaying);
+ DCHECK_EQ(state_, kPlaying);
- // Can happen when demuxers are preparing for a new Seek().
- if (!frame.get()) {
- DCHECK_EQ(status, VideoFrameStream::DEMUXER_READ_ABORTED);
- return;
- }
+ // Can happen when demuxers are preparing for a new Seek().
+ if (!frame.get()) {
+ DCHECK_EQ(status, VideoFrameStream::DEMUXER_READ_ABORTED);
+ return;
+ }
- if (frame->end_of_stream()) {
- DCHECK(!received_end_of_stream_);
- received_end_of_stream_ = true;
- } else {
- // Maintain the latest frame decoded so the correct frame is displayed after
- // prerolling has completed.
- if (frame->timestamp() <= start_timestamp_)
- ready_frames_.clear();
- AddReadyFrame_Locked(frame);
- }
+ if (frame->end_of_stream()) {
+ DCHECK(!received_end_of_stream_);
+ received_end_of_stream_ = true;
- // Signal buffering state if we've met our conditions for having enough data.
- if (buffering_state_ != BUFFERING_HAVE_ENOUGH && HaveEnoughData_Locked())
- TransitionToHaveEnough_Locked();
+ // See if we can fire EOS immediately instead of waiting for Render() or
+ // BackgroundRender() to tick. We also want to fire EOS if we have no
+ // frames and received EOS.
+ if (use_new_video_renderering_path_ &&
+ (time_progressing_ || !algorithm_->frames_queued())) {
+ MaybeFireEndedCallback();
+ }
+ } else {
+ // Maintain the latest frame decoded so the correct frame is displayed
+ // after prerolling has completed.
+ if (frame->timestamp() <= start_timestamp_) {
+ if (use_new_video_renderering_path_)
+ algorithm_->Reset();
+ ready_frames_.clear();
+ }
+ AddReadyFrame_Locked(frame);
+ }
- // 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();
+ // Signal buffering state if we've met our conditions for having enough
+ // data.
+ if (buffering_state_ != BUFFERING_HAVE_ENOUGH && HaveEnoughData_Locked()) {
+ TransitionToHaveEnough_Locked();
+ if (use_new_video_renderering_path_ && !sink_started_ &&
+ !rendered_end_of_stream_) {
+ start_sink = true;
+ render_first_frame_and_stop_ = true;
+ }
+ }
+
+ // BackgroundRender may not be ticking fast enough by itself to remove
+ // expired frames, so give it a boost here by ensuring we don't exit the
+ // decoding cycle too early.
+ if (is_background_rendering_) {
+ DCHECK(use_new_video_renderering_path_);
+ BackgroundRender_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();
+ }
+
+ // If time is progressing, the sink has already been started; this may be true
+ // if we have previously underflowed, yet weren't stopped because of audio.
+ if (use_new_video_renderering_path_ && start_sink) {
+ DCHECK(!sink_started_);
+ StartSink();
+ }
}
bool VideoRendererImpl::HaveEnoughData_Locked() {
DCHECK_EQ(state_, kPlaying);
- return received_end_of_stream_ ||
- !video_frame_stream_->CanReadWithoutStalling() ||
- ready_frames_.size() >= static_cast<size_t>(limits::kMaxVideoFrames) ||
- (low_delay_ && ready_frames_.size() > 0);
+
+ if (received_end_of_stream_ || !video_frame_stream_->CanReadWithoutStalling())
+ return true;
+
+ if (HaveReachedBufferingCap())
+ return true;
+
+ if (!low_delay_)
+ return false;
+
+ return ready_frames_.size() > 0 ||
+ (use_new_video_renderering_path_ && algorithm_->frames_queued() > 0);
}
void VideoRendererImpl::TransitionToHaveEnough_Locked() {
@@ -394,6 +540,7 @@ void VideoRendererImpl::TransitionToHaveEnough_Locked() {
DCHECK_EQ(buffering_state_, BUFFERING_HAVE_NOTHING);
if (!ready_frames_.empty()) {
+ DCHECK(!use_new_video_renderering_path_);
// Because the clock might remain paused in for an undetermined amount
// of time (e.g., seeking while paused), paint the first frame.
PaintNextReadyFrame_Locked();
@@ -409,6 +556,13 @@ void VideoRendererImpl::AddReadyFrame_Locked(
lock_.AssertAcquired();
DCHECK(!frame->end_of_stream());
+ frames_decoded_++;
+
+ if (use_new_video_renderering_path_) {
+ algorithm_->EnqueueFrame(frame);
+ return;
+ }
+
ready_frames_.push_back(frame);
DCHECK_LE(ready_frames_.size(),
static_cast<size_t>(limits::kMaxVideoFrames));
@@ -427,10 +581,11 @@ void VideoRendererImpl::AttemptRead_Locked() {
DCHECK(task_runner_->BelongsToCurrentThread());
lock_.AssertAcquired();
- if (pending_read_ || received_end_of_stream_ ||
- ready_frames_.size() == static_cast<size_t>(limits::kMaxVideoFrames)) {
+ if (pending_read_ || received_end_of_stream_)
+ return;
+
+ if (HaveReachedBufferingCap())
return;
- }
switch (state_) {
case kPlaying:
@@ -465,9 +620,9 @@ void VideoRendererImpl::UpdateStatsAndWait_Locked(
base::TimeDelta wait_duration) {
lock_.AssertAcquired();
DCHECK_GE(frames_decoded_, 0);
- DCHECK_LE(frames_dropped_, frames_decoded_);
+ DCHECK_GE(frames_dropped_, 0);
- if (frames_decoded_) {
+ if (frames_decoded_ || frames_dropped_) {
PipelineStatistics statistics;
statistics.video_frames_decoded = frames_decoded_;
statistics.video_frames_dropped = frames_dropped_;
@@ -477,7 +632,130 @@ void VideoRendererImpl::UpdateStatsAndWait_Locked(
frames_dropped_ = 0;
}
- frame_available_.TimedWait(wait_duration);
+ if (wait_duration > base::TimeDelta())
+ frame_available_.TimedWait(wait_duration);
+}
+
+void VideoRendererImpl::MaybeStopSinkAfterFirstPaint() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ DCHECK(use_new_video_renderering_path_);
+
+ {
+ base::AutoLock auto_lock(lock_);
+ render_first_frame_and_stop_ = false;
+ }
+
+ if (!time_progressing_ && sink_started_)
+ StopSink();
+}
+
+void VideoRendererImpl::StartBackgroundRenderTimer() {
+ DCHECK(use_new_video_renderering_path_);
+ if (!drop_frames_ || !should_use_background_renderering_)
+ return;
+
+ // |last_render_time_| and |background_rendering_timer_| work in conjunction
+ // to ensure we don't exceed |kBackgroundRenderingTimeoutMs| between calls to
+ // Render(). BackgroundRender() will tick repeatedly and verify that Render()
+ // has fired within the allotted time, by checking |last_render_time_|.
+ last_render_time_ = tick_clock_->NowTicks();
+ background_rendering_timer_.Start(
+ FROM_HERE, background_rendering_timeout_,
+ base::Bind(&VideoRendererImpl::BackgroundRender,
+ weak_factory_.GetWeakPtr()));
+}
+
+void VideoRendererImpl::BackgroundRender() {
+ base::AutoLock auto_lock(lock_);
+ BackgroundRender_Locked();
+}
+
+void VideoRendererImpl::BackgroundRender_Locked() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ DCHECK_EQ(state_, kPlaying);
+ lock_.AssertAcquired();
+
+ // If a Render() call never occurs after starting playback for the first frame
+ // we need to carry out the duties of Render() and stop the sink. We don't
+ // call MaybeStopSinkAfterFirstPaint() since it may need to mutate |sink_|,
+ // which can't be done under lock.
+ if (render_first_frame_and_stop_) {
+ task_runner_->PostTask(
+ FROM_HERE, base::Bind(&VideoRendererImpl::MaybeStopSinkAfterFirstPaint,
+ weak_factory_.GetWeakPtr()));
+
+ // MaybeStopSinkAfterFirstPaint isn't going to stop the sink if time is
+ // currently progressing, so only bail out if necessary.
+ if (!time_progressing_)
+ return;
+ }
+
+ // Do nothing if Render() calls are progressing normally.
+ if (tick_clock_->NowTicks() - last_render_time_ <
+ background_rendering_timeout_) {
+ return;
+ }
+
+ // First clear as many expired frames as we can.
+ algorithm_->RemoveExpiredFrames(tick_clock_->NowTicks());
+ is_background_rendering_ = true;
+
+ // See if we've run out of frames and need to fire the ended callback.
+ MaybeFireEndedCallback();
+ if (rendered_end_of_stream_)
+ return;
+
+ // Start queuing new frames and scheduled this process again otherwise.
+ AttemptRead_Locked();
+ UpdateStatsAndWait_Locked(base::TimeDelta());
+}
+
+bool VideoRendererImpl::HaveReachedBufferingCap() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ if (use_new_video_renderering_path_) {
+ // When the display rate is less than the frame rate, the effective frames
+ // queued may be much smaller than the actual number of frames queued. Here
+ // we ensure that frames_queued() doesn't get excessive.
+ return algorithm_->EffectiveFramesQueued() >=
+ static_cast<size_t>(limits::kMaxVideoFrames) ||
+ algorithm_->frames_queued() >=
+ static_cast<size_t>(3 * limits::kMaxVideoFrames);
+ }
+
+ return ready_frames_.size() >= static_cast<size_t>(limits::kMaxVideoFrames);
+}
+
+void VideoRendererImpl::StartSink() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ sink_->Start(this);
+ sink_started_ = true;
+ StartBackgroundRenderTimer();
+}
+
+void VideoRendererImpl::StopSink() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ sink_->Stop();
+ sink_started_ = false;
+ background_rendering_timer_.Stop();
+ is_background_rendering_ = false;
+}
+
+size_t VideoRendererImpl::MaybeFireEndedCallback() {
+ // If there's only one frame in the video or Render() was never called, the
+ // algorithm will have one frame linger indefinitely. So in cases where the
+ // frame duration is unknown and we've received EOS, fire it once we get down
+ // to a single frame.
+ const size_t effective_frames = algorithm_->EffectiveFramesQueued();
+
+ if ((!effective_frames ||
+ (algorithm_->frames_queued() == 1u &&
+ algorithm_->average_frame_duration() == base::TimeDelta())) &&
+ received_end_of_stream_ && !rendered_end_of_stream_) {
+ rendered_end_of_stream_ = true;
+ task_runner_->PostTask(FROM_HERE, ended_cb_);
+ }
+
+ return effective_frames;
}
} // namespace media
« no previous file with comments | « media/renderers/video_renderer_impl.h ('k') | media/renderers/video_renderer_impl_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698