Chromecast: adds media::Renderer implementation for CMA pipeline.

BUG=408189

Review URL: https://codereview.chromium.org/750683003

Cr-Commit-Position: refs/heads/master@{#307277}
diff --git a/chromecast/media/cma/filters/cma_renderer.cc b/chromecast/media/cma/filters/cma_renderer.cc
new file mode 100644
index 0000000..600ec5b
--- /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
diff --git a/chromecast/media/cma/filters/cma_renderer.h b/chromecast/media/cma/filters/cma_renderer.h
new file mode 100644
index 0000000..d1c83ac
--- /dev/null
+++ b/chromecast/media/cma/filters/cma_renderer.h
@@ -0,0 +1,150 @@
+// 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.
+
+#ifndef CHROMECAST_MEDIA_CMA_FILTERS_CMA_RENDERER_H_
+#define CHROMECAST_MEDIA_CMA_FILTERS_CMA_RENDERER_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/default_tick_clock.h"
+#include "media/base/buffering_state.h"
+#include "media/base/renderer.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace base {
+class MessageLoopProxy;
+}
+
+namespace media {
+class DemuxerStreamProvider;
+class TimeDeltaInterpolator;
+class VideoFrame;
+}
+
+namespace chromecast {
+namespace media {
+class AudioPipeline;
+class BalancedMediaTaskRunnerFactory;
+class MediaPipeline;
+class VideoPipeline;
+
+class CmaRenderer : public ::media::Renderer {
+ public:
+  explicit CmaRenderer(scoped_ptr<MediaPipeline> media_pipeline);
+  ~CmaRenderer() override;
+
+  // ::media::Renderer implementation:
+  void 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) override;
+  void Flush(const base::Closure& flush_cb) override;
+  void StartPlayingFrom(base::TimeDelta time) override;
+  void SetPlaybackRate(float playback_rate) override;
+  void SetVolume(float volume) override;
+  base::TimeDelta GetMediaTime() override;
+  bool HasAudio() override;
+  bool HasVideo() override;
+  void SetCdm(::media::CdmContext* cdm_context,
+              const ::media::CdmAttachedCB& cdm_attached_cb) override;
+
+ private:
+  enum State {
+    kUninitialized,
+    kPlaying,
+    kFlushed,
+    kError,
+  };
+
+  // Asynchronous initialization sequence. These four methods are invoked in
+  // the order below, with a successful initialization making it to
+  // OnVideoPipelineInitializeDone, regardless of which streams are present.
+  void InitializeAudioPipeline();
+  void OnAudioPipelineInitializeDone(::media::PipelineStatus status);
+  void InitializeVideoPipeline();
+  void OnVideoPipelineInitializeDone(::media::PipelineStatus status);
+
+  // Callbacks for AvPipelineClient.
+  void OnEosReached(bool is_audio);
+  void OnStatisticsUpdated(const ::media::PipelineStatistics& stats);
+  void OnNaturalSizeChanged(const gfx::Size& size);
+
+  // Callbacks for MediaPipelineClient.
+  void OnPlaybackTimeUpdated(base::TimeDelta time,
+                             base::TimeDelta max_time,
+                             base::TimeTicks capture_time);
+  void OnBufferingNotification(::media::BufferingState state);
+
+  void OnFlushDone(::media::PipelineStatus status);
+  void OnError(::media::PipelineStatus status);
+
+  void FireAllPendingCallbacks();
+
+  // Begin a state transition.
+  // Return true if delayed because of a pending state transition.
+  void BeginStateTransition();
+  void CompleteStateTransition(State new_state);
+
+  base::ThreadChecker thread_checker_;
+
+  scoped_refptr<BalancedMediaTaskRunnerFactory> media_task_runner_factory_;
+  scoped_ptr<MediaPipeline> media_pipeline_;
+  AudioPipeline* audio_pipeline_;
+  VideoPipeline* video_pipeline_;
+
+  ::media::DemuxerStreamProvider* demuxer_stream_provider_;
+
+  // Set of callbacks.
+  PaintCB paint_cb_;
+  base::Closure init_cb_;
+  ::media::StatisticsCB statistics_cb_;
+  base::Closure ended_cb_;
+  ::media::PipelineStatusCB error_cb_;
+  ::media::BufferingStateCB buffering_state_cb_;
+  base::Closure flush_cb_;
+
+  // Renderer state.
+  // Used mostly for checking that transitions are correct.
+  State state_;
+  bool is_pending_transition_;
+
+  bool has_audio_;
+  bool has_video_;
+
+  bool received_audio_eos_;
+  bool received_video_eos_;
+
+  // Data members for helping the creation of the initial video hole frame.
+  gfx::Size initial_natural_size_;
+  bool initial_video_hole_created_;
+
+  // Lock protecting access to |time_interpolator_|.
+  base::Lock time_interpolator_lock_;
+
+  // base::TickClock used by |time_interpolator_|.
+  base::DefaultTickClock default_tick_clock_;
+
+  // Tracks the most recent media time update and provides interpolated values
+  // as playback progresses.
+  scoped_ptr< ::media::TimeDeltaInterpolator> time_interpolator_;
+
+  float playback_rate_;
+
+  base::WeakPtr<CmaRenderer> weak_this_;
+  base::WeakPtrFactory<CmaRenderer> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(CmaRenderer);
+};
+
+}  // namespace media
+}  // namespace chromecast
+
+#endif  // CHROMECAST_MEDIA_CMA_FILTERS_CMA_RENDERER_H_
diff --git a/chromecast/media/media.gyp b/chromecast/media/media.gyp
index 8c09410b..4912f78 100644
--- a/chromecast/media/media.gyp
+++ b/chromecast/media/media.gyp
@@ -216,6 +216,8 @@
         'cma_base',
       ],
       'sources': [
+        'cma/filters/cma_renderer.cc',
+        'cma/filters/cma_renderer.h',
         'cma/filters/demuxer_stream_adapter.cc',
         'cma/filters/demuxer_stream_adapter.h',
       ],