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

Unified Diff: chromecast/media/cma/filters/cma_renderer.cc

Issue 750683003: Chromecast: adds media::Renderer implementation for CMA pipeline. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: address comments Created 6 years 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 | « chromecast/media/cma/filters/cma_renderer.h ('k') | chromecast/media/media.gyp » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chromecast/media/cma/filters/cma_renderer.cc
diff --git a/chromecast/media/cma/filters/cma_renderer.cc b/chromecast/media/cma/filters/cma_renderer.cc
new file mode 100644
index 0000000000000000000000000000000000000000..600ec5bae6eb48c6a5c96780438250522bcff8d6
--- /dev/null
+++ b/chromecast/media/cma/filters/cma_renderer.cc
@@ -0,0 +1,453 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/cma/filters/cma_renderer.h"
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "chromecast/media/cma/base/balanced_media_task_runner_factory.h"
+#include "chromecast/media/cma/base/cma_logging.h"
+#include "chromecast/media/cma/filters/demuxer_stream_adapter.h"
+#include "chromecast/media/cma/pipeline/audio_pipeline.h"
+#include "chromecast/media/cma/pipeline/av_pipeline_client.h"
+#include "chromecast/media/cma/pipeline/media_pipeline.h"
+#include "chromecast/media/cma/pipeline/media_pipeline_client.h"
+#include "chromecast/media/cma/pipeline/video_pipeline.h"
+#include "chromecast/media/cma/pipeline/video_pipeline_client.h"
+#include "media/base/bind_to_current_loop.h"
+#include "media/base/demuxer_stream_provider.h"
+#include "media/base/pipeline_status.h"
+#include "media/base/time_delta_interpolator.h"
+#include "media/base/video_frame.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace chromecast {
+namespace media {
+
+namespace {
+
+// Maximum difference between audio frame PTS and video frame PTS
+// for frames read from the DemuxerStream.
+const base::TimeDelta kMaxDeltaFetcher(
+ base::TimeDelta::FromMilliseconds(2000));
+
+} // namespace
+
+CmaRenderer::CmaRenderer(scoped_ptr<MediaPipeline> media_pipeline)
+ : media_task_runner_factory_(
+ new BalancedMediaTaskRunnerFactory(kMaxDeltaFetcher)),
+ media_pipeline_(media_pipeline.Pass()),
+ audio_pipeline_(media_pipeline_->GetAudioPipeline()),
+ video_pipeline_(media_pipeline_->GetVideoPipeline()),
+ state_(kUninitialized),
+ is_pending_transition_(false),
+ has_audio_(false),
+ has_video_(false),
+ received_audio_eos_(false),
+ received_video_eos_(false),
+ initial_natural_size_(gfx::Size()),
+ initial_video_hole_created_(false),
+ time_interpolator_(
+ new ::media::TimeDeltaInterpolator(&default_tick_clock_)),
+ playback_rate_(1.0f),
+ weak_factory_(this) {
+ weak_this_ = weak_factory_.GetWeakPtr();
+ thread_checker_.DetachFromThread();
+
+ time_interpolator_->SetUpperBound(base::TimeDelta());
+}
+
+CmaRenderer::~CmaRenderer() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ FireAllPendingCallbacks();
+}
+
+void CmaRenderer::Initialize(
+ ::media::DemuxerStreamProvider* demuxer_stream_provider,
+ const base::Closure& init_cb,
+ const ::media::StatisticsCB& statistics_cb,
+ const ::media::BufferingStateCB& buffering_state_cb,
+ const PaintCB& paint_cb,
+ const base::Closure& ended_cb,
+ const ::media::PipelineStatusCB& error_cb) {
+ CMALOG(kLogControl) << __FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(state_, kUninitialized) << state_;
+ DCHECK(!init_cb.is_null());
+ DCHECK(!statistics_cb.is_null());
+ DCHECK(!ended_cb.is_null());
+ DCHECK(!error_cb.is_null());
+ DCHECK(!buffering_state_cb.is_null());
+ DCHECK(demuxer_stream_provider_->GetStream(::media::DemuxerStream::AUDIO) ||
+ demuxer_stream_provider_->GetStream(::media::DemuxerStream::VIDEO));
+
+ BeginStateTransition();
+
+ demuxer_stream_provider_ = demuxer_stream_provider;
+ statistics_cb_ = statistics_cb;
+ buffering_state_cb_ = buffering_state_cb;
+ paint_cb_ = paint_cb;
+ ended_cb_ = ended_cb;
+ error_cb_ = error_cb;
+
+ MediaPipelineClient media_pipeline_client;
+ media_pipeline_client.error_cb = error_cb_;
+ media_pipeline_client.buffering_state_cb = ::media::BindToCurrentLoop(
+ base::Bind(&CmaRenderer::OnBufferingNotification, weak_this_));
+ media_pipeline_client.time_update_cb = ::media::BindToCurrentLoop(
+ base::Bind(&CmaRenderer::OnPlaybackTimeUpdated, weak_this_));
+ media_pipeline_->SetClient(media_pipeline_client);
+
+ init_cb_ = init_cb;
+ InitializeAudioPipeline();
+}
+
+void CmaRenderer::Flush(const base::Closure& flush_cb) {
+ CMALOG(kLogControl) << __FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
+ BeginStateTransition();
+
+ DCHECK(flush_cb_.is_null());
+ flush_cb_ = flush_cb;
+
+ if (state_ == kError) {
+ OnError(::media::PIPELINE_ERROR_ABORT);
+ return;
+ }
+
+ DCHECK_EQ(state_, kPlaying) << state_;
+ media_pipeline_->Flush(
+ ::media::BindToCurrentLoop(
+ base::Bind(&CmaRenderer::OnFlushDone, weak_this_)));
+
+ {
+ base::AutoLock auto_lock(time_interpolator_lock_);
+ time_interpolator_->StopInterpolating();
+ }
+}
+
+void CmaRenderer::StartPlayingFrom(base::TimeDelta time) {
+ CMALOG(kLogControl) << __FUNCTION__ << ": " << time.InMilliseconds();
+ DCHECK(thread_checker_.CalledOnValidThread());
+ BeginStateTransition();
+
+ if (state_ == kError) {
+ error_cb_.Run(::media::PIPELINE_ERROR_ABORT);
+ CompleteStateTransition(kError);
+ return;
+ }
+
+#if defined(VIDEO_HOLE)
+ // Create a video hole frame just before starting playback.
+ // Note that instead of creating the video hole frame in Initialize(), we do
+ // it here because paint_cb_ (which eventually calls OnOpacityChanged)
+ // expects the current state to not be HaveNothing. And the place where
+ // the ready state is changed to HaveMetadata (OnPipelineMetadata) is
+ // right before the pipeline calls StartPlayingFrom (in
+ // Pipeline::StateTransitionTask).
+ if (!initial_video_hole_created_) {
+ initial_video_hole_created_ = true;
+ paint_cb_.Run(::media::VideoFrame::CreateHoleFrame(initial_natural_size_));
+ }
+#endif
+
+ {
+ base::AutoLock auto_lock(time_interpolator_lock_);
+ time_interpolator_.reset(
+ new ::media::TimeDeltaInterpolator(&default_tick_clock_));
+ time_interpolator_->SetPlaybackRate(playback_rate_);
+ time_interpolator_->SetBounds(time, time);
+ time_interpolator_->StartInterpolating();
+ }
+
+ received_audio_eos_ = false;
+ received_video_eos_ = false;
+
+ DCHECK_EQ(state_, kFlushed) << state_;
+ // Immediately update transition to playing.
+ // Error case will be handled on response from host.
+ media_pipeline_->StartPlayingFrom(time);
+ CompleteStateTransition(kPlaying);
+}
+
+void CmaRenderer::SetPlaybackRate(float playback_rate) {
+ CMALOG(kLogControl) << __FUNCTION__ << ": " << playback_rate;
+ DCHECK(thread_checker_.CalledOnValidThread());
+ media_pipeline_->SetPlaybackRate(playback_rate);
+ playback_rate_ = playback_rate;
+
+ {
+ base::AutoLock auto_lock(time_interpolator_lock_);
+ time_interpolator_->SetPlaybackRate(playback_rate);
+ }
+}
+
+void CmaRenderer::SetVolume(float volume) {
+ CMALOG(kLogControl) << __FUNCTION__ << ": " << volume;
+ DCHECK(thread_checker_.CalledOnValidThread());
+ audio_pipeline_->SetVolume(volume);
+}
+
+base::TimeDelta CmaRenderer::GetMediaTime() {
+ base::AutoLock auto_lock(time_interpolator_lock_);
+ return time_interpolator_->GetInterpolatedTime();
+}
+
+bool CmaRenderer::HasAudio() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return has_audio_;
+}
+
+bool CmaRenderer::HasVideo() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return has_video_;
+}
+
+void CmaRenderer::SetCdm(::media::CdmContext* cdm_context,
+ const ::media::CdmAttachedCB& cdm_attached_cb) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+#if defined(ENABLE_BROWSER_CDMS)
+ media_pipeline_->SetCdm(cdm_context->GetCdmId());
+#endif
+ cdm_attached_cb.Run(true);
+}
+
+void CmaRenderer::InitializeAudioPipeline() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(state_, kUninitialized) << state_;
+ DCHECK(!init_cb_.is_null());
+
+ ::media::PipelineStatusCB audio_initialization_done_cb =
+ ::media::BindToCurrentLoop(
+ base::Bind(&CmaRenderer::OnAudioPipelineInitializeDone, weak_this_));
+
+ ::media::DemuxerStream* stream =
+ demuxer_stream_provider_->GetStream(::media::DemuxerStream::AUDIO);
+ if (!stream) {
+ audio_initialization_done_cb.Run(::media::PIPELINE_OK);
+ return;
+ }
+
+ // Receive events from the audio pipeline.
+ AvPipelineClient av_pipeline_client;
+ av_pipeline_client.eos_cb = ::media::BindToCurrentLoop(
+ base::Bind(&CmaRenderer::OnEosReached, weak_this_, true));
+ av_pipeline_client.playback_error_cb = ::media::BindToCurrentLoop(
+ base::Bind(&CmaRenderer::OnError, weak_this_));
+ av_pipeline_client.statistics_cb = ::media::BindToCurrentLoop(
+ base::Bind(&CmaRenderer::OnStatisticsUpdated, weak_this_));
+ audio_pipeline_->SetClient(av_pipeline_client);
+
+ scoped_ptr<CodedFrameProvider> frame_provider(
+ new DemuxerStreamAdapter(
+ base::MessageLoopProxy::current(),
+ media_task_runner_factory_,
+ stream));
+
+ const ::media::AudioDecoderConfig& config = stream->audio_decoder_config();
+ if (config.codec() == ::media::kCodecAAC)
+ stream->EnableBitstreamConverter();
+
+ has_audio_ = true;
+ media_pipeline_->InitializeAudio(
+ config, frame_provider.Pass(), audio_initialization_done_cb);
+}
+
+void CmaRenderer::OnAudioPipelineInitializeDone(
+ ::media::PipelineStatus status) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(state_, kUninitialized) << state_;
+ DCHECK(!init_cb_.is_null());
+
+ if (status != ::media::PIPELINE_OK) {
+ has_audio_ = false;
+ OnError(status);
+ return;
+ }
+
+ InitializeVideoPipeline();
+}
+
+void CmaRenderer::InitializeVideoPipeline() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(state_, kUninitialized) << state_;
+ DCHECK(!init_cb_.is_null());
+
+ ::media::PipelineStatusCB video_initialization_done_cb =
+ ::media::BindToCurrentLoop(
+ base::Bind(&CmaRenderer::OnVideoPipelineInitializeDone, weak_this_));
+
+ ::media::DemuxerStream* stream =
+ demuxer_stream_provider_->GetStream(::media::DemuxerStream::VIDEO);
+ if (!stream) {
+ video_initialization_done_cb.Run(::media::PIPELINE_OK);
+ return;
+ }
+
+ // Receive events from the video pipeline.
+ VideoPipelineClient client;
+ client.av_pipeline_client.eos_cb = ::media::BindToCurrentLoop(
+ base::Bind(&CmaRenderer::OnEosReached, weak_this_, false));
+ client.av_pipeline_client.playback_error_cb = ::media::BindToCurrentLoop(
+ base::Bind(&CmaRenderer::OnError, weak_this_));
+ client.av_pipeline_client.statistics_cb = ::media::BindToCurrentLoop(
+ base::Bind(&CmaRenderer::OnStatisticsUpdated, weak_this_));
+ client.natural_size_changed_cb = ::media::BindToCurrentLoop(
+ base::Bind(&CmaRenderer::OnNaturalSizeChanged, weak_this_));
+ video_pipeline_->SetClient(client);
+
+ scoped_ptr<CodedFrameProvider> frame_provider(
+ new DemuxerStreamAdapter(
+ base::MessageLoopProxy::current(),
+ media_task_runner_factory_,
+ stream));
+
+ const ::media::VideoDecoderConfig& config = stream->video_decoder_config();
+ if (config.codec() == ::media::kCodecH264)
+ stream->EnableBitstreamConverter();
+
+ initial_natural_size_ = config.natural_size();
+
+ has_video_ = true;
+ media_pipeline_->InitializeVideo(
+ config,
+ frame_provider.Pass(),
+ video_initialization_done_cb);
+}
+
+void CmaRenderer::OnVideoPipelineInitializeDone(
+ ::media::PipelineStatus status) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(state_, kUninitialized) << state_;
+ DCHECK(!init_cb_.is_null());
+
+ if (status != ::media::PIPELINE_OK) {
+ has_video_ = false;
+ OnError(status);
+ return;
+ }
+
+ CompleteStateTransition(kFlushed);
+ base::ResetAndReturn(&init_cb_).Run();
+}
+
+void CmaRenderer::OnEosReached(bool is_audio) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ CMALOG(kLogControl) << __FUNCTION__;
+ if (state_ != kPlaying) {
+ LOG(WARNING) << "Ignoring a late EOS event";
+ return;
+ }
+
+ if (is_audio) {
+ DCHECK(!received_audio_eos_);
+ received_audio_eos_ = true;
+ } else {
+ DCHECK(!received_video_eos_);
+ received_video_eos_ = true;
+ }
+
+ bool audio_finished = !has_audio_ || received_audio_eos_;
+ bool video_finished = !has_video_ || received_video_eos_;
+ if (audio_finished && video_finished)
+ ended_cb_.Run();
+}
+
+void CmaRenderer::OnStatisticsUpdated(
+ const ::media::PipelineStatistics& stats) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ statistics_cb_.Run(stats);
+}
+
+void CmaRenderer::OnNaturalSizeChanged(const gfx::Size& size) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+#if defined(VIDEO_HOLE)
+ paint_cb_.Run(::media::VideoFrame::CreateHoleFrame(size));
+#endif
+}
+
+void CmaRenderer::OnPlaybackTimeUpdated(
+ base::TimeDelta time,
+ base::TimeDelta max_time,
+ base::TimeTicks capture_time) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (state_ != kPlaying) {
+ LOG(WARNING) << "Ignoring a late time update";
+ return;
+ }
+
+ // TODO(halliwell): arguably, TimeDeltaInterpolator::SetBounds should perform
+ // this calculation to avoid calling TimeTicks::Now twice (it's slower and has
+ // potential accuracy problems).
+ base::TimeDelta lower_bound =
+ std::min(max_time, time + base::TimeTicks::Now() - capture_time);
+
+ base::AutoLock auto_lock(time_interpolator_lock_);
+ time_interpolator_->SetBounds(lower_bound, max_time);
+}
+
+void CmaRenderer::OnBufferingNotification(
+ ::media::BufferingState buffering_state) {
+ CMALOG(kLogControl) << __FUNCTION__ << ": state=" << state_
+ << ", buffering=" << buffering_state;
+ // TODO(gunsch): WebMediaPlayerImpl currently only handles HAVE_ENOUGH while
+ // playing. See OnPipelineBufferingStateChanged, http://crbug.com/144683.
+ if (state_ != kPlaying) {
+ LOG(WARNING) << "Ignoring buffering notification in state: " << state_;
+ return;
+ }
+ if (buffering_state != ::media::BUFFERING_HAVE_ENOUGH) {
+ LOG(WARNING) << "Ignoring buffering notification during playing: "
+ << buffering_state;
+ return;
+ }
+ buffering_state_cb_.Run(buffering_state);
+}
+
+void CmaRenderer::OnFlushDone(::media::PipelineStatus status) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (status != ::media::PIPELINE_OK) {
+ OnError(status);
+ return;
+ }
+
+ CompleteStateTransition(kFlushed);
+ base::ResetAndReturn(&flush_cb_).Run();
+}
+
+void CmaRenderer::OnError(::media::PipelineStatus error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_NE(::media::PIPELINE_OK, error) << "PIPELINE_OK isn't an error!";
+ LOG(ERROR) << "CMA error encountered: " << error;
+
+ // Pipeline will destroy |this| as the result of error.
+ if (state_ != kError)
+ error_cb_.Run(error);
+
+ CompleteStateTransition(kError);
+ FireAllPendingCallbacks();
+}
+
+void CmaRenderer::FireAllPendingCallbacks() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!init_cb_.is_null())
+ base::ResetAndReturn(&init_cb_).Run();
+ if (!flush_cb_.is_null())
+ base::ResetAndReturn(&flush_cb_).Run();
+}
+
+void CmaRenderer::BeginStateTransition() {
+ DCHECK(!is_pending_transition_) << state_;
+ is_pending_transition_ = true;
+}
+
+void CmaRenderer::CompleteStateTransition(State new_state) {
+ state_ = new_state;
+ is_pending_transition_ = false;
+}
+
+} // namespace media
+} // namespace chromecast
« no previous file with comments | « chromecast/media/cma/filters/cma_renderer.h ('k') | chromecast/media/media.gyp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698