diff options
author | gunsch <gunsch@chromium.org> | 2014-11-20 13:31:07 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-11-20 21:31:35 +0000 |
commit | bd08add63918abae4ef8fb10b515bef9ce153d14 (patch) | |
tree | 9bea26ffeb5b0a13aaf8d925b536b1139640632c /chromecast/media | |
parent | 34cf1b98e64108d50969638dc39a1b1bc92ee8f7 (diff) | |
download | chromium_src-bd08add63918abae4ef8fb10b515bef9ce153d14.zip chromium_src-bd08add63918abae4ef8fb10b515bef9ce153d14.tar.gz chromium_src-bd08add63918abae4ef8fb10b515bef9ce153d14.tar.bz2 |
Chromecast: adds a media pipeline feeding data to CMA device backends.
The pipeline state transitions are based closely on the interface
of ::media::Renderer. The pipeline interfaces themselves are intended
to be used both renderer- and browser-side, with future renderer-side
implementations using the CMA IPC code for data feeding.
R=lcwu@chromium.org,damienv@chromium.org,servolk@chromium.org
BUG=408189
Review URL: https://codereview.chromium.org/741863002
Cr-Commit-Position: refs/heads/master@{#305081}
Diffstat (limited to 'chromecast/media')
27 files changed, 2329 insertions, 7 deletions
diff --git a/chromecast/media/cma/base/buffering_defs.cc b/chromecast/media/cma/base/buffering_defs.cc new file mode 100644 index 0000000..3afd0f8 --- /dev/null +++ b/chromecast/media/cma/base/buffering_defs.cc @@ -0,0 +1,14 @@ +// 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/base/buffering_defs.h" + +namespace chromecast { +namespace media { + +const size_t kAppAudioBufferSize = 64 * 1024; +const size_t kAppVideoBufferSize = 4 * 1024 * 1024; + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/base/buffering_defs.h b/chromecast/media/cma/base/buffering_defs.h new file mode 100644 index 0000000..a56a9a6 --- /dev/null +++ b/chromecast/media/cma/base/buffering_defs.h @@ -0,0 +1,19 @@ +// 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_BASE_BUFFERING_DEFS_H_ +#define CHROMECAST_MEDIA_CMA_BASE_BUFFERING_DEFS_H_ + +#include "base/basictypes.h" + +namespace chromecast { +namespace media { + +extern const size_t kAppAudioBufferSize; +extern const size_t kAppVideoBufferSize; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_BUFFERING_DEFS_H_ diff --git a/chromecast/media/cma/filters/demuxer_stream_adapter_unittest.cc b/chromecast/media/cma/filters/demuxer_stream_adapter_unittest.cc index bee63eb..d0573ab 100644 --- a/chromecast/media/cma/filters/demuxer_stream_adapter_unittest.cc +++ b/chromecast/media/cma/filters/demuxer_stream_adapter_unittest.cc @@ -44,12 +44,12 @@ class DummyDemuxerStream : public ::media::DemuxerStream { virtual ~DummyDemuxerStream(); // ::media::DemuxerStream implementation. - virtual void Read(const ReadCB& read_cb) override; - virtual ::media::AudioDecoderConfig audio_decoder_config() override; - virtual ::media::VideoDecoderConfig video_decoder_config() override; - virtual Type type() override; - virtual bool SupportsConfigChanges() override; - virtual ::media::VideoRotation video_rotation() override; + void Read(const ReadCB& read_cb) override; + ::media::AudioDecoderConfig audio_decoder_config() override; + ::media::VideoDecoderConfig video_decoder_config() override; + Type type() const override; + bool SupportsConfigChanges() override; + ::media::VideoRotation video_rotation() override; bool has_pending_read() const { return has_pending_read_; @@ -127,7 +127,7 @@ void DummyDemuxerStream::Read(const ReadCB& read_cb) { false); } -::media::DemuxerStream::Type DummyDemuxerStream::type() { +::media::DemuxerStream::Type DummyDemuxerStream::type() const { return VIDEO; } diff --git a/chromecast/media/cma/pipeline/audio_pipeline.cc b/chromecast/media/cma/pipeline/audio_pipeline.cc new file mode 100644 index 0000000..7d98e09 --- /dev/null +++ b/chromecast/media/cma/pipeline/audio_pipeline.cc @@ -0,0 +1,17 @@ +// 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/pipeline/audio_pipeline.h" + +namespace chromecast { +namespace media { + +AudioPipeline::AudioPipeline() { +} + +AudioPipeline::~AudioPipeline() { +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/pipeline/audio_pipeline.h b/chromecast/media/cma/pipeline/audio_pipeline.h new file mode 100644 index 0000000..d7284a1 --- /dev/null +++ b/chromecast/media/cma/pipeline/audio_pipeline.h @@ -0,0 +1,34 @@ +// 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_PIPELINE_AUDIO_PIPELINE_H_ +#define CHROMECAST_MEDIA_CMA_PIPELINE_AUDIO_PIPELINE_H_ + +#include "base/macros.h" +#include "chromecast/media/cma/pipeline/av_pipeline_client.h" + +namespace chromecast { +namespace media { + +class AudioPipeline { + public: + AudioPipeline(); + virtual ~AudioPipeline(); + + // Set the audio client. + virtual void SetClient(const AvPipelineClient& client) = 0; + + // Set the stream volume. + // - A value of 1.0 is the neutral value. + // - |volume|=0.0 mutes the stream. + virtual void SetVolume(float volume) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(AudioPipeline); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_PIPELINE_AUDIO_PIPELINE_H_ diff --git a/chromecast/media/cma/pipeline/audio_pipeline_impl.cc b/chromecast/media/cma/pipeline/audio_pipeline_impl.cc new file mode 100644 index 0000000..404031f --- /dev/null +++ b/chromecast/media/cma/pipeline/audio_pipeline_impl.cc @@ -0,0 +1,157 @@ +// 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/pipeline/audio_pipeline_impl.h" + +#include "base/bind.h" +#include "chromecast/media/cma/backend/audio_pipeline_device.h" +#include "chromecast/media/cma/base/buffering_defs.h" +#include "chromecast/media/cma/base/cma_logging.h" +#include "chromecast/media/cma/base/coded_frame_provider.h" +#include "chromecast/media/cma/pipeline/av_pipeline_impl.h" +#include "media/base/audio_decoder_config.h" + +namespace chromecast { +namespace media { + +namespace { +const size_t kMaxAudioFrameSize = 32 * 1024; +} + +AudioPipelineImpl::AudioPipelineImpl(AudioPipelineDevice* audio_device) + : audio_device_(audio_device), + weak_factory_(this) { + av_pipeline_impl_.reset(new AvPipelineImpl( + audio_device_, + base::Bind(&AudioPipelineImpl::OnUpdateConfig, base::Unretained(this)))); + weak_this_ = weak_factory_.GetWeakPtr(); +} + +AudioPipelineImpl::~AudioPipelineImpl() { +} + +void AudioPipelineImpl::SetCodedFrameProvider( + scoped_ptr<CodedFrameProvider> frame_provider) { + av_pipeline_impl_->SetCodedFrameProvider( + frame_provider.Pass(), kAppAudioBufferSize, kMaxAudioFrameSize); +} + +void AudioPipelineImpl::SetClient(const AvPipelineClient& client) { + audio_client_ = client; + av_pipeline_impl_->SetClient(client); +} + +bool AudioPipelineImpl::StartPlayingFrom( + base::TimeDelta time, + const scoped_refptr<BufferingState>& buffering_state) { + CMALOG(kLogControl) << "AudioPipelineImpl::StartPlayingFrom t0=" + << time.InMilliseconds(); + + // Reset the pipeline statistics. + previous_stats_ = ::media::PipelineStatistics(); + + // Start playing. + if (av_pipeline_impl_->GetState() == AvPipelineImpl::kError) + return false; + DCHECK_EQ(av_pipeline_impl_->GetState(), AvPipelineImpl::kFlushed); + + if (!av_pipeline_impl_->StartPlayingFrom(time, buffering_state)) { + av_pipeline_impl_->TransitionToState(AvPipelineImpl::kError); + return false; + } + av_pipeline_impl_->TransitionToState(AvPipelineImpl::kPlaying); + + return true; +} + +void AudioPipelineImpl::Flush(const ::media::PipelineStatusCB& status_cb) { + CMALOG(kLogControl) << "AudioPipelineImpl::Flush"; + if (av_pipeline_impl_->GetState() == AvPipelineImpl::kError) { + status_cb.Run(::media::PIPELINE_ERROR_ABORT); + return; + } + DCHECK_EQ(av_pipeline_impl_->GetState(), AvPipelineImpl::kPlaying); + av_pipeline_impl_->TransitionToState(AvPipelineImpl::kFlushing); + av_pipeline_impl_->Flush( + base::Bind(&AudioPipelineImpl::OnFlushDone, weak_this_, status_cb)); +} + +void AudioPipelineImpl::OnFlushDone( + const ::media::PipelineStatusCB& status_cb) { + CMALOG(kLogControl) << "AudioPipelineImpl::OnFlushDone"; + if (av_pipeline_impl_->GetState() == AvPipelineImpl::kError) { + status_cb.Run(::media::PIPELINE_ERROR_ABORT); + return; + } + av_pipeline_impl_->TransitionToState(AvPipelineImpl::kFlushed); + status_cb.Run(::media::PIPELINE_OK); +} + +void AudioPipelineImpl::Stop() { + CMALOG(kLogControl) << "AudioPipelineImpl::Stop"; + av_pipeline_impl_->Stop(); + av_pipeline_impl_->TransitionToState(AvPipelineImpl::kStopped); +} + +void AudioPipelineImpl::SetCdm(BrowserCdmCast* media_keys) { + av_pipeline_impl_->SetCdm(media_keys); +} + +void AudioPipelineImpl::Initialize( + const ::media::AudioDecoderConfig& audio_config, + scoped_ptr<CodedFrameProvider> frame_provider, + const ::media::PipelineStatusCB& status_cb) { + CMALOG(kLogControl) << "AudioPipelineImpl::Initialize " + << audio_config.AsHumanReadableString(); + if (frame_provider) + SetCodedFrameProvider(frame_provider.Pass()); + + if (!audio_device_->SetConfig(audio_config) || + !av_pipeline_impl_->Initialize()) { + status_cb.Run(::media::PIPELINE_ERROR_INITIALIZATION_FAILED); + return; + } + av_pipeline_impl_->TransitionToState(AvPipelineImpl::kFlushed); + status_cb.Run(::media::PIPELINE_OK); +} + +void AudioPipelineImpl::SetVolume(float volume) { + audio_device_->SetStreamVolumeMultiplier(volume); +} + +void AudioPipelineImpl::OnUpdateConfig( + const ::media::AudioDecoderConfig& audio_config, + const ::media::VideoDecoderConfig& video_config) { + if (audio_config.IsValidConfig()) { + CMALOG(kLogControl) << "AudioPipelineImpl::OnUpdateConfig " + << audio_config.AsHumanReadableString(); + + bool success = audio_device_->SetConfig(audio_config); + if (!success && !audio_client_.playback_error_cb.is_null()) + audio_client_.playback_error_cb.Run(::media::PIPELINE_ERROR_DECODE); + } +} + +void AudioPipelineImpl::UpdateStatistics() { + if (audio_client_.statistics_cb.is_null()) + return; + + MediaComponentDevice::Statistics device_stats; + if (!audio_device_->GetStatistics(&device_stats)) + return; + + ::media::PipelineStatistics current_stats; + current_stats.audio_bytes_decoded = device_stats.decoded_bytes; + + ::media::PipelineStatistics delta_stats; + delta_stats.audio_bytes_decoded = + current_stats.audio_bytes_decoded - previous_stats_.audio_bytes_decoded; + + previous_stats_ = current_stats; + + audio_client_.statistics_cb.Run(delta_stats); +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/pipeline/audio_pipeline_impl.h b/chromecast/media/cma/pipeline/audio_pipeline_impl.h new file mode 100644 index 0000000..275ef0c --- /dev/null +++ b/chromecast/media/cma/pipeline/audio_pipeline_impl.h @@ -0,0 +1,79 @@ +// 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_BASE_AUDIO_PIPELINE_IMPL_H_ +#define CHROMECAST_MEDIA_CMA_BASE_AUDIO_PIPELINE_IMPL_H_ + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" +#include "chromecast/media/cma/pipeline/audio_pipeline.h" +#include "chromecast/media/cma/pipeline/av_pipeline_client.h" + +namespace media { +class AudioDecoderConfig; +class VideoDecoderConfig; +} + +namespace chromecast { +namespace media { +class AudioPipelineDevice; +class AvPipelineImpl; +class BrowserCdmCast; +class BufferingState; +class CodedFrameProvider; + +class AudioPipelineImpl : public AudioPipeline { + public: + // |buffering_controller| can be NULL. + explicit AudioPipelineImpl(AudioPipelineDevice* audio_device); + ~AudioPipelineImpl() override; + + // Input port of the pipeline. + void SetCodedFrameProvider(scoped_ptr<CodedFrameProvider> frame_provider); + + // Provide the CDM to use to decrypt samples. + void SetCdm(BrowserCdmCast* media_keys); + + // Functions to control the state of the audio pipeline. + void Initialize( + const ::media::AudioDecoderConfig& config, + scoped_ptr<CodedFrameProvider> frame_provider, + const ::media::PipelineStatusCB& status_cb); + bool StartPlayingFrom(base::TimeDelta time, + const scoped_refptr<BufferingState>& buffering_state); + void Flush(const ::media::PipelineStatusCB& status_cb); + void Stop(); + + // Update the playback statistics for this audio stream. + void UpdateStatistics(); + + // AudioPipeline implementation. + void SetClient(const AvPipelineClient& client) override; + void SetVolume(float volume) override; + + private: + void OnFlushDone(const ::media::PipelineStatusCB& status_cb); + void OnUpdateConfig(const ::media::AudioDecoderConfig& audio_config, + const ::media::VideoDecoderConfig& video_config); + + AudioPipelineDevice* audio_device_; + + scoped_ptr<AvPipelineImpl> av_pipeline_impl_; + AvPipelineClient audio_client_; + + ::media::PipelineStatistics previous_stats_; + + base::WeakPtrFactory<AudioPipelineImpl> weak_factory_; + base::WeakPtr<AudioPipelineImpl> weak_this_; + + DISALLOW_COPY_AND_ASSIGN(AudioPipelineImpl); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_AUDIO_PIPELINE_IMPL_H_ diff --git a/chromecast/media/cma/pipeline/audio_video_pipeline_impl_unittest.cc b/chromecast/media/cma/pipeline/audio_video_pipeline_impl_unittest.cc new file mode 100644 index 0000000..86bb29e --- /dev/null +++ b/chromecast/media/cma/pipeline/audio_video_pipeline_impl_unittest.cc @@ -0,0 +1,211 @@ +// 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 <vector> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/time/time.h" +#include "chromecast/media/cma/backend/audio_pipeline_device.h" +#include "chromecast/media/cma/backend/media_clock_device.h" +#include "chromecast/media/cma/backend/media_pipeline_device.h" +#include "chromecast/media/cma/backend/media_pipeline_device_fake.h" +#include "chromecast/media/cma/base/buffering_controller.h" +#include "chromecast/media/cma/base/decoder_buffer_base.h" +#include "chromecast/media/cma/pipeline/audio_pipeline_impl.h" +#include "chromecast/media/cma/pipeline/av_pipeline_client.h" +#include "chromecast/media/cma/pipeline/media_pipeline_impl.h" +#include "chromecast/media/cma/pipeline/video_pipeline_client.h" +#include "chromecast/media/cma/pipeline/video_pipeline_impl.h" +#include "chromecast/media/cma/test/frame_generator_for_test.h" +#include "chromecast/media/cma/test/mock_frame_provider.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/buffers.h" +#include "media/base/decoder_buffer.h" +#include "media/base/video_decoder_config.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromecast { +namespace media { + +class AudioVideoPipelineImplTest : public testing::Test { + public: + AudioVideoPipelineImplTest(); + virtual ~AudioVideoPipelineImplTest(); + + void Initialize(const base::Closure& done_cb, + ::media::PipelineStatus status, + bool is_audio); + void StartPlaying(const base::Closure& done_cb, + ::media::PipelineStatus status); + + void Flush(const base::Closure& done_cb, ::media::PipelineStatus status); + void Stop(const base::Closure& done_cb, ::media::PipelineStatus status); + + void OnEos(); + + base::Closure task_after_eos_cb_; + + private: + scoped_ptr<MediaPipelineImpl> media_pipeline_; + + DISALLOW_COPY_AND_ASSIGN(AudioVideoPipelineImplTest); +}; + +AudioVideoPipelineImplTest::AudioVideoPipelineImplTest() + : media_pipeline_(new MediaPipelineImpl()) { + scoped_ptr<MediaPipelineDevice> media_pipeline_device( + new MediaPipelineDeviceFake()); + media_pipeline_->Initialize(kLoadTypeURL, media_pipeline_device.Pass()); + media_pipeline_->SetPlaybackRate(1.0); +} + +AudioVideoPipelineImplTest::~AudioVideoPipelineImplTest() { +} + +void AudioVideoPipelineImplTest::Initialize( + const base::Closure& done_cb, + ::media::PipelineStatus status, + bool is_audio) { + if (is_audio) { + AvPipelineClient client; + client.eos_cb = + base::Bind(&AudioVideoPipelineImplTest::OnEos, base::Unretained(this)); + media_pipeline_->GetAudioPipeline()->SetClient(client); + } else { + VideoPipelineClient client; + client.av_pipeline_client.eos_cb = + base::Bind(&AudioVideoPipelineImplTest::OnEos, base::Unretained(this)); + media_pipeline_->GetVideoPipeline()->SetClient(client); + } + + ::media::AudioDecoderConfig audio_config( + ::media::kCodecMP3, + ::media::kSampleFormatS16, + ::media::CHANNEL_LAYOUT_STEREO, + 44100, + NULL, 0, false); + ::media::VideoDecoderConfig video_config( + ::media::kCodecH264, + ::media::H264PROFILE_MAIN, + ::media::VideoFrame::I420, + gfx::Size(640, 480), + gfx::Rect(0, 0, 640, 480), + gfx::Size(640, 480), + NULL, 0, false); + + // Frame generation on the producer side. + std::vector<FrameGeneratorForTest::FrameSpec> frame_specs; + frame_specs.resize(100); + for (size_t k = 0; k < frame_specs.size() - 1; k++) { + frame_specs[k].has_config = (k == 0); + frame_specs[k].timestamp = base::TimeDelta::FromMilliseconds(40) * k; + frame_specs[k].size = 512; + frame_specs[k].has_decrypt_config = false; + } + frame_specs[frame_specs.size() - 1].is_eos = true; + + scoped_ptr<FrameGeneratorForTest> frame_generator_provider( + new FrameGeneratorForTest(frame_specs)); + bool provider_delayed_pattern[] = { true, false }; + scoped_ptr<MockFrameProvider> frame_provider(new MockFrameProvider()); + frame_provider->Configure( + std::vector<bool>( + provider_delayed_pattern, + provider_delayed_pattern + arraysize(provider_delayed_pattern)), + frame_generator_provider.Pass()); + + ::media::PipelineStatusCB next_task = + base::Bind(&AudioVideoPipelineImplTest::StartPlaying, + base::Unretained(this), + done_cb); + + scoped_ptr<CodedFrameProvider> frame_provider_base(frame_provider.release()); + base::Closure task = is_audio ? + base::Bind(&MediaPipeline::InitializeAudio, + base::Unretained(media_pipeline_.get()), + audio_config, + base::Passed(&frame_provider_base), + next_task) : + base::Bind(&MediaPipeline::InitializeVideo, + base::Unretained(media_pipeline_.get()), + video_config, + base::Passed(&frame_provider_base), + next_task); + + base::MessageLoopProxy::current()->PostTask(FROM_HERE, task); +} + +void AudioVideoPipelineImplTest::StartPlaying( + const base::Closure& done_cb, ::media::PipelineStatus status) { + base::TimeDelta start_time = base::TimeDelta::FromMilliseconds(0); + + media_pipeline_->StartPlayingFrom(start_time); + if (!done_cb.is_null()) + done_cb.Run(); +} + +void AudioVideoPipelineImplTest::OnEos() { + task_after_eos_cb_.Run(); +} + +void AudioVideoPipelineImplTest::Flush( + const base::Closure& done_cb, ::media::PipelineStatus status) { + ::media::PipelineStatusCB next_task = + base::Bind(&AudioVideoPipelineImplTest::Stop, base::Unretained(this), + done_cb); + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&MediaPipeline::Flush, + base::Unretained(media_pipeline_.get()), + next_task)); +} + +void AudioVideoPipelineImplTest::Stop( + const base::Closure& done_cb, ::media::PipelineStatus status) { + media_pipeline_->Stop(); + if (!done_cb.is_null()) + done_cb.Run(); + base::MessageLoop::current()->QuitWhenIdle(); +} + + +TEST_F(AudioVideoPipelineImplTest, AudioFullCycleInitToStop) { + bool is_audio = true; + task_after_eos_cb_ = base::Bind( + &AudioVideoPipelineImplTest::Flush, base::Unretained(this), + base::Closure(), ::media::PIPELINE_OK); + + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + message_loop->PostTask( + FROM_HERE, + base::Bind(&AudioVideoPipelineImplTest::Initialize, + base::Unretained(this), + base::Closure(), + ::media::PIPELINE_OK, is_audio)); + message_loop->Run(); +}; + +TEST_F(AudioVideoPipelineImplTest, VideoFullCycleInitToStop) { + bool is_audio = false; + task_after_eos_cb_ = base::Bind( + &AudioVideoPipelineImplTest::Flush, base::Unretained(this), + base::Closure(), ::media::PIPELINE_OK); + + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + message_loop->PostTask( + FROM_HERE, + base::Bind(&AudioVideoPipelineImplTest::Initialize, + base::Unretained(this), + base::Closure(), + ::media::PIPELINE_OK, is_audio)); + message_loop->Run(); +}; + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/pipeline/av_pipeline_client.cc b/chromecast/media/cma/pipeline/av_pipeline_client.cc new file mode 100644 index 0000000..1845c6c --- /dev/null +++ b/chromecast/media/cma/pipeline/av_pipeline_client.cc @@ -0,0 +1,17 @@ +// 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/pipeline/av_pipeline_client.h" + +namespace chromecast { +namespace media { + +AvPipelineClient::AvPipelineClient() { +} + +AvPipelineClient::~AvPipelineClient() { +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/pipeline/av_pipeline_client.h b/chromecast/media/cma/pipeline/av_pipeline_client.h new file mode 100644 index 0000000..6928716 --- /dev/null +++ b/chromecast/media/cma/pipeline/av_pipeline_client.h @@ -0,0 +1,35 @@ +// 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_PIPELINE_AV_PIPELINE_CLIENT_H_ +#define CHROMECAST_MEDIA_CMA_PIPELINE_AV_PIPELINE_CLIENT_H_ + +#include "base/callback.h" +#include "base/time/time.h" +#include "media/base/pipeline_status.h" + +namespace chromecast { +namespace media { + +struct AvPipelineClient { + typedef base::Callback<void( + base::TimeDelta, base::TimeDelta, base::TimeTicks)> TimeUpdateCB; + + AvPipelineClient(); + ~AvPipelineClient(); + + // End of stream notification. + base::Closure eos_cb; + + // Asynchronous playback error notification. + ::media::PipelineStatusCB playback_error_cb; + + // Callback used to report the playback statistics. + ::media::StatisticsCB statistics_cb; +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_PIPELINE_AV_PIPELINE_CLIENT_H_ diff --git a/chromecast/media/cma/pipeline/av_pipeline_impl.cc b/chromecast/media/cma/pipeline/av_pipeline_impl.cc new file mode 100644 index 0000000..dd80a0a5 --- /dev/null +++ b/chromecast/media/cma/pipeline/av_pipeline_impl.cc @@ -0,0 +1,382 @@ +// 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/pipeline/av_pipeline_impl.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/strings/string_number_conversions.h" +#include "chromecast/media/base/decrypt_context.h" +#include "chromecast/media/cdm/browser_cdm_cast.h" +#include "chromecast/media/cma/backend/media_clock_device.h" +#include "chromecast/media/cma/backend/media_component_device.h" +#include "chromecast/media/cma/base/buffering_frame_provider.h" +#include "chromecast/media/cma/base/buffering_state.h" +#include "chromecast/media/cma/base/cma_logging.h" +#include "chromecast/media/cma/base/coded_frame_provider.h" +#include "chromecast/media/cma/base/decoder_buffer_base.h" +#include "chromecast/media/cma/pipeline/decrypt_util.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/decrypt_config.h" + +namespace chromecast { +namespace media { + +namespace { + +const int kNoCallbackId = -1; + +} // namespace + +AvPipelineImpl::AvPipelineImpl( + MediaComponentDevice* media_component_device, + const UpdateConfigCB& update_config_cb) + : update_config_cb_(update_config_cb), + media_component_device_(media_component_device), + state_(kUninitialized), + buffered_time_(::media::kNoTimestamp()), + playable_buffered_time_(::media::kNoTimestamp()), + enable_feeding_(false), + pending_read_(false), + pending_push_(false), + enable_time_update_(false), + pending_time_update_task_(false), + media_keys_(NULL), + media_keys_callback_id_(kNoCallbackId), + weak_factory_(this) { + DCHECK(media_component_device); + weak_this_ = weak_factory_.GetWeakPtr(); + thread_checker_.DetachFromThread(); +} + +AvPipelineImpl::~AvPipelineImpl() { + // If there are weak pointers in the wild, they must be invalidated + // on the right thread. + DCHECK(thread_checker_.CalledOnValidThread()); + media_component_device_->SetClient(MediaComponentDevice::Client()); + + if (media_keys_ && media_keys_callback_id_ != kNoCallbackId) + media_keys_->UnregisterPlayer(media_keys_callback_id_); +} + +void AvPipelineImpl::TransitionToState(State state) { + DCHECK(thread_checker_.CalledOnValidThread()); + state_ = state; +} + +void AvPipelineImpl::SetCodedFrameProvider( + scoped_ptr<CodedFrameProvider> frame_provider, + size_t max_buffer_size, + size_t max_frame_size) { + DCHECK_EQ(state_, kUninitialized); + DCHECK(frame_provider); + + // Wrap the incoming frame provider to add some buffering capabilities. + frame_provider_.reset( + new BufferingFrameProvider( + frame_provider.Pass(), + max_buffer_size, + max_frame_size, + base::Bind(&AvPipelineImpl::OnFrameBuffered, weak_this_))); +} + +void AvPipelineImpl::SetClient(const AvPipelineClient& client) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK_EQ(state_, kUninitialized); + client_ = client; +} + +bool AvPipelineImpl::Initialize() { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK_EQ(state_, kUninitialized); + + MediaComponentDevice::Client client; + client.eos_cb = base::Bind(&AvPipelineImpl::OnEos, weak_this_); + media_component_device_->SetClient(client); + if (!media_component_device_->SetState(MediaComponentDevice::kStateIdle)) + return false; + + return true; +} + +bool AvPipelineImpl::StartPlayingFrom( + base::TimeDelta time, + const scoped_refptr<BufferingState>& buffering_state) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK_EQ(state_, kFlushed); + + // Media time where rendering should start + // and switch to a state where the audio device accepts incoming buffers. + if (!media_component_device_->SetStartPts(time) || + !media_component_device_->SetState(MediaComponentDevice::kStatePaused)) { + return false; + } + + // Buffering related initialization. + DCHECK(frame_provider_); + buffering_state_ = buffering_state; + if (buffering_state_.get()) + buffering_state_->SetMediaTime(time); + + if (!media_component_device_->SetState(MediaComponentDevice::kStateRunning)) + return false; + + // Start feeding the pipeline. + enable_feeding_ = true; + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&AvPipelineImpl::FetchBufferIfNeeded, weak_this_)); + + return true; +} + +void AvPipelineImpl::Flush(const base::Closure& done_cb) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK_EQ(state_, kFlushing); + DCHECK_EQ( + media_component_device_->GetState(), MediaComponentDevice::kStateRunning); + // Note: returning to idle state aborts any pending frame push. + media_component_device_->SetState(MediaComponentDevice::kStateIdle); + pending_push_ = false; + + // Break the feeding loop. + enable_feeding_ = false; + + // Remove any pending buffer. + pending_buffer_ = scoped_refptr<DecoderBufferBase>(); + + // Finally, remove any frames left in the frame provider. + pending_read_ = false; + buffered_time_ = ::media::kNoTimestamp(); + playable_buffered_time_ = ::media::kNoTimestamp(); + non_playable_frames_.clear(); + frame_provider_->Flush(done_cb); +} + +void AvPipelineImpl::Stop() { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Stop can be called from any state. + if (state_ == kUninitialized || state_ == kStopped) + return; + + // Stop feeding the pipeline. + enable_feeding_ = false; + + // Release hardware resources on Stop. + if (media_component_device_->GetState() == + MediaComponentDevice::kStatePaused || + media_component_device_->GetState() == + MediaComponentDevice::kStateRunning) { + media_component_device_->SetState(MediaComponentDevice::kStateIdle); + } + if (media_component_device_->GetState() == MediaComponentDevice::kStateIdle) { + media_component_device_->SetState( + MediaComponentDevice::kStateUninitialized); + } +} + +void AvPipelineImpl::SetCdm(BrowserCdmCast* media_keys) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(media_keys); + + if (media_keys_ && media_keys_callback_id_ != kNoCallbackId) + media_keys_->UnregisterPlayer(media_keys_callback_id_); + + media_keys_ = media_keys; + media_keys_callback_id_ = media_keys_->RegisterPlayer( + base::Bind(&AvPipelineImpl::OnCdmStateChanged, weak_this_), + base::Bind(&AvPipelineImpl::OnCdmDestroyed, weak_this_)); +} + +void AvPipelineImpl::OnEos() { + DCHECK(thread_checker_.CalledOnValidThread()); + CMALOG(kLogControl) << __FUNCTION__; + if (state_ != kPlaying) + return; + + if (!client_.eos_cb.is_null()) + client_.eos_cb.Run(); +} + +void AvPipelineImpl::FetchBufferIfNeeded() { + DCHECK(thread_checker_.CalledOnValidThread()); + if (!enable_feeding_) + return; + + if (pending_read_ || pending_buffer_.get()) + return; + + pending_read_ = true; + frame_provider_->Read( + base::Bind(&AvPipelineImpl::OnNewFrame, weak_this_)); +} + +void AvPipelineImpl::OnNewFrame( + const scoped_refptr<DecoderBufferBase>& buffer, + const ::media::AudioDecoderConfig& audio_config, + const ::media::VideoDecoderConfig& video_config) { + DCHECK(thread_checker_.CalledOnValidThread()); + pending_read_ = false; + + if (audio_config.IsValidConfig() || video_config.IsValidConfig()) + update_config_cb_.Run(audio_config, video_config); + + pending_buffer_ = buffer; + ProcessPendingBuffer(); + + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&AvPipelineImpl::FetchBufferIfNeeded, weak_this_)); +} + +void AvPipelineImpl::ProcessPendingBuffer() { + if (!enable_feeding_) + return; + + // Initiate a read if there isn't already one. + if (!pending_buffer_.get() && !pending_read_) { + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&AvPipelineImpl::FetchBufferIfNeeded, weak_this_)); + return; + } + + if (!pending_buffer_.get() || pending_push_) + return; + + // Break the feeding loop when the end of stream is reached. + if (pending_buffer_->end_of_stream()) { + CMALOG(kLogControl) << __FUNCTION__ << ": EOS reached, stopped feeding"; + enable_feeding_ = false; + } + + scoped_refptr<DecryptContext> decrypt_context; + if (!pending_buffer_->end_of_stream() && + pending_buffer_->decrypt_config()) { + // Verify that CDM has the key ID. + // Should not send the frame if the key ID is not available yet. + std::string key_id(pending_buffer_->decrypt_config()->key_id()); + if (!media_keys_) { + CMALOG(kLogControl) << "No CDM for frame: pts=" + << pending_buffer_->timestamp().InMilliseconds(); + return; + } + decrypt_context = media_keys_->GetDecryptContext(key_id); + if (!decrypt_context.get()) { + CMALOG(kLogControl) << "frame(pts=" + << pending_buffer_->timestamp().InMilliseconds() + << "): waiting for key id " + << base::HexEncode(&key_id[0], key_id.size()); + return; + } + + // If we do have the clear key, decrypt the pending buffer + // and reset the decryption context (not needed anymore). + crypto::SymmetricKey* key = decrypt_context->GetKey(); + if (key != NULL) { + pending_buffer_ = DecryptDecoderBuffer(pending_buffer_, key); + decrypt_context = scoped_refptr<DecryptContext>(); + } + } + + MediaComponentDevice::FrameStatus status = media_component_device_->PushFrame( + decrypt_context, + pending_buffer_, + base::Bind(&AvPipelineImpl::OnFramePushed, weak_this_)); + pending_buffer_ = scoped_refptr<DecoderBufferBase>(); + + pending_push_ = (status == MediaComponentDevice::kFramePending); + if (!pending_push_) + OnFramePushed(status); +} + +void AvPipelineImpl::OnFramePushed(MediaComponentDevice::FrameStatus status) { + DCHECK(thread_checker_.CalledOnValidThread()); + pending_push_ = false; + if (status == MediaComponentDevice::kFrameFailed) { + LOG(WARNING) << "AvPipelineImpl: PushFrame failed"; + enable_feeding_ = false; + state_ = kError; + return; + } + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&AvPipelineImpl::ProcessPendingBuffer, weak_this_)); +} + +void AvPipelineImpl::OnCdmStateChanged() { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Update the buffering state if needed. + if (buffering_state_.get()) + UpdatePlayableFrames(); + + // Process the pending buffer in case the CDM now has the frame key id. + ProcessPendingBuffer(); +} + +void AvPipelineImpl::OnCdmDestroyed() { + DCHECK(thread_checker_.CalledOnValidThread()); + media_keys_ = NULL; +} + +void AvPipelineImpl::OnFrameBuffered( + const scoped_refptr<DecoderBufferBase>& buffer, + bool is_at_max_capacity) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (!buffering_state_.get()) + return; + + if (!buffer->end_of_stream() && + (buffered_time_ == ::media::kNoTimestamp() || + buffered_time_ < buffer->timestamp())) { + buffered_time_ = buffer->timestamp(); + } + + if (is_at_max_capacity) + buffering_state_->NotifyMaxCapacity(buffered_time_); + + // No need to update the list of playable frames, + // if we are already blocking on a frame. + bool update_playable_frames = non_playable_frames_.empty(); + non_playable_frames_.push_back(buffer); + if (update_playable_frames) + UpdatePlayableFrames(); +} + +void AvPipelineImpl::UpdatePlayableFrames() { + while (!non_playable_frames_.empty()) { + const scoped_refptr<DecoderBufferBase>& non_playable_frame = + non_playable_frames_.front(); + + if (non_playable_frame->end_of_stream()) { + buffering_state_->NotifyEos(); + } else { + const ::media::DecryptConfig* decrypt_config = + non_playable_frame->decrypt_config(); + if (decrypt_config && + !(media_keys_ && + media_keys_->GetDecryptContext(decrypt_config->key_id()).get())) { + // The frame is still not playable. All the following are thus not + // playable. + break; + } + + if (playable_buffered_time_ == ::media::kNoTimestamp() || + playable_buffered_time_ < non_playable_frame->timestamp()) { + playable_buffered_time_ = non_playable_frame->timestamp(); + buffering_state_->SetBufferedTime(playable_buffered_time_); + } + } + + // The frame is playable: remove it from the list of non playable frames. + non_playable_frames_.pop_front(); + } +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/pipeline/av_pipeline_impl.h b/chromecast/media/cma/pipeline/av_pipeline_impl.h new file mode 100644 index 0000000..4b8856f --- /dev/null +++ b/chromecast/media/cma/pipeline/av_pipeline_impl.h @@ -0,0 +1,172 @@ +// 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_BASE_AV_PIPELINE_IMPL_H_ +#define CHROMECAST_MEDIA_CMA_BASE_AV_PIPELINE_IMPL_H_ + +#include <list> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" +#include "chromecast/media/cma/backend/media_component_device.h" +#include "chromecast/media/cma/pipeline/av_pipeline_client.h" + +namespace media { +class AudioDecoderConfig; +class VideoDecoderConfig; +} + +namespace chromecast { +namespace media { +class BrowserCdmCast; +class BufferingFrameProvider; +class BufferingState; +class CodedFrameProvider; +class DecoderBufferBase; +class MediaComponentDevice; + +class AvPipelineImpl { + public: + // Pipeline states. + enum State { + kUninitialized, + kPlaying, + kFlushing, + kFlushed, + kStopped, + kError, + }; + + typedef base::Callback< + void(const ::media::AudioDecoderConfig&, + const ::media::VideoDecoderConfig&)> UpdateConfigCB; + + AvPipelineImpl( + MediaComponentDevice* media_component_device, + const UpdateConfigCB& update_config_cb); + ~AvPipelineImpl(); + + // Setting the frame provider or the client must be done in the + // |kUninitialized| state. + void SetCodedFrameProvider(scoped_ptr<CodedFrameProvider> frame_provider, + size_t max_buffer_size, + size_t max_frame_size); + void SetClient(const AvPipelineClient& client); + + // Initialize the pipeline. + bool Initialize(); + + // Setup the pipeline and ensure samples are available for the given media + // time, then start rendering samples. + bool StartPlayingFrom(base::TimeDelta time, + const scoped_refptr<BufferingState>& buffering_state); + + // Flush any remaining samples in the pipeline. + // Invoke |done_cb| when flush is completed. + void Flush(const base::Closure& done_cb); + + // Tear down the pipeline and release the hardware resources. + void Stop(); + + State GetState() const { return state_; } + void TransitionToState(State state); + + void SetCdm(BrowserCdmCast* media_keys); + + private: + // Callback invoked when the CDM state has changed in a way that might + // impact media playback. + void OnCdmStateChange(); + + // Callback invoked when playback has reached the end of stream. + void OnEos(); + + // Feed the pipeline, getting the frames from |frame_provider_|. + void FetchBufferIfNeeded(); + + // Callback invoked when receiving a new frame from |frame_provider_|. + void OnNewFrame(const scoped_refptr<DecoderBufferBase>& buffer, + const ::media::AudioDecoderConfig& audio_config, + const ::media::VideoDecoderConfig& video_config); + + // Process a pending buffer. + void ProcessPendingBuffer(); + + void OnFramePushed(MediaComponentDevice::FrameStatus status); + + // Callbacks: + // - when BrowserCdm updated its state. + // - when BrowserCdm has been destroyed. + void OnCdmStateChanged(); + void OnCdmDestroyed(); + + // Callback invoked when a frame has been buffered by |frame_provider_| + // which is a BufferingFrameProvider. + void OnFrameBuffered(const scoped_refptr<DecoderBufferBase>& buffer, + bool is_at_max_capacity); + void UpdatePlayableFrames(); + + base::ThreadChecker thread_checker_; + + UpdateConfigCB update_config_cb_; + + AvPipelineClient client_; + + // Backends. + MediaComponentDevice* media_component_device_; + + // AV pipeline state. + State state_; + + // Buffering state. + // Can be NULL if there is no buffering strategy. + scoped_refptr<BufferingState> buffering_state_; + + // |buffered_time_| is the maximum timestamp of buffered frames. + // |playable_buffered_time_| is the maximum timestamp of buffered and + // playable frames (i.e. the key id is available for those frames). + base::TimeDelta buffered_time_; + base::TimeDelta playable_buffered_time_; + + // List of frames buffered but not playable right away due to a missing + // key id. + std::list<scoped_refptr<DecoderBufferBase> > non_playable_frames_; + + // Buffer provider. + scoped_ptr<BufferingFrameProvider> frame_provider_; + + // Indicate whether the frame fetching process is active. + bool enable_feeding_; + + // Indicate whether there is a pending buffer read. + bool pending_read_; + + // Pending buffer. + scoped_refptr<DecoderBufferBase> pending_buffer_; + + // Indicate if there is a frame being pushed to the audio device. + bool pending_push_; + + // The media time is retrieved at regular intervals. + // Indicate whether time update is enabled. + bool enable_time_update_; + bool pending_time_update_task_; + + // Decryption keys, if available. + BrowserCdmCast* media_keys_; + int media_keys_callback_id_; + + base::WeakPtrFactory<AvPipelineImpl> weak_factory_; + base::WeakPtr<AvPipelineImpl> weak_this_; + + DISALLOW_COPY_AND_ASSIGN(AvPipelineImpl); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_AV_PIPELINE_IMPL_H_ diff --git a/chromecast/media/cma/pipeline/decrypt_util.cc b/chromecast/media/cma/pipeline/decrypt_util.cc new file mode 100644 index 0000000..04d5a47 --- /dev/null +++ b/chromecast/media/cma/pipeline/decrypt_util.cc @@ -0,0 +1,128 @@ +// 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/pipeline/decrypt_util.h" + +#include <openssl/aes.h> +#include <string> + +#include "base/logging.h" +#include "chromecast/media/cma/base/decoder_buffer_base.h" +#include "crypto/symmetric_key.h" +#include "media/base/decrypt_config.h" + +namespace chromecast { +namespace media { + +namespace { + +class DecoderBufferClear : public DecoderBufferBase { + public: + explicit DecoderBufferClear(const scoped_refptr<DecoderBufferBase>& buffer); + + // DecoderBufferBase implementation. + base::TimeDelta timestamp() const override; + const uint8* data() const override; + uint8* writable_data() const override; + int data_size() const override; + const ::media::DecryptConfig* decrypt_config() const override; + bool end_of_stream() const override; + + private: + virtual ~DecoderBufferClear(); + + scoped_refptr<DecoderBufferBase> const buffer_; + + DISALLOW_COPY_AND_ASSIGN(DecoderBufferClear); +}; + +DecoderBufferClear::DecoderBufferClear( + const scoped_refptr<DecoderBufferBase>& buffer) + : buffer_(buffer) { +} + +DecoderBufferClear::~DecoderBufferClear() { +} + +base::TimeDelta DecoderBufferClear::timestamp() const { + return buffer_->timestamp(); +} + +const uint8* DecoderBufferClear::data() const { + return buffer_->data(); +} + +uint8* DecoderBufferClear::writable_data() const { + return buffer_->writable_data(); +} + +int DecoderBufferClear::data_size() const { + return buffer_->data_size(); +} + +const ::media::DecryptConfig* DecoderBufferClear::decrypt_config() const { + // Buffer is clear so no decryption info. + return NULL; +} + +bool DecoderBufferClear::end_of_stream() const { + return buffer_->end_of_stream(); +} + +} // namespace + +scoped_refptr<DecoderBufferBase> DecryptDecoderBuffer( + const scoped_refptr<DecoderBufferBase>& buffer, + crypto::SymmetricKey* key) { + if (buffer->end_of_stream()) + return buffer; + + const ::media::DecryptConfig* decrypt_config = buffer->decrypt_config(); + if (!decrypt_config || decrypt_config->iv().size() == 0) + return buffer; + + // Get the key. + std::string raw_key; + if (!key->GetRawKey(&raw_key)) { + LOG(ERROR) << "Failed to get the underlying AES key"; + return buffer; + } + DCHECK_EQ(static_cast<int>(raw_key.length()), AES_BLOCK_SIZE); + const uint8* key_u8 = reinterpret_cast<const uint8*>(raw_key.data()); + AES_KEY aes_key; + if (AES_set_encrypt_key(key_u8, AES_BLOCK_SIZE * 8, &aes_key) != 0) { + LOG(ERROR) << "Failed to set the AES key"; + return buffer; + } + + // Get the IV. + uint8 aes_iv[AES_BLOCK_SIZE]; + DCHECK_EQ(static_cast<int>(decrypt_config->iv().length()), + AES_BLOCK_SIZE); + memcpy(aes_iv, decrypt_config->iv().data(), AES_BLOCK_SIZE); + + // Decryption state. + unsigned int encrypted_byte_offset = 0; + uint8 ecount_buf[AES_BLOCK_SIZE]; + + // Perform the decryption. + const std::vector< ::media::SubsampleEntry>& subsamples = + decrypt_config->subsamples(); + uint8* data = buffer->writable_data(); + uint32 offset = 0; + for (size_t k = 0; k < subsamples.size(); k++) { + offset += subsamples[k].clear_bytes; + uint32 cypher_bytes = subsamples[k].cypher_bytes; + CHECK_LE(offset + cypher_bytes, buffer->data_size()); + AES_ctr128_encrypt( + data + offset, data + offset, cypher_bytes, &aes_key, + aes_iv, ecount_buf, &encrypted_byte_offset); + offset += cypher_bytes; + } + + return scoped_refptr<DecoderBufferBase>(new DecoderBufferClear(buffer)); +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/pipeline/decrypt_util.h b/chromecast/media/cma/pipeline/decrypt_util.h new file mode 100644 index 0000000..5a59afc --- /dev/null +++ b/chromecast/media/cma/pipeline/decrypt_util.h @@ -0,0 +1,31 @@ +// 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_PIPELINE_DECRYPT_UTIL_H_ +#define CHROMECAST_MEDIA_CMA_PIPELINE_DECRYPT_UTIL_H_ + +#include "base/memory/ref_counted.h" + +namespace crypto { +class SymmetricKey; +} + +namespace chromecast { +namespace media { + +class DecoderBufferBase; + +// Create a new buffer which corresponds to the clear version of |buffer|. +// Note: the memory area corresponding to the ES data of the new buffer +// is the same as the ES data of |buffer| (for efficiency). +// After the function is called, |buffer| is left in a inconsistent state +// in the sense it has some decryption info but the ES data is now in clear. +scoped_refptr<DecoderBufferBase> DecryptDecoderBuffer( + const scoped_refptr<DecoderBufferBase>& buffer, + crypto::SymmetricKey* key); + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_PIPELINE_DECRYPT_UTIL_H_ diff --git a/chromecast/media/cma/pipeline/load_type.h b/chromecast/media/cma/pipeline/load_type.h new file mode 100644 index 0000000..569b2ef --- /dev/null +++ b/chromecast/media/cma/pipeline/load_type.h @@ -0,0 +1,20 @@ +// 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_PIPELINE_LOAD_TYPE_H_ +#define CHROMECAST_MEDIA_CMA_PIPELINE_LOAD_TYPE_H_ + +namespace chromecast { +namespace media { + +enum LoadType { + kLoadTypeURL, + kLoadTypeMediaSource, + kLoadTypeMediaStream, +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_PIPELINE_LOAD_TYPE_H_ diff --git a/chromecast/media/cma/pipeline/media_pipeline.h b/chromecast/media/cma/pipeline/media_pipeline.h new file mode 100644 index 0000000..2b82c4f --- /dev/null +++ b/chromecast/media/cma/pipeline/media_pipeline.h @@ -0,0 +1,69 @@ +// 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_PIPELINE_MEDIA_PIPELINE_H_ +#define CHROMECAST_MEDIA_CMA_PIPELINE_MEDIA_PIPELINE_H_ + +#include "base/basictypes.h" +#include "base/macros.h" +#include "base/time/time.h" +#include "media/base/pipeline_status.h" + +namespace media { +class AudioDecoderConfig; +class BrowserCdm; +class VideoDecoderConfig; +} + +namespace chromecast { +namespace media { +class AudioPipeline; +class CodedFrameProvider; +struct MediaPipelineClient; +class VideoPipeline; + +class MediaPipeline { + public: + MediaPipeline() {} + virtual ~MediaPipeline() {} + + // Set the media pipeline client. + virtual void SetClient(const MediaPipelineClient& client) = 0; + + // Set the CDM to use for decryption. + // The CDM is refered by its id. + virtual void SetCdm(int cdm_id) = 0; + + // Return the audio/video pipeline owned by the MediaPipeline. + virtual AudioPipeline* GetAudioPipeline() const = 0; + virtual VideoPipeline* GetVideoPipeline() const = 0; + + // Create an audio/video pipeline. + // MediaPipeline owns the resulting audio/video pipeline. + // Only one audio and one video pipeline can be created. + virtual void InitializeAudio( + const ::media::AudioDecoderConfig& config, + scoped_ptr<CodedFrameProvider> frame_provider, + const ::media::PipelineStatusCB& status_cb) = 0; + virtual void InitializeVideo( + const ::media::VideoDecoderConfig& config, + scoped_ptr<CodedFrameProvider> frame_provider, + const ::media::PipelineStatusCB& status_cb) = 0; + + // Control the media pipeline state machine. + virtual void StartPlayingFrom(base::TimeDelta time) = 0; + virtual void Flush(const ::media::PipelineStatusCB& status_cb) = 0; + virtual void Stop() = 0; + + // Set the playback rate. + virtual void SetPlaybackRate(float playback_rate) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(MediaPipeline); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_PIPELINE_MEDIA_PIPELINE_H_ diff --git a/chromecast/media/cma/pipeline/media_pipeline_client.cc b/chromecast/media/cma/pipeline/media_pipeline_client.cc new file mode 100644 index 0000000..8dbfc8d --- /dev/null +++ b/chromecast/media/cma/pipeline/media_pipeline_client.cc @@ -0,0 +1,17 @@ +// 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/pipeline/media_pipeline_client.h" + +namespace chromecast { +namespace media { + +MediaPipelineClient::MediaPipelineClient() { +} + +MediaPipelineClient::~MediaPipelineClient() { +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/pipeline/media_pipeline_client.h b/chromecast/media/cma/pipeline/media_pipeline_client.h new file mode 100644 index 0000000..692263c --- /dev/null +++ b/chromecast/media/cma/pipeline/media_pipeline_client.h @@ -0,0 +1,37 @@ +// 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_PIPELINE_MEDIA_PIPELINE_CLIENT_H_ +#define CHROMECAST_MEDIA_CMA_PIPELINE_MEDIA_PIPELINE_CLIENT_H_ + +#include "base/callback.h" +#include "base/time/time.h" +#include "media/base/buffering_state.h" +#include "media/base/pipeline_status.h" + +namespace chromecast { +namespace media { + +struct MediaPipelineClient { + typedef base::Callback<void( + base::TimeDelta, base::TimeDelta, base::TimeTicks)> TimeUpdateCB; + + MediaPipelineClient(); + ~MediaPipelineClient(); + + // Callback used to report a playback error as a ::media::PipelineStatus. + ::media::PipelineStatusCB error_cb; + + // Callback used to report the latest playback time, + // as well as the maximum time available for rendering. + TimeUpdateCB time_update_cb; + + // Callback used to report the buffering status. + ::media::BufferingStateCB buffering_state_cb; +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_PIPELINE_MEDIA_PIPELINE_CLIENT_H_ diff --git a/chromecast/media/cma/pipeline/media_pipeline_impl.cc b/chromecast/media/cma/pipeline/media_pipeline_impl.cc new file mode 100644 index 0000000..2a89df5 --- /dev/null +++ b/chromecast/media/cma/pipeline/media_pipeline_impl.cc @@ -0,0 +1,366 @@ +// 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/pipeline/media_pipeline_impl.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/callback_helpers.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/time/time.h" +#include "chromecast/media/cdm/browser_cdm_cast.h" +#include "chromecast/media/cma/backend/media_clock_device.h" +#include "chromecast/media/cma/backend/media_pipeline_device.h" +#include "chromecast/media/cma/base/buffering_controller.h" +#include "chromecast/media/cma/base/buffering_state.h" +#include "chromecast/media/cma/base/cma_logging.h" +#include "chromecast/media/cma/base/coded_frame_provider.h" +#include "chromecast/media/cma/pipeline/audio_pipeline_impl.h" +#include "chromecast/media/cma/pipeline/video_pipeline_impl.h" +#include "media/base/buffers.h" + +namespace chromecast { +namespace media { + +namespace { + +// Buffering parameters when load_type is kLoadTypeUrl. +const base::TimeDelta kLowBufferThresholdURL( + base::TimeDelta::FromMilliseconds(2000)); +const base::TimeDelta kHighBufferThresholdURL( + base::TimeDelta::FromMilliseconds(6000)); + +// Buffering parameters when load_type is kLoadTypeMediaSource. +const base::TimeDelta kLowBufferThresholdMediaSource( + base::TimeDelta::FromMilliseconds(0)); +const base::TimeDelta kHighBufferThresholdMediaSource( + base::TimeDelta::FromMilliseconds(300)); + +// Interval between two updates of the media time. +const base::TimeDelta kTimeUpdateInterval( + base::TimeDelta::FromMilliseconds(250)); + +// Interval between two updates of the statistics is equal to: +// kTimeUpdateInterval * kStatisticsUpdatePeriod. +const int kStatisticsUpdatePeriod = 4; + +} // namespace + +MediaPipelineImpl::MediaPipelineImpl() + : has_audio_(false), + has_video_(false), + target_playback_rate_(0.0), + enable_time_update_(false), + pending_time_update_task_(false), + statistics_rolling_counter_(0), + weak_factory_(this) { + CMALOG(kLogControl) << __FUNCTION__; + weak_this_ = weak_factory_.GetWeakPtr(); + thread_checker_.DetachFromThread(); +} + +MediaPipelineImpl::~MediaPipelineImpl() { + CMALOG(kLogControl) << __FUNCTION__; + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void MediaPipelineImpl::Initialize( + LoadType load_type, + scoped_ptr<MediaPipelineDevice> media_pipeline_device) { + CMALOG(kLogControl) << __FUNCTION__; + DCHECK(thread_checker_.CalledOnValidThread()); + media_pipeline_device_.reset(media_pipeline_device.release()); + clock_device_ = media_pipeline_device_->GetMediaClockDevice(); + + if (load_type == kLoadTypeURL || load_type == kLoadTypeMediaSource) { + base::TimeDelta low_threshold(kLowBufferThresholdURL); + base::TimeDelta high_threshold(kHighBufferThresholdURL); + if (load_type == kLoadTypeMediaSource) { + low_threshold = kLowBufferThresholdMediaSource; + high_threshold = kHighBufferThresholdMediaSource; + } + scoped_refptr<BufferingConfig> buffering_config( + new BufferingConfig(low_threshold, high_threshold)); + buffering_controller_.reset(new BufferingController( + buffering_config, + base::Bind(&MediaPipelineImpl::OnBufferingNotification, weak_this_))); + } + + audio_pipeline_.reset(new AudioPipelineImpl( + media_pipeline_device_->GetAudioPipelineDevice())); + + video_pipeline_.reset(new VideoPipelineImpl( + media_pipeline_device_->GetVideoPipelineDevice())); +} + +void MediaPipelineImpl::SetClient(const MediaPipelineClient& client) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!client.error_cb.is_null()); + DCHECK(!client.time_update_cb.is_null()); + DCHECK(!client.buffering_state_cb.is_null()); + client_ = client; +} + +void MediaPipelineImpl::SetCdm(int cdm_id) { + CMALOG(kLogControl) << __FUNCTION__ << " cdm_id=" << cdm_id; + DCHECK(thread_checker_.CalledOnValidThread()); + NOTIMPLEMENTED(); + // TODO(gunsch): SetCdm(int) is not implemented. + // One possibility would be a GetCdmByIdCB that's passed in. +} + +void MediaPipelineImpl::SetCdm(::media::BrowserCdm* media_keys) { + CMALOG(kLogControl) << __FUNCTION__; + audio_pipeline_->SetCdm(static_cast<BrowserCdmCast*>(media_keys)); + video_pipeline_->SetCdm(static_cast<BrowserCdmCast*>(media_keys)); +} + +AudioPipeline* MediaPipelineImpl::GetAudioPipeline() const { + return audio_pipeline_.get(); +} + +VideoPipeline* MediaPipelineImpl::GetVideoPipeline() const { + return video_pipeline_.get(); +} + +void MediaPipelineImpl::InitializeAudio( + const ::media::AudioDecoderConfig& config, + scoped_ptr<CodedFrameProvider> frame_provider, + const ::media::PipelineStatusCB& status_cb) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!has_audio_); + if (clock_device_->GetState() == MediaClockDevice::kStateUninitialized && + !clock_device_->SetState(MediaClockDevice::kStateIdle)) { + status_cb.Run(::media::PIPELINE_ERROR_INITIALIZATION_FAILED); + return; + } + has_audio_ = true; + audio_pipeline_->Initialize(config, frame_provider.Pass(), status_cb); +} + +void MediaPipelineImpl::InitializeVideo( + const ::media::VideoDecoderConfig& config, + scoped_ptr<CodedFrameProvider> frame_provider, + const ::media::PipelineStatusCB& status_cb) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!has_video_); + if (clock_device_->GetState() == MediaClockDevice::kStateUninitialized && + !clock_device_->SetState(MediaClockDevice::kStateIdle)) { + status_cb.Run(::media::PIPELINE_ERROR_INITIALIZATION_FAILED); + return; + } + has_video_ = true; + video_pipeline_->Initialize(config, frame_provider.Pass(), status_cb); +} + +void MediaPipelineImpl::StartPlayingFrom(base::TimeDelta time) { + CMALOG(kLogControl) << __FUNCTION__ << " t0=" << time.InMilliseconds(); + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(has_audio_ || has_video_); + DCHECK(!pending_callbacks_); + + // Reset the start of the timeline. + DCHECK_EQ(clock_device_->GetState(), MediaClockDevice::kStateIdle); + clock_device_->ResetTimeline(time); + + // Start the clock. If the playback rate is 0, then the clock is started + // but does not increase. + if (!clock_device_->SetState(MediaClockDevice::kStateRunning)) { + OnError(::media::PIPELINE_ERROR_ABORT); + return; + } + + // Enable time updates. + enable_time_update_ = true; + statistics_rolling_counter_ = 0; + if (!pending_time_update_task_) { + pending_time_update_task_ = true; + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&MediaPipelineImpl::UpdateMediaTime, weak_this_)); + } + + // Setup the audio and video pipeline for the new timeline. + if (has_audio_) { + scoped_refptr<BufferingState> buffering_state; + if (buffering_controller_) + buffering_state = buffering_controller_->AddStream(); + if (!audio_pipeline_->StartPlayingFrom(time, buffering_state)) { + OnError(::media::PIPELINE_ERROR_ABORT); + return; + } + } + if (has_video_) { + scoped_refptr<BufferingState> buffering_state; + if (buffering_controller_) + buffering_state = buffering_controller_->AddStream(); + if (!video_pipeline_->StartPlayingFrom(time, buffering_state)) { + OnError(::media::PIPELINE_ERROR_ABORT); + return; + } + } +} + +void MediaPipelineImpl::Flush(const ::media::PipelineStatusCB& status_cb) { + CMALOG(kLogControl) << __FUNCTION__; + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(has_audio_ || has_video_); + DCHECK(!pending_callbacks_); + + // No need to update media time anymore. + enable_time_update_ = false; + + buffering_controller_->Reset(); + + // The clock should return to idle. + if (!clock_device_->SetState(MediaClockDevice::kStateIdle)) { + status_cb.Run(::media::PIPELINE_ERROR_ABORT); + return; + } + + // Flush both the audio and video pipeline. + ::media::SerialRunner::Queue bound_fns; + if (has_audio_) { + bound_fns.Push(base::Bind( + &AudioPipelineImpl::Flush, + base::Unretained(audio_pipeline_.get()))); + } + if (has_video_) { + bound_fns.Push(base::Bind( + &VideoPipelineImpl::Flush, + base::Unretained(video_pipeline_.get()))); + } + ::media::PipelineStatusCB transition_cb = + base::Bind(&MediaPipelineImpl::StateTransition, weak_this_, status_cb); + pending_callbacks_ = + ::media::SerialRunner::Run(bound_fns, transition_cb); +} + +void MediaPipelineImpl::Stop() { + CMALOG(kLogControl) << __FUNCTION__; + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(has_audio_ || has_video_); + DCHECK(!pending_callbacks_); + + // No need to update media time anymore. + enable_time_update_ = false; + + // Release hardware resources on Stop. + // Note: Stop can be called from any state. + if (clock_device_->GetState() == MediaClockDevice::kStateRunning) + clock_device_->SetState(MediaClockDevice::kStateIdle); + if (clock_device_->GetState() == MediaClockDevice::kStateIdle) + clock_device_->SetState(MediaClockDevice::kStateUninitialized); + + // Stop both the audio and video pipeline. + if (has_audio_) + audio_pipeline_->Stop(); + if (has_video_) + video_pipeline_->Stop(); +} + +void MediaPipelineImpl::SetPlaybackRate(float rate) { + CMALOG(kLogControl) << __FUNCTION__ << " rate=" << rate; + DCHECK(thread_checker_.CalledOnValidThread()); + target_playback_rate_ = rate; + if (!buffering_controller_ || !buffering_controller_->IsBuffering()) + media_pipeline_device_->GetMediaClockDevice()->SetRate(rate); +} + +AudioPipelineImpl* MediaPipelineImpl::GetAudioPipelineImpl() const { + return audio_pipeline_.get(); +} + +VideoPipelineImpl* MediaPipelineImpl::GetVideoPipelineImpl() const { + return video_pipeline_.get(); +} + +void MediaPipelineImpl::StateTransition( + const ::media::PipelineStatusCB& status_cb, + ::media::PipelineStatus status) { + pending_callbacks_.reset(); + status_cb.Run(status); +} + +void MediaPipelineImpl::OnBufferingNotification(bool is_buffering) { + CMALOG(kLogControl) << __FUNCTION__ << " is_buffering=" << is_buffering; + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(buffering_controller_); + + if (!client_.buffering_state_cb.is_null()) { + ::media::BufferingState buffering_state = is_buffering ? + ::media::BUFFERING_HAVE_NOTHING : ::media::BUFFERING_HAVE_ENOUGH; + client_.buffering_state_cb.Run(buffering_state); + } + + if (media_pipeline_device_->GetMediaClockDevice()->GetState() == + MediaClockDevice::kStateUninitialized) { + return; + } + + if (is_buffering) { + // Do not consume data in a rebuffering phase. + media_pipeline_device_->GetMediaClockDevice()->SetRate(0.0); + } else { + media_pipeline_device_->GetMediaClockDevice()->SetRate( + target_playback_rate_); + } +} + +void MediaPipelineImpl::UpdateMediaTime() { + pending_time_update_task_ = false; + if (!enable_time_update_) + return; + + if (statistics_rolling_counter_ == 0) { + audio_pipeline_->UpdateStatistics(); + video_pipeline_->UpdateStatistics(); + } + statistics_rolling_counter_ = + (statistics_rolling_counter_ + 1) % kStatisticsUpdatePeriod; + + base::TimeDelta media_time(clock_device_->GetTime()); + if (media_time == ::media::kNoTimestamp()) { + pending_time_update_task_ = true; + base::MessageLoopProxy::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&MediaPipelineImpl::UpdateMediaTime, weak_this_), + kTimeUpdateInterval); + return; + } + base::TimeTicks stc = base::TimeTicks::Now(); + + base::TimeDelta max_rendering_time = media_time; + if (buffering_controller_) { + buffering_controller_->SetMediaTime(media_time); + + if (media_time != last_media_time_) { + max_rendering_time = buffering_controller_->GetMaxRenderingTime(); + if (max_rendering_time == ::media::kNoTimestamp()) + max_rendering_time = media_time; + } + } + + last_media_time_ = media_time; + if (!client_.time_update_cb.is_null()) + client_.time_update_cb.Run(media_time, max_rendering_time, stc); + + pending_time_update_task_ = true; + base::MessageLoopProxy::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&MediaPipelineImpl::UpdateMediaTime, weak_this_), + kTimeUpdateInterval); +} + +void MediaPipelineImpl::OnError(::media::PipelineStatus error) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK_NE(error, ::media::PIPELINE_OK) << "PIPELINE_OK is not an error!"; + if (!client_.error_cb.is_null()) + client_.error_cb.Run(error); +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/pipeline/media_pipeline_impl.h b/chromecast/media/cma/pipeline/media_pipeline_impl.h new file mode 100644 index 0000000..0b7a8e7 --- /dev/null +++ b/chromecast/media/cma/pipeline/media_pipeline_impl.h @@ -0,0 +1,112 @@ +// 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_PIPELINE_MEDIA_PIPELINE_IMPL_H_ +#define CHROMECAST_MEDIA_CMA_PIPELINE_MEDIA_PIPELINE_IMPL_H_ + +#include "base/basictypes.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" +#include "chromecast/media/cma/pipeline/load_type.h" +#include "chromecast/media/cma/pipeline/media_pipeline.h" +#include "chromecast/media/cma/pipeline/media_pipeline_client.h" +#include "media/base/serial_runner.h" + +namespace chromecast { +namespace media { +class AudioPipelineImpl; +class BufferingController; +class MediaClockDevice; +class MediaPipelineDevice; +class VideoPipelineImpl; + +class MediaPipelineImpl : public MediaPipeline { + public: + MediaPipelineImpl(); + ~MediaPipelineImpl() override; + + // Initialize the media pipeline: the pipeline is configured based on + // |load_type|. + void Initialize(LoadType load_type, + scoped_ptr<MediaPipelineDevice> media_pipeline_device); + + // MediaPipeline implementation. + void SetClient(const MediaPipelineClient& client) override; + void SetCdm(int cdm_id) override; + AudioPipeline* GetAudioPipeline() const override; + VideoPipeline* GetVideoPipeline() const override; + void InitializeAudio( + const ::media::AudioDecoderConfig& config, + scoped_ptr<CodedFrameProvider> frame_provider, + const ::media::PipelineStatusCB& status_cb) override; + void InitializeVideo( + const ::media::VideoDecoderConfig& config, + scoped_ptr<CodedFrameProvider> frame_provider, + const ::media::PipelineStatusCB& status_cb) override; + void StartPlayingFrom(base::TimeDelta time) override; + void Flush(const ::media::PipelineStatusCB& status_cb) override; + void Stop() override; + void SetPlaybackRate(float playback_rate) override; + + AudioPipelineImpl* GetAudioPipelineImpl() const; + VideoPipelineImpl* GetVideoPipelineImpl() const; + + void SetCdm(::media::BrowserCdm* cdm); + + private: + void StateTransition(const ::media::PipelineStatusCB& status_cb, + ::media::PipelineStatus status); + + // Invoked to notify about a change of buffering state. + void OnBufferingNotification(bool is_buffering); + + void UpdateMediaTime(); + + void OnError(::media::PipelineStatus error); + + base::ThreadChecker thread_checker_; + + MediaPipelineClient client_; + + scoped_ptr<BufferingController> buffering_controller_; + + // Interface with the underlying hardware media pipeline. + scoped_ptr<MediaPipelineDevice> media_pipeline_device_; + MediaClockDevice* clock_device_; + + bool has_audio_; + bool has_video_; + scoped_ptr<AudioPipelineImpl> audio_pipeline_; + scoped_ptr<VideoPipelineImpl> video_pipeline_; + scoped_ptr< ::media::SerialRunner> pending_callbacks_; + + // Playback rate set by the upper layer. + float target_playback_rate_; + + // Indicate a possible re-buffering phase. + bool is_buffering_; + + // The media time is retrieved at regular intervals. + // Indicate whether time update is enabled. + bool enable_time_update_; + bool pending_time_update_task_; + base::TimeDelta last_media_time_; + + // Used to make the statistics update period a multiplier of the time update + // period. + int statistics_rolling_counter_; + + base::WeakPtrFactory<MediaPipelineImpl> weak_factory_; + base::WeakPtr<MediaPipelineImpl> weak_this_; + + DISALLOW_COPY_AND_ASSIGN(MediaPipelineImpl); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_PIPELINE_MEDIA_PIPELINE_IMPL_H_ diff --git a/chromecast/media/cma/pipeline/video_pipeline.cc b/chromecast/media/cma/pipeline/video_pipeline.cc new file mode 100644 index 0000000..6b428b5 --- /dev/null +++ b/chromecast/media/cma/pipeline/video_pipeline.cc @@ -0,0 +1,17 @@ +// 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/pipeline/video_pipeline.h" + +namespace chromecast { +namespace media { + +VideoPipeline::VideoPipeline() { +} + +VideoPipeline::~VideoPipeline() { +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/pipeline/video_pipeline.h b/chromecast/media/cma/pipeline/video_pipeline.h new file mode 100644 index 0000000..68a9b8c --- /dev/null +++ b/chromecast/media/cma/pipeline/video_pipeline.h @@ -0,0 +1,28 @@ +// 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_PIPELINE_VIDEO_PIPELINE_H_ +#define CHROMECAST_MEDIA_CMA_PIPELINE_VIDEO_PIPELINE_H_ + +#include "base/macros.h" + +namespace chromecast { +namespace media { +struct VideoPipelineClient; + +class VideoPipeline { + public: + VideoPipeline(); + virtual ~VideoPipeline(); + + virtual void SetClient(const VideoPipelineClient& client) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(VideoPipeline); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_PIPELINE_VIDEO_PIPELINE_H_ diff --git a/chromecast/media/cma/pipeline/video_pipeline_client.cc b/chromecast/media/cma/pipeline/video_pipeline_client.cc new file mode 100644 index 0000000..bb7388e --- /dev/null +++ b/chromecast/media/cma/pipeline/video_pipeline_client.cc @@ -0,0 +1,17 @@ +// 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/pipeline/video_pipeline_client.h" + +namespace chromecast { +namespace media { + +VideoPipelineClient::VideoPipelineClient() { +} + +VideoPipelineClient::~VideoPipelineClient() { +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/pipeline/video_pipeline_client.h b/chromecast/media/cma/pipeline/video_pipeline_client.h new file mode 100644 index 0000000..105b8a2 --- /dev/null +++ b/chromecast/media/cma/pipeline/video_pipeline_client.h @@ -0,0 +1,35 @@ +// 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_PIPELINE_VIDEO_PIPELINE_CLIENT_H_ +#define CHROMECAST_MEDIA_CMA_PIPELINE_VIDEO_PIPELINE_CLIENT_H_ + +#include "base/callback.h" +#include "chromecast/media/cma/pipeline/av_pipeline_client.h" + +namespace gfx { +class Size; +} + +namespace chromecast { +namespace media { + +struct VideoPipelineClient { + typedef base::Callback<void( + const gfx::Size& natural_size)> NaturalSizeChangedCB; + + VideoPipelineClient(); + ~VideoPipelineClient(); + + // All the default callbacks. + AvPipelineClient av_pipeline_client; + + // Video resolution change notification. + NaturalSizeChangedCB natural_size_changed_cb; +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_PIPELINE_VIDEO_PIPELINE_CLIENT_H_ diff --git a/chromecast/media/cma/pipeline/video_pipeline_impl.cc b/chromecast/media/cma/pipeline/video_pipeline_impl.cc new file mode 100644 index 0000000..d3ae9be --- /dev/null +++ b/chromecast/media/cma/pipeline/video_pipeline_impl.cc @@ -0,0 +1,174 @@ +// 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/pipeline/video_pipeline_impl.h" + +#include "base/bind.h" +#include "chromecast/media/cma/backend/video_pipeline_device.h" +#include "chromecast/media/cma/base/buffering_defs.h" +#include "chromecast/media/cma/base/cma_logging.h" +#include "chromecast/media/cma/base/coded_frame_provider.h" +#include "chromecast/media/cma/pipeline/av_pipeline_impl.h" +#include "media/base/video_decoder_config.h" + +namespace chromecast { +namespace media { + +namespace { +const size_t kMaxVideoFrameSize = 1024 * 1024; +} + +VideoPipelineImpl::VideoPipelineImpl(VideoPipelineDevice* video_device) + : video_device_(video_device), + weak_factory_(this) { + weak_this_ = weak_factory_.GetWeakPtr(); + av_pipeline_impl_.reset(new AvPipelineImpl( + video_device_, + base::Bind(&VideoPipelineImpl::OnUpdateConfig, base::Unretained(this)))); +} + +VideoPipelineImpl::~VideoPipelineImpl() { +} + +void VideoPipelineImpl::SetCodedFrameProvider( + scoped_ptr<CodedFrameProvider> frame_provider) { + av_pipeline_impl_->SetCodedFrameProvider( + frame_provider.Pass(), kAppVideoBufferSize, kMaxVideoFrameSize); +} + +bool VideoPipelineImpl::StartPlayingFrom( + base::TimeDelta time, + const scoped_refptr<BufferingState>& buffering_state) { + CMALOG(kLogControl) << "VideoPipelineImpl::StartPlayingFrom t0=" + << time.InMilliseconds(); + + // Reset the pipeline statistics. + previous_stats_ = ::media::PipelineStatistics(); + + // Start playing. + if (av_pipeline_impl_->GetState() == AvPipelineImpl::kError) + return false; + DCHECK_EQ(av_pipeline_impl_->GetState(), AvPipelineImpl::kFlushed); + + if (!av_pipeline_impl_->StartPlayingFrom(time, buffering_state)) { + av_pipeline_impl_->TransitionToState(AvPipelineImpl::kError); + return false; + } + av_pipeline_impl_->TransitionToState(AvPipelineImpl::kPlaying); + + return true; +} + +void VideoPipelineImpl::Flush(const ::media::PipelineStatusCB& status_cb) { + CMALOG(kLogControl) << "VideoPipelineImpl::Flush"; + if (av_pipeline_impl_->GetState() == AvPipelineImpl::kError) { + status_cb.Run(::media::PIPELINE_ERROR_ABORT); + return; + } + DCHECK_EQ(av_pipeline_impl_->GetState(), AvPipelineImpl::kPlaying); + av_pipeline_impl_->TransitionToState(AvPipelineImpl::kFlushing); + av_pipeline_impl_->Flush( + base::Bind(&VideoPipelineImpl::OnFlushDone, weak_this_, status_cb)); +} + +void VideoPipelineImpl::OnFlushDone( + const ::media::PipelineStatusCB& status_cb) { + CMALOG(kLogControl) << "VideoPipelineImpl::OnFlushDone"; + if (av_pipeline_impl_->GetState() == AvPipelineImpl::kError) { + status_cb.Run(::media::PIPELINE_ERROR_ABORT); + return; + } + av_pipeline_impl_->TransitionToState(AvPipelineImpl::kFlushed); + status_cb.Run(::media::PIPELINE_OK); +} + +void VideoPipelineImpl::Stop() { + CMALOG(kLogControl) << "VideoPipelineImpl::Stop"; + av_pipeline_impl_->Stop(); + av_pipeline_impl_->TransitionToState(AvPipelineImpl::kStopped); +} + +void VideoPipelineImpl::SetCdm(BrowserCdmCast* media_keys) { + av_pipeline_impl_->SetCdm(media_keys); +} + +void VideoPipelineImpl::SetClient(const VideoPipelineClient& client) { + video_client_ = client; + av_pipeline_impl_->SetClient(client.av_pipeline_client); +} + +void VideoPipelineImpl::Initialize( + const ::media::VideoDecoderConfig& video_config, + scoped_ptr<CodedFrameProvider> frame_provider, + const ::media::PipelineStatusCB& status_cb) { + CMALOG(kLogControl) << "VideoPipelineImpl::Initialize " + << video_config.AsHumanReadableString(); + VideoPipelineDevice::VideoClient client; + client.natural_size_changed_cb = + base::Bind(&VideoPipelineImpl::OnNaturalSizeChanged, weak_this_); + video_device_->SetVideoClient(client); + if (frame_provider) + SetCodedFrameProvider(frame_provider.Pass()); + + if (!video_device_->SetConfig(video_config) || + !av_pipeline_impl_->Initialize()) { + status_cb.Run(::media::PIPELINE_ERROR_INITIALIZATION_FAILED); + return; + } + av_pipeline_impl_->TransitionToState(AvPipelineImpl::kFlushed); + status_cb.Run(::media::PIPELINE_OK); +} + +void VideoPipelineImpl::OnUpdateConfig( + const ::media::AudioDecoderConfig& audio_config, + const ::media::VideoDecoderConfig& video_config) { + if (video_config.IsValidConfig()) { + CMALOG(kLogControl) << "VideoPipelineImpl::OnUpdateConfig " + << video_config.AsHumanReadableString(); + + bool success = video_device_->SetConfig(video_config); + if (!success && + !video_client_.av_pipeline_client.playback_error_cb.is_null()) { + video_client_.av_pipeline_client.playback_error_cb.Run( + ::media::PIPELINE_ERROR_DECODE); + } + } +} + +void VideoPipelineImpl::OnNaturalSizeChanged(const gfx::Size& size) { + if (av_pipeline_impl_->GetState() != AvPipelineImpl::kPlaying) + return; + + if (!video_client_.natural_size_changed_cb.is_null()) + video_client_.natural_size_changed_cb.Run(size); +} + +void VideoPipelineImpl::UpdateStatistics() { + if (video_client_.av_pipeline_client.statistics_cb.is_null()) + return; + + MediaComponentDevice::Statistics device_stats; + if (!video_device_->GetStatistics(&device_stats)) + return; + + ::media::PipelineStatistics current_stats; + current_stats.video_bytes_decoded = device_stats.decoded_bytes; + current_stats.video_frames_decoded = device_stats.decoded_samples; + current_stats.video_frames_dropped = device_stats.dropped_samples; + + ::media::PipelineStatistics delta_stats; + delta_stats.video_bytes_decoded = + current_stats.video_bytes_decoded - previous_stats_.video_bytes_decoded; + delta_stats.video_frames_decoded = + current_stats.video_frames_decoded - previous_stats_.video_frames_decoded; + delta_stats.video_frames_dropped = + current_stats.video_frames_dropped - previous_stats_.video_frames_dropped; + + previous_stats_ = current_stats; + + video_client_.av_pipeline_client.statistics_cb.Run(delta_stats); +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/pipeline/video_pipeline_impl.h b/chromecast/media/cma/pipeline/video_pipeline_impl.h new file mode 100644 index 0000000..a393559 --- /dev/null +++ b/chromecast/media/cma/pipeline/video_pipeline_impl.h @@ -0,0 +1,82 @@ +// 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_BASE_VIDEO_PIPELINE_IMPL_H_ +#define CHROMECAST_MEDIA_CMA_BASE_VIDEO_PIPELINE_IMPL_H_ + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" +#include "chromecast/media/cma/pipeline/video_pipeline.h" +#include "chromecast/media/cma/pipeline/video_pipeline_client.h" + +namespace gfx { +class Size; +} + +namespace media { +class AudioDecoderConfig; +class VideoDecoderConfig; +} + +namespace chromecast { +namespace media { +class AvPipelineImpl; +class BrowserCdmCast; +class BufferingState; +class CodedFrameProvider; +class VideoPipelineDevice; + +class VideoPipelineImpl : public VideoPipeline { + public: + // |buffering_controller| can be NULL. + explicit VideoPipelineImpl(VideoPipelineDevice* video_device); + ~VideoPipelineImpl() override; + + // Input port of the pipeline. + void SetCodedFrameProvider(scoped_ptr<CodedFrameProvider> frame_provider); + + // Provide the CDM to use to decrypt samples. + void SetCdm(BrowserCdmCast* media_keys); + + // Functions to control the state of the audio pipeline. + void Initialize( + const ::media::VideoDecoderConfig& config, + scoped_ptr<CodedFrameProvider> frame_provider, + const ::media::PipelineStatusCB& status_cb); + bool StartPlayingFrom(base::TimeDelta time, + const scoped_refptr<BufferingState>& buffering_state); + void Flush(const ::media::PipelineStatusCB& status_cb); + void Stop(); + + // Update the playback statistics for this video stream. + void UpdateStatistics(); + + // VideoPipeline implementation. + void SetClient(const VideoPipelineClient& client) override; + + private: + void OnFlushDone(const ::media::PipelineStatusCB& status_cb); + void OnUpdateConfig(const ::media::AudioDecoderConfig& audio_config, + const ::media::VideoDecoderConfig& video_config); + void OnNaturalSizeChanged(const gfx::Size& size); + + VideoPipelineDevice* video_device_; + + scoped_ptr<AvPipelineImpl> av_pipeline_impl_; + VideoPipelineClient video_client_; + + ::media::PipelineStatistics previous_stats_; + + base::WeakPtrFactory<VideoPipelineImpl> weak_factory_; + base::WeakPtr<VideoPipelineImpl> weak_this_; + + DISALLOW_COPY_AND_ASSIGN(VideoPipelineImpl); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_VIDEO_PIPELINE_IMPL_H_ diff --git a/chromecast/media/media.gyp b/chromecast/media/media.gyp index b5bd621..8c09410 100644 --- a/chromecast/media/media.gyp +++ b/chromecast/media/media.gyp @@ -64,6 +64,8 @@ 'cma/base/balanced_media_task_runner_factory.h', 'cma/base/buffering_controller.cc', 'cma/base/buffering_controller.h', + 'cma/base/buffering_defs.cc', + 'cma/base/buffering_defs.h', 'cma/base/buffering_frame_provider.cc', 'cma/base/buffering_frame_provider.h', 'cma/base/buffering_state.cc', @@ -158,6 +160,54 @@ ], }, { + 'target_name': 'cma_pipeline', + 'type': '<(component)', + 'dependencies': [ + 'cma_backend', + 'cma_base', + 'media_base', + 'media_cdm', + '../../base/base.gyp:base', + '../../crypto/crypto.gyp:crypto', + '../../media/media.gyp:media', + ], + 'conditions': [ + ['chromecast_branding=="Chrome"', { + 'dependencies': [ + '../internal/cast_system.gyp:openssl', + ], + }, { + 'dependencies': [ + '../../third_party/boringssl/boringssl.gyp:boringssl', + ], + }], + ], + 'sources': [ + 'cma/pipeline/audio_pipeline.cc', + 'cma/pipeline/audio_pipeline.h', + 'cma/pipeline/audio_pipeline_impl.cc', + 'cma/pipeline/audio_pipeline_impl.h', + 'cma/pipeline/av_pipeline_client.cc', + 'cma/pipeline/av_pipeline_client.h', + 'cma/pipeline/av_pipeline_impl.cc', + 'cma/pipeline/av_pipeline_impl.h', + 'cma/pipeline/decrypt_util.cc', + 'cma/pipeline/decrypt_util.h', + 'cma/pipeline/load_type.h', + 'cma/pipeline/media_pipeline.h', + 'cma/pipeline/media_pipeline_client.cc', + 'cma/pipeline/media_pipeline_client.h', + 'cma/pipeline/media_pipeline_impl.cc', + 'cma/pipeline/media_pipeline_impl.h', + 'cma/pipeline/video_pipeline.cc', + 'cma/pipeline/video_pipeline.h', + 'cma/pipeline/video_pipeline_client.cc', + 'cma/pipeline/video_pipeline_client.h', + 'cma/pipeline/video_pipeline_impl.cc', + 'cma/pipeline/video_pipeline_impl.h', + ], + }, + { 'target_name': 'cma_filters', 'type': '<(component)', 'dependencies': [ @@ -179,6 +229,7 @@ 'cma_filters', 'cma_ipc', 'cma_ipc_streamer', + 'cma_pipeline', 'media_cdm', ], }, @@ -205,6 +256,7 @@ 'cma/ipc/media_message_fifo_unittest.cc', 'cma/ipc/media_message_unittest.cc', 'cma/ipc_streamer/av_streamer_unittest.cc', + 'cma/pipeline/audio_video_pipeline_impl_unittest.cc', 'cma/test/frame_generator_for_test.cc', 'cma/test/frame_generator_for_test.h', 'cma/test/frame_segmenter_for_test.cc', |