diff options
author | Zeno Albisser <zeno.albisser@theqtcompany.com> | 2014-12-05 15:04:29 +0100 |
---|---|---|
committer | Andras Becsi <andras.becsi@theqtcompany.com> | 2014-12-09 10:49:28 +0100 |
commit | af6588f8d723931a298c995fa97259bb7f7deb55 (patch) | |
tree | 060ca707847ba1735f01af2372e0d5e494dc0366 /chromium/chromecast/media | |
parent | 2fff84d821cc7b1c785f6404e0f8091333283e74 (diff) | |
download | qtwebengine-chromium-af6588f8d723931a298c995fa97259bb7f7deb55.tar.gz |
BASELINE: Update chromium to 40.0.2214.28 and ninja to 1.5.3.
Change-Id: I759465284fd64d59ad120219cbe257f7402c4181
Reviewed-by: Andras Becsi <andras.becsi@theqtcompany.com>
Diffstat (limited to 'chromium/chromecast/media')
70 files changed, 7377 insertions, 0 deletions
diff --git a/chromium/chromecast/media/DEPS b/chromium/chromecast/media/DEPS new file mode 100644 index 00000000000..1891d1afe50 --- /dev/null +++ b/chromium/chromecast/media/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+media/base", + "+media/cdm", +] diff --git a/chromium/chromecast/media/base/decrypt_context.cc b/chromium/chromecast/media/base/decrypt_context.cc new file mode 100644 index 00000000000..38da3af7c06 --- /dev/null +++ b/chromium/chromecast/media/base/decrypt_context.cc @@ -0,0 +1,22 @@ +// 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/base/decrypt_context.h" + +namespace chromecast { +namespace media { + +DecryptContext::DecryptContext(CastKeySystem key_system) + : key_system_(key_system) { +} + +DecryptContext::~DecryptContext() { +} + +crypto::SymmetricKey* DecryptContext::GetKey() const { + return NULL; +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/base/decrypt_context.h b/chromium/chromecast/media/base/decrypt_context.h new file mode 100644 index 00000000000..5d053c0cafa --- /dev/null +++ b/chromium/chromecast/media/base/decrypt_context.h @@ -0,0 +1,46 @@ +// 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_BASE_DECRYPT_CONTEXT_H_ +#define CHROMECAST_MEDIA_BASE_DECRYPT_CONTEXT_H_ + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "chromecast/media/base/key_systems_common.h" + +namespace crypto { +class SymmetricKey; +} + +namespace chromecast { +namespace media { + +// Base class of a decryption context: a decryption context gathers all the +// information needed to decrypt frames with a given key id. +// Each CDM should implement this and add fields needed to fully describe a +// decryption context. +// +class DecryptContext : public base::RefCountedThreadSafe<DecryptContext> { + public: + explicit DecryptContext(CastKeySystem key_system); + + CastKeySystem key_system() const { return key_system_; } + + // Returns the clear key if available, NULL otherwise. + virtual crypto::SymmetricKey* GetKey() const; + + protected: + friend class base::RefCountedThreadSafe<DecryptContext>; + virtual ~DecryptContext(); + + private: + CastKeySystem key_system_; + + DISALLOW_COPY_AND_ASSIGN(DecryptContext); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_BASE_DECRYPT_CONTEXT_H_
\ No newline at end of file diff --git a/chromium/chromecast/media/base/decrypt_context_clearkey.cc b/chromium/chromecast/media/base/decrypt_context_clearkey.cc new file mode 100644 index 00000000000..248cbb459c6 --- /dev/null +++ b/chromium/chromecast/media/base/decrypt_context_clearkey.cc @@ -0,0 +1,27 @@ +// 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/base/decrypt_context_clearkey.h" + +#include "base/logging.h" +#include "crypto/symmetric_key.h" + +namespace chromecast { +namespace media { + +DecryptContextClearKey::DecryptContextClearKey(crypto::SymmetricKey* key) + : DecryptContext(KEY_SYSTEM_CLEAR_KEY), + key_(key) { + CHECK(key); +} + +DecryptContextClearKey::~DecryptContextClearKey() { +} + +crypto::SymmetricKey* DecryptContextClearKey::GetKey() const { + return key_; +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/base/decrypt_context_clearkey.h b/chromium/chromecast/media/base/decrypt_context_clearkey.h new file mode 100644 index 00000000000..770cea08144 --- /dev/null +++ b/chromium/chromecast/media/base/decrypt_context_clearkey.h @@ -0,0 +1,33 @@ +// 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_BASE_DECRYPT_CONTEXT_CLEARKEY_H_ +#define CHROMECAST_MEDIA_BASE_DECRYPT_CONTEXT_CLEARKEY_H_ + +#include "base/macros.h" +#include "chromecast/media/base/decrypt_context.h" + +namespace chromecast { +namespace media { + +class DecryptContextClearKey : public DecryptContext { + public: + // Note: DecryptContextClearKey does not take ownership of |key|. + explicit DecryptContextClearKey(crypto::SymmetricKey* key); + + // DecryptContext implementation. + virtual crypto::SymmetricKey* GetKey() const override; + + private: + virtual ~DecryptContextClearKey(); + + crypto::SymmetricKey* const key_; + + DISALLOW_COPY_AND_ASSIGN(DecryptContextClearKey); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_BASE_DECRYPT_CONTEXT_CLEARKEY_H_
\ No newline at end of file diff --git a/chromium/chromecast/media/base/key_systems_common.cc b/chromium/chromecast/media/base/key_systems_common.cc new file mode 100644 index 00000000000..568aaa8a3e1 --- /dev/null +++ b/chromium/chromecast/media/base/key_systems_common.cc @@ -0,0 +1,41 @@ +// 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/base/key_systems_common.h" + +#include <cstddef> + +#include "media/cdm/key_system_names.h" + +#include "widevine_cdm_version.h" // In SHARED_INTERMEDIATE_DIR. + +namespace chromecast { +namespace media { + +#if defined(PLAYREADY_CDM_AVAILABLE) +const char kChromecastPlayreadyKeySystem[] = "com.chromecast.playready"; +#endif // defined(PLAYREADY_CDM_AVAILABLE) + +CastKeySystem GetKeySystemByName(const std::string& key_system_name) { +#if defined(WIDEVINE_CDM_AVAILABLE) + if (key_system_name.compare(kWidevineKeySystem) == 0) { + return KEY_SYSTEM_WIDEVINE; + } +#endif // defined(WIDEVINE_CDM_AVAILABLE) + +#if defined(PLAYREADY_CDM_AVAILABLE) + if (key_system_name.compare(kChromecastPlayreadyKeySystem) == 0) { + return KEY_SYSTEM_PLAYREADY; + } +#endif // defined(PLAYREADY_CDM_AVAILABLE) + + if (key_system_name.compare(::media::kClearKey) == 0) { + return KEY_SYSTEM_CLEAR_KEY; + } + + return GetPlatformKeySystemByName(key_system_name); +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/base/key_systems_common.h b/chromium/chromecast/media/base/key_systems_common.h new file mode 100644 index 00000000000..4ab8ea75dec --- /dev/null +++ b/chromium/chromecast/media/base/key_systems_common.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_BASE_KEY_SYSTEMS_COMMON_H_ +#define CHROMECAST_MEDIA_BASE_KEY_SYSTEMS_COMMON_H_ + +#include <string> + +namespace chromecast { +namespace media { + +#if defined(PLAYREADY_CDM_AVAILABLE) +extern const char kChromecastPlayreadyKeySystem[]; +#endif // defined(PLAYREADY_CDM_AVAILABLE) + +enum CastKeySystem { + KEY_SYSTEM_NONE = 0, + KEY_SYSTEM_CLEAR_KEY, + KEY_SYSTEM_PLAYREADY, + KEY_SYSTEM_WIDEVINE +}; + +// Translates a key system string into a CastKeySystem, calling into the +// platform for known key systems if needed. +CastKeySystem GetKeySystemByName(const std::string& key_system_name); + +// Translates a platform-specific key system string into a CastKeySystem. +// TODO(gunsch): Remove when prefixed EME is removed. +CastKeySystem GetPlatformKeySystemByName(const std::string& key_system_name); + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_BASE_KEY_SYSTEMS_COMMON_H_ diff --git a/chromium/chromecast/media/base/key_systems_common_simple.cc b/chromium/chromecast/media/base/key_systems_common_simple.cc new file mode 100644 index 00000000000..e6dbd028902 --- /dev/null +++ b/chromium/chromecast/media/base/key_systems_common_simple.cc @@ -0,0 +1,15 @@ +// 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/base/key_systems_common.h" + +namespace chromecast { +namespace media { + +CastKeySystem GetPlatformKeySystemByName(const std::string& key_system_name) { + return KEY_SYSTEM_NONE; +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/backend/audio_pipeline_device.cc b/chromium/chromecast/media/cma/backend/audio_pipeline_device.cc new file mode 100644 index 00000000000..60c6c3092e1 --- /dev/null +++ b/chromium/chromecast/media/cma/backend/audio_pipeline_device.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/backend/audio_pipeline_device.h" + +namespace chromecast { +namespace media { + +AudioPipelineDevice::AudioPipelineDevice() { +} + +AudioPipelineDevice::~AudioPipelineDevice() { +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/backend/audio_pipeline_device.h b/chromium/chromecast/media/cma/backend/audio_pipeline_device.h new file mode 100644 index 00000000000..3fbf5731dbe --- /dev/null +++ b/chromium/chromecast/media/cma/backend/audio_pipeline_device.h @@ -0,0 +1,41 @@ +// 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_BACKEND_AUDIO_PIPELINE_DEVICE_H_ +#define CHROMECAST_MEDIA_CMA_BACKEND_AUDIO_PIPELINE_DEVICE_H_ + +#include "base/macros.h" +#include "chromecast/media/cma/backend/media_component_device.h" + +namespace media { +class AudioDecoderConfig; +} + +namespace chromecast { +namespace media { +class AudioPipelineDeviceClient; + +class AudioPipelineDevice : public MediaComponentDevice { + public: + AudioPipelineDevice(); + virtual ~AudioPipelineDevice(); + + // Provide the audio configuration. + // Must be called before switching from |kStateUninitialized| to |kStateIdle|. + // Afterwards, this can be invoked any time the configuration changes. + // Returns true if the configuration is a supported configuration. + virtual bool SetConfig(const ::media::AudioDecoderConfig& config) = 0; + + // Sets the volume multiplier. + // The multiplier must be in the range [0.0, 1.0]. + virtual void SetStreamVolumeMultiplier(float multiplier) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(AudioPipelineDevice); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BACKEND_AUDIO_PIPELINE_DEVICE_H_ diff --git a/chromium/chromecast/media/cma/backend/audio_video_pipeline_device_unittest.cc b/chromium/chromecast/media/cma/backend/audio_video_pipeline_device_unittest.cc new file mode 100644 index 00000000000..cf1daca9ab0 --- /dev/null +++ b/chromium/chromecast/media/cma/backend/audio_video_pipeline_device_unittest.cc @@ -0,0 +1,386 @@ +// 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/files/file_path.h" +#include "base/files/memory_mapped_file.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/path_service.h" +#include "base/threading/thread.h" +#include "base/time/time.h" +#include "chromecast/media/base/decrypt_context.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_params.h" +#include "chromecast/media/cma/backend/video_pipeline_device.h" +#include "chromecast/media/cma/base/decoder_buffer_adapter.h" +#include "chromecast/media/cma/base/decoder_buffer_base.h" +#include "chromecast/media/cma/test/frame_segmenter_for_test.h" +#include "chromecast/media/cma/test/media_component_device_feeder_for_test.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 { + +namespace { + +typedef ScopedVector<MediaComponentDeviceFeederForTest>::iterator + ComponentDeviceIterator; + +const base::TimeDelta kMonitorLoopDelay = base::TimeDelta::FromMilliseconds(20); + +base::FilePath GetTestDataFilePath(const std::string& name) { + base::FilePath file_path; + CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &file_path)); + + file_path = file_path.Append(FILE_PATH_LITERAL("media")) + .Append(FILE_PATH_LITERAL("test")).Append(FILE_PATH_LITERAL("data")) + .AppendASCII(name); + return file_path; +} + +} // namespace + +class AudioVideoPipelineDeviceTest : public testing::Test { + public: + struct PauseInfo { + PauseInfo() {} + PauseInfo(base::TimeDelta d, base::TimeDelta l) : delay(d), length(l) {} + ~PauseInfo() {} + + base::TimeDelta delay; + base::TimeDelta length; + }; + + AudioVideoPipelineDeviceTest(); + virtual ~AudioVideoPipelineDeviceTest(); + + void ConfigureForFile(std::string filename); + void ConfigureForAudioOnly(std::string filename); + void ConfigureForVideoOnly(std::string filename, bool raw_h264); + + // Pattern loops, waiting >= pattern[i].delay against media clock between + // pauses, then pausing for >= pattern[i].length against MessageLoop + // A pause with delay <0 signals to stop sequence and do not loop + void SetPausePattern(const std::vector<PauseInfo> pattern); + + // Adds a pause to the end of pause pattern + void AddPause(base::TimeDelta delay, base::TimeDelta length); + + void Start(); + + private: + void Initialize(); + + void LoadAudioStream(std::string filename); + void LoadVideoStream(std::string filename, bool raw_h264); + + void MonitorLoop(); + + void OnPauseCompleted(); + + void OnEos(MediaComponentDeviceFeederForTest* device_feeder); + + scoped_ptr<MediaPipelineDevice> media_pipeline_device_; + MediaClockDevice* media_clock_device_; + + // Devices to feed + ScopedVector<MediaComponentDeviceFeederForTest> + component_device_feeders_; + + // Current media time. + base::TimeDelta pause_time_; + + // Pause settings + std::vector<PauseInfo> pause_pattern_; + int pause_pattern_idx_; + + DISALLOW_COPY_AND_ASSIGN(AudioVideoPipelineDeviceTest); +}; + +AudioVideoPipelineDeviceTest::AudioVideoPipelineDeviceTest() + : pause_pattern_() { +} + +AudioVideoPipelineDeviceTest::~AudioVideoPipelineDeviceTest() { +} + +void AudioVideoPipelineDeviceTest::AddPause(base::TimeDelta delay, + base::TimeDelta length) { + pause_pattern_.push_back(PauseInfo(delay, length)); +} + +void AudioVideoPipelineDeviceTest::SetPausePattern( + const std::vector<PauseInfo> pattern) { + pause_pattern_ = pattern; +} + +void AudioVideoPipelineDeviceTest::ConfigureForAudioOnly(std::string filename) { + Initialize(); + LoadAudioStream(filename); +} + +void AudioVideoPipelineDeviceTest::ConfigureForVideoOnly(std::string filename, + bool raw_h264) { + Initialize(); + LoadVideoStream(filename, raw_h264); +} + +void AudioVideoPipelineDeviceTest::ConfigureForFile(std::string filename) { + Initialize(); + LoadVideoStream(filename, false /* raw_h264 */); + LoadAudioStream(filename); +} + +void AudioVideoPipelineDeviceTest::LoadAudioStream(std::string filename) { + base::FilePath file_path = GetTestDataFilePath(filename); + DemuxResult demux_result = FFmpegDemuxForTest(file_path, true /* audio */); + BufferList frames = demux_result.frames; + + AudioPipelineDevice* audio_pipeline_device = + media_pipeline_device_->GetAudioPipelineDevice(); + + bool success = audio_pipeline_device->SetConfig(demux_result.audio_config); + ASSERT_TRUE(success); + + VLOG(2) << "Got " << frames.size() << " audio input frames"; + + frames.push_back( + scoped_refptr<DecoderBufferBase>( + new DecoderBufferAdapter(::media::DecoderBuffer::CreateEOSBuffer()))); + + MediaComponentDeviceFeederForTest* device_feeder = + new MediaComponentDeviceFeederForTest(audio_pipeline_device, frames); + device_feeder->Initialize(base::Bind(&AudioVideoPipelineDeviceTest::OnEos, + base::Unretained(this), + device_feeder)); + component_device_feeders_.push_back(device_feeder); +} + +void AudioVideoPipelineDeviceTest::LoadVideoStream(std::string filename, + bool raw_h264) { + BufferList frames; + ::media::VideoDecoderConfig video_config; + + if (raw_h264) { + base::FilePath file_path = GetTestDataFilePath(filename); + base::MemoryMappedFile video_stream; + ASSERT_TRUE(video_stream.Initialize(file_path)) + << "Couldn't open stream file: " << file_path.MaybeAsASCII(); + frames = H264SegmenterForTest(video_stream.data(), video_stream.length()); + + // Use arbitraty sizes. + gfx::Size coded_size(320, 240); + gfx::Rect visible_rect(0, 0, 320, 240); + gfx::Size natural_size(320, 240); + + // TODO(kjoswiak): Either pull data from stream or make caller specify value + video_config = ::media::VideoDecoderConfig( + ::media::kCodecH264, + ::media::H264PROFILE_MAIN, + ::media::VideoFrame::I420, + coded_size, + visible_rect, + natural_size, + NULL, 0, false); + } else { + base::FilePath file_path = GetTestDataFilePath(filename); + DemuxResult demux_result = FFmpegDemuxForTest(file_path, + /*audio*/ false); + frames = demux_result.frames; + video_config = demux_result.video_config; + } + + VideoPipelineDevice* video_pipeline_device = + media_pipeline_device_->GetVideoPipelineDevice(); + + // Set configuration. + bool success = video_pipeline_device->SetConfig(video_config); + ASSERT_TRUE(success); + + VLOG(2) << "Got " << frames.size() << " video input frames"; + + frames.push_back( + scoped_refptr<DecoderBufferBase>(new DecoderBufferAdapter( + ::media::DecoderBuffer::CreateEOSBuffer()))); + + MediaComponentDeviceFeederForTest* device_feeder = + new MediaComponentDeviceFeederForTest(video_pipeline_device, frames); + device_feeder->Initialize(base::Bind(&AudioVideoPipelineDeviceTest::OnEos, + base::Unretained(this), + device_feeder)); + component_device_feeders_.push_back(device_feeder); +} + +void AudioVideoPipelineDeviceTest::Start() { + pause_time_ = base::TimeDelta(); + pause_pattern_idx_ = 0; + + for (int i = 0; i < component_device_feeders_.size(); i++) { + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&MediaComponentDeviceFeederForTest::Feed, + base::Unretained(component_device_feeders_[i]))); + } + + media_clock_device_->SetState(MediaClockDevice::kStateRunning); + + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&AudioVideoPipelineDeviceTest::MonitorLoop, + base::Unretained(this))); +} + +void AudioVideoPipelineDeviceTest::MonitorLoop() { + base::TimeDelta media_time = media_clock_device_->GetTime(); + + if (!pause_pattern_.empty() && + pause_pattern_[pause_pattern_idx_].delay >= base::TimeDelta() && + media_time >= pause_time_ + pause_pattern_[pause_pattern_idx_].delay) { + // Do Pause + media_clock_device_->SetRate(0.0); + pause_time_ = media_clock_device_->GetTime(); + + VLOG(2) << "Pausing at " << pause_time_.InMilliseconds() << "ms for " << + pause_pattern_[pause_pattern_idx_].length.InMilliseconds() << "ms"; + + // Wait for pause finish + base::MessageLoopProxy::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&AudioVideoPipelineDeviceTest::OnPauseCompleted, + base::Unretained(this)), + pause_pattern_[pause_pattern_idx_].length); + return; + } + + // Check state again in a little while + base::MessageLoopProxy::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&AudioVideoPipelineDeviceTest::MonitorLoop, + base::Unretained(this)), + kMonitorLoopDelay); +} + +void AudioVideoPipelineDeviceTest::OnPauseCompleted() { + // Make sure the media time didn't move during that time. + base::TimeDelta media_time = media_clock_device_->GetTime(); + + // TODO(damienv): + // Should be: + // EXPECT_EQ(media_time, media_time_); + // However, some backends, when rendering the first frame while in paused + // mode moves the time forward. + // This behaviour is not intended. + EXPECT_GE(media_time, pause_time_); + EXPECT_LE(media_time, pause_time_ + base::TimeDelta::FromMilliseconds(50)); + + pause_time_ = media_time; + pause_pattern_idx_ = (pause_pattern_idx_ + 1) % pause_pattern_.size(); + + VLOG(2) << "Pause complete, restarting media clock"; + + // Resume playback and frame feeding. + media_clock_device_->SetRate(1.0); + + MonitorLoop(); +} + +void AudioVideoPipelineDeviceTest::OnEos( + MediaComponentDeviceFeederForTest* device_feeder) { + for (ComponentDeviceIterator it = component_device_feeders_.begin(); + it != component_device_feeders_.end(); + ++it) { + if (*it == device_feeder) { + component_device_feeders_.erase(it); + break; + } + } + + // Check if all streams finished + if (component_device_feeders_.empty()) + base::MessageLoop::current()->QuitWhenIdle(); +} + +void AudioVideoPipelineDeviceTest::Initialize() { + // Create the media device. + MediaPipelineDeviceParams params; + media_pipeline_device_.reset(CreateMediaPipelineDevice(params).release()); + media_clock_device_ = media_pipeline_device_->GetMediaClockDevice(); + + // Clock initialization and configuration. + bool success = + media_clock_device_->SetState(MediaClockDevice::kStateIdle); + ASSERT_TRUE(success); + success = media_clock_device_->ResetTimeline(base::TimeDelta()); + ASSERT_TRUE(success); + media_clock_device_->SetRate(1.0); +} + +TEST_F(AudioVideoPipelineDeviceTest, Mp3Playback) { + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + + ConfigureForAudioOnly("sfx.mp3"); + Start(); + message_loop->Run(); +} + +TEST_F(AudioVideoPipelineDeviceTest, VorbisPlayback) { + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + + ConfigureForAudioOnly("sfx.ogg"); + Start(); + message_loop->Run(); +} + +TEST_F(AudioVideoPipelineDeviceTest, H264Playback) { + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + + ConfigureForVideoOnly("bear.h264", true /* raw_h264 */); + Start(); + message_loop->Run(); +} + +TEST_F(AudioVideoPipelineDeviceTest, WebmPlaybackWithPause) { + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + + // Setup to pause for 100ms every 500ms + AddPause(base::TimeDelta::FromMilliseconds(500), + base::TimeDelta::FromMilliseconds(100)); + + ConfigureForVideoOnly("bear-640x360.webm", false /* raw_h264 */); + Start(); + message_loop->Run(); +} + +TEST_F(AudioVideoPipelineDeviceTest, Vp8Playback) { + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + + ConfigureForVideoOnly("bear-vp8a.webm", false /* raw_h264 */); + Start(); + message_loop->Run(); +} + +TEST_F(AudioVideoPipelineDeviceTest, WebmPlayback) { + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + + ConfigureForFile("bear-640x360.webm"); + Start(); + message_loop->Run(); +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/backend/media_clock_device.cc b/chromium/chromecast/media/cma/backend/media_clock_device.cc new file mode 100644 index 00000000000..853e5bafbd0 --- /dev/null +++ b/chromium/chromecast/media/cma/backend/media_clock_device.cc @@ -0,0 +1,55 @@ +// 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/backend/media_clock_device.h" + +#include "base/logging.h" + +namespace chromecast { +namespace media { + +MediaClockDevice::MediaClockDevice() { +} + +MediaClockDevice::~MediaClockDevice() { +} + +// static +bool MediaClockDevice::IsValidStateTransition(State state1, State state2) { + if (state2 == state1) + return true; + + // All states can transition to |kStateError|. + bool is_transition_valid = (state2 == kStateError); + + // All the other valid FSM transitions. + is_transition_valid = is_transition_valid || + (state1 == kStateUninitialized && (state2 == kStateIdle)) || + (state1 == kStateIdle && (state2 == kStateRunning || + state2 == kStateUninitialized)) || + (state1 == kStateRunning && (state2 == kStateIdle)) || + (state1 == kStateError && (state2 == kStateUninitialized)); + + return is_transition_valid; +} + +// static +std::string MediaClockDevice::StateToString(const State& state) { + switch (state) { + case kStateUninitialized: + return "Uninitialized"; + case kStateIdle: + return "Idle"; + case kStateRunning: + return "Running"; + case kStateError: + return "Error"; + default: + NOTREACHED() << "Unknown MediaClockDevice::State: " << state; + return ""; + } +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/backend/media_clock_device.h b/chromium/chromecast/media/cma/backend/media_clock_device.h new file mode 100644 index 00000000000..b625dd57f82 --- /dev/null +++ b/chromium/chromecast/media/cma/backend/media_clock_device.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_BACKEND_MEDIA_CLOCK_DEVICE_H_ +#define CHROMECAST_MEDIA_CMA_BACKEND_MEDIA_CLOCK_DEVICE_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/macros.h" +#include "base/threading/non_thread_safe.h" +#include "base/time/time.h" + +namespace chromecast { +namespace media { + +// MediaClockDevice - +// +// State machine: +// ------------------- +// | | +// v | +// kUninitialized --> kIdle --------- kRunning +// +// {any state} --> kError +// +// Notes: +// - Hardware resources are acquired when transitioning from the +// |kUninitialized| state to the |kIdle| state. +// - The initial value of the timeline can only be set in the kIdle state. +// +class MediaClockDevice + : NON_EXPORTED_BASE(public base::NonThreadSafe) { + public: + enum State { + kStateUninitialized, + kStateIdle, + kStateRunning, + kStateError, + }; + + // Return true if transition from |state1| to |state2| is a valid state + // transition. + static bool IsValidStateTransition(State state1, State state2); + + // Returns string representation of state (for logging) + static std::string StateToString(const State& state); + + MediaClockDevice(); + virtual ~MediaClockDevice(); + + // Return the current state of the media clock. + virtual State GetState() const = 0; + + // Changes the state and performs any necessary transitions. + // Returns true when successful. + virtual bool SetState(State new_state) = 0; + + // Sets the initial value of the timeline. + // Can only be invoked in state kStateIdle. + // Returns true when successful. + virtual bool ResetTimeline(base::TimeDelta time) = 0; + + // Sets the clock rate. + // Setting it to 0 means the clock is not progressing and that the renderer + // tied to this media clock should pause rendering. + // Can only be invoked in states kStateIdle or kStateRunning. + virtual bool SetRate(float rate) = 0; + + // Retrieves the media clock time. + // Can only be invoked in states kStateIdle or kStateRunning. + virtual base::TimeDelta GetTime() = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(MediaClockDevice); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BACKEND_MEDIA_CLOCK_DEVICE_H_ diff --git a/chromium/chromecast/media/cma/backend/media_component_device.cc b/chromium/chromecast/media/cma/backend/media_component_device.cc new file mode 100644 index 00000000000..7331946f47c --- /dev/null +++ b/chromium/chromecast/media/cma/backend/media_component_device.cc @@ -0,0 +1,67 @@ +// 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/backend/media_component_device.h" + +#include "base/logging.h" + +namespace chromecast { +namespace media { + +MediaComponentDevice::Client::Client() { +} + +MediaComponentDevice::Client::~Client() { +} + +MediaComponentDevice::MediaComponentDevice() { +} + +MediaComponentDevice::~MediaComponentDevice() { +} + +// static +bool MediaComponentDevice::IsValidStateTransition(State state1, State state2) { + if (state2 == state1) + return true; + + // All states can transition to |kStateError|. + bool is_transition_valid = (state2 == kStateError); + + // All the other valid FSM transitions. + is_transition_valid = is_transition_valid || + (state1 == kStateUninitialized && (state2 == kStateIdle)) || + (state1 == kStateIdle && (state2 == kStateRunning || + state2 == kStatePaused || + state2 == kStateUninitialized)) || + (state1 == kStatePaused && (state2 == kStateIdle || + state2 == kStateRunning)) || + (state1 == kStateRunning && (state2 == kStateIdle || + state2 == kStatePaused)) || + (state1 == kStateError && (state2 == kStateUninitialized)); + + return is_transition_valid; +} + +// static +std::string MediaComponentDevice::StateToString(const State& state) { + switch (state) { + case kStateUninitialized: + return "Uninitialized"; + case kStateIdle: + return "Idle"; + case kStateRunning: + return "Running"; + case kStatePaused: + return "Paused"; + case kStateError: + return "Error"; + default: + NOTREACHED() << "Unknown MediaComponentDevice::State: " << state; + return ""; + } +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/backend/media_component_device.h b/chromium/chromecast/media/cma/backend/media_component_device.h new file mode 100644 index 00000000000..fd62d8dfa2f --- /dev/null +++ b/chromium/chromecast/media/cma/backend/media_component_device.h @@ -0,0 +1,140 @@ +// 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_BACKEND_MEDIA_COMPONENT_DEVICE_H_ +#define CHROMECAST_MEDIA_CMA_BACKEND_MEDIA_COMPONENT_DEVICE_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/threading/non_thread_safe.h" +#include "base/time/time.h" + +namespace chromecast { +namespace media { +class DecoderBufferBase; +class DecryptContext; + +// MediaComponentDevice - +// +// State machine: +// -------------- kRunning <--- +// | ^ | +// v | | +// kUninitialized <--> kIdle -------------- | +// ^ | | +// | v | +// -------------- kPaused <---- +// {any state} --> kError +// kError --> kUninitialized +// +// Notes: +// - Hardware resources are acquired when transitioning from the +// |kUninitialized| state to the |kIdle| state. +// - Buffers can be pushed only in the kRunning or kPaused states. +// - The end of stream is signaled through a special buffer. +// Once the end of stream buffer is fed, no other buffer +// can be fed until the FSM goes through the kIdle state again. +// - In both kPaused and kRunning states, frames can be fed. +// However, frames are possibly rendered only in the kRunning state. +// - In the kRunning state, frames are rendered according to the clock rate. +// - All the hardware resources must be released in the |kError| state. +// +class MediaComponentDevice + : NON_EXPORTED_BASE(public base::NonThreadSafe) { + public: + enum State { + kStateUninitialized, + kStateIdle, + kStateRunning, + kStatePaused, + kStateError, + }; + + enum FrameStatus { + kFrameSuccess, + kFrameFailed, + kFramePending, + }; + typedef base::Callback<void(FrameStatus)> FrameStatusCB; + + struct Client { + Client(); + ~Client(); + + // Invoked when playback reaches the end of stream. + base::Closure eos_cb; + }; + + // The statistics are computed since the media component left the idle state. + // For video, a sample is defined as a frame. + struct Statistics { + uint64 decoded_bytes; + uint64 decoded_samples; + uint64 dropped_samples; + }; + + // Returns whether or not transitioning from |state1| to |state2| is valid. + static bool IsValidStateTransition(State state1, State state2); + + // Returns string representation of state (for logging) + static std::string StateToString(const State& state); + + MediaComponentDevice(); + virtual ~MediaComponentDevice(); + + // Register |client| as the media event handler. + virtual void SetClient(const Client& client) = 0; + + // Changes the state and performs any necessary transitions. + // Returns true when successful. + virtual bool SetState(State new_state) = 0; + + // Returns the current state of the media component. + virtual State GetState() const = 0; + + // Sets the time where rendering should start. + // Return true when successful. + // Can only be invoked in state kStateIdle. + virtual bool SetStartPts(base::TimeDelta time) = 0; + + // Pushes a frame. + // |completion_cb| is only invoked if the returned value is |kFramePending|. + // In this specific case, no additional frame can be pushed before + // |completion_cb| is invoked. + // Note: |completion_cb| cannot be invoked with |kFramePending|. + // Note: pushing the pending frame should be aborted when the state goes back + // to kStateIdle. |completion_cb| is not invoked in that case. + virtual FrameStatus PushFrame( + const scoped_refptr<DecryptContext>& decrypt_context, + const scoped_refptr<DecoderBufferBase>& buffer, + const FrameStatusCB& completion_cb) = 0; + + // Returns the rendering time of the latest rendered sample. + // Can be invoked only in states kStateRunning or kStatePaused. + // Returns |kNoTimestamp()| if the playback time cannot be retrieved. + virtual base::TimeDelta GetRenderingTime() const = 0; + + // Returns the pipeline latency: i.e. the amount of data + // in the pipeline that have not been rendered yet. + // Returns |kNoTimestamp()| if the latency is not available. + virtual base::TimeDelta GetRenderingDelay() const = 0; + + // Returns the playback statistics. Statistics are computed since the media + // component left the idle state. + // Returns true when successful. + // Can only be invoked in state kStateRunning. + virtual bool GetStatistics(Statistics* stats) const = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(MediaComponentDevice); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BACKEND_MEDIA_COMPONENT_DEVICE_H_ diff --git a/chromium/chromecast/media/cma/backend/media_pipeline_device.cc b/chromium/chromecast/media/cma/backend/media_pipeline_device.cc new file mode 100644 index 00000000000..ea120c47829 --- /dev/null +++ b/chromium/chromecast/media/cma/backend/media_pipeline_device.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/backend/media_pipeline_device.h" + +namespace chromecast { +namespace media { + +MediaPipelineDevice::MediaPipelineDevice() { +} + +MediaPipelineDevice::~MediaPipelineDevice() { +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/backend/media_pipeline_device.h b/chromium/chromecast/media/cma/backend/media_pipeline_device.h new file mode 100644 index 00000000000..8a961dc7fda --- /dev/null +++ b/chromium/chromecast/media/cma/backend/media_pipeline_device.h @@ -0,0 +1,42 @@ +// 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_BACKEND_MEDIA_PIPELINE_DEVICE_H_ +#define CHROMECAST_MEDIA_CMA_BACKEND_MEDIA_PIPELINE_DEVICE_H_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" + +namespace chromecast { +namespace media { +class AudioPipelineDevice; +class MediaClockDevice; +class MediaPipelineDeviceParams; +class VideoPipelineDevice; + +// MediaPipelineDevice is the owner of the underlying audio/video/clock +// devices. +class MediaPipelineDevice { + public: + MediaPipelineDevice(); + virtual ~MediaPipelineDevice(); + + virtual AudioPipelineDevice* GetAudioPipelineDevice() const = 0; + + virtual VideoPipelineDevice* GetVideoPipelineDevice() const = 0; + + virtual MediaClockDevice* GetMediaClockDevice() const = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(MediaPipelineDevice); +}; + +// Factory to create a MediaPipelineDevice. +scoped_ptr<MediaPipelineDevice> CreateMediaPipelineDevice( + const MediaPipelineDeviceParams& params); + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BACKEND_MEDIA_PIPELINE_DEVICE_H_ diff --git a/chromium/chromecast/media/cma/backend/media_pipeline_device_fake.cc b/chromium/chromecast/media/cma/backend/media_pipeline_device_fake.cc new file mode 100644 index 00000000000..c075191e62f --- /dev/null +++ b/chromium/chromecast/media/cma/backend/media_pipeline_device_fake.cc @@ -0,0 +1,573 @@ +// 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/backend/media_pipeline_device_fake.h" + +#include <list> + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/message_loop/message_loop_proxy.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_component_device.h" +#include "chromecast/media/cma/backend/video_pipeline_device.h" +#include "chromecast/media/cma/base/decoder_buffer_base.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/buffers.h" +#include "media/base/video_decoder_config.h" + +namespace chromecast { +namespace media { + +class MediaClockDeviceFake : public MediaClockDevice { + public: + MediaClockDeviceFake(); + virtual ~MediaClockDeviceFake(); + + // MediaClockDevice implementation. + virtual State GetState() const override; + virtual bool SetState(State new_state) override; + virtual bool ResetTimeline(base::TimeDelta time) override; + virtual bool SetRate(float rate) override; + virtual base::TimeDelta GetTime() override; + + private: + State state_; + + // Media time sampled at STC time |stc_|. + base::TimeDelta media_time_; + base::TimeTicks stc_; + + float rate_; + + DISALLOW_COPY_AND_ASSIGN(MediaClockDeviceFake); +}; + +MediaClockDeviceFake::MediaClockDeviceFake() + : state_(kStateUninitialized), + media_time_(::media::kNoTimestamp()) { + DetachFromThread(); +} + +MediaClockDeviceFake::~MediaClockDeviceFake() { +} + +MediaClockDevice::State MediaClockDeviceFake::GetState() const { + DCHECK(CalledOnValidThread()); + return state_; +} + +bool MediaClockDeviceFake::SetState(State new_state) { + DCHECK(CalledOnValidThread()); + if (!MediaClockDevice::IsValidStateTransition(state_, new_state)) + return false; + + if (new_state == state_) + return true; + + state_ = new_state; + + if (state_ == kStateRunning) { + stc_ = base::TimeTicks::Now(); + DCHECK(media_time_ != ::media::kNoTimestamp()); + return true; + } + + if (state_ == kStateIdle) { + media_time_ = ::media::kNoTimestamp(); + return true; + } + + return true; +} + +bool MediaClockDeviceFake::ResetTimeline(base::TimeDelta time) { + DCHECK(CalledOnValidThread()); + DCHECK_EQ(state_, kStateIdle); + media_time_ = time; + return true; +} + +bool MediaClockDeviceFake::SetRate(float rate) { + DCHECK(CalledOnValidThread()); + if (state_ == kStateRunning) { + base::TimeTicks now = base::TimeTicks::Now(); + media_time_ = media_time_ + (now - stc_) * rate_; + stc_ = now; + } + + rate_ = rate; + return true; +} + +base::TimeDelta MediaClockDeviceFake::GetTime() { + DCHECK(CalledOnValidThread()); + if (state_ != kStateRunning) + return media_time_; + + if (media_time_ == ::media::kNoTimestamp()) + return ::media::kNoTimestamp(); + + base::TimeTicks now = base::TimeTicks::Now(); + base::TimeDelta interpolated_media_time = + media_time_ + (now - stc_) * rate_; + return interpolated_media_time; +} + + +namespace { + +// Maximum number of frames that can be buffered. +const size_t kMaxFrameCount = 20; + +} // namespace + +class MediaComponentDeviceFake : public MediaComponentDevice { + public: + explicit MediaComponentDeviceFake(MediaClockDeviceFake* media_clock_device); + virtual ~MediaComponentDeviceFake(); + + // MediaComponentDevice implementation. + virtual void SetClient(const Client& client) override; + virtual State GetState() const override; + virtual bool SetState(State new_state) override; + virtual bool SetStartPts(base::TimeDelta time) override; + virtual FrameStatus PushFrame( + const scoped_refptr<DecryptContext>& decrypt_context, + const scoped_refptr<DecoderBufferBase>& buffer, + const FrameStatusCB& completion_cb) override; + virtual base::TimeDelta GetRenderingTime() const override; + virtual base::TimeDelta GetRenderingDelay() const override; + virtual bool GetStatistics(Statistics* stats) const override; + + private: + struct FakeDecoderBuffer { + FakeDecoderBuffer(); + ~FakeDecoderBuffer(); + + // Buffer size. + size_t size; + + // Presentation timestamp. + base::TimeDelta pts; + }; + + void RenderTask(); + + MediaClockDeviceFake* const media_clock_device_; + Client client_; + + State state_; + + // Indicate whether the end of stream has been received. + bool is_eos_; + + // Media time of the last rendered audio sample. + base::TimeDelta rendering_time_; + + // Frame decoded/rendered since the pipeline left the idle state. + uint64 decoded_frame_count_; + uint64 decoded_byte_count_; + + // List of frames not rendered yet. + std::list<FakeDecoderBuffer> frames_; + + // Indicate whether there is a scheduled rendering task. + bool scheduled_rendering_task_; + + // Pending frame. + scoped_refptr<DecoderBufferBase> pending_buffer_; + FrameStatusCB frame_pushed_cb_; + + base::WeakPtr<MediaComponentDeviceFake> weak_this_; + base::WeakPtrFactory<MediaComponentDeviceFake> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(MediaComponentDeviceFake); +}; + +MediaComponentDeviceFake::FakeDecoderBuffer::FakeDecoderBuffer() + : size(0) { +} + +MediaComponentDeviceFake::FakeDecoderBuffer::~FakeDecoderBuffer() { +} + +MediaComponentDeviceFake::MediaComponentDeviceFake( + MediaClockDeviceFake* media_clock_device) + : media_clock_device_(media_clock_device), + state_(kStateUninitialized), + rendering_time_(::media::kNoTimestamp()), + decoded_frame_count_(0), + decoded_byte_count_(0), + scheduled_rendering_task_(false), + weak_factory_(this) { + weak_this_ = weak_factory_.GetWeakPtr(); + DetachFromThread(); +} + +MediaComponentDeviceFake::~MediaComponentDeviceFake() { +} + +void MediaComponentDeviceFake::SetClient(const Client& client) { + DCHECK(CalledOnValidThread()); + client_ = client; +} + +MediaComponentDevice::State MediaComponentDeviceFake::GetState() const { + DCHECK(CalledOnValidThread()); + return state_; +} + +bool MediaComponentDeviceFake::SetState(State new_state) { + DCHECK(CalledOnValidThread()); + if (!MediaComponentDevice::IsValidStateTransition(state_, new_state)) + return false; + state_ = new_state; + + if (state_ == kStateIdle) { + // Back to the idle state: reset a bunch of parameters. + is_eos_ = false; + rendering_time_ = ::media::kNoTimestamp(); + decoded_frame_count_ = 0; + decoded_byte_count_ = 0; + frames_.clear(); + pending_buffer_ = scoped_refptr<DecoderBufferBase>(); + frame_pushed_cb_.Reset(); + return true; + } + + if (state_ == kStateRunning) { + if (!scheduled_rendering_task_) { + scheduled_rendering_task_ = true; + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&MediaComponentDeviceFake::RenderTask, weak_this_)); + } + return true; + } + + return true; +} + +bool MediaComponentDeviceFake::SetStartPts(base::TimeDelta time) { + DCHECK(CalledOnValidThread()); + DCHECK_EQ(state_, kStateIdle); + rendering_time_ = time; + return true; +} + +MediaComponentDevice::FrameStatus MediaComponentDeviceFake::PushFrame( + const scoped_refptr<DecryptContext>& decrypt_context, + const scoped_refptr<DecoderBufferBase>& buffer, + const FrameStatusCB& completion_cb) { + DCHECK(CalledOnValidThread()); + DCHECK(state_ == kStatePaused || state_ == kStateRunning); + DCHECK(!is_eos_); + DCHECK(!pending_buffer_.get()); + DCHECK(buffer.get()); + + if (buffer->end_of_stream()) { + is_eos_ = true; + return kFrameSuccess; + } + + if (frames_.size() > kMaxFrameCount) { + pending_buffer_ = buffer; + frame_pushed_cb_ = completion_cb; + return kFramePending; + } + + FakeDecoderBuffer fake_buffer; + fake_buffer.size = buffer->data_size(); + fake_buffer.pts = buffer->timestamp(); + frames_.push_back(fake_buffer); + return kFrameSuccess; +} + +base::TimeDelta MediaComponentDeviceFake::GetRenderingTime() const { + return rendering_time_; +} + +base::TimeDelta MediaComponentDeviceFake::GetRenderingDelay() const { + NOTIMPLEMENTED(); + return ::media::kNoTimestamp(); +} + +void MediaComponentDeviceFake::RenderTask() { + scheduled_rendering_task_ = false; + + if (state_ != kStateRunning) + return; + + base::TimeDelta media_time = media_clock_device_->GetTime(); + if (media_time == ::media::kNoTimestamp()) { + scheduled_rendering_task_ = true; + base::MessageLoopProxy::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&MediaComponentDeviceFake::RenderTask, weak_this_), + base::TimeDelta::FromMilliseconds(50)); + return; + } + + while (!frames_.empty() && frames_.front().pts <= media_time) { + rendering_time_ = frames_.front().pts; + decoded_frame_count_++; + decoded_byte_count_ += frames_.front().size; + frames_.pop_front(); + if (pending_buffer_.get()) { + FakeDecoderBuffer fake_buffer; + fake_buffer.size = pending_buffer_->data_size(); + fake_buffer.pts = pending_buffer_->timestamp(); + frames_.push_back(fake_buffer); + pending_buffer_ = scoped_refptr<DecoderBufferBase>(); + base::ResetAndReturn(&frame_pushed_cb_).Run(kFrameSuccess); + } + } + + if (frames_.empty() && is_eos_) { + if (!client_.eos_cb.is_null()) + client_.eos_cb.Run(); + return; + } + + scheduled_rendering_task_ = true; + base::MessageLoopProxy::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&MediaComponentDeviceFake::RenderTask, weak_this_), + base::TimeDelta::FromMilliseconds(50)); +} + +bool MediaComponentDeviceFake::GetStatistics(Statistics* stats) const { + if (state_ != kStateRunning) + return false; + + // Note: what is returned here is not the number of samples but the number of + // frames. The value is different for audio. + stats->decoded_bytes = decoded_byte_count_; + stats->decoded_samples = decoded_frame_count_; + stats->dropped_samples = 0; + return true; +} + + +class AudioPipelineDeviceFake : public AudioPipelineDevice { + public: + explicit AudioPipelineDeviceFake(MediaClockDeviceFake* media_clock_device); + virtual ~AudioPipelineDeviceFake(); + + // AudioPipelineDevice implementation. + virtual void SetClient(const Client& client) override; + virtual State GetState() const override; + virtual bool SetState(State new_state) override; + virtual bool SetStartPts(base::TimeDelta time) override; + virtual FrameStatus PushFrame( + const scoped_refptr<DecryptContext>& decrypt_context, + const scoped_refptr<DecoderBufferBase>& buffer, + const FrameStatusCB& completion_cb) override; + virtual base::TimeDelta GetRenderingTime() const override; + virtual base::TimeDelta GetRenderingDelay() const override; + virtual bool SetConfig(const ::media::AudioDecoderConfig& config) override; + virtual void SetStreamVolumeMultiplier(float multiplier) override; + virtual bool GetStatistics(Statistics* stats) const override; + + private: + scoped_ptr<MediaComponentDeviceFake> fake_pipeline_; + + ::media::AudioDecoderConfig config_; + + DISALLOW_COPY_AND_ASSIGN(AudioPipelineDeviceFake); +}; + +AudioPipelineDeviceFake::AudioPipelineDeviceFake( + MediaClockDeviceFake* media_clock_device) + : fake_pipeline_(new MediaComponentDeviceFake(media_clock_device)) { + DetachFromThread(); +} + +AudioPipelineDeviceFake::~AudioPipelineDeviceFake() { +} + +void AudioPipelineDeviceFake::SetClient(const Client& client) { + fake_pipeline_->SetClient(client); +} + +MediaComponentDevice::State AudioPipelineDeviceFake::GetState() const { + return fake_pipeline_->GetState(); +} + +bool AudioPipelineDeviceFake::SetState(State new_state) { + bool success = fake_pipeline_->SetState(new_state); + if (!success) + return false; + + if (new_state == kStateIdle) { + DCHECK(config_.IsValidConfig()); + } + if (new_state == kStateUninitialized) { + config_ = ::media::AudioDecoderConfig(); + } + return true; +} + +bool AudioPipelineDeviceFake::SetStartPts(base::TimeDelta time) { + return fake_pipeline_->SetStartPts(time); +} + +MediaComponentDevice::FrameStatus AudioPipelineDeviceFake::PushFrame( + const scoped_refptr<DecryptContext>& decrypt_context, + const scoped_refptr<DecoderBufferBase>& buffer, + const FrameStatusCB& completion_cb) { + return fake_pipeline_->PushFrame(decrypt_context, buffer, completion_cb); +} + +base::TimeDelta AudioPipelineDeviceFake::GetRenderingTime() const { + return fake_pipeline_->GetRenderingTime(); +} + +base::TimeDelta AudioPipelineDeviceFake::GetRenderingDelay() const { + return fake_pipeline_->GetRenderingDelay(); +} + +bool AudioPipelineDeviceFake::SetConfig( + const ::media::AudioDecoderConfig& config) { + DCHECK(CalledOnValidThread()); + if (!config.IsValidConfig()) + return false; + config_ = config; + return true; +} + +void AudioPipelineDeviceFake::SetStreamVolumeMultiplier(float multiplier) { + DCHECK(CalledOnValidThread()); +} + +bool AudioPipelineDeviceFake::GetStatistics(Statistics* stats) const { + return fake_pipeline_->GetStatistics(stats); +} + + +class VideoPipelineDeviceFake : public VideoPipelineDevice { + public: + explicit VideoPipelineDeviceFake(MediaClockDeviceFake* media_clock_device); + virtual ~VideoPipelineDeviceFake(); + + // VideoPipelineDevice implementation. + virtual void SetClient(const Client& client) override; + virtual State GetState() const override; + virtual bool SetState(State new_state) override; + virtual bool SetStartPts(base::TimeDelta time) override; + virtual FrameStatus PushFrame( + const scoped_refptr<DecryptContext>& decrypt_context, + const scoped_refptr<DecoderBufferBase>& buffer, + const FrameStatusCB& completion_cb) override; + virtual base::TimeDelta GetRenderingTime() const override; + virtual base::TimeDelta GetRenderingDelay() const override; + virtual void SetVideoClient(const VideoClient& client) override; + virtual bool SetConfig(const ::media::VideoDecoderConfig& config) override; + virtual bool GetStatistics(Statistics* stats) const override; + + private: + scoped_ptr<MediaComponentDeviceFake> fake_pipeline_; + + ::media::VideoDecoderConfig config_; + + DISALLOW_COPY_AND_ASSIGN(VideoPipelineDeviceFake); +}; + +VideoPipelineDeviceFake::VideoPipelineDeviceFake( + MediaClockDeviceFake* media_clock_device) + : fake_pipeline_(new MediaComponentDeviceFake(media_clock_device)) { + DetachFromThread(); +} + +VideoPipelineDeviceFake::~VideoPipelineDeviceFake() { +} + +void VideoPipelineDeviceFake::SetClient(const Client& client) { + fake_pipeline_->SetClient(client); +} + +MediaComponentDevice::State VideoPipelineDeviceFake::GetState() const { + return fake_pipeline_->GetState(); +} + +bool VideoPipelineDeviceFake::SetState(State new_state) { + bool success = fake_pipeline_->SetState(new_state); + if (!success) + return false; + + if (new_state == kStateIdle) { + DCHECK(config_.IsValidConfig()); + } + if (new_state == kStateUninitialized) { + config_ = ::media::VideoDecoderConfig(); + } + return true; +} + +bool VideoPipelineDeviceFake::SetStartPts(base::TimeDelta time) { + return fake_pipeline_->SetStartPts(time); +} + +MediaComponentDevice::FrameStatus VideoPipelineDeviceFake::PushFrame( + const scoped_refptr<DecryptContext>& decrypt_context, + const scoped_refptr<DecoderBufferBase>& buffer, + const FrameStatusCB& completion_cb) { + return fake_pipeline_->PushFrame(decrypt_context, buffer, completion_cb); +} + +base::TimeDelta VideoPipelineDeviceFake::GetRenderingTime() const { + return fake_pipeline_->GetRenderingTime(); +} + +base::TimeDelta VideoPipelineDeviceFake::GetRenderingDelay() const { + return fake_pipeline_->GetRenderingDelay(); +} + +void VideoPipelineDeviceFake::SetVideoClient(const VideoClient& client) { +} + +bool VideoPipelineDeviceFake::SetConfig( + const ::media::VideoDecoderConfig& config) { + DCHECK(CalledOnValidThread()); + if (!config.IsValidConfig()) + return false; + config_ = config; + return true; +} + +bool VideoPipelineDeviceFake::GetStatistics(Statistics* stats) const { + return fake_pipeline_->GetStatistics(stats); +} + + +MediaPipelineDeviceFake::MediaPipelineDeviceFake() + : media_clock_device_(new MediaClockDeviceFake()), + audio_pipeline_device_( + new AudioPipelineDeviceFake(media_clock_device_.get())), + video_pipeline_device_( + new VideoPipelineDeviceFake(media_clock_device_.get())) { +} + +MediaPipelineDeviceFake::~MediaPipelineDeviceFake() { +} + +AudioPipelineDevice* MediaPipelineDeviceFake::GetAudioPipelineDevice() const { + return audio_pipeline_device_.get(); +} + +VideoPipelineDevice* MediaPipelineDeviceFake::GetVideoPipelineDevice() const { + return video_pipeline_device_.get(); +} + +MediaClockDevice* MediaPipelineDeviceFake::GetMediaClockDevice() const { + return media_clock_device_.get(); +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/backend/media_pipeline_device_fake.h b/chromium/chromecast/media/cma/backend/media_pipeline_device_fake.h new file mode 100644 index 00000000000..0534515bf9f --- /dev/null +++ b/chromium/chromecast/media/cma/backend/media_pipeline_device_fake.h @@ -0,0 +1,40 @@ +// 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_BACKEND_MEDIA_PIPELINE_DEVICE_FAKE_H_ +#define CHROMECAST_MEDIA_CMA_BACKEND_MEDIA_PIPELINE_DEVICE_FAKE_H_ + +#include "chromecast/media/cma/backend/media_pipeline_device.h" + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" + +namespace chromecast { +namespace media { +class AudioPipelineDeviceFake; +class MediaClockDeviceFake; +class VideoPipelineDeviceFake; + +class MediaPipelineDeviceFake : public MediaPipelineDevice { + public: + MediaPipelineDeviceFake(); + virtual ~MediaPipelineDeviceFake(); + + // MediaPipelineDevice implementation. + virtual AudioPipelineDevice* GetAudioPipelineDevice() const override; + virtual VideoPipelineDevice* GetVideoPipelineDevice() const override; + virtual MediaClockDevice* GetMediaClockDevice() const override; + + private: + scoped_ptr<MediaClockDeviceFake> media_clock_device_; + scoped_ptr<AudioPipelineDeviceFake> audio_pipeline_device_; + scoped_ptr<VideoPipelineDeviceFake> video_pipeline_device_; + + DISALLOW_COPY_AND_ASSIGN(MediaPipelineDeviceFake); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BACKEND_MEDIA_PIPELINE_DEVICE_H_ diff --git a/chromium/chromecast/media/cma/backend/media_pipeline_device_fake_factory.cc b/chromium/chromecast/media/cma/backend/media_pipeline_device_fake_factory.cc new file mode 100644 index 00000000000..646a4637158 --- /dev/null +++ b/chromium/chromecast/media/cma/backend/media_pipeline_device_fake_factory.cc @@ -0,0 +1,18 @@ +// 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/backend/media_pipeline_device_fake.h" + +#include "base/memory/scoped_ptr.h" + +namespace chromecast { +namespace media { + +scoped_ptr<MediaPipelineDevice> CreateMediaPipelineDevice( + const MediaPipelineDeviceParams& params) { + return scoped_ptr<MediaPipelineDevice>(new MediaPipelineDeviceFake()); +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/backend/media_pipeline_device_params.cc b/chromium/chromecast/media/cma/backend/media_pipeline_device_params.cc new file mode 100644 index 00000000000..531c76884f9 --- /dev/null +++ b/chromium/chromecast/media/cma/backend/media_pipeline_device_params.cc @@ -0,0 +1,18 @@ +// 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/backend/media_pipeline_device_params.h" + +namespace chromecast { +namespace media { + +MediaPipelineDeviceParams::MediaPipelineDeviceParams() + : disable_synchronization(false) { +} + +MediaPipelineDeviceParams::~MediaPipelineDeviceParams() { +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/backend/media_pipeline_device_params.h b/chromium/chromecast/media/cma/backend/media_pipeline_device_params.h new file mode 100644 index 00000000000..9bf9c044f16 --- /dev/null +++ b/chromium/chromecast/media/cma/backend/media_pipeline_device_params.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_BACKEND_MEDIA_PIPELINE_DEVICE_PARAMS_H_ +#define CHROMECAST_MEDIA_CMA_BACKEND_MEDIA_PIPELINE_DEVICE_PARAMS_H_ + +#include "base/macros.h" + +namespace chromecast { +namespace media { + +class MediaPipelineDeviceParams { + public: + MediaPipelineDeviceParams(); + ~MediaPipelineDeviceParams(); + + // When set to true, synchronization is disabled and audio/video frames are + // rendered "right away": + // - for audio, frames are still rendered based on the sampling frequency + // - for video, frames are rendered as soon as available at the output of + // the video decoder. + // The assumption is that no B frames are used when synchronization is + // disabled, otherwise B frames would always be skipped. + bool disable_synchronization; +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BACKEND_MEDIA_PIPELINE_DEVICE_PARAMS_H_ diff --git a/chromium/chromecast/media/cma/backend/video_pipeline_device.cc b/chromium/chromecast/media/cma/backend/video_pipeline_device.cc new file mode 100644 index 00000000000..d00c2cd4afe --- /dev/null +++ b/chromium/chromecast/media/cma/backend/video_pipeline_device.cc @@ -0,0 +1,23 @@ +// 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/backend/video_pipeline_device.h" + +namespace chromecast { +namespace media { + +VideoPipelineDevice::VideoClient::VideoClient() { +} + +VideoPipelineDevice::VideoClient::~VideoClient() { +} + +VideoPipelineDevice::VideoPipelineDevice() { +} + +VideoPipelineDevice::~VideoPipelineDevice() { +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/backend/video_pipeline_device.h b/chromium/chromecast/media/cma/backend/video_pipeline_device.h new file mode 100644 index 00000000000..eda48e206f4 --- /dev/null +++ b/chromium/chromecast/media/cma/backend/video_pipeline_device.h @@ -0,0 +1,63 @@ +// 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_BACKEND_VIDEO_PIPELINE_DEVICE_H_ +#define CHROMECAST_MEDIA_CMA_BACKEND_VIDEO_PIPELINE_DEVICE_H_ + +#include "base/callback.h" +#include "base/macros.h" +#include "chromecast/media/cma/backend/media_component_device.h" + +namespace gfx { +class Size; +} + +namespace media { +class VideoDecoderConfig; +} + +namespace chromecast { +namespace media { +class DecoderBufferBase; + +// VideoPipelineDevice - +// +// Notes: +// - Like a regular MediaComponentDevice, frames are possibly rendered only +// in the kRunning state. +// However, the first frame must be rendered regardless of the clock state: +// - no synchronization needed to display the first frame, +// - the clock rate has no impact on the presentation of the first frame. +// +class VideoPipelineDevice : public MediaComponentDevice { + public: + struct VideoClient { + VideoClient(); + ~VideoClient(); + + // Invoked each time the natural size is updated. + base::Callback<void(const gfx::Size& natural_size)> + natural_size_changed_cb; + }; + + VideoPipelineDevice(); + virtual ~VideoPipelineDevice(); + + // Registers |client| as the video specific event handler. + virtual void SetVideoClient(const VideoClient& client) = 0; + + // Provide the video configuration. + // Must be called before switching from |kStateUninitialized| to |kStateIdle|. + // Afterwards, this can be invoked any time the configuration changes. + // Returns true if the configuration is a supported configuration. + virtual bool SetConfig(const ::media::VideoDecoderConfig& config) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(VideoPipelineDevice); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BACKEND_VIDEO_PIPELINE_DEVICE_H_ diff --git a/chromium/chromecast/media/cma/base/balanced_media_task_runner_factory.cc b/chromium/chromecast/media/cma/base/balanced_media_task_runner_factory.cc new file mode 100644 index 00000000000..48413c0a9e2 --- /dev/null +++ b/chromium/chromecast/media/cma/base/balanced_media_task_runner_factory.cc @@ -0,0 +1,252 @@ +// 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/balanced_media_task_runner_factory.h" + +#include <map> + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/logging.h" +#include "base/single_thread_task_runner.h" +#include "chromecast/media/cma/base/media_task_runner.h" +#include "media/base/buffers.h" + +namespace chromecast { +namespace media { + +// MediaTaskRunnerWithNotification - +// Media task runner which also behaves as a media task runner observer. +class MediaTaskRunnerWithNotification : public MediaTaskRunner { + public: + // Wraps a MediaTaskRunner so that a third party can: + // - be notified when a PostMediaTask is performed on this media task runner. + // |new_task_cb| is invoked in that case. + // - monitor the lifetime of the media task runner, i.e. check when the media + // task runner is not needed anymore. + // |shutdown_cb| is invoked in that case. + MediaTaskRunnerWithNotification( + const scoped_refptr<MediaTaskRunner>& media_task_runner, + const base::Closure& new_task_cb, + const base::Closure& shutdown_cb); + + // MediaTaskRunner implementation. + virtual bool PostMediaTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta timestamp) override; + + private: + virtual ~MediaTaskRunnerWithNotification(); + + scoped_refptr<MediaTaskRunner> const media_task_runner_; + + const base::Closure new_task_cb_; + const base::Closure shutdown_cb_; + + DISALLOW_COPY_AND_ASSIGN(MediaTaskRunnerWithNotification); +}; + +MediaTaskRunnerWithNotification::MediaTaskRunnerWithNotification( + const scoped_refptr<MediaTaskRunner>& media_task_runner, + const base::Closure& new_task_cb, + const base::Closure& shutdown_cb) + : media_task_runner_(media_task_runner), + new_task_cb_(new_task_cb), + shutdown_cb_(shutdown_cb) { +} + +MediaTaskRunnerWithNotification::~MediaTaskRunnerWithNotification() { + shutdown_cb_.Run(); +} + +bool MediaTaskRunnerWithNotification::PostMediaTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta timestamp) { + bool may_run_in_future = + media_task_runner_->PostMediaTask(from_here, task, timestamp); + if (may_run_in_future) + new_task_cb_.Run(); + return may_run_in_future; +} + + +// BalancedMediaTaskRunner - +// Run media tasks whose timestamp is less or equal to a max timestamp. +// +// Restrictions of BalancedMediaTaskRunner: +// - Can have at most one task in the queue. +// - Tasks should be given by increasing timestamps. +class BalancedMediaTaskRunner + : public MediaTaskRunner { + public: + explicit BalancedMediaTaskRunner( + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner); + + // Schedule tasks whose timestamp is less than or equal to |max_timestamp|. + void ScheduleWork(base::TimeDelta max_timestamp); + + // Return the timestamp of the last media task. + // Return ::media::kNoTimestamp() if no media task has been posted. + base::TimeDelta GetMediaTimestamp() const; + + // MediaTaskRunner implementation. + virtual bool PostMediaTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta timestamp) override; + + private: + virtual ~BalancedMediaTaskRunner(); + + scoped_refptr<base::SingleThreadTaskRunner> const task_runner_; + + // Protects the following variables. + mutable base::Lock lock_; + + // Possible pending media task. + tracked_objects::Location from_here_; + base::Closure pending_task_; + + // Timestamp of the last posted task. + // Is initialized to ::media::kNoTimestamp(). + base::TimeDelta last_timestamp_; + + DISALLOW_COPY_AND_ASSIGN(BalancedMediaTaskRunner); +}; + +BalancedMediaTaskRunner::BalancedMediaTaskRunner( + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner) + : task_runner_(task_runner), + last_timestamp_(::media::kNoTimestamp()) { +} + +BalancedMediaTaskRunner::~BalancedMediaTaskRunner() { +} + +void BalancedMediaTaskRunner::ScheduleWork(base::TimeDelta max_media_time) { + base::Closure task; + { + base::AutoLock auto_lock(lock_); + if (pending_task_.is_null()) + return; + + if (last_timestamp_ != ::media::kNoTimestamp() && + last_timestamp_ >= max_media_time) { + return; + } + + task = base::ResetAndReturn(&pending_task_); + } + task_runner_->PostTask(from_here_, task); +} + +base::TimeDelta BalancedMediaTaskRunner::GetMediaTimestamp() const { + base::AutoLock auto_lock(lock_); + return last_timestamp_; +} + +bool BalancedMediaTaskRunner::PostMediaTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta timestamp) { + DCHECK(!task.is_null()); + + // Pass through for a task with no timestamp. + if (timestamp == ::media::kNoTimestamp()) { + return task_runner_->PostTask(from_here, task); + } + + base::AutoLock auto_lock(lock_); + + // Timestamps must be in order. + // Any task that does not meet that condition is simply discarded. + if (last_timestamp_ != ::media::kNoTimestamp() && + timestamp < last_timestamp_) { + return false; + } + + // Only support one pending task at a time. + DCHECK(pending_task_.is_null()); + from_here_ = from_here; + pending_task_ = task; + last_timestamp_ = timestamp; + + return true; +} + + +BalancedMediaTaskRunnerFactory::BalancedMediaTaskRunnerFactory( + base::TimeDelta max_delta) + : max_delta_(max_delta) { +} + +BalancedMediaTaskRunnerFactory::~BalancedMediaTaskRunnerFactory() { +} + +scoped_refptr<MediaTaskRunner> +BalancedMediaTaskRunnerFactory::CreateMediaTaskRunner( + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner) { + scoped_refptr<BalancedMediaTaskRunner> media_task_runner( + new BalancedMediaTaskRunner(task_runner)); + scoped_refptr<MediaTaskRunnerWithNotification> media_task_runner_wrapper( + new MediaTaskRunnerWithNotification( + media_task_runner, + base::Bind(&BalancedMediaTaskRunnerFactory::OnNewTask, this), + base::Bind( + &BalancedMediaTaskRunnerFactory::UnregisterMediaTaskRunner, + this, media_task_runner))); + base::AutoLock auto_lock(lock_); + // Note that |media_task_runner| is inserted here and + // not |media_task_runner_wrapper|. Otherwise, we would always have one + // ref on |media_task_runner_wrapper| and would never get the release + // notification. + // When |media_task_runner_wrapper| is going away, + // BalancedMediaTaskRunnerFactory will receive a notification and will in + // turn remove |media_task_runner|. + task_runners_.insert(media_task_runner); + return media_task_runner_wrapper; +} + +void BalancedMediaTaskRunnerFactory::OnNewTask() { + typedef + std::multimap<base::TimeDelta, scoped_refptr<BalancedMediaTaskRunner> > + TaskRunnerMap; + TaskRunnerMap runnable_task_runner; + + base::AutoLock auto_lock(lock_); + + // Get the minimum timestamp among all streams. + for (MediaTaskRunnerSet::const_iterator it = task_runners_.begin(); + it != task_runners_.end(); ++it) { + base::TimeDelta timestamp((*it)->GetMediaTimestamp()); + if (timestamp == ::media::kNoTimestamp()) + continue; + runnable_task_runner.insert( + std::pair<base::TimeDelta, scoped_refptr<BalancedMediaTaskRunner> >( + timestamp, *it)); + } + + // If there is no media task, just returns. + if (runnable_task_runner.empty()) + return; + + // Run tasks which meet the balancing criteria. + base::TimeDelta min_timestamp(runnable_task_runner.begin()->first); + base::TimeDelta max_timestamp = min_timestamp + max_delta_; + for (TaskRunnerMap::iterator it = runnable_task_runner.begin(); + it != runnable_task_runner.end(); ++it) { + (*it).second->ScheduleWork(max_timestamp); + } +} + +void BalancedMediaTaskRunnerFactory::UnregisterMediaTaskRunner( + const scoped_refptr<BalancedMediaTaskRunner>& media_task_runner) { + base::AutoLock auto_lock(lock_); + task_runners_.erase(media_task_runner); +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/base/balanced_media_task_runner_factory.h b/chromium/chromecast/media/cma/base/balanced_media_task_runner_factory.h new file mode 100644 index 00000000000..488373d5633 --- /dev/null +++ b/chromium/chromecast/media/cma/base/balanced_media_task_runner_factory.h @@ -0,0 +1,68 @@ +// 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_BALANCED_TASK_RUNNER_FACTORY_H_ +#define CHROMECAST_MEDIA_CMA_BASE_BALANCED_TASK_RUNNER_FACTORY_H_ + +#include <set> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "base/time/time.h" + +namespace base { +class SingleThreadTaskRunner; +} + +namespace chromecast { +namespace media { +class BalancedMediaTaskRunner; +class MediaTaskRunner; + +// BalancedMediaTaskRunnerFactory - +// Create media tasks runners that are loosely synchronized between each other. +// For two tasks T1 and T2 with timestamps ts1 and ts2, the scheduler ensures +// T2 is not scheduled before T1 if ts2 > ts1 + |max_delta|. +class BalancedMediaTaskRunnerFactory + : public base::RefCountedThreadSafe<BalancedMediaTaskRunnerFactory> { + public: + explicit BalancedMediaTaskRunnerFactory(base::TimeDelta max_delta); + + // Creates a media task runner using |task_runner| as the underlying + // regular task runner. + // Restriction on the returned media task runner: + // - can only schedule only one media task at a time. + // - timestamps of tasks posted on that task runner must be increasing. + scoped_refptr<MediaTaskRunner> CreateMediaTaskRunner( + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner); + + private: + typedef std::set<scoped_refptr<BalancedMediaTaskRunner> > MediaTaskRunnerSet; + + friend class base::RefCountedThreadSafe<BalancedMediaTaskRunnerFactory>; + virtual ~BalancedMediaTaskRunnerFactory(); + + // Invoked when one of the registered media task runners received a new media + // task. + void OnNewTask(); + + // Unregister a media task runner. + void UnregisterMediaTaskRunner( + const scoped_refptr<BalancedMediaTaskRunner>& media_task_runner); + + // Maximum timestamp deviation between tasks from the registered task runners. + const base::TimeDelta max_delta_; + + // Task runners created by the factory that have not been unregistered yet. + base::Lock lock_; + MediaTaskRunnerSet task_runners_; + + DISALLOW_COPY_AND_ASSIGN(BalancedMediaTaskRunnerFactory); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_BALANCED_TASK_RUNNER_FACTORY_H_ diff --git a/chromium/chromecast/media/cma/base/balanced_media_task_runner_unittest.cc b/chromium/chromecast/media/cma/base/balanced_media_task_runner_unittest.cc new file mode 100644 index 00000000000..e3448f67cac --- /dev/null +++ b/chromium/chromecast/media/cma/base/balanced_media_task_runner_unittest.cc @@ -0,0 +1,263 @@ +// 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 <list> +#include <vector> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread.h" +#include "base/time/time.h" +#include "chromecast/media/cma/base/balanced_media_task_runner_factory.h" +#include "chromecast/media/cma/base/media_task_runner.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromecast { +namespace media { + +namespace { + +struct MediaTaskRunnerTestContext { + MediaTaskRunnerTestContext(); + ~MediaTaskRunnerTestContext(); + + scoped_refptr<MediaTaskRunner> media_task_runner; + + bool is_pending_task; + + std::vector<base::TimeDelta> task_timestamp_list; + + size_t task_index; + base::TimeDelta max_timestamp; +}; + +MediaTaskRunnerTestContext::MediaTaskRunnerTestContext() { +} + +MediaTaskRunnerTestContext::~MediaTaskRunnerTestContext() { +} + +} // namespace + +class BalancedMediaTaskRunnerTest : public testing::Test { + public: + BalancedMediaTaskRunnerTest(); + virtual ~BalancedMediaTaskRunnerTest(); + + void SetupTest(base::TimeDelta max_delta, + const std::vector<std::vector<int> >& timestamps_in_ms, + const std::vector<size_t>& pattern, + const std::vector<int>& expected_task_timestamps_ms); + void ProcessAllTasks(); + + protected: + // Expected task order based on their timestamps. + std::list<base::TimeDelta> expected_task_timestamps_; + + private: + void ScheduleTask(); + void Task(size_t task_runner_id, base::TimeDelta timestamp); + + void OnTestTimeout(); + + scoped_refptr<BalancedMediaTaskRunnerFactory> media_task_runner_factory_; + + // Schedule first a task on media task runner #scheduling_pattern[0] + // then a task on media task runner #scheduling_pattern[1] and so on. + // Wrap around when reaching the end of the pattern. + std::vector<size_t> scheduling_pattern_; + size_t pattern_index_; + + // For each media task runner, keep a track of which task has already been + // scheduled. + std::vector<MediaTaskRunnerTestContext> contexts_; + + DISALLOW_COPY_AND_ASSIGN(BalancedMediaTaskRunnerTest); +}; + +BalancedMediaTaskRunnerTest::BalancedMediaTaskRunnerTest() { +} + +BalancedMediaTaskRunnerTest::~BalancedMediaTaskRunnerTest() { +} + +void BalancedMediaTaskRunnerTest::SetupTest( + base::TimeDelta max_delta, + const std::vector<std::vector<int> >& timestamps_in_ms, + const std::vector<size_t>& pattern, + const std::vector<int>& expected_task_timestamps_ms) { + media_task_runner_factory_ = new BalancedMediaTaskRunnerFactory(max_delta); + + scheduling_pattern_ = pattern; + pattern_index_ = 0; + + // Setup each task runner. + size_t n = timestamps_in_ms.size(); + contexts_.resize(n); + for (size_t k = 0; k < n; k++) { + contexts_[k].media_task_runner = + media_task_runner_factory_->CreateMediaTaskRunner( + base::MessageLoopProxy::current()); + contexts_[k].is_pending_task = false; + contexts_[k].task_index = 0; + contexts_[k].task_timestamp_list.resize( + timestamps_in_ms[k].size()); + for (size_t i = 0; i < timestamps_in_ms[k].size(); i++) { + contexts_[k].task_timestamp_list[i] = + base::TimeDelta::FromMilliseconds(timestamps_in_ms[k][i]); + } + } + + // Expected task order (for tasks that are actually run). + for (size_t k = 0; k < expected_task_timestamps_ms.size(); k++) { + expected_task_timestamps_.push_back( + base::TimeDelta::FromMilliseconds(expected_task_timestamps_ms[k])); + } +} + +void BalancedMediaTaskRunnerTest::ProcessAllTasks() { + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&BalancedMediaTaskRunnerTest::OnTestTimeout, + base::Unretained(this)), + base::TimeDelta::FromSeconds(5)); + ScheduleTask(); +} + +void BalancedMediaTaskRunnerTest::ScheduleTask() { + bool has_task = false; + for (size_t k = 0; k < contexts_.size(); k++) { + if (contexts_[k].task_index < contexts_[k].task_timestamp_list.size()) + has_task = true; + } + if (!has_task) { + base::MessageLoop::current()->QuitWhenIdle(); + return; + } + + size_t next_pattern_index = + (pattern_index_ + 1) % scheduling_pattern_.size(); + + size_t task_runner_id = scheduling_pattern_[pattern_index_]; + MediaTaskRunnerTestContext& context = contexts_[task_runner_id]; + + // Check whether all tasks have been scheduled for that task runner + // or if there is already one pending task. + if (context.task_index >= context.task_timestamp_list.size() || + context.is_pending_task) { + pattern_index_ = next_pattern_index; + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&BalancedMediaTaskRunnerTest::ScheduleTask, + base::Unretained(this))); + return; + } + + bool expected_may_run = false; + if (context.task_timestamp_list[context.task_index] >= + context.max_timestamp) { + expected_may_run = true; + context.max_timestamp = context.task_timestamp_list[context.task_index]; + } + + bool may_run = context.media_task_runner->PostMediaTask( + FROM_HERE, + base::Bind(&BalancedMediaTaskRunnerTest::Task, + base::Unretained(this), + task_runner_id, + context.task_timestamp_list[context.task_index]), + context.task_timestamp_list[context.task_index]); + EXPECT_EQ(may_run, expected_may_run); + + if (may_run) + context.is_pending_task = true; + + context.task_index++; + pattern_index_ = next_pattern_index; + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&BalancedMediaTaskRunnerTest::ScheduleTask, + base::Unretained(this))); +} + +void BalancedMediaTaskRunnerTest::Task( + size_t task_runner_id, base::TimeDelta timestamp) { + ASSERT_FALSE(expected_task_timestamps_.empty()); + EXPECT_EQ(timestamp, expected_task_timestamps_.front()); + expected_task_timestamps_.pop_front(); + + contexts_[task_runner_id].is_pending_task = false; +} + +void BalancedMediaTaskRunnerTest::OnTestTimeout() { + ADD_FAILURE() << "Test timed out"; + if (base::MessageLoop::current()) + base::MessageLoop::current()->QuitWhenIdle(); +} + +TEST_F(BalancedMediaTaskRunnerTest, OneTaskRunner) { + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + + // Timestamps of tasks for the single task runner. + int timestamps0_ms[] = {0, 10, 20, 30, 40, 30, 50, 60, 20, 30, 70}; + std::vector<std::vector<int> > timestamps_ms(1); + timestamps_ms[0] = std::vector<int>( + timestamps0_ms, timestamps0_ms + arraysize(timestamps0_ms)); + + // Scheduling pattern. + std::vector<size_t> scheduling_pattern(1); + scheduling_pattern[0] = 0; + + // Expected results. + int expected_timestamps[] = {0, 10, 20, 30, 40, 50, 60, 70}; + std::vector<int> expected_timestamps_ms(std::vector<int>( + expected_timestamps, + expected_timestamps + arraysize(expected_timestamps))); + + SetupTest(base::TimeDelta::FromMilliseconds(30), + timestamps_ms, + scheduling_pattern, + expected_timestamps_ms); + ProcessAllTasks(); + message_loop->Run(); + EXPECT_TRUE(expected_task_timestamps_.empty()); +} + +TEST_F(BalancedMediaTaskRunnerTest, TwoTaskRunnerUnbalanced) { + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + + // Timestamps of tasks for the 2 task runners. + int timestamps0_ms[] = {0, 10, 20, 30, 40, 30, 50, 60, 20, 30, 70}; + int timestamps1_ms[] = {5, 15, 25, 35, 45, 35, 55, 65, 25, 35, 75}; + std::vector<std::vector<int> > timestamps_ms(2); + timestamps_ms[0] = std::vector<int>( + timestamps0_ms, timestamps0_ms + arraysize(timestamps0_ms)); + timestamps_ms[1] = std::vector<int>( + timestamps1_ms, timestamps1_ms + arraysize(timestamps1_ms)); + + // Scheduling pattern. + size_t pattern[] = {1, 0, 0, 0, 0}; + std::vector<size_t> scheduling_pattern = std::vector<size_t>( + pattern, pattern + arraysize(pattern)); + + // Expected results. + int expected_timestamps[] = { + 5, 0, 10, 20, 30, 15, 40, 25, 50, 35, 60, 45, 70, 55, 65, 75 }; + std::vector<int> expected_timestamps_ms(std::vector<int>( + expected_timestamps, + expected_timestamps + arraysize(expected_timestamps))); + + SetupTest(base::TimeDelta::FromMilliseconds(30), + timestamps_ms, + scheduling_pattern, + expected_timestamps_ms); + ProcessAllTasks(); + message_loop->Run(); + EXPECT_TRUE(expected_task_timestamps_.empty()); +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/base/buffering_controller.cc b/chromium/chromecast/media/cma/base/buffering_controller.cc new file mode 100644 index 00000000000..183b12628d2 --- /dev/null +++ b/chromium/chromecast/media/cma/base/buffering_controller.cc @@ -0,0 +1,206 @@ +// 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_controller.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/message_loop/message_loop_proxy.h" +#include "chromecast/base/metrics/cast_metrics_helper.h" +#include "chromecast/media/cma/base/buffering_state.h" +#include "chromecast/media/cma/base/cma_logging.h" +#include "media/base/buffers.h" + +namespace chromecast { +namespace media { + +BufferingController::BufferingController( + const scoped_refptr<BufferingConfig>& config, + const BufferingNotificationCB& buffering_notification_cb) + : config_(config), + buffering_notification_cb_(buffering_notification_cb), + is_buffering_(false), + begin_buffering_time_(base::Time()), + initial_buffering_(true), + weak_factory_(this) { + weak_this_ = weak_factory_.GetWeakPtr(); + thread_checker_.DetachFromThread(); +} + +BufferingController::~BufferingController() { + // Some weak pointers might possibly be invalidated here. + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void BufferingController::UpdateHighLevelThreshold( + base::TimeDelta high_level_threshold) { + // Can only decrease the high level threshold. + if (high_level_threshold > config_->high_level()) + return; + CMALOG(kLogControl) << "High buffer threshold: " + << high_level_threshold.InMilliseconds(); + config_->set_high_level(high_level_threshold); + + // Make sure the low level threshold is somewhat consistent. + // Currently, we set it to one third of the high level threshold: + // this value could be adjusted in the future. + base::TimeDelta low_level_threshold = high_level_threshold / 3; + if (low_level_threshold <= config_->low_level()) { + CMALOG(kLogControl) << "Low buffer threshold: " + << low_level_threshold.InMilliseconds(); + config_->set_low_level(low_level_threshold); + } + + // Signal all the streams the config has changed. + for (StreamList::iterator it = stream_list_.begin(); + it != stream_list_.end(); ++it) { + (*it)->OnConfigChanged(); + } + + // Once all the streams have been notified, the buffering state must be + // updated (no notification is received from the streams). + OnBufferingStateChanged(false, false); +} + +scoped_refptr<BufferingState> BufferingController::AddStream() { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Add a new stream to the list of streams being monitored. + scoped_refptr<BufferingState> buffering_state(new BufferingState( + config_, + base::Bind(&BufferingController::OnBufferingStateChanged, weak_this_, + false, false), + base::Bind(&BufferingController::UpdateHighLevelThreshold, weak_this_))); + stream_list_.push_back(buffering_state); + + // Update the state and force a notification to the streams. + // TODO(damienv): Should this be a PostTask ? + OnBufferingStateChanged(true, false); + + return buffering_state; +} + +void BufferingController::SetMediaTime(base::TimeDelta time) { + for (StreamList::iterator it = stream_list_.begin(); + it != stream_list_.end(); ++it) { + (*it)->SetMediaTime(time); + } +} + +base::TimeDelta BufferingController::GetMaxRenderingTime() const { + base::TimeDelta max_rendering_time(::media::kNoTimestamp()); + for (StreamList::const_iterator it = stream_list_.begin(); + it != stream_list_.end(); ++it) { + base::TimeDelta max_stream_rendering_time = + (*it)->GetMaxRenderingTime(); + if (max_stream_rendering_time == ::media::kNoTimestamp()) + return ::media::kNoTimestamp(); + if (max_rendering_time == ::media::kNoTimestamp() || + max_stream_rendering_time < max_rendering_time) { + max_rendering_time = max_stream_rendering_time; + } + } + return max_rendering_time; +} + +void BufferingController::Reset() { + DCHECK(thread_checker_.CalledOnValidThread()); + + is_buffering_ = false; + initial_buffering_ = true; + stream_list_.clear(); +} + +void BufferingController::OnBufferingStateChanged( + bool force_notification, bool buffering_timeout) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Log the state of each stream. + DumpState(); + + bool is_low_buffering = IsLowBufferLevel(); + bool is_high_buffering = !is_low_buffering; + if (!buffering_timeout) { + // Hysteresis: + // - to leave buffering, not only should we leave the low buffer level state + // but we should go to the high buffer level state (medium is not enough). + is_high_buffering = IsHighBufferLevel(); + } + + bool is_buffering_prv = is_buffering_; + if (is_buffering_) { + if (is_high_buffering) + is_buffering_ = false; + } else { + if (is_low_buffering) + is_buffering_ = true; + } + + // Start buffering. + if (is_buffering_ && !is_buffering_prv) { + begin_buffering_time_ = base::Time::Now(); + } + + // End buffering. + if (is_buffering_prv && !is_buffering_) { + // TODO(damienv): |buffering_user_time| could be a UMA histogram. + base::Time current_time = base::Time::Now(); + base::TimeDelta buffering_user_time = current_time - begin_buffering_time_; + CMALOG(kLogControl) + << "Buffering took: " + << buffering_user_time.InMilliseconds() << "ms"; + chromecast::metrics::CastMetricsHelper::BufferingType buffering_type = + initial_buffering_ ? + chromecast::metrics::CastMetricsHelper::kInitialBuffering : + chromecast::metrics::CastMetricsHelper::kBufferingAfterUnderrun; + chromecast::metrics::CastMetricsHelper::GetInstance()->LogTimeToBufferAv( + buffering_type, buffering_user_time); + + // Only the first buffering report is considered "initial buffering". + initial_buffering_ = false; + } + + if (is_buffering_prv != is_buffering_ || force_notification) + buffering_notification_cb_.Run(is_buffering_); +} + +bool BufferingController::IsHighBufferLevel() { + if (stream_list_.empty()) + return true; + + bool is_high_buffering = true; + for (StreamList::iterator it = stream_list_.begin(); + it != stream_list_.end(); ++it) { + BufferingState::State stream_state = (*it)->GetState(); + is_high_buffering = is_high_buffering && + ((stream_state == BufferingState::kHighLevel) || + (stream_state == BufferingState::kEosReached)); + } + return is_high_buffering; +} + +bool BufferingController::IsLowBufferLevel() { + if (stream_list_.empty()) + return false; + + for (StreamList::iterator it = stream_list_.begin(); + it != stream_list_.end(); ++it) { + BufferingState::State stream_state = (*it)->GetState(); + if (stream_state == BufferingState::kLowLevel) + return true; + } + + return false; +} + +void BufferingController::DumpState() const { + CMALOG(kLogControl) << __FUNCTION__; + for (StreamList::const_iterator it = stream_list_.begin(); + it != stream_list_.end(); ++it) { + CMALOG(kLogControl) << (*it)->ToString(); + } +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/base/buffering_controller.h b/chromium/chromecast/media/cma/base/buffering_controller.h new file mode 100644 index 00000000000..bfc2c5cb9aa --- /dev/null +++ b/chromium/chromecast/media/cma/base/buffering_controller.h @@ -0,0 +1,109 @@ +// 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_CONTROLLER_H +#define CHROMECAST_MEDIA_CMA_BASE_BUFFERING_CONTROLLER_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 "base/time/time.h" + +namespace chromecast { +namespace media { +class BufferingConfig; +class BufferingState; + +class BufferingController { + public: + typedef base::Callback<void(bool)> BufferingNotificationCB; + + // Creates a buffering controller where the conditions to trigger rebuffering + // are given by |config|. The whole point of the buffering controller is to + // derive a single buffering state from the buffering state of various + // streams. + // |buffering_notification_cb| is a callback invoked to inform about possible + // changes of the buffering state. + BufferingController( + const scoped_refptr<BufferingConfig>& config, + const BufferingNotificationCB& buffering_notification_cb); + ~BufferingController(); + + // Creates a buffering state for one stream. This state is added to the list + // of streams monitored by the buffering controller. + scoped_refptr<BufferingState> AddStream(); + + // Sets the playback time. + void SetMediaTime(base::TimeDelta time); + + // Returns the maximum media time available for rendering. + // Return kNoTimestamp() if unknown. + base::TimeDelta GetMaxRenderingTime() const; + + // Returns whether there is an active buffering phase. + bool IsBuffering() const { return is_buffering_; } + + // Resets the buffering controller. This includes removing all the streams + // that were previously added. + void Reset(); + + private: + // Invoked each time the buffering state of one of the streams has changed. + // If |force_notification| is set, |buffering_notification_cb_| is invoked + // regardless whether the buffering state has changed or not. + // If |buffering_timeout| is set, then the condition to leave the buffering + // state is relaxed (we don't want to wait more). + void OnBufferingStateChanged(bool force_notification, + bool buffering_timeout); + + // Updates the high buffer level threshold to |high_level_threshold| + // if needed. + // This condition is triggered when one of the stream reached its maximum + // capacity. In that case, to avoid possible race condition (the buffering + // controller waits for more data to come but the buffer is to small to + // accomodate additional data), the thresholds in |config_| are adjusted + // accordingly. + void UpdateHighLevelThreshold(base::TimeDelta high_level_threshold); + + // Determines the overall buffer level based on the buffer level of each + // stream. + bool IsHighBufferLevel(); + bool IsLowBufferLevel(); + + // Logs the state of the buffering controller. + void DumpState() const; + + base::ThreadChecker thread_checker_; + + // Settings used to determine when to start/stop buffering. + scoped_refptr<BufferingConfig> config_; + + // Callback invoked each time there is a change of the buffering state. + BufferingNotificationCB buffering_notification_cb_; + + // State of the buffering controller. + bool is_buffering_; + + // Start time of a re-buffering phase. + base::Time begin_buffering_time_; + bool initial_buffering_; + + // Buffering level for each individual stream. + typedef std::list<scoped_refptr<BufferingState> > StreamList; + StreamList stream_list_; + + base::WeakPtr<BufferingController> weak_this_; + base::WeakPtrFactory<BufferingController> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(BufferingController); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_BUFFERING_CONTROLLER_H diff --git a/chromium/chromecast/media/cma/base/buffering_controller_unittest.cc b/chromium/chromecast/media/cma/base/buffering_controller_unittest.cc new file mode 100644 index 00000000000..75eaed9ce1a --- /dev/null +++ b/chromium/chromecast/media/cma/base/buffering_controller_unittest.cc @@ -0,0 +1,133 @@ +// 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 "base/bind.h" +#include "base/callback_helpers.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "chromecast/media/cma/base/buffering_controller.h" +#include "chromecast/media/cma/base/buffering_state.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromecast { +namespace media { + +namespace { + +class MockBufferingControllerClient { + public: + MockBufferingControllerClient(); + ~MockBufferingControllerClient(); + + MOCK_METHOD1(OnBufferingNotification, void(bool is_buffering)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockBufferingControllerClient); +}; + +MockBufferingControllerClient::MockBufferingControllerClient() { +} + +MockBufferingControllerClient::~MockBufferingControllerClient() { +} + +} // namespace + +class BufferingControllerTest : public testing::Test { + public: + BufferingControllerTest(); + virtual ~BufferingControllerTest(); + + protected: + scoped_ptr<BufferingController> buffering_controller_; + + MockBufferingControllerClient client_; + + // Buffer level under the low level threshold. + base::TimeDelta d1_; + + // Buffer level between the low and the high level. + base::TimeDelta d2_; + + // Buffer level above the high level. + base::TimeDelta d3_; + + private: + DISALLOW_COPY_AND_ASSIGN(BufferingControllerTest); +}; + +BufferingControllerTest::BufferingControllerTest() { + base::TimeDelta low_level_threshold( + base::TimeDelta::FromMilliseconds(2000)); + base::TimeDelta high_level_threshold( + base::TimeDelta::FromMilliseconds(6000)); + + d1_ = low_level_threshold - base::TimeDelta::FromMilliseconds(50); + d2_ = (low_level_threshold + high_level_threshold) / 2; + d3_ = high_level_threshold + base::TimeDelta::FromMilliseconds(50); + + scoped_refptr<BufferingConfig> buffering_config( + new BufferingConfig(low_level_threshold, high_level_threshold)); + buffering_controller_.reset(new BufferingController( + buffering_config, + base::Bind(&MockBufferingControllerClient::OnBufferingNotification, + base::Unretained(&client_)))); +} + +BufferingControllerTest::~BufferingControllerTest() { +} + +TEST_F(BufferingControllerTest, OneStream_Typical) { + EXPECT_CALL(client_, OnBufferingNotification(true)).Times(1); + scoped_refptr<BufferingState> buffering_state = + buffering_controller_->AddStream(); + buffering_state->SetMediaTime(base::TimeDelta()); + + // Simulate pre-buffering. + buffering_state->SetBufferedTime(d2_); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kMediumLevel); + + EXPECT_CALL(client_, OnBufferingNotification(false)).Times(1); + buffering_state->SetBufferedTime(d3_); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kHighLevel); + + // Simulate some fluctuations of the buffering level. + buffering_state->SetBufferedTime(d2_); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kMediumLevel); + + // Simulate an underrun. + EXPECT_CALL(client_, OnBufferingNotification(true)).Times(1); + buffering_state->SetBufferedTime(d1_); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kLowLevel); + + EXPECT_CALL(client_, OnBufferingNotification(false)).Times(1); + buffering_state->SetBufferedTime(d3_); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kHighLevel); + + // Simulate the end of stream. + buffering_state->NotifyEos(); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kEosReached); + + buffering_state->SetBufferedTime(d2_); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kEosReached); + + buffering_state->SetBufferedTime(d1_); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kEosReached); +} + +TEST_F(BufferingControllerTest, OneStream_LeaveBufferingOnEos) { + EXPECT_CALL(client_, OnBufferingNotification(true)).Times(1); + scoped_refptr<BufferingState> buffering_state = + buffering_controller_->AddStream(); + buffering_state->SetMediaTime(base::TimeDelta()); + + EXPECT_CALL(client_, OnBufferingNotification(false)).Times(1); + buffering_state->NotifyEos(); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kEosReached); +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/base/buffering_frame_provider.cc b/chromium/chromecast/media/cma/base/buffering_frame_provider.cc new file mode 100644 index 00000000000..b8d56985da4 --- /dev/null +++ b/chromium/chromecast/media/cma/base/buffering_frame_provider.cc @@ -0,0 +1,140 @@ +// 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_frame_provider.h" + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "chromecast/media/cma/base/buffering_state.h" +#include "chromecast/media/cma/base/decoder_buffer_base.h" +#include "media/base/bind_to_current_loop.h" +#include "media/base/buffers.h" + +namespace chromecast { +namespace media { + +BufferingFrameProvider::BufferWithConfig::BufferWithConfig( + const scoped_refptr<DecoderBufferBase>& buffer, + const ::media::AudioDecoderConfig& audio_config, + const ::media::VideoDecoderConfig& video_config) + : buffer_(buffer), + audio_config_(audio_config), + video_config_(video_config) { +} + +BufferingFrameProvider::BufferWithConfig::~BufferWithConfig() { +} + +BufferingFrameProvider::BufferingFrameProvider( + scoped_ptr<CodedFrameProvider> coded_frame_provider, + size_t max_buffer_size, + size_t max_frame_size, + const FrameBufferedCB& frame_buffered_cb) + : coded_frame_provider_(coded_frame_provider.Pass()), + is_pending_request_(false), + is_eos_(false), + total_buffer_size_(0), + max_buffer_size_(max_buffer_size), + max_frame_size_(max_frame_size), + frame_buffered_cb_(frame_buffered_cb), + weak_factory_(this) { + DCHECK_LE(max_frame_size, max_buffer_size); + weak_this_ = weak_factory_.GetWeakPtr(); + thread_checker_.DetachFromThread(); +} + +BufferingFrameProvider::~BufferingFrameProvider() { + // Required since some weak pointers might be released in the destructor. + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void BufferingFrameProvider::Read(const ReadCB& read_cb) { + DCHECK(thread_checker_.CalledOnValidThread()); + + DCHECK(!read_cb.is_null()); + read_cb_ = read_cb; + + CompleteReadIfNeeded(); + + RequestBufferIfNeeded(); +} + +void BufferingFrameProvider::Flush(const base::Closure& flush_cb) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Invalidate all the buffers that belong to this media timeline. + // This is needed since, even though |coded_frame_provider_| is flushed later + // in this function, there might be a pending task holding onto a buffer. + weak_factory_.InvalidateWeakPtrs(); + + // Create a new valid weak pointer that is used for the next media timeline. + weak_this_ = weak_factory_.GetWeakPtr(); + + is_pending_request_ = false; + is_eos_ = false; + buffer_list_.clear(); + total_buffer_size_ = 0; + read_cb_.Reset(); + coded_frame_provider_->Flush(flush_cb); +} + +void BufferingFrameProvider::OnNewBuffer( + const scoped_refptr<DecoderBufferBase>& buffer, + const ::media::AudioDecoderConfig& audio_config, + const ::media::VideoDecoderConfig& video_config) { + is_pending_request_ = false; + buffer_list_.push_back( + BufferWithConfig(buffer, audio_config, video_config)); + + if (buffer->end_of_stream()) { + is_eos_ = true; + } else { + total_buffer_size_ += buffer->data_size(); + } + + if (!frame_buffered_cb_.is_null()) { + // If the next upcoming frame is possibly filling the whole buffer, + // then the buffer is considered as having reached its max capacity. + bool max_capacity_flag = + (total_buffer_size_ + max_frame_size_ >= max_buffer_size_); + frame_buffered_cb_.Run(buffer, max_capacity_flag); + } + + RequestBufferIfNeeded(); + + CompleteReadIfNeeded(); +} + +void BufferingFrameProvider::RequestBufferIfNeeded() { + if (is_pending_request_) + return; + + if (is_eos_ || total_buffer_size_ >= max_buffer_size_) + return; + + is_pending_request_ = true; + coded_frame_provider_->Read(BindToCurrentLoop( + base::Bind(&BufferingFrameProvider::OnNewBuffer, weak_this_))); +} + +void BufferingFrameProvider::CompleteReadIfNeeded() { + if (read_cb_.is_null()) + return; + + if (buffer_list_.empty()) + return; + + BufferWithConfig buffer_with_config(buffer_list_.front()); + buffer_list_.pop_front(); + if (!buffer_with_config.buffer()->end_of_stream()) + total_buffer_size_ -= buffer_with_config.buffer()->data_size(); + + base::ResetAndReturn(&read_cb_).Run( + buffer_with_config.buffer(), + buffer_with_config.audio_config(), + buffer_with_config.video_config()); +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/base/buffering_frame_provider.h b/chromium/chromecast/media/cma/base/buffering_frame_provider.h new file mode 100644 index 00000000000..0397676bca0 --- /dev/null +++ b/chromium/chromecast/media/cma/base/buffering_frame_provider.h @@ -0,0 +1,117 @@ +// 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_FRAME_PROVIDER_H_ +#define CHROMECAST_MEDIA_CMA_BASE_BUFFERING_FRAME_PROVIDER_H_ + +#include <list> + +#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/base/coded_frame_provider.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/video_decoder_config.h" + +namespace chromecast { +namespace media { +class DecoderBufferBase; + +// BufferingFrameProvider - +// Fetch some data from another CodedFrameProvider up to a certain size limit. +class BufferingFrameProvider : public CodedFrameProvider { + public: + typedef base::Callback<void(const scoped_refptr<DecoderBufferBase>&, bool)> + FrameBufferedCB; + + // Creates a frame provider that buffers coded frames up to the + // |max_buffer_size| limit (given as a number of bytes). + // |max_frame_size| corresponds to an upper bound of the expected frame size. + // Each time a frame is buffered, |frame_buffered_cb| is invoked with the + // last frame buffered. The second parameter of the callback indicates + // whether the maximum capacity has been reached, i.e. whether the next frame + // size might overflow the buffer: |total_buffer_size_| + next_frame_size + // might be greater than |max_buffer_size|. + // Note: takes ownership of |coded_frame_provider|. + BufferingFrameProvider( + scoped_ptr<CodedFrameProvider> coded_frame_provider, + size_t max_buffer_size, + size_t max_frame_size, + const FrameBufferedCB& frame_buffered_cb); + virtual ~BufferingFrameProvider(); + + // CodedFrameProvider implementation. + virtual void Read(const ReadCB& read_cb) override; + virtual void Flush(const base::Closure& flush_cb) override; + + private: + class BufferWithConfig { + public: + BufferWithConfig( + const scoped_refptr<DecoderBufferBase>& buffer, + const ::media::AudioDecoderConfig& audio_config, + const ::media::VideoDecoderConfig& video_config); + ~BufferWithConfig(); + + const scoped_refptr<DecoderBufferBase>& buffer() const { return buffer_; } + const ::media::AudioDecoderConfig& audio_config() const { + return audio_config_; + } + const ::media::VideoDecoderConfig& video_config() const { + return video_config_; + } + + private: + scoped_refptr<DecoderBufferBase> buffer_; + ::media::AudioDecoderConfig audio_config_; + ::media::VideoDecoderConfig video_config_; + }; + + void OnNewBuffer(const scoped_refptr<DecoderBufferBase>& buffer, + const ::media::AudioDecoderConfig& audio_config, + const ::media::VideoDecoderConfig& video_config); + void RequestBufferIfNeeded(); + void CompleteReadIfNeeded(); + + base::ThreadChecker thread_checker_; + + // Frame provider the buffering frame provider fetches data from. + scoped_ptr<CodedFrameProvider> coded_frame_provider_; + + // Indicates whether there is a pending read request on + // |coded_frame_provider_|. + bool is_pending_request_; + + // Indicates whether the end of stream has been reached. + bool is_eos_; + + std::list<BufferWithConfig> buffer_list_; + + // Size in bytes of audio/video buffers in |buffer_list_|. + size_t total_buffer_size_; + + // Max amount of data to buffer. + // i.e. this is the maximum size of buffers in |buffer_list_|. + const size_t max_buffer_size_; + + // Maximum expected frame size. + const size_t max_frame_size_; + + // Callback invoked each time there is a new frame buffered. + FrameBufferedCB frame_buffered_cb_; + + // Pending read callback. + ReadCB read_cb_; + + base::WeakPtr<BufferingFrameProvider> weak_this_; + base::WeakPtrFactory<BufferingFrameProvider> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(BufferingFrameProvider); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_BUFFERING_FRAME_PROVIDER_H_ diff --git a/chromium/chromecast/media/cma/base/buffering_frame_provider_unittest.cc b/chromium/chromecast/media/cma/base/buffering_frame_provider_unittest.cc new file mode 100644 index 00000000000..c24862f4932 --- /dev/null +++ b/chromium/chromecast/media/cma/base/buffering_frame_provider_unittest.cc @@ -0,0 +1,187 @@ +// 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 <list> +#include <vector> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread.h" +#include "base/time/time.h" +#include "chromecast/media/cma/base/buffering_frame_provider.h" +#include "chromecast/media/cma/base/decoder_buffer_base.h" +#include "chromecast/media/cma/test/frame_generator_for_test.h" +#include "chromecast/media/cma/test/mock_frame_consumer.h" +#include "chromecast/media/cma/test/mock_frame_provider.h" +#include "media/base/audio_decoder_config.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 BufferingFrameProviderTest : public testing::Test { + public: + BufferingFrameProviderTest(); + virtual ~BufferingFrameProviderTest(); + + // Setup the test. + void Configure( + size_t frame_count, + const std::vector<bool>& provider_delayed_pattern, + const std::vector<bool>& consumer_delayed_pattern); + + // Start the test. + void Start(); + + protected: + scoped_ptr<BufferingFrameProvider> buffering_frame_provider_; + scoped_ptr<MockFrameConsumer> frame_consumer_; + + private: + void OnTestTimeout(); + void OnTestCompleted(); + + DISALLOW_COPY_AND_ASSIGN(BufferingFrameProviderTest); +}; + +BufferingFrameProviderTest::BufferingFrameProviderTest() { +} + +BufferingFrameProviderTest::~BufferingFrameProviderTest() { +} + +void BufferingFrameProviderTest::Configure( + size_t frame_count, + const std::vector<bool>& provider_delayed_pattern, + const std::vector<bool>& consumer_delayed_pattern) { + DCHECK_GE(frame_count, 1u); + + // Frame generation on the producer and consumer side. + std::vector<FrameGeneratorForTest::FrameSpec> frame_specs(frame_count); + 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 = ((k % 3) == 0); + } + frame_specs[frame_specs.size() - 1].is_eos = true; + + scoped_ptr<FrameGeneratorForTest> frame_generator_provider( + new FrameGeneratorForTest(frame_specs)); + scoped_ptr<FrameGeneratorForTest> frame_generator_consumer( + new FrameGeneratorForTest(frame_specs)); + + scoped_ptr<MockFrameProvider> frame_provider(new MockFrameProvider()); + frame_provider->Configure(provider_delayed_pattern, + frame_generator_provider.Pass()); + + size_t max_frame_size = 10 * 1024; + size_t buffer_size = 10 * max_frame_size; + buffering_frame_provider_.reset( + new BufferingFrameProvider( + scoped_ptr<CodedFrameProvider>(frame_provider.release()), + buffer_size, + max_frame_size, + BufferingFrameProvider::FrameBufferedCB())); + + frame_consumer_.reset( + new MockFrameConsumer(buffering_frame_provider_.get())); + frame_consumer_->Configure( + consumer_delayed_pattern, + false, + frame_generator_consumer.Pass()); +} + +void BufferingFrameProviderTest::Start() { + frame_consumer_->Start( + base::Bind(&BufferingFrameProviderTest::OnTestCompleted, + base::Unretained(this))); +} + +void BufferingFrameProviderTest::OnTestTimeout() { + ADD_FAILURE() << "Test timed out"; + if (base::MessageLoop::current()) + base::MessageLoop::current()->QuitWhenIdle(); +} + +void BufferingFrameProviderTest::OnTestCompleted() { + base::MessageLoop::current()->QuitWhenIdle(); +} + +TEST_F(BufferingFrameProviderTest, FastProviderSlowConsumer) { + bool provider_delayed_pattern[] = { false }; + bool consumer_delayed_pattern[] = { true }; + + const size_t frame_count = 100u; + Configure( + frame_count, + std::vector<bool>( + provider_delayed_pattern, + provider_delayed_pattern + arraysize(provider_delayed_pattern)), + std::vector<bool>( + consumer_delayed_pattern, + consumer_delayed_pattern + arraysize(consumer_delayed_pattern))); + + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + message_loop->PostTask( + FROM_HERE, + base::Bind(&BufferingFrameProviderTest::Start, base::Unretained(this))); + message_loop->Run(); +}; + +TEST_F(BufferingFrameProviderTest, SlowProviderFastConsumer) { + bool provider_delayed_pattern[] = { true }; + bool consumer_delayed_pattern[] = { false }; + + const size_t frame_count = 100u; + Configure( + frame_count, + std::vector<bool>( + provider_delayed_pattern, + provider_delayed_pattern + arraysize(provider_delayed_pattern)), + std::vector<bool>( + consumer_delayed_pattern, + consumer_delayed_pattern + arraysize(consumer_delayed_pattern))); + + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + message_loop->PostTask( + FROM_HERE, + base::Bind(&BufferingFrameProviderTest::Start, base::Unretained(this))); + message_loop->Run(); +}; + +TEST_F(BufferingFrameProviderTest, SlowFastProducerConsumer) { + // Lengths are prime between each other so we can test a lot of combinations. + bool provider_delayed_pattern[] = { + true, true, true, true, true, + false, false, false, false + }; + bool consumer_delayed_pattern[] = { + true, true, true, true, true, true, true, + false, false, false, false, false, false, false + }; + + const size_t frame_count = 100u; + Configure( + frame_count, + std::vector<bool>( + provider_delayed_pattern, + provider_delayed_pattern + arraysize(provider_delayed_pattern)), + std::vector<bool>( + consumer_delayed_pattern, + consumer_delayed_pattern + arraysize(consumer_delayed_pattern))); + + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + message_loop->PostTask( + FROM_HERE, + base::Bind(&BufferingFrameProviderTest::Start, base::Unretained(this))); + message_loop->Run(); +}; + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/base/buffering_state.cc b/chromium/chromecast/media/cma/base/buffering_state.cc new file mode 100644 index 00000000000..e1fc49fe188 --- /dev/null +++ b/chromium/chromecast/media/cma/base/buffering_state.cc @@ -0,0 +1,129 @@ +// 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_state.h" + +#include <sstream> + +#include "base/logging.h" +#include "media/base/buffers.h" + +namespace chromecast { +namespace media { + +BufferingConfig::BufferingConfig( + base::TimeDelta low_level_threshold, + base::TimeDelta high_level_threshold) + : low_level_threshold_(low_level_threshold), + high_level_threshold_(high_level_threshold) { +} + +BufferingConfig::~BufferingConfig() { +} + + +BufferingState::BufferingState( + const scoped_refptr<BufferingConfig>& config, + const base::Closure& state_changed_cb, + const HighLevelBufferCB& high_level_buffer_cb) + : config_(config), + state_changed_cb_(state_changed_cb), + high_level_buffer_cb_(high_level_buffer_cb), + state_(kLowLevel), + media_time_(::media::kNoTimestamp()), + max_rendering_time_(::media::kNoTimestamp()), + buffered_time_(::media::kNoTimestamp()) { +} + +BufferingState::~BufferingState() { +} + +void BufferingState::OnConfigChanged() { + state_ = GetBufferLevelState(); +} + +void BufferingState::SetMediaTime(base::TimeDelta media_time) { + media_time_ = media_time; + switch (state_) { + case kLowLevel: + case kMediumLevel: + case kHighLevel: + UpdateState(GetBufferLevelState()); + break; + case kEosReached: + break; + } +} + +void BufferingState::SetMaxRenderingTime(base::TimeDelta max_rendering_time) { + max_rendering_time_ = max_rendering_time; +} + +base::TimeDelta BufferingState::GetMaxRenderingTime() const { + return max_rendering_time_; +} + +void BufferingState::SetBufferedTime(base::TimeDelta buffered_time) { + buffered_time_ = buffered_time; + switch (state_) { + case kLowLevel: + case kMediumLevel: + case kHighLevel: + UpdateState(GetBufferLevelState()); + break; + case kEosReached: + break; + } +} + +void BufferingState::NotifyEos() { + UpdateState(kEosReached); +} + +void BufferingState::NotifyMaxCapacity(base::TimeDelta buffered_time) { + if (media_time_ == ::media::kNoTimestamp() || + buffered_time == ::media::kNoTimestamp()) { + LOG(WARNING) << "Max capacity with no timestamp"; + return; + } + base::TimeDelta buffer_duration = buffered_time - media_time_; + if (buffer_duration < config_->high_level()) + high_level_buffer_cb_.Run(buffer_duration); +} + +std::string BufferingState::ToString() const { + std::ostringstream s; + s << "state=" << state_ + << " media_time_ms=" << media_time_.InMilliseconds() + << " buffered_time_ms=" << buffered_time_.InMilliseconds() + << " low_level_ms=" << config_->low_level().InMilliseconds() + << " high_level_ms=" << config_->high_level().InMilliseconds(); + return s.str(); +} + +BufferingState::State BufferingState::GetBufferLevelState() const { + if (media_time_ == ::media::kNoTimestamp() || + buffered_time_ == ::media::kNoTimestamp()) { + return kLowLevel; + } + + base::TimeDelta buffer_duration = buffered_time_ - media_time_; + if (buffer_duration < config_->low_level()) + return kLowLevel; + if (buffer_duration >= config_->high_level()) + return kHighLevel; + return kMediumLevel; +} + +void BufferingState::UpdateState(State new_state) { + if (new_state == state_) + return; + + state_ = new_state; + if (!state_changed_cb_.is_null()) + state_changed_cb_.Run(); +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/base/buffering_state.h b/chromium/chromecast/media/cma/base/buffering_state.h new file mode 100644 index 00000000000..ced8206d8a1 --- /dev/null +++ b/chromium/chromecast/media/cma/base/buffering_state.h @@ -0,0 +1,138 @@ +// 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_STATE_H_ +#define CHROMECAST_MEDIA_CMA_BASE_BUFFERING_STATE_H_ + +#include <string> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/time/time.h" + +namespace chromecast { +namespace media { + +class BufferingConfig : public base::RefCountedThreadSafe<BufferingConfig> { + public: + BufferingConfig(base::TimeDelta low_level_threshold, + base::TimeDelta high_level_threshold); + + base::TimeDelta low_level() const { return low_level_threshold_; } + base::TimeDelta high_level() const { return high_level_threshold_; } + + void set_low_level(base::TimeDelta low_level) { + low_level_threshold_ = low_level; + } + void set_high_level(base::TimeDelta high_level) { + high_level_threshold_ = high_level; + } + + private: + friend class base::RefCountedThreadSafe<BufferingConfig>; + virtual ~BufferingConfig(); + + base::TimeDelta low_level_threshold_; + base::TimeDelta high_level_threshold_; + + DISALLOW_COPY_AND_ASSIGN(BufferingConfig); +}; + +class BufferingState + : public base::RefCountedThreadSafe<BufferingState> { + public: + typedef base::Callback<void(base::TimeDelta)> HighLevelBufferCB; + + enum State { + kLowLevel, + kMediumLevel, + kHighLevel, + kEosReached, + }; + + // Creates a new buffering state. The initial state is |kLowLevel|. + // |state_changed_cb| is used to notify about possible state changes. + // |high_level_buffer_cb| is used to adjust the high buffer threshold + // when the underlying buffer is not large enough to accomodate + // the current high buffer level. + BufferingState(const scoped_refptr<BufferingConfig>& config, + const base::Closure& state_changed_cb, + const HighLevelBufferCB& high_level_buffer_cb); + + // Returns the buffering state. + State GetState() const { return state_; } + + // Invoked when the buffering configuration has changed. + // Based on the new configuration, the buffering state might change. + // However, |state_changed_cb_| is not triggered in that case. + void OnConfigChanged(); + + // Sets the current rendering time for this stream. + void SetMediaTime(base::TimeDelta media_time); + + // Sets/gets the maximum rendering media time for this stream. + // The maximum rendering time is always lower than the buffered time. + void SetMaxRenderingTime(base::TimeDelta max_rendering_time); + base::TimeDelta GetMaxRenderingTime() const; + + // Sets the buffered time. + void SetBufferedTime(base::TimeDelta buffered_time); + + // Notifies the buffering state that all the frames for this stream have been + // buffered, i.e. the end of stream has been reached. + void NotifyEos(); + + // Notifies the buffering state the underlying buffer has reached + // its maximum capacity. + // The maximum frame timestamp in the buffer is given by |buffered_time|. + // Note: this timestamp can be different from the one provided through + // SetBufferedTime since SetBufferedTime takes the timestamp of a playable + // frame which is not necessarily the case here (e.g. missing key id). + void NotifyMaxCapacity(base::TimeDelta buffered_time); + + // Buffering state as a human readable string, for debugging. + std::string ToString() const; + + private: + friend class base::RefCountedThreadSafe<BufferingState>; + virtual ~BufferingState(); + + // Returns the state solely based on the buffered time. + State GetBufferLevelState() const; + + // Updates the state to |new_state|. + void UpdateState(State new_state); + + scoped_refptr<BufferingConfig> const config_; + + // Callback invoked each time there is a change of state. + base::Closure state_changed_cb_; + + // Callback invoked to adjust the high buffer level. + HighLevelBufferCB high_level_buffer_cb_; + + // State. + State state_; + + // Playback media time. + // Equal to kNoTimestamp() when not known. + base::TimeDelta media_time_; + + // Maximum rendering media time. + // This corresponds to the timestamp of the last frame sent to the hardware + // decoder/renderer. + base::TimeDelta max_rendering_time_; + + // Buffered media time. + // Equal to kNoTimestamp() when not known. + base::TimeDelta buffered_time_; + + DISALLOW_COPY_AND_ASSIGN(BufferingState); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_BUFFERING_STATE_H_ diff --git a/chromium/chromecast/media/cma/base/cma_logging.h b/chromium/chromecast/media/cma/base/cma_logging.h new file mode 100644 index 00000000000..1743cea310e --- /dev/null +++ b/chromium/chromecast/media/cma/base/cma_logging.h @@ -0,0 +1,23 @@ +// 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_CMA_LOGGING_H_ +#define CHROMECAST_MEDIA_CMA_BASE_CMA_LOGGING_H_ + +#include "base/logging.h" + +namespace chromecast { +namespace media { + +#define CMALOG(loglevel) VLOG(loglevel) + +enum { + kLogControl = 2, + kLogFrame = 3 +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_CMA_LOGGING_H_ diff --git a/chromium/chromecast/media/cma/base/coded_frame_provider.cc b/chromium/chromecast/media/cma/base/coded_frame_provider.cc new file mode 100644 index 00000000000..56e461cfd8d --- /dev/null +++ b/chromium/chromecast/media/cma/base/coded_frame_provider.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/base/coded_frame_provider.h" + +namespace chromecast { +namespace media { + +CodedFrameProvider::CodedFrameProvider() { +} + +CodedFrameProvider::~CodedFrameProvider() { +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/base/coded_frame_provider.h b/chromium/chromecast/media/cma/base/coded_frame_provider.h new file mode 100644 index 00000000000..b13231d8e59 --- /dev/null +++ b/chromium/chromecast/media/cma/base/coded_frame_provider.h @@ -0,0 +1,51 @@ +// 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_CODED_FRAME_PROVIDER_H_ +#define CHROMECAST_MEDIA_CMA_BASE_CODED_FRAME_PROVIDER_H_ + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" + +namespace media { +class AudioDecoderConfig; +class VideoDecoderConfig; +} + +namespace chromecast { +namespace media { +class DecoderBufferBase; + +class CodedFrameProvider { + public: + typedef base::Callback<void(const scoped_refptr<DecoderBufferBase>&, + const ::media::AudioDecoderConfig&, + const ::media::VideoDecoderConfig&)> ReadCB; + + CodedFrameProvider(); + virtual ~CodedFrameProvider(); + + // Request a coded frame which is provided asynchronously through callback + // |read_cb|. + // If the frame is associated with a new video/audio configuration, + // these configurations are returned as part of the |read_cb| callback. + // Invoking the |read_cb| callback with invalid audio/video configurations + // means the configurations have not changed. + virtual void Read(const ReadCB& read_cb) = 0; + + // Flush the coded frames held by the frame provider. + // Invoke callback |flush_cb| when completed. + // Note: any pending read is cancelled, meaning that any pending |read_cb| + // callback will not be invoked. + virtual void Flush(const base::Closure& flush_cb) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(CodedFrameProvider); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_CODED_FRAME_PROVIDER_H_ diff --git a/chromium/chromecast/media/cma/base/decoder_buffer_adapter.cc b/chromium/chromecast/media/cma/base/decoder_buffer_adapter.cc new file mode 100644 index 00000000000..236505b9950 --- /dev/null +++ b/chromium/chromecast/media/cma/base/decoder_buffer_adapter.cc @@ -0,0 +1,45 @@ +// 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/decoder_buffer_adapter.h" + +#include "media/base/decoder_buffer.h" + +namespace chromecast { +namespace media { + +DecoderBufferAdapter::DecoderBufferAdapter( + const scoped_refptr< ::media::DecoderBuffer>& buffer) + : buffer_(buffer) { +} + +DecoderBufferAdapter::~DecoderBufferAdapter() { +} + +base::TimeDelta DecoderBufferAdapter::timestamp() const { + return buffer_->timestamp(); +} + +const uint8* DecoderBufferAdapter::data() const { + return buffer_->data(); +} + +uint8* DecoderBufferAdapter::writable_data() const { + return buffer_->writable_data(); +} + +int DecoderBufferAdapter::data_size() const { + return buffer_->data_size(); +} + +const ::media::DecryptConfig* DecoderBufferAdapter::decrypt_config() const { + return buffer_->decrypt_config(); +} + +bool DecoderBufferAdapter::end_of_stream() const { + return buffer_->end_of_stream(); +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/base/decoder_buffer_adapter.h b/chromium/chromecast/media/cma/base/decoder_buffer_adapter.h new file mode 100644 index 00000000000..1967fbebd67 --- /dev/null +++ b/chromium/chromecast/media/cma/base/decoder_buffer_adapter.h @@ -0,0 +1,45 @@ +// 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_DECODER_BUFFER_ADAPTER_H_ +#define CHROMECAST_MEDIA_CMA_BASE_DECODER_BUFFER_ADAPTER_H_ + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "chromecast/media/cma/base/decoder_buffer_base.h" + +namespace media { +class DecoderBuffer; +} + +namespace chromecast { +namespace media { + +// DecoderBufferAdapter wraps a ::media::DecoderBuffer +// into a DecoderBufferBase. +class DecoderBufferAdapter : public DecoderBufferBase { + public: + explicit DecoderBufferAdapter( + const scoped_refptr< ::media::DecoderBuffer>& buffer); + + // DecoderBufferBase implementation. + virtual base::TimeDelta timestamp() const override; + virtual const uint8* data() const override; + virtual uint8* writable_data() const override; + virtual int data_size() const override; + virtual const ::media::DecryptConfig* decrypt_config() const override; + virtual bool end_of_stream() const override; + + private: + virtual ~DecoderBufferAdapter(); + + scoped_refptr< ::media::DecoderBuffer> const buffer_; + + DISALLOW_COPY_AND_ASSIGN(DecoderBufferAdapter); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_DECODER_BUFFER_ADAPTER_H_ diff --git a/chromium/chromecast/media/cma/base/decoder_buffer_base.cc b/chromium/chromecast/media/cma/base/decoder_buffer_base.cc new file mode 100644 index 00000000000..40c0a7d0066 --- /dev/null +++ b/chromium/chromecast/media/cma/base/decoder_buffer_base.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/base/decoder_buffer_base.h" + +namespace chromecast { +namespace media { + +DecoderBufferBase::DecoderBufferBase() { +} + +DecoderBufferBase::~DecoderBufferBase() { +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/base/decoder_buffer_base.h b/chromium/chromecast/media/cma/base/decoder_buffer_base.h new file mode 100644 index 00000000000..9143104f14a --- /dev/null +++ b/chromium/chromecast/media/cma/base/decoder_buffer_base.h @@ -0,0 +1,57 @@ +// 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_DECODER_BUFFER_BASE_H_ +#define CHROMECAST_MEDIA_CMA_BASE_DECODER_BUFFER_BASE_H_ + +#include "base/basictypes.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/time/time.h" + +namespace media { +class DecryptConfig; +} + +namespace chromecast { +namespace media { + +// DecoderBufferBase exposes only the properties of an audio/video buffer. +// The way a DecoderBufferBase is created and organized in memory +// is left as a detail of the implementation of derived classes. +class DecoderBufferBase + : public base::RefCountedThreadSafe<DecoderBufferBase> { + public: + DecoderBufferBase(); + + // Returns the PTS of the frame. + virtual base::TimeDelta timestamp() const = 0; + + // Gets the frame data. + virtual const uint8* data() const = 0; + virtual uint8* writable_data() const = 0; + + // Returns the size of the frame in bytes. + virtual int data_size() const = 0; + + // Returns the decrypt configuration. + // Returns NULL if the buffer has no decrypt info. + virtual const ::media::DecryptConfig* decrypt_config() const = 0; + + // Indicate if this is a special frame that indicates the end of the stream. + // If true, functions to access the frame content cannot be called. + virtual bool end_of_stream() const = 0; + + protected: + friend class base::RefCountedThreadSafe<DecoderBufferBase>; + virtual ~DecoderBufferBase(); + + private: + DISALLOW_COPY_AND_ASSIGN(DecoderBufferBase); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_DECODER_BUFFER_BASE_H_ diff --git a/chromium/chromecast/media/cma/base/media_task_runner.cc b/chromium/chromecast/media/cma/base/media_task_runner.cc new file mode 100644 index 00000000000..5b5423bbdf1 --- /dev/null +++ b/chromium/chromecast/media/cma/base/media_task_runner.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/base/media_task_runner.h" + +namespace chromecast { +namespace media { + +MediaTaskRunner::MediaTaskRunner() { +} + +MediaTaskRunner::~MediaTaskRunner() { +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/base/media_task_runner.h b/chromium/chromecast/media/cma/base/media_task_runner.h new file mode 100644 index 00000000000..c4a3012c941 --- /dev/null +++ b/chromium/chromecast/media/cma/base/media_task_runner.h @@ -0,0 +1,44 @@ +// 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_MEDIA_TASK_RUNNER_H_ +#define CHROMECAST_MEDIA_CMA_BASE_MEDIA_TASK_RUNNER_H_ + +#include "base/callback.h" +#include "base/location.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/time/time.h" + +namespace chromecast { +namespace media { + +class MediaTaskRunner + : public base::RefCountedThreadSafe<MediaTaskRunner> { + public: + MediaTaskRunner(); + + // Post a task with the given media |timestamp|. If |timestamp| is equal to + // |kNoTimestamp()|, the task is scheduled right away. + // How the media timestamp is used to schedule the task is an implementation + // detail of derived classes. + // Returns true if the task may be run at some point in the future, and false + // if the task definitely will not be run. + virtual bool PostMediaTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta timestamp) = 0; + + protected: + virtual ~MediaTaskRunner(); + friend class base::RefCountedThreadSafe<MediaTaskRunner>; + + private: + DISALLOW_COPY_AND_ASSIGN(MediaTaskRunner); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_MEDIA_TASK_RUNNER_H_ diff --git a/chromium/chromecast/media/cma/filters/demuxer_stream_adapter.cc b/chromium/chromecast/media/cma/filters/demuxer_stream_adapter.cc new file mode 100644 index 00000000000..db0d7b70ab2 --- /dev/null +++ b/chromium/chromecast/media/cma/filters/demuxer_stream_adapter.cc @@ -0,0 +1,207 @@ +// 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/demuxer_stream_adapter.h" + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/single_thread_task_runner.h" +#include "chromecast/media/cma/base/balanced_media_task_runner_factory.h" +#include "chromecast/media/cma/base/cma_logging.h" +#include "chromecast/media/cma/base/decoder_buffer_adapter.h" +#include "chromecast/media/cma/base/media_task_runner.h" +#include "media/base/bind_to_current_loop.h" +#include "media/base/buffers.h" +#include "media/base/decoder_buffer.h" +#include "media/base/demuxer_stream.h" + +namespace chromecast { +namespace media { + +namespace { + +class DummyMediaTaskRunner : public MediaTaskRunner { + public: + DummyMediaTaskRunner( + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner); + + // MediaTaskRunner implementation. + virtual bool PostMediaTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta timestamp) override; + + private: + virtual ~DummyMediaTaskRunner(); + + scoped_refptr<base::SingleThreadTaskRunner> const task_runner_; + + DISALLOW_COPY_AND_ASSIGN(DummyMediaTaskRunner); +}; + +DummyMediaTaskRunner::DummyMediaTaskRunner( + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner) + : task_runner_(task_runner) { +} + +DummyMediaTaskRunner::~DummyMediaTaskRunner() { +} + +bool DummyMediaTaskRunner::PostMediaTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta timestamp) { + return task_runner_->PostTask(from_here, task); +} + +} // namespace + +DemuxerStreamAdapter::DemuxerStreamAdapter( + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, + const scoped_refptr<BalancedMediaTaskRunnerFactory>& + media_task_runner_factory, + ::media::DemuxerStream* demuxer_stream) + : task_runner_(task_runner), + media_task_runner_factory_(media_task_runner_factory), + media_task_runner_(new DummyMediaTaskRunner(task_runner)), + demuxer_stream_(demuxer_stream), + is_pending_read_(false), + is_pending_demuxer_read_(false), + weak_factory_(this) { + weak_this_ = weak_factory_.GetWeakPtr(); + ResetMediaTaskRunner(); + thread_checker_.DetachFromThread(); +} + +DemuxerStreamAdapter::~DemuxerStreamAdapter() { + // Needed since we use weak pointers: + // weak pointers must be invalidated on the same thread. + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void DemuxerStreamAdapter::Read(const ReadCB& read_cb) { + DCHECK(thread_checker_.CalledOnValidThread()); + + DCHECK(flush_cb_.is_null()); + + // Support only one read at a time. + DCHECK(!is_pending_read_); + is_pending_read_ = true; + ReadInternal(read_cb); +} + +void DemuxerStreamAdapter::ReadInternal(const ReadCB& read_cb) { + bool may_run_in_future = media_task_runner_->PostMediaTask( + FROM_HERE, + base::Bind(&DemuxerStreamAdapter::RequestBuffer, weak_this_, read_cb), + max_pts_); + DCHECK(may_run_in_future); +} + +void DemuxerStreamAdapter::Flush(const base::Closure& flush_cb) { + DCHECK(thread_checker_.CalledOnValidThread()); + CMALOG(kLogControl) << __FUNCTION__; + + // Flush cancels any pending read. + is_pending_read_ = false; + + // Reset the decoder configurations. + audio_config_ = ::media::AudioDecoderConfig(); + video_config_ = ::media::VideoDecoderConfig(); + + // Create a new media task runner for the upcoming media timeline. + ResetMediaTaskRunner(); + + DCHECK(flush_cb_.is_null()); + if (is_pending_demuxer_read_) { + // If there is a pending demuxer read, the implicit contract + // is that the pending read must be completed before invoking the + // flush callback. + flush_cb_ = flush_cb; + return; + } + + // At this point, there is no more pending demuxer read, + // so all the previous tasks associated with the current timeline + // can be cancelled. + weak_factory_.InvalidateWeakPtrs(); + weak_this_ = weak_factory_.GetWeakPtr(); + + CMALOG(kLogControl) << "Flush done"; + flush_cb.Run(); +} + +void DemuxerStreamAdapter::ResetMediaTaskRunner() { + DCHECK(thread_checker_.CalledOnValidThread()); + + max_pts_ = ::media::kNoTimestamp(); + if (media_task_runner_factory_.get()) { + media_task_runner_ = + media_task_runner_factory_->CreateMediaTaskRunner(task_runner_); + } +} + +void DemuxerStreamAdapter::RequestBuffer(const ReadCB& read_cb) { + DCHECK(thread_checker_.CalledOnValidThread()); + is_pending_demuxer_read_ = true; + demuxer_stream_->Read(::media::BindToCurrentLoop( + base::Bind(&DemuxerStreamAdapter::OnNewBuffer, weak_this_, read_cb))); +} + +void DemuxerStreamAdapter::OnNewBuffer( + const ReadCB& read_cb, + ::media::DemuxerStream::Status status, + const scoped_refptr< ::media::DecoderBuffer>& input) { + DCHECK(thread_checker_.CalledOnValidThread()); + + is_pending_demuxer_read_ = false; + + // Just discard the buffer in the flush stage. + if (!flush_cb_.is_null()) { + CMALOG(kLogControl) << "Flush done"; + base::ResetAndReturn(&flush_cb_).Run(); + return; + } + + if (status == ::media::DemuxerStream::kAborted) { + DCHECK(input.get() == NULL); + return; + } + + if (status == ::media::DemuxerStream::kConfigChanged) { + DCHECK(input.get() == NULL); + if (demuxer_stream_->type() == ::media::DemuxerStream::VIDEO) + video_config_ = demuxer_stream_->video_decoder_config(); + if (demuxer_stream_->type() == ::media::DemuxerStream::AUDIO) + audio_config_ = demuxer_stream_->audio_decoder_config(); + + // Got a new config, but we still need to get a frame. + ReadInternal(read_cb); + return; + } + + DCHECK_EQ(status, ::media::DemuxerStream::kOk); + + // Updates the timestamp used for task scheduling. + if (!input->end_of_stream() && + input->timestamp() != ::media::kNoTimestamp() && + (max_pts_ == ::media::kNoTimestamp() || input->timestamp() > max_pts_)) { + max_pts_ = input->timestamp(); + } + + // Provides the buffer as well as possibly valid audio and video configs. + is_pending_read_ = false; + scoped_refptr<DecoderBufferBase> buffer(new DecoderBufferAdapter(input)); + read_cb.Run(buffer, audio_config_, video_config_); + + // Back to the default audio/video config: + // an invalid audio/video config means there is no config update. + if (audio_config_.IsValidConfig()) + audio_config_ = ::media::AudioDecoderConfig(); + if (video_config_.IsValidConfig()) + video_config_ = ::media::VideoDecoderConfig(); +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/filters/demuxer_stream_adapter.h b/chromium/chromecast/media/cma/filters/demuxer_stream_adapter.h new file mode 100644 index 00000000000..7d8c8b33702 --- /dev/null +++ b/chromium/chromecast/media/cma/filters/demuxer_stream_adapter.h @@ -0,0 +1,93 @@ +// 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_DEMUXER_STREAM_ADAPTER_H_ +#define CHROMECAST_MEDIA_CMA_FILTERS_DEMUXER_STREAM_ADAPTER_H_ + +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" +#include "base/time/time.h" +#include "chromecast/media/cma/base/coded_frame_provider.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/demuxer_stream.h" +#include "media/base/video_decoder_config.h" + +namespace base { +class SingleThreadTaskRunner; +} + +namespace media { +class DemuxerStream; +} + +namespace chromecast { +namespace media { +class BalancedMediaTaskRunnerFactory; +class MediaTaskRunner; + +// DemuxerStreamAdapter wraps a DemuxerStream into a CodedFrameProvider. +class DemuxerStreamAdapter : public CodedFrameProvider { + public: + DemuxerStreamAdapter( + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, + const scoped_refptr<BalancedMediaTaskRunnerFactory>& + media_task_runner_factory, + ::media::DemuxerStream* demuxer_stream); + virtual ~DemuxerStreamAdapter(); + + // CodedFrameProvider implementation. + virtual void Read(const ReadCB& read_cb) override; + virtual void Flush(const base::Closure& flush_cb) override; + + private: + void ResetMediaTaskRunner(); + + void ReadInternal(const ReadCB& read_cb); + void RequestBuffer(const ReadCB& read_cb); + + // Callback invoked from the demuxer stream to signal a buffer is ready. + void OnNewBuffer(const ReadCB& read_cb, + ::media::DemuxerStream::Status status, + const scoped_refptr< ::media::DecoderBuffer>& input); + + base::ThreadChecker thread_checker_; + + // Task runner DemuxerStreamAdapter is running on. + scoped_refptr<base::SingleThreadTaskRunner> const task_runner_; + + // Media task runner to pace requests to the DemuxerStream. + scoped_refptr<BalancedMediaTaskRunnerFactory> const + media_task_runner_factory_; + scoped_refptr<MediaTaskRunner> media_task_runner_; + base::TimeDelta max_pts_; + + // Frames are provided by |demuxer_stream_|. + ::media::DemuxerStream* const demuxer_stream_; + + // Indicate if there is a pending read. + bool is_pending_read_; + + // Indicate if |demuxer_stream_| has a pending read. + bool is_pending_demuxer_read_; + + // In case of a pending flush operation, this is the callback + // that is invoked when flush is completed. + base::Closure flush_cb_; + + // Audio/video configuration that applies to the next frame. + ::media::AudioDecoderConfig audio_config_; + ::media::VideoDecoderConfig video_config_; + + base::WeakPtr<DemuxerStreamAdapter> weak_this_; + base::WeakPtrFactory<DemuxerStreamAdapter> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(DemuxerStreamAdapter); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_FILTERS_DEMUXER_STREAM_ADAPTER_H_ diff --git a/chromium/chromecast/media/cma/filters/demuxer_stream_adapter_unittest.cc b/chromium/chromecast/media/cma/filters/demuxer_stream_adapter_unittest.cc new file mode 100644 index 00000000000..bee63eb4d45 --- /dev/null +++ b/chromium/chromecast/media/cma/filters/demuxer_stream_adapter_unittest.cc @@ -0,0 +1,345 @@ +// 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 <list> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread.h" +#include "base/time/time.h" +#include "chromecast/media/cma/base/balanced_media_task_runner_factory.h" +#include "chromecast/media/cma/base/decoder_buffer_base.h" +#include "chromecast/media/cma/filters/demuxer_stream_adapter.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/decoder_buffer.h" +#include "media/base/demuxer_stream.h" +#include "media/base/video_decoder_config.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromecast { +namespace media { + +namespace { + +class DummyDemuxerStream : public ::media::DemuxerStream { + public: + // Creates a demuxer stream which provides frames either with a delay + // or instantly. The scheduling pattern is the following: + // - provides |delayed_frame_count| frames with a delay, + // - then provides the following |cycle_count| - |delayed_frame_count| + // instantly, + // - then provides |delayed_frame_count| frames with a delay, + // - ... and so on. + // Special cases: + // - all frames are delayed: |delayed_frame_count| = |cycle_count| + // - all frames are provided instantly: |delayed_frame_count| = 0 + // |config_idx| is a list of frame index before which there is + // a change of decoder configuration. + DummyDemuxerStream(int cycle_count, + int delayed_frame_count, + const std::list<int>& config_idx); + 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; + + bool has_pending_read() const { + return has_pending_read_; + } + + private: + void DoRead(const ReadCB& read_cb); + + // Demuxer configuration. + const int cycle_count_; + const int delayed_frame_count_; + std::list<int> config_idx_; + + // Number of frames sent so far. + int frame_count_; + + bool has_pending_read_; + + DISALLOW_COPY_AND_ASSIGN(DummyDemuxerStream); +}; + +DummyDemuxerStream::DummyDemuxerStream( + int cycle_count, + int delayed_frame_count, + const std::list<int>& config_idx) + : cycle_count_(cycle_count), + delayed_frame_count_(delayed_frame_count), + config_idx_(config_idx), + frame_count_(0), + has_pending_read_(false) { + DCHECK_LE(delayed_frame_count, cycle_count); +} + +DummyDemuxerStream::~DummyDemuxerStream() { +} + +void DummyDemuxerStream::Read(const ReadCB& read_cb) { + has_pending_read_ = true; + if (!config_idx_.empty() && config_idx_.front() == frame_count_) { + config_idx_.pop_front(); + has_pending_read_ = false; + read_cb.Run(kConfigChanged, + scoped_refptr< ::media::DecoderBuffer>()); + return; + } + + if ((frame_count_ % cycle_count_) < delayed_frame_count_) { + base::MessageLoopProxy::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&DummyDemuxerStream::DoRead, base::Unretained(this), + read_cb), + base::TimeDelta::FromMilliseconds(20)); + return; + } + DoRead(read_cb); +} + +::media::AudioDecoderConfig DummyDemuxerStream::audio_decoder_config() { + LOG(FATAL) << "DummyDemuxerStream is a video DemuxerStream"; + return ::media::AudioDecoderConfig(); +} + +::media::VideoDecoderConfig DummyDemuxerStream::video_decoder_config() { + gfx::Size coded_size(640, 480); + gfx::Rect visible_rect(640, 480); + gfx::Size natural_size(640, 480); + return ::media::VideoDecoderConfig( + ::media::kCodecH264, + ::media::VIDEO_CODEC_PROFILE_UNKNOWN, + ::media::VideoFrame::YV12, + coded_size, + visible_rect, + natural_size, + NULL, 0, + false); +} + +::media::DemuxerStream::Type DummyDemuxerStream::type() { + return VIDEO; +} + +bool DummyDemuxerStream::SupportsConfigChanges() { + return true; +} + +::media::VideoRotation DummyDemuxerStream::video_rotation() { + return ::media::VIDEO_ROTATION_0; +} + +void DummyDemuxerStream::DoRead(const ReadCB& read_cb) { + has_pending_read_ = false; + scoped_refptr< ::media::DecoderBuffer> buffer( + new ::media::DecoderBuffer(16)); + buffer->set_timestamp(frame_count_ * base::TimeDelta::FromMilliseconds(40)); + frame_count_++; + read_cb.Run(kOk, buffer); +} + +} // namespace + +class DemuxerStreamAdapterTest : public testing::Test { + public: + DemuxerStreamAdapterTest(); + virtual ~DemuxerStreamAdapterTest(); + + void Initialize(::media::DemuxerStream* demuxer_stream); + void Start(); + + protected: + void OnTestTimeout(); + void OnNewFrame(const scoped_refptr<DecoderBufferBase>& buffer, + const ::media::AudioDecoderConfig& audio_config, + const ::media::VideoDecoderConfig& video_config); + void OnFlushCompleted(); + + // Total number of frames to request. + int total_frames_; + + // Number of demuxer read before issuing an early flush. + int early_flush_idx_; + bool use_post_task_for_flush_; + + // Number of expected read frames. + int total_expected_frames_; + + // Number of frames actually read so far. + int frame_received_count_; + + // List of expected frame indices with decoder config changes. + std::list<int> config_idx_; + + scoped_ptr<DummyDemuxerStream> demuxer_stream_; + + scoped_ptr<CodedFrameProvider> coded_frame_provider_; + + DISALLOW_COPY_AND_ASSIGN(DemuxerStreamAdapterTest); +}; + +DemuxerStreamAdapterTest::DemuxerStreamAdapterTest() + : use_post_task_for_flush_(false) { +} + +DemuxerStreamAdapterTest::~DemuxerStreamAdapterTest() { +} + +void DemuxerStreamAdapterTest::Initialize( + ::media::DemuxerStream* demuxer_stream) { + coded_frame_provider_.reset( + new DemuxerStreamAdapter( + base::MessageLoopProxy::current(), + scoped_refptr<BalancedMediaTaskRunnerFactory>(), + demuxer_stream)); +} + +void DemuxerStreamAdapterTest::Start() { + frame_received_count_ = 0; + + // TODO(damienv): currently, test assertions which fail do not trigger the + // exit of the unit test, the message loop is still running. Find a different + // way to exit the unit test. + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&DemuxerStreamAdapterTest::OnTestTimeout, + base::Unretained(this)), + base::TimeDelta::FromSeconds(5)); + + coded_frame_provider_->Read( + base::Bind(&DemuxerStreamAdapterTest::OnNewFrame, + base::Unretained(this))); +} + +void DemuxerStreamAdapterTest::OnTestTimeout() { + ADD_FAILURE() << "Test timed out"; + if (base::MessageLoop::current()) + base::MessageLoop::current()->QuitWhenIdle(); +} + +void DemuxerStreamAdapterTest::OnNewFrame( + const scoped_refptr<DecoderBufferBase>& buffer, + const ::media::AudioDecoderConfig& audio_config, + const ::media::VideoDecoderConfig& video_config) { + if (video_config.IsValidConfig()) { + ASSERT_GT(config_idx_.size(), 0); + ASSERT_EQ(frame_received_count_, config_idx_.front()); + config_idx_.pop_front(); + } + + ASSERT_TRUE(buffer.get() != NULL); + ASSERT_EQ(buffer->timestamp(), + frame_received_count_ * base::TimeDelta::FromMilliseconds(40)); + frame_received_count_++; + + if (frame_received_count_ >= total_frames_) { + coded_frame_provider_->Flush( + base::Bind(&DemuxerStreamAdapterTest::OnFlushCompleted, + base::Unretained(this))); + return; + } + + coded_frame_provider_->Read( + base::Bind(&DemuxerStreamAdapterTest::OnNewFrame, + base::Unretained(this))); + + ASSERT_LE(frame_received_count_, early_flush_idx_); + if (frame_received_count_ == early_flush_idx_) { + base::Closure flush_cb = + base::Bind(&DemuxerStreamAdapterTest::OnFlushCompleted, + base::Unretained(this)); + if (use_post_task_for_flush_) { + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&CodedFrameProvider::Flush, + base::Unretained(coded_frame_provider_.get()), + flush_cb)); + } else { + coded_frame_provider_->Flush(flush_cb); + } + return; + } +} + +void DemuxerStreamAdapterTest::OnFlushCompleted() { + ASSERT_EQ(frame_received_count_, total_expected_frames_); + ASSERT_FALSE(demuxer_stream_->has_pending_read()); + base::MessageLoop::current()->QuitWhenIdle(); +} + +TEST_F(DemuxerStreamAdapterTest, NoDelay) { + total_frames_ = 10; + early_flush_idx_ = total_frames_; // No early flush. + total_expected_frames_ = 10; + config_idx_.push_back(0); + config_idx_.push_back(5); + + int cycle_count = 1; + int delayed_frame_count = 0; + demuxer_stream_.reset( + new DummyDemuxerStream( + cycle_count, delayed_frame_count, config_idx_)); + + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + Initialize(demuxer_stream_.get()); + message_loop->PostTask( + FROM_HERE, + base::Bind(&DemuxerStreamAdapterTest::Start, base::Unretained(this))); + message_loop->Run(); +} + +TEST_F(DemuxerStreamAdapterTest, AllDelayed) { + total_frames_ = 10; + early_flush_idx_ = total_frames_; // No early flush. + total_expected_frames_ = 10; + config_idx_.push_back(0); + config_idx_.push_back(5); + + int cycle_count = 1; + int delayed_frame_count = 1; + demuxer_stream_.reset( + new DummyDemuxerStream( + cycle_count, delayed_frame_count, config_idx_)); + + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + Initialize(demuxer_stream_.get()); + message_loop->PostTask( + FROM_HERE, + base::Bind(&DemuxerStreamAdapterTest::Start, base::Unretained(this))); + message_loop->Run(); +} + +TEST_F(DemuxerStreamAdapterTest, AllDelayedEarlyFlush) { + total_frames_ = 10; + early_flush_idx_ = 5; + use_post_task_for_flush_ = true; + total_expected_frames_ = 5; + config_idx_.push_back(0); + config_idx_.push_back(3); + + int cycle_count = 1; + int delayed_frame_count = 1; + demuxer_stream_.reset( + new DummyDemuxerStream( + cycle_count, delayed_frame_count, config_idx_)); + + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + Initialize(demuxer_stream_.get()); + message_loop->PostTask( + FROM_HERE, + base::Bind(&DemuxerStreamAdapterTest::Start, base::Unretained(this))); + message_loop->Run(); +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/ipc/media_memory_chunk.cc b/chromium/chromecast/media/cma/ipc/media_memory_chunk.cc new file mode 100644 index 00000000000..0d6896958b9 --- /dev/null +++ b/chromium/chromecast/media/cma/ipc/media_memory_chunk.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/ipc/media_memory_chunk.h" + +namespace chromecast { +namespace media { + +MediaMemoryChunk::~MediaMemoryChunk() { +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/ipc/media_memory_chunk.h b/chromium/chromecast/media/cma/ipc/media_memory_chunk.h new file mode 100644 index 00000000000..1b91c840a9d --- /dev/null +++ b/chromium/chromecast/media/cma/ipc/media_memory_chunk.h @@ -0,0 +1,39 @@ +// 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_IPC_MEDIA_MEMORY_CHUNK_H_ +#define CHROMECAST_MEDIA_CMA_IPC_MEDIA_MEMORY_CHUNK_H_ + +#include "base/basictypes.h" + +namespace chromecast { +namespace media { + +// MediaMemoryChunk represents a block of memory without doing any assumption +// about the type of memory (e.g. shared memory) nor about the underlying +// memory ownership. +// The block of memory can be invalidated under the cover (e.g. if the derived +// class does not own the underlying memory), +// in that case, MediaMemoryChunk::valid() will return false. +class MediaMemoryChunk { + public: + virtual ~MediaMemoryChunk(); + + // Returns the start of the block of memory. + virtual void* data() const = 0; + + // Returns the size of the block of memory. + virtual size_t size() const = 0; + + // Returns whether the underlying block of memory is valid. + // Since MediaMemoryChunk does not specify a memory ownership model, + // the underlying block of memory might be invalidated by a third party. + // In that case, valid() will return false. + virtual bool valid() const = 0; +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_IPC_MEDIA_MEMORY_CHUNK_H_ diff --git a/chromium/chromecast/media/cma/ipc/media_message.cc b/chromium/chromecast/media/cma/ipc/media_message.cc new file mode 100644 index 00000000000..8dfcd7cb885 --- /dev/null +++ b/chromium/chromecast/media/cma/ipc/media_message.cc @@ -0,0 +1,198 @@ +// 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/ipc/media_message.h" + +#include <limits> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "chromecast/media/cma/ipc/media_memory_chunk.h" + +namespace chromecast { +namespace media { + +// static +scoped_ptr<MediaMessage> MediaMessage::CreateDummyMessage( + uint32 type) { + return scoped_ptr<MediaMessage>( + new MediaMessage(type, std::numeric_limits<size_t>::max())); +} + +// static +scoped_ptr<MediaMessage> MediaMessage::CreateMessage( + uint32 type, + const MemoryAllocatorCB& memory_allocator, + size_t msg_content_capacity) { + size_t msg_size = minimum_msg_size() + msg_content_capacity; + + // Make the message size a multiple of the alignment + // so that if we have proper alignment for array of messages. + size_t end_alignment = msg_size % ALIGNOF(SerializedMsg); + if (end_alignment != 0) + msg_size += ALIGNOF(SerializedMsg) - end_alignment; + + scoped_ptr<MediaMemoryChunk> memory(memory_allocator.Run(msg_size)); + if (!memory) + return scoped_ptr<MediaMessage>(); + + return scoped_ptr<MediaMessage>(new MediaMessage(type, memory.Pass())); +} + +// static +scoped_ptr<MediaMessage> MediaMessage::CreateMessage( + uint32 type, + scoped_ptr<MediaMemoryChunk> memory) { + return scoped_ptr<MediaMessage>(new MediaMessage(type, memory.Pass())); +} + +// static +scoped_ptr<MediaMessage> MediaMessage::MapMessage( + scoped_ptr<MediaMemoryChunk> memory) { + return scoped_ptr<MediaMessage>(new MediaMessage(memory.Pass())); +} + +MediaMessage::MediaMessage(uint32 type, size_t msg_size) + : is_dummy_msg_(true), + cached_header_(&cached_msg_.header), + msg_(&cached_msg_), + msg_read_only_(&cached_msg_), + rd_offset_(0) { + cached_header_->size = msg_size; + cached_header_->type = type; + cached_header_->content_size = 0; +} + +MediaMessage::MediaMessage(uint32 type, scoped_ptr<MediaMemoryChunk> memory) + : is_dummy_msg_(false), + cached_header_(&cached_msg_.header), + msg_(static_cast<SerializedMsg*>(memory->data())), + msg_read_only_(msg_), + mem_(memory.Pass()), + rd_offset_(0) { + CHECK(mem_->valid()); + CHECK_GE(mem_->size(), minimum_msg_size()); + + // Check memory alignment: + // needed to cast properly |msg_dst| to a SerializedMsg. + CHECK_EQ( + reinterpret_cast<uintptr_t>(mem_->data()) % ALIGNOF(SerializedMsg), 0u); + + // Make sure that |mem_->data()| + |mem_->size()| is also aligned correctly. + // This is needed if we append a second serialized message next to this one. + // The second serialized message must be aligned correctly. + // It is similar to what a compiler is doing for arrays of structures. + CHECK_EQ(mem_->size() % ALIGNOF(SerializedMsg), 0u); + + cached_header_->size = mem_->size(); + cached_header_->type = type; + cached_header_->content_size = 0; + msg_->header = *cached_header_; +} + +MediaMessage::MediaMessage(scoped_ptr<MediaMemoryChunk> memory) + : is_dummy_msg_(false), + cached_header_(&cached_msg_.header), + msg_(NULL), + msg_read_only_(static_cast<SerializedMsg*>(memory->data())), + mem_(memory.Pass()), + rd_offset_(0) { + CHECK(mem_->valid()); + + // Check memory alignment. + CHECK_EQ( + reinterpret_cast<uintptr_t>(mem_->data()) % ALIGNOF(SerializedMsg), 0u); + + // Cache the message header which cannot be modified while reading. + CHECK_GE(mem_->size(), minimum_msg_size()); + *cached_header_ = msg_read_only_->header; + CHECK_GE(cached_header_->size, minimum_msg_size()); + + // Make sure if we have 2 consecutive serialized messages in memory, + // the 2nd message is also aligned correctly. + CHECK_EQ(cached_header_->size % ALIGNOF(SerializedMsg), 0u); + + size_t max_content_size = cached_header_->size - minimum_msg_size(); + CHECK_LE(cached_header_->content_size, max_content_size); +} + +MediaMessage::~MediaMessage() { +} + +bool MediaMessage::IsSerializedMsgAvailable() const { + return !is_dummy_msg_ && mem_->valid(); +} + +bool MediaMessage::WriteBuffer(const void* src, size_t size) { + // No message to write into. + if (!msg_) + return false; + + // The underlying memory was invalidated. + if (!is_dummy_msg_ && !mem_->valid()) + return false; + + size_t max_content_size = cached_header_->size - minimum_msg_size(); + if (cached_header_->content_size + size > max_content_size) { + cached_header_->content_size = max_content_size; + msg_->header.content_size = cached_header_->content_size; + return false; + } + + // Write the message only for non-dummy messages. + if (!is_dummy_msg_) { + uint8* wr_ptr = &msg_->content + cached_header_->content_size; + memcpy(wr_ptr, src, size); + } + + cached_header_->content_size += size; + msg_->header.content_size = cached_header_->content_size; + return true; +} + +bool MediaMessage::ReadBuffer(void* dst, size_t size) { + // No read possible for a dummy message. + if (is_dummy_msg_) + return false; + + // The underlying memory was invalidated. + if (!mem_->valid()) + return false; + + if (rd_offset_ + size > cached_header_->content_size) { + rd_offset_ = cached_header_->content_size; + return false; + } + + const uint8* rd_ptr = &msg_read_only_->content + rd_offset_; + memcpy(dst, rd_ptr, size); + rd_offset_ += size; + return true; +} + +void* MediaMessage::GetWritableBuffer(size_t size) { + // No read possible for a dummy message. + if (is_dummy_msg_) + return NULL; + + // The underlying memory was invalidated. + if (!mem_->valid()) + return NULL; + + if (rd_offset_ + size > cached_header_->content_size) { + rd_offset_ = cached_header_->content_size; + return NULL; + } + + uint8* rd_ptr = &msg_read_only_->content + rd_offset_; + rd_offset_ += size; + return rd_ptr; +} + +const void* MediaMessage::GetBuffer(size_t size) { + return GetWritableBuffer(size); +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/ipc/media_message.h b/chromium/chromecast/media/cma/ipc/media_message.h new file mode 100644 index 00000000000..066409be3d8 --- /dev/null +++ b/chromium/chromecast/media/cma/ipc/media_message.h @@ -0,0 +1,165 @@ +// 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_IPC_MEDIA_MESSAGE_H_ +#define CHROMECAST_MEDIA_CMA_IPC_MEDIA_MESSAGE_H_ + +#include <stddef.h> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" + +namespace chromecast { +namespace media { +class MediaMemoryChunk; + +// MediaMessage - +// Represents a media message, including: +// - a message header that gives for example the message size or its type, +// - the content of the message, +// - and some possible padding if the content does not occupy the whole +// reserved space. +// +class MediaMessage { + public: + // Memory allocator: given a number of bytes to allocate, + // return the pointer to the allocated block if successful + // or NULL if allocation failed. + typedef base::Callback<scoped_ptr<MediaMemoryChunk>(size_t)> + MemoryAllocatorCB; + + // Creates a message with no associated memory for its content, i.e. + // each write on this message is a dummy operation. + // This type of message can be useful to calculate first the size of the + // message, before allocating the real message. + static scoped_ptr<MediaMessage> CreateDummyMessage(uint32 type); + + // Creates a message with a capacity of at least |msg_content_capacity| + // bytes. The actual content size can be smaller than its capacity. + // The message can be populated with some Write functions. + static scoped_ptr<MediaMessage> CreateMessage( + uint32 type, + const MemoryAllocatorCB& memory_allocator, + size_t msg_content_capacity); + + // Creates a message of type |type| whose serialized structure is stored + // in |mem|. + static scoped_ptr<MediaMessage> CreateMessage( + uint32 type, + scoped_ptr<MediaMemoryChunk> mem); + + // Creates a message from a memory area which already contains + // the serialized structure of the message. + // Only Read functions can be invoked on this type of message. + static scoped_ptr<MediaMessage> MapMessage( + scoped_ptr<MediaMemoryChunk> mem); + + // Return the minimum size of a message. + static size_t minimum_msg_size() { + return offsetof(SerializedMsg, content); + } + + ~MediaMessage(); + + // Indicate whether the underlying serialized structure of the message is + // available. + // Note: the serialized structure might be unavailable in case of a dummy + // message or if the underlying memory has been invalidated. + bool IsSerializedMsgAvailable() const; + + // Return the message and the total size of the message + // incuding the header, the content and the possible padding. + const void* msg() const { return msg_read_only_; } + size_t size() const { return cached_msg_.header.size; } + + // Return the size of the message without padding. + size_t actual_size() const { + return minimum_msg_size() + cached_msg_.header.content_size; + } + + // Return the size of the content of the message. + size_t content_size() const { return cached_msg_.header.content_size; } + + // Return the type of the message. + uint32 type() const { return cached_msg_.header.type; } + + // Append a POD to the message. + // Return true if the POD has been succesfully written. + template<typename T> bool WritePod(T* const& pod); + template<typename T> bool WritePod(const T& pod) { + return WriteBuffer(&pod, sizeof(T)); + } + + // Append a raw buffer to the message. + bool WriteBuffer(const void* src, size_t size); + + // Read a POD from the message. + template<typename T> bool ReadPod(T* pod) { + return ReadBuffer(pod, sizeof(T)); + } + + // Read |size| bytes from the message from the last read position + // and write it to |dst|. + bool ReadBuffer(void* dst, size_t size); + + // Return a pointer to a buffer of size |size|. + // Return NULL if not successful. + const void* GetBuffer(size_t size); + void* GetWritableBuffer(size_t size); + + private: + MediaMessage(uint32 type, size_t msg_size); + MediaMessage(uint32 type, scoped_ptr<MediaMemoryChunk> memory); + MediaMessage(scoped_ptr<MediaMemoryChunk> memory); + + struct Header { + // Total size of the message (including both header & content). + uint32 size; + // Indicate the message type. + uint32 type; + // Actual size of the content in the message. + uint32 content_size; + }; + + struct SerializedMsg { + // Message header. + Header header; + + // Start of the content of the message. + // Use uint8_t since no special alignment is needed. + uint8 content; + }; + + // Indicate whether the message is a dummy message, i.e. a message without + // a complete underlying serialized structure: only the message header is + // available. + bool is_dummy_msg_; + + // |cached_msg_| is used for 2 purposes: + // - to create a dummy message + // - for security purpose: cache the msg header to avoid browser security + // issues. + SerializedMsg cached_msg_; + Header* const cached_header_; + + SerializedMsg* msg_; + SerializedMsg* msg_read_only_; + + // Memory allocated to store the underlying serialized structure into memory. + // Note: a dummy message has no underlying serialized structure: + // |mem_| is a null pointer in that case. + scoped_ptr<MediaMemoryChunk> mem_; + + // Read iterator into the message. + size_t rd_offset_; + + DISALLOW_COPY_AND_ASSIGN(MediaMessage); +}; + +} // namespace media +} // namespace chromecast + +#endif diff --git a/chromium/chromecast/media/cma/ipc/media_message_fifo.cc b/chromium/chromecast/media/cma/ipc/media_message_fifo.cc new file mode 100644 index 00000000000..9f8ad001dc8 --- /dev/null +++ b/chromium/chromecast/media/cma/ipc/media_message_fifo.cc @@ -0,0 +1,403 @@ +// 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/ipc/media_message_fifo.h" + +#include "base/atomicops.h" +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/message_loop/message_loop_proxy.h" +#include "chromecast/media/cma/base/cma_logging.h" +#include "chromecast/media/cma/ipc/media_memory_chunk.h" +#include "chromecast/media/cma/ipc/media_message.h" +#include "chromecast/media/cma/ipc/media_message_type.h" + +namespace chromecast { +namespace media { + +class MediaMessageFlag + : public base::RefCountedThreadSafe<MediaMessageFlag> { + public: + // |offset| is the offset in the fifo of the media message. + explicit MediaMessageFlag(size_t offset); + + bool IsValid() const; + + void Invalidate(); + + size_t offset() const { return offset_; } + + private: + friend class base::RefCountedThreadSafe<MediaMessageFlag>; + virtual ~MediaMessageFlag(); + + const size_t offset_; + bool flag_; + + DISALLOW_COPY_AND_ASSIGN(MediaMessageFlag); +}; + +MediaMessageFlag::MediaMessageFlag(size_t offset) + : offset_(offset), + flag_(true) { +} + +MediaMessageFlag::~MediaMessageFlag() { +} + +bool MediaMessageFlag::IsValid() const { + return flag_; +} + +void MediaMessageFlag::Invalidate() { + flag_ = false; +} + +class FifoOwnedMemory : public MediaMemoryChunk { + public: + FifoOwnedMemory(void* data, size_t size, + const scoped_refptr<MediaMessageFlag>& flag, + const base::Closure& release_msg_cb); + virtual ~FifoOwnedMemory(); + + // MediaMemoryChunk implementation. + virtual void* data() const override { return data_; } + virtual size_t size() const override { return size_; } + virtual bool valid() const override { return flag_->IsValid(); } + + private: + scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + base::Closure release_msg_cb_; + + void* const data_; + const size_t size_; + scoped_refptr<MediaMessageFlag> flag_; + + DISALLOW_COPY_AND_ASSIGN(FifoOwnedMemory); +}; + +FifoOwnedMemory::FifoOwnedMemory( + void* data, size_t size, + const scoped_refptr<MediaMessageFlag>& flag, + const base::Closure& release_msg_cb) + : task_runner_(base::MessageLoopProxy::current()), + release_msg_cb_(release_msg_cb), + data_(data), + size_(size), + flag_(flag) { +} + +FifoOwnedMemory::~FifoOwnedMemory() { + // Release the flag before notifying that the message has been released. + flag_ = scoped_refptr<MediaMessageFlag>(); + if (!release_msg_cb_.is_null()) { + if (task_runner_->BelongsToCurrentThread()) { + release_msg_cb_.Run(); + } else { + task_runner_->PostTask(FROM_HERE, release_msg_cb_); + } + } +} + +MediaMessageFifo::MediaMessageFifo( + scoped_ptr<MediaMemoryChunk> mem, bool init) + : mem_(mem.Pass()), + weak_factory_(this) { + CHECK_EQ(reinterpret_cast<uintptr_t>(mem_->data()) % ALIGNOF(Descriptor), + 0u); + CHECK_GE(mem_->size(), sizeof(Descriptor)); + Descriptor* desc = static_cast<Descriptor*>(mem_->data()); + base_ = static_cast<void*>(&desc->first_item); + + // TODO(damienv): remove cast when atomic size_t is defined in Chrome. + // Currently, the sign differs. + rd_offset_ = reinterpret_cast<AtomicSize*>(&(desc->rd_offset)); + wr_offset_ = reinterpret_cast<AtomicSize*>(&(desc->wr_offset)); + + size_t max_size = mem_->size() - + (static_cast<char*>(base_) - static_cast<char*>(mem_->data())); + if (init) { + size_ = max_size; + desc->size = size_; + internal_rd_offset_ = 0; + internal_wr_offset_ = 0; + base::subtle::Release_Store(rd_offset_, 0); + base::subtle::Release_Store(wr_offset_, 0); + } else { + size_ = desc->size; + CHECK_LE(size_, max_size); + internal_rd_offset_ = current_rd_offset(); + internal_wr_offset_ = current_wr_offset(); + } + CMALOG(kLogControl) + << "MediaMessageFifo:" << " init=" << init << " size=" << size_; + CHECK_GT(size_, 0) << size_; + + weak_this_ = weak_factory_.GetWeakPtr(); + thread_checker_.DetachFromThread(); +} + +MediaMessageFifo::~MediaMessageFifo() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void MediaMessageFifo::ObserveReadActivity( + const base::Closure& read_event_cb) { + read_event_cb_ = read_event_cb; +} + +void MediaMessageFifo::ObserveWriteActivity( + const base::Closure& write_event_cb) { + write_event_cb_ = write_event_cb; +} + +scoped_ptr<MediaMemoryChunk> MediaMessageFifo::ReserveMemory( + size_t size_to_reserve) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Capture first both the read and write offsets. + // and exit right away if not enough free space. + size_t wr_offset = internal_wr_offset(); + size_t rd_offset = current_rd_offset(); + size_t allocated_size = (size_ + wr_offset - rd_offset) % size_; + size_t free_size = size_ - 1 - allocated_size; + if (free_size < size_to_reserve) + return scoped_ptr<MediaMemoryChunk>(); + CHECK_LE(MediaMessage::minimum_msg_size(), size_to_reserve); + + // Note: in the next 2 conditions, we have: + // trailing_byte_count < size_to_reserve + // and since at this stage: size_to_reserve <= free_size + // we also have trailing_byte_count <= free_size + // which means that all the trailing bytes are free space in the fifo. + size_t trailing_byte_count = size_ - wr_offset; + if (trailing_byte_count < MediaMessage::minimum_msg_size()) { + // If there is no space to even write the smallest message, + // skip the trailing bytes and come back to the beginning of the fifo. + // (no way to insert a padding message). + if (free_size < trailing_byte_count) + return scoped_ptr<MediaMemoryChunk>(); + wr_offset = 0; + CommitInternalWrite(wr_offset); + + } else if (trailing_byte_count < size_to_reserve) { + // At this point, we know we have at least the space to write a message. + // However, to avoid splitting a message, a padding message is needed. + scoped_ptr<MediaMemoryChunk> mem( + ReserveMemoryNoCheck(trailing_byte_count)); + scoped_ptr<MediaMessage> padding_message( + MediaMessage::CreateMessage(PaddingMediaMsg, mem.Pass())); + } + + // Recalculate the free size and exit if not enough free space. + wr_offset = internal_wr_offset(); + allocated_size = (size_ + wr_offset - rd_offset) % size_; + free_size = size_ - 1 - allocated_size; + if (free_size < size_to_reserve) + return scoped_ptr<MediaMemoryChunk>(); + + return ReserveMemoryNoCheck(size_to_reserve); +} + +scoped_ptr<MediaMessage> MediaMessageFifo::Pop() { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Capture the read and write offsets. + size_t rd_offset = internal_rd_offset(); + size_t wr_offset = current_wr_offset(); + size_t allocated_size = (size_ + wr_offset - rd_offset) % size_; + + if (allocated_size < MediaMessage::minimum_msg_size()) + return scoped_ptr<MediaMessage>(); + + size_t trailing_byte_count = size_ - rd_offset; + if (trailing_byte_count < MediaMessage::minimum_msg_size()) { + // If there is no space to even have the smallest message, + // skip the trailing bytes and come back to the beginning of the fifo. + // Note: all the trailing bytes correspond to allocated bytes since: + // trailing_byte_count < MediaMessage::minimum_msg_size() <= allocated_size + rd_offset = 0; + allocated_size -= trailing_byte_count; + trailing_byte_count = size_; + CommitInternalRead(rd_offset); + } + + // The message should not be longer than the allocated size + // but since a message is a contiguous area of memory, it should also be + // smaller than |trailing_byte_count|. + size_t max_msg_size = std::min(allocated_size, trailing_byte_count); + if (max_msg_size < MediaMessage::minimum_msg_size()) + return scoped_ptr<MediaMessage>(); + void* msg_src = static_cast<uint8*>(base_) + rd_offset; + + // Create a flag to protect the serialized structure of the message + // from being overwritten. + // The serialized structure starts at offset |rd_offset|. + scoped_refptr<MediaMessageFlag> rd_flag(new MediaMessageFlag(rd_offset)); + rd_flags_.push_back(rd_flag); + scoped_ptr<MediaMemoryChunk> mem( + new FifoOwnedMemory( + msg_src, max_msg_size, rd_flag, + base::Bind(&MediaMessageFifo::OnRdMemoryReleased, weak_this_))); + + // Create the message which wraps its the serialized structure. + scoped_ptr<MediaMessage> message(MediaMessage::MapMessage(mem.Pass())); + CHECK(message); + + // Update the internal read pointer. + rd_offset = (rd_offset + message->size()) % size_; + CommitInternalRead(rd_offset); + + return message.Pass(); +} + +void MediaMessageFifo::Flush() { + DCHECK(thread_checker_.CalledOnValidThread()); + + size_t wr_offset = current_wr_offset(); + + // Invalidate every memory region before flushing. + while (!rd_flags_.empty()) { + CMALOG(kLogControl) << "Invalidate flag"; + rd_flags_.front()->Invalidate(); + rd_flags_.pop_front(); + } + + // Flush by setting the read pointer to the value of the write pointer. + // Update first the internal read pointer then the public one. + CommitInternalRead(wr_offset); + CommitRead(wr_offset); +} + +scoped_ptr<MediaMemoryChunk> MediaMessageFifo::ReserveMemoryNoCheck( + size_t size_to_reserve) { + size_t wr_offset = internal_wr_offset(); + + // Memory block corresponding to the serialized structure of the message. + void* msg_start = static_cast<uint8*>(base_) + wr_offset; + scoped_refptr<MediaMessageFlag> wr_flag(new MediaMessageFlag(wr_offset)); + wr_flags_.push_back(wr_flag); + scoped_ptr<MediaMemoryChunk> mem( + new FifoOwnedMemory( + msg_start, size_to_reserve, wr_flag, + base::Bind(&MediaMessageFifo::OnWrMemoryReleased, weak_this_))); + + // Update the internal write pointer. + wr_offset = (wr_offset + size_to_reserve) % size_; + CommitInternalWrite(wr_offset); + + return mem.Pass(); +} + +void MediaMessageFifo::OnWrMemoryReleased() { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (wr_flags_.empty()) { + // Sanity check: when there is no protected memory area, + // the external write offset has no reason to be different from + // the internal write offset. + DCHECK_EQ(current_wr_offset(), internal_wr_offset()); + return; + } + + // Update the external write offset. + while (!wr_flags_.empty() && + (!wr_flags_.front()->IsValid() || wr_flags_.front()->HasOneRef())) { + // TODO(damienv): Could add a sanity check to make sure the offset is + // between the external write offset and the read offset (not included). + wr_flags_.pop_front(); + } + + // Update the read offset to the first locked memory area + // or to the internal read pointer if nothing prevents it. + size_t external_wr_offset = internal_wr_offset(); + if (!wr_flags_.empty()) + external_wr_offset = wr_flags_.front()->offset(); + CommitWrite(external_wr_offset); +} + +void MediaMessageFifo::OnRdMemoryReleased() { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (rd_flags_.empty()) { + // Sanity check: when there is no protected memory area, + // the external read offset has no reason to be different from + // the internal read offset. + DCHECK_EQ(current_rd_offset(), internal_rd_offset()); + return; + } + + // Update the external read offset. + while (!rd_flags_.empty() && + (!rd_flags_.front()->IsValid() || rd_flags_.front()->HasOneRef())) { + // TODO(damienv): Could add a sanity check to make sure the offset is + // between the external read offset and the write offset. + rd_flags_.pop_front(); + } + + // Update the read offset to the first locked memory area + // or to the internal read pointer if nothing prevents it. + size_t external_rd_offset = internal_rd_offset(); + if (!rd_flags_.empty()) + external_rd_offset = rd_flags_.front()->offset(); + CommitRead(external_rd_offset); +} + +size_t MediaMessageFifo::current_rd_offset() const { + DCHECK_EQ(sizeof(size_t), sizeof(AtomicSize)); + size_t rd_offset = base::subtle::Acquire_Load(rd_offset_); + CHECK_LT(rd_offset, size_); + return rd_offset; +} + +size_t MediaMessageFifo::current_wr_offset() const { + DCHECK_EQ(sizeof(size_t), sizeof(AtomicSize)); + + // When the fifo consumer acquires the write offset, + // we have to make sure that any possible following reads are actually + // returning results at least inline with the memory snapshot taken + // when the write offset was sampled. + // That's why an Acquire_Load is used here. + size_t wr_offset = base::subtle::Acquire_Load(wr_offset_); + CHECK_LT(wr_offset, size_); + return wr_offset; +} + +void MediaMessageFifo::CommitRead(size_t new_rd_offset) { + // Add a memory fence to ensure the message content is completely read + // before updating the read offset. + base::subtle::Release_Store(rd_offset_, new_rd_offset); + + // Since rd_offset_ is updated by a release_store above, any thread that + // does acquire_load is guaranteed to see the new rd_offset_ set above. + // So it is safe to send the notification. + if (!read_event_cb_.is_null()) { + read_event_cb_.Run(); + } +} + +void MediaMessageFifo::CommitWrite(size_t new_wr_offset) { + // Add a memory fence to ensure the message content is written + // before updating the write offset. + base::subtle::Release_Store(wr_offset_, new_wr_offset); + + // Since wr_offset_ is updated by a release_store above, any thread that + // does acquire_load is guaranteed to see the new wr_offset_ set above. + // So it is safe to send the notification. + if (!write_event_cb_.is_null()) { + write_event_cb_.Run(); + } +} + +void MediaMessageFifo::CommitInternalRead(size_t new_rd_offset) { + internal_rd_offset_ = new_rd_offset; +} + +void MediaMessageFifo::CommitInternalWrite(size_t new_wr_offset) { + internal_wr_offset_ = new_wr_offset; +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/ipc/media_message_fifo.h b/chromium/chromecast/media/cma/ipc/media_message_fifo.h new file mode 100644 index 00000000000..dcc60dbd60b --- /dev/null +++ b/chromium/chromecast/media/cma/ipc/media_message_fifo.h @@ -0,0 +1,208 @@ +// 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_IPC_MEDIA_MESSAGE_FIFO_H_ +#define CHROMECAST_MEDIA_CMA_IPC_MEDIA_MESSAGE_FIFO_H_ + +#include <list> + +#include "base/atomicops.h" +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/logging.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" + +namespace chromecast { +namespace media { +class MediaMemoryChunk; +class MediaMessage; +class MediaMessageFlag; + +// MediaMessageFifo is a lock free fifo implementation +// to pass audio/video data from one thread to another or from one process +// to another one (in that case using shared memory). +// +// Assuming the feeder and the consumer have a common block of shared memory +// (representing the serialized structure of the fifo), +// the feeder (which must be running on a single thread) instantiates its own +// instance of MediaMessageFifo, same applies to the consumer. +// +// Example: assuming the block of shared memory is given by |mem|, a typical +// feeder (using MediaMessageFifo instance fifo_feeder) will push messages +// in the following way: +// // Create a dummy message to calculate the size of the serialized message. +// scoped_ptr<MediaMessage> dummy_msg( +// MediaMessage::CreateDummyMessage(msg_type)); +// // ... +// // Write all the fields to the dummy message. +// // ... +// +// // Create the real message, once the size of the serialized message +// // is known. +// scoped_ptr<MediaMessage> msg( +// MediaMessage::CreateMessage( +// msg_type, +// base::Bind(&MediaMessageFifo::ReserveMemory, +// base::Unretained(fifo_feeder.get())), +// dummy_msg->content_size())); +// if (!msg) { +// // Not enough space for the message: +// // retry later (e.g. when receiving a read activity event, meaning +// // some enough space might have been release). +// return; +// } +// // ... +// // Write all the fields to the real message +// // in exactly the same way it was done for the dummy message. +// // ... +// // Once message |msg| is going out of scope, then MediaMessageFifo +// // fifo_feeder is informed that the message is not needed anymore +// // (i.e. it was fully written), and fifo_feeder can then update +// // the external write pointer of the fifo so that the consumer +// // can start consuming this message. +// +// A typical consumer (using MediaMessageFifo instance fifo_consumer) +// will retrive messages in the following way: +// scoped_ptr<MediaMessage> msg(fifo_consumer->Pop()); +// if (!msg) { +// // The fifo is empty, i.e. no message left. +// // Try reading again later (e.g. after receiving a write activity event. +// return; +// } +// // Parse the message using Read functions of MediaMessage: +// // ... +// // Once the message is going out of scope, MediaMessageFifo will receive +// // a notification that the underlying memory can be released +// // (i.e. the external read pointer can be updated). +// +// +class MediaMessageFifo { + public: + // Creates a media message fifo using |mem| as the underlying serialized + // structure. + // If |init| is true, the underlying fifo structure is initialized. + MediaMessageFifo(scoped_ptr<MediaMemoryChunk> mem, bool init); + ~MediaMessageFifo(); + + // When the consumer and the feeder are living in two different processes, + // we might want to convey some messages between these two processes to notify + // about some fifo activity. + void ObserveReadActivity(const base::Closure& read_event_cb); + void ObserveWriteActivity(const base::Closure& write_event_cb); + + // Reserves a writeable block of memory at the back of the fifo, + // corresponding to the serialized structure of the message. + // Returns NULL if the required size cannot be allocated. + scoped_ptr<MediaMemoryChunk> ReserveMemory(size_t size); + + // Pop a message from the queue. + // Returns a null pointer if there is no message left. + scoped_ptr<MediaMessage> Pop(); + + // Flush the fifo. + void Flush(); + + private: + struct Descriptor { + size_t size; + size_t rd_offset; + size_t wr_offset; + + // Ensure the first item has the same alignment as an int64. + int64 first_item; + }; + + // Add some accessors to ensure security on the browser process side. + size_t current_rd_offset() const; + size_t current_wr_offset() const; + size_t internal_rd_offset() const { + DCHECK_LT(internal_rd_offset_, size_); + return internal_rd_offset_; + } + size_t internal_wr_offset() const { + DCHECK_LT(internal_wr_offset_, size_); + return internal_wr_offset_; + } + + // Reserve a block of free memory without doing any check on the available + // space. Invoke this function only when all the checks have been done. + scoped_ptr<MediaMemoryChunk> ReserveMemoryNoCheck(size_t size); + + // Invoked each time there is a memory region in the free space of the fifo + // that has possibly been written. + void OnWrMemoryReleased(); + + // Invoked each time there is a memory region in the allocated space + // of the fifo that has possibly been released. + void OnRdMemoryReleased(); + + // Functions to modify the internal/external read/write pointers. + void CommitRead(size_t new_rd_offset); + void CommitWrite(size_t new_wr_offset); + void CommitInternalRead(size_t new_rd_offset); + void CommitInternalWrite(size_t new_wr_offset); + + // An instance of MediaMessageFifo must be running on a single thread. + // If the fifo feeder and consumer are living on 2 different threads + // or 2 different processes, they must create their own instance + // of MediaMessageFifo using the same underlying block of (shared) memory + // in the constructor. + base::ThreadChecker thread_checker_; + + // Callbacks invoked to notify either of some read or write activity on the + // fifo. This is especially useful when the feeder and consumer are living in + // two different processes. + base::Closure read_event_cb_; + base::Closure write_event_cb_; + + // The serialized structure of the fifo. + scoped_ptr<MediaMemoryChunk> mem_; + + // The size in bytes of the fifo is cached locally for security purpose. + // (the renderer process cannot modify the size and make the browser process + // access out of range addresses). + size_t size_; + + // TODO(damienv): This is a work-around since atomicops.h does not define + // an atomic size_t type. +#if SIZE_MAX == UINT32_MAX + typedef base::subtle::Atomic32 AtomicSize; +#elif SIZE_MAX == UINT64_MAX + typedef base::subtle::Atomic64 AtomicSize; +#elif +#error "Unsupported size_t" +#endif + AtomicSize* rd_offset_; + AtomicSize* wr_offset_; + + // Internal read offset: this is where data is actually read from. + // The external offset |rd_offset_| is only used to protect data from being + // overwritten by the feeder. + // At any time, the internal read pointer must be between the external read + // offset and the write offset (circular fifo definition of "between"). + size_t internal_rd_offset_; + size_t internal_wr_offset_; + + // Note: all the memory read/write are followed by a memory fence before + // updating the rd/wr pointer. + void* base_; + + // Protects the messages that are either being read or written. + std::list<scoped_refptr<MediaMessageFlag> > rd_flags_; + std::list<scoped_refptr<MediaMessageFlag> > wr_flags_; + + base::WeakPtr<MediaMessageFifo> weak_this_; + base::WeakPtrFactory<MediaMessageFifo> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(MediaMessageFifo); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_IPC_MEDIA_MESSAGE_FIFO_H_ diff --git a/chromium/chromecast/media/cma/ipc/media_message_fifo_unittest.cc b/chromium/chromecast/media/cma/ipc/media_message_fifo_unittest.cc new file mode 100644 index 00000000000..bc04b70a0cc --- /dev/null +++ b/chromium/chromecast/media/cma/ipc/media_message_fifo_unittest.cc @@ -0,0 +1,195 @@ +// 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 "base/basictypes.h" +#include "base/bind.h" +#include "base/memory/scoped_ptr.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "chromecast/media/cma/ipc/media_memory_chunk.h" +#include "chromecast/media/cma/ipc/media_message.h" +#include "chromecast/media/cma/ipc/media_message_fifo.h" +#include "chromecast/media/cma/ipc/media_message_type.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromecast { +namespace media { + +namespace { + +class FifoMemoryChunk : public MediaMemoryChunk { + public: + FifoMemoryChunk(void* mem, size_t size) + : mem_(mem), size_(size) {} + virtual ~FifoMemoryChunk() {} + + virtual void* data() const override { return mem_; } + virtual size_t size() const override { return size_; } + virtual bool valid() const override { return true; } + + private: + void* mem_; + size_t size_; + + DISALLOW_COPY_AND_ASSIGN(FifoMemoryChunk); +}; + +void MsgProducer(scoped_ptr<MediaMessageFifo> fifo, + int msg_count, + base::WaitableEvent* event) { + + for (int k = 0; k < msg_count; k++) { + uint32 msg_type = 0x2 + (k % 5); + uint32 max_msg_content_size = k % 64; + do { + scoped_ptr<MediaMessage> msg1( + MediaMessage::CreateMessage( + msg_type, + base::Bind(&MediaMessageFifo::ReserveMemory, + base::Unretained(fifo.get())), + max_msg_content_size)); + if (msg1) + break; + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10)); + } while(true); + } + + fifo.reset(); + + event->Signal(); +} + +void MsgConsumer(scoped_ptr<MediaMessageFifo> fifo, + int msg_count, + base::WaitableEvent* event) { + + int k = 0; + while (k < msg_count) { + uint32 msg_type = 0x2 + (k % 5); + do { + scoped_ptr<MediaMessage> msg2(fifo->Pop()); + if (msg2) { + if (msg2->type() != PaddingMediaMsg) { + EXPECT_EQ(msg2->type(), msg_type); + k++; + } + break; + } + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10)); + } while(true); + } + + fifo.reset(); + + event->Signal(); +} + +void MsgProducerConsumer( + scoped_ptr<MediaMessageFifo> producer_fifo, + scoped_ptr<MediaMessageFifo> consumer_fifo, + base::WaitableEvent* event) { + for (int k = 0; k < 2048; k++) { + // Should have enough space to create a message. + uint32 msg_type = 0x2 + (k % 5); + uint32 max_msg_content_size = k % 64; + scoped_ptr<MediaMessage> msg1( + MediaMessage::CreateMessage( + msg_type, + base::Bind(&MediaMessageFifo::ReserveMemory, + base::Unretained(producer_fifo.get())), + max_msg_content_size)); + EXPECT_TRUE(msg1); + + // Make sure the message is commited. + msg1.reset(); + + // At this point, we should have a message to read. + scoped_ptr<MediaMessage> msg2(consumer_fifo->Pop()); + EXPECT_TRUE(msg2); + } + + producer_fifo.reset(); + consumer_fifo.reset(); + + event->Signal(); +} + +} // namespace + +TEST(MediaMessageFifoTest, AlternateWriteRead) { + size_t buffer_size = 64 * 1024; + scoped_ptr<uint64[]> buffer(new uint64[buffer_size / sizeof(uint64)]); + + scoped_ptr<base::Thread> thread( + new base::Thread("FeederConsumerThread")); + thread->Start(); + + scoped_ptr<MediaMessageFifo> producer_fifo(new MediaMessageFifo( + scoped_ptr<MediaMemoryChunk>( + new FifoMemoryChunk(&buffer[0], buffer_size)), + true)); + scoped_ptr<MediaMessageFifo> consumer_fifo(new MediaMessageFifo( + scoped_ptr<MediaMemoryChunk>( + new FifoMemoryChunk(&buffer[0], buffer_size)), + false)); + + base::WaitableEvent event(false, false); + thread->message_loop_proxy()->PostTask( + FROM_HERE, + base::Bind(&MsgProducerConsumer, + base::Passed(&producer_fifo), + base::Passed(&consumer_fifo), + &event)); + event.Wait(); + + thread.reset(); +} + +TEST(MediaMessageFifoTest, MultiThreaded) { + size_t buffer_size = 64 * 1024; + scoped_ptr<uint64[]> buffer(new uint64[buffer_size / sizeof(uint64)]); + + scoped_ptr<base::Thread> producer_thread( + new base::Thread("FeederThread")); + scoped_ptr<base::Thread> consumer_thread( + new base::Thread("ConsumerThread")); + producer_thread->Start(); + consumer_thread->Start(); + + scoped_ptr<MediaMessageFifo> producer_fifo(new MediaMessageFifo( + scoped_ptr<MediaMemoryChunk>( + new FifoMemoryChunk(&buffer[0], buffer_size)), + true)); + scoped_ptr<MediaMessageFifo> consumer_fifo(new MediaMessageFifo( + scoped_ptr<MediaMemoryChunk>( + new FifoMemoryChunk(&buffer[0], buffer_size)), + false)); + + base::WaitableEvent producer_event_done(false, false); + base::WaitableEvent consumer_event_done(false, false); + + const int msg_count = 2048; + producer_thread->message_loop_proxy()->PostTask( + FROM_HERE, + base::Bind(&MsgProducer, + base::Passed(&producer_fifo), + msg_count, + &producer_event_done)); + consumer_thread->message_loop_proxy()->PostTask( + FROM_HERE, + base::Bind(&MsgConsumer, + base::Passed(&consumer_fifo), + msg_count, + &consumer_event_done)); + + producer_event_done.Wait(); + consumer_event_done.Wait(); + + producer_thread.reset(); + consumer_thread.reset(); +} + +} // namespace media +} // namespace chromecast + diff --git a/chromium/chromecast/media/cma/ipc/media_message_type.h b/chromium/chromecast/media/cma/ipc/media_message_type.h new file mode 100644 index 00000000000..23ff8474d32 --- /dev/null +++ b/chromium/chromecast/media/cma/ipc/media_message_type.h @@ -0,0 +1,15 @@ +// 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_IPC_MEDIA_MESSAGE_TYPE_H_ +#define CHROMECAST_MEDIA_CMA_IPC_MEDIA_MESSAGE_TYPE_H_ + +enum MediaMessageType { + PaddingMediaMsg = 1, + FrameMediaMsg, + AudioConfigMediaMsg, + VideoConfigMediaMsg, +}; + +#endif diff --git a/chromium/chromecast/media/cma/ipc/media_message_unittest.cc b/chromium/chromecast/media/cma/ipc/media_message_unittest.cc new file mode 100644 index 00000000000..6f34f3eaf89 --- /dev/null +++ b/chromium/chromecast/media/cma/ipc/media_message_unittest.cc @@ -0,0 +1,146 @@ +// 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 "base/basictypes.h" +#include "base/bind.h" +#include "base/memory/scoped_ptr.h" +#include "chromecast/media/cma/ipc/media_memory_chunk.h" +#include "chromecast/media/cma/ipc/media_message.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromecast { +namespace media { + +namespace { + +class ExternalMemoryBlock + : public MediaMemoryChunk { + public: + ExternalMemoryBlock(void* data, size_t size) + : data_(data), size_(size) {} + virtual ~ExternalMemoryBlock() {} + + // MediaMemoryChunk implementation. + virtual void* data() const override { return data_; } + virtual size_t size() const override { return size_; } + virtual bool valid() const override { return true; } + + private: + void* const data_; + const size_t size_; +}; + +scoped_ptr<MediaMemoryChunk> DummyAllocator( + void* data, size_t size, size_t alloc_size) { + CHECK_LE(alloc_size, size); + return scoped_ptr<MediaMemoryChunk>( + new ExternalMemoryBlock(data, alloc_size)); +} + +} + +TEST(MediaMessageTest, WriteRead) { + int buffer_size = 1024; + scoped_ptr<uint8[]> buffer(new uint8[buffer_size]); + MediaMessage::MemoryAllocatorCB mem_alloc_cb( + base::Bind(&DummyAllocator, buffer.get(), buffer_size)); + uint32 type = 0x1; + int msg_content_capacity = 512; + + // Write a message. + int count = 64; + scoped_ptr<MediaMessage> msg1( + MediaMessage::CreateMessage(type, mem_alloc_cb, msg_content_capacity)); + for (int k = 0; k < count; k++) { + int v1 = 2 * k + 1; + EXPECT_TRUE(msg1->WritePod(v1)); + uint8 v2 = k; + EXPECT_TRUE(msg1->WritePod(v2)); + } + EXPECT_EQ(msg1->content_size(), count * (sizeof(int) + sizeof(uint8))); + + // Verify the integrity of the message. + scoped_ptr<MediaMessage> msg2( + MediaMessage::MapMessage(scoped_ptr<MediaMemoryChunk>( + new ExternalMemoryBlock(&buffer[0], buffer_size)))); + for (int k = 0; k < count; k++) { + int v1; + int expected_v1 = 2 * k + 1; + EXPECT_TRUE(msg2->ReadPod(&v1)); + EXPECT_EQ(v1, expected_v1); + uint8 v2; + uint8 expected_v2 = k; + EXPECT_TRUE(msg2->ReadPod(&v2)); + EXPECT_EQ(v2, expected_v2); + } +} + +TEST(MediaMessageTest, WriteOverflow) { + int buffer_size = 1024; + scoped_ptr<uint8[]> buffer(new uint8[buffer_size]); + MediaMessage::MemoryAllocatorCB mem_alloc_cb( + base::Bind(&DummyAllocator, buffer.get(), buffer_size)); + uint32 type = 0x1; + int msg_content_capacity = 8; + + scoped_ptr<MediaMessage> msg1( + MediaMessage::CreateMessage(type, mem_alloc_cb, msg_content_capacity)); + uint32 v1 = 0; + uint8 v2 = 0; + EXPECT_TRUE(msg1->WritePod(v1)); + EXPECT_TRUE(msg1->WritePod(v1)); + + EXPECT_FALSE(msg1->WritePod(v1)); + EXPECT_FALSE(msg1->WritePod(v2)); +} + +TEST(MediaMessageTest, ReadOverflow) { + int buffer_size = 1024; + scoped_ptr<uint8[]> buffer(new uint8[buffer_size]); + MediaMessage::MemoryAllocatorCB mem_alloc_cb( + base::Bind(&DummyAllocator, buffer.get(), buffer_size)); + uint32 type = 0x1; + int msg_content_capacity = 8; + + scoped_ptr<MediaMessage> msg1( + MediaMessage::CreateMessage(type, mem_alloc_cb, msg_content_capacity)); + uint32 v1 = 0xcd; + EXPECT_TRUE(msg1->WritePod(v1)); + EXPECT_TRUE(msg1->WritePod(v1)); + + scoped_ptr<MediaMessage> msg2( + MediaMessage::MapMessage(scoped_ptr<MediaMemoryChunk>( + new ExternalMemoryBlock(&buffer[0], buffer_size)))); + uint32 v2; + EXPECT_TRUE(msg2->ReadPod(&v2)); + EXPECT_EQ(v2, v1); + EXPECT_TRUE(msg2->ReadPod(&v2)); + EXPECT_EQ(v2, v1); + EXPECT_FALSE(msg2->ReadPod(&v2)); +} + +TEST(MediaMessageTest, DummyMessage) { + int buffer_size = 1024; + scoped_ptr<uint8[]> buffer(new uint8[buffer_size]); + MediaMessage::MemoryAllocatorCB mem_alloc_cb( + base::Bind(&DummyAllocator, buffer.get(), buffer_size)); + uint32 type = 0x1; + + // Create first a dummy message to estimate the content size. + scoped_ptr<MediaMessage> msg1( + MediaMessage::CreateDummyMessage(type)); + uint32 v1 = 0xcd; + EXPECT_TRUE(msg1->WritePod(v1)); + EXPECT_TRUE(msg1->WritePod(v1)); + + // Create the real message and write the actual content. + scoped_ptr<MediaMessage> msg2( + MediaMessage::CreateMessage(type, mem_alloc_cb, msg1->content_size())); + EXPECT_TRUE(msg2->WritePod(v1)); + EXPECT_TRUE(msg2->WritePod(v1)); + EXPECT_FALSE(msg2->WritePod(v1)); +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.cc b/chromium/chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.cc new file mode 100644 index 00000000000..d595bdaac3d --- /dev/null +++ b/chromium/chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.cc @@ -0,0 +1,70 @@ +// 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/ipc_streamer/audio_decoder_config_marshaller.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "chromecast/media/cma/ipc/media_message.h" +#include "media/base/audio_decoder_config.h" + +namespace chromecast { +namespace media { + +namespace { +const size_t kMaxExtraDataSize = 16 * 1024; +} + +// static +void AudioDecoderConfigMarshaller::Write( + const ::media::AudioDecoderConfig& config, MediaMessage* msg) { + CHECK(msg->WritePod(config.codec())); + CHECK(msg->WritePod(config.channel_layout())); + CHECK(msg->WritePod(config.samples_per_second())); + CHECK(msg->WritePod(config.sample_format())); + CHECK(msg->WritePod(config.is_encrypted())); + CHECK(msg->WritePod(config.extra_data_size())); + if (config.extra_data_size() > 0) + CHECK(msg->WriteBuffer(config.extra_data(), config.extra_data_size())); +} + +// static +::media::AudioDecoderConfig AudioDecoderConfigMarshaller::Read( + MediaMessage* msg) { + ::media::AudioCodec codec; + ::media::SampleFormat sample_format; + ::media::ChannelLayout channel_layout; + int samples_per_second; + bool is_encrypted; + size_t extra_data_size; + scoped_ptr<uint8[]> extra_data; + + CHECK(msg->ReadPod(&codec)); + CHECK(msg->ReadPod(&channel_layout)); + CHECK(msg->ReadPod(&samples_per_second)); + CHECK(msg->ReadPod(&sample_format)); + CHECK(msg->ReadPod(&is_encrypted)); + CHECK(msg->ReadPod(&extra_data_size)); + + CHECK_GE(codec, ::media::kUnknownAudioCodec); + CHECK_LE(codec, ::media::kAudioCodecMax); + CHECK_GE(channel_layout, ::media::CHANNEL_LAYOUT_NONE); + CHECK_LE(channel_layout, ::media::CHANNEL_LAYOUT_MAX); + CHECK_GE(sample_format, ::media::kUnknownSampleFormat); + CHECK_LE(sample_format, ::media::kSampleFormatMax); + CHECK_LT(extra_data_size, kMaxExtraDataSize); + if (extra_data_size > 0) { + extra_data.reset(new uint8[extra_data_size]); + CHECK(msg->ReadBuffer(extra_data.get(), extra_data_size)); + } + + return ::media::AudioDecoderConfig( + codec, sample_format, + channel_layout, samples_per_second, + extra_data.get(), extra_data_size, + is_encrypted); +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.h b/chromium/chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.h new file mode 100644 index 00000000000..33f960e0c8b --- /dev/null +++ b/chromium/chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.h @@ -0,0 +1,27 @@ +// 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_IPC_STREAMER_AUDIO_DECODER_CONFIG_MARSHALLER_H_ +#define CHROMECAST_MEDIA_CMA_IPC_STREAMER_AUDIO_DECODER_CONFIG_MARSHALLER_H_ + +#include "media/base/audio_decoder_config.h" + +namespace chromecast { +namespace media { +class MediaMessage; + +class AudioDecoderConfigMarshaller { + public: + // Writes the serialized structure of |config| into |msg|. + static void Write( + const ::media::AudioDecoderConfig& config, MediaMessage* msg); + + // Returns an AudioDecoderConfig from its serialized structure. + static ::media::AudioDecoderConfig Read(MediaMessage* msg); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_IPC_STREAMER_AUDIO_DECODER_CONFIG_MARSHALLER_H_ diff --git a/chromium/chromecast/media/cma/ipc_streamer/av_streamer_proxy.cc b/chromium/chromecast/media/cma/ipc_streamer/av_streamer_proxy.cc new file mode 100644 index 00000000000..6bc25edc386 --- /dev/null +++ b/chromium/chromecast/media/cma/ipc_streamer/av_streamer_proxy.cc @@ -0,0 +1,200 @@ +// 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/ipc_streamer/av_streamer_proxy.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/message_loop/message_loop_proxy.h" +#include "chromecast/media/cma/base/coded_frame_provider.h" +#include "chromecast/media/cma/base/decoder_buffer_base.h" +#include "chromecast/media/cma/ipc/media_memory_chunk.h" +#include "chromecast/media/cma/ipc/media_message.h" +#include "chromecast/media/cma/ipc/media_message_fifo.h" +#include "chromecast/media/cma/ipc/media_message_type.h" +#include "chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.h" +#include "chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.h" +#include "chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.h" + +namespace chromecast { +namespace media { + +AvStreamerProxy::AvStreamerProxy() + : is_running_(false), + pending_read_(false), + pending_av_data_(false), + weak_factory_(this) { + weak_this_ = weak_factory_.GetWeakPtr(); + thread_checker_.DetachFromThread(); +} + +AvStreamerProxy::~AvStreamerProxy() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void AvStreamerProxy::SetCodedFrameProvider( + scoped_ptr<CodedFrameProvider> frame_provider) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!frame_provider_); + frame_provider_.reset(frame_provider.release()); +} + +void AvStreamerProxy::SetMediaMessageFifo( + scoped_ptr<MediaMessageFifo> fifo) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!fifo_); + fifo_.reset(fifo.release()); +} + +void AvStreamerProxy::Start() { + DCHECK(!is_running_); + + is_running_ = true; + RequestBufferIfNeeded(); +} + +void AvStreamerProxy::StopAndFlush(const base::Closure& done_cb) { + is_running_ = false; + + pending_av_data_ = false; + pending_audio_config_ = ::media::AudioDecoderConfig(); + pending_video_config_ = ::media::VideoDecoderConfig(); + pending_buffer_ = scoped_refptr<DecoderBufferBase>(); + + pending_read_ = false; + frame_provider_->Flush(done_cb); +} + +void AvStreamerProxy::OnFifoReadEvent() { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Some enough space might have been released + // to accommodate the pending data. + if (pending_av_data_) + ProcessPendingData(); +} + +void AvStreamerProxy::RequestBufferIfNeeded() { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (!is_running_ || pending_read_ || pending_av_data_) + return; + + // |frame_provider_| is assumed to run on the same message loop. + // Add a BindToCurrentLoop if that's not the case in the future. + pending_read_ = true; + frame_provider_->Read(base::Bind(&AvStreamerProxy::OnNewBuffer, weak_this_)); +} + +void AvStreamerProxy::OnNewBuffer( + const scoped_refptr<DecoderBufferBase>& buffer, + const ::media::AudioDecoderConfig& audio_config, + const ::media::VideoDecoderConfig& video_config) { + DCHECK(thread_checker_.CalledOnValidThread()); + + pending_read_ = false; + + if (buffer->end_of_stream()) + is_running_ = false; + + DCHECK(!pending_av_data_); + pending_av_data_ = true; + + pending_buffer_ = buffer; + pending_audio_config_ = audio_config; + pending_video_config_ = video_config; + + ProcessPendingData(); +} + +void AvStreamerProxy::ProcessPendingData() { + if (pending_audio_config_.IsValidConfig()) { + if (!SendAudioDecoderConfig(pending_audio_config_)) + return; + pending_audio_config_ = ::media::AudioDecoderConfig(); + } + + if (pending_video_config_.IsValidConfig()) { + if (!SendVideoDecoderConfig(pending_video_config_)) + return; + pending_video_config_ = ::media::VideoDecoderConfig(); + } + + if (pending_buffer_.get()) { + if (!SendBuffer(pending_buffer_)) + return; + pending_buffer_ = scoped_refptr<DecoderBufferBase>(); + } + + pending_av_data_ = false; + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&AvStreamerProxy::RequestBufferIfNeeded, weak_this_)); +} + +bool AvStreamerProxy::SendAudioDecoderConfig( + const ::media::AudioDecoderConfig& config) { + // Create a dummy message to calculate first the message size. + scoped_ptr<MediaMessage> dummy_msg( + MediaMessage::CreateDummyMessage(AudioConfigMediaMsg)); + AudioDecoderConfigMarshaller::Write(config, dummy_msg.get()); + + // Create the real message and write the actual content. + scoped_ptr<MediaMessage> msg( + MediaMessage::CreateMessage( + AudioConfigMediaMsg, + base::Bind(&MediaMessageFifo::ReserveMemory, + base::Unretained(fifo_.get())), + dummy_msg->content_size())); + if (!msg) + return false; + + AudioDecoderConfigMarshaller::Write(config, msg.get()); + return true; +} + +bool AvStreamerProxy::SendVideoDecoderConfig( + const ::media::VideoDecoderConfig& config) { + // Create a dummy message to calculate first the message size. + scoped_ptr<MediaMessage> dummy_msg( + MediaMessage::CreateDummyMessage(VideoConfigMediaMsg)); + VideoDecoderConfigMarshaller::Write(config, dummy_msg.get()); + + // Create the real message and write the actual content. + scoped_ptr<MediaMessage> msg( + MediaMessage::CreateMessage( + VideoConfigMediaMsg, + base::Bind(&MediaMessageFifo::ReserveMemory, + base::Unretained(fifo_.get())), + dummy_msg->content_size())); + if (!msg) + return false; + + VideoDecoderConfigMarshaller::Write(config, msg.get()); + return true; +} + +bool AvStreamerProxy::SendBuffer( + const scoped_refptr<DecoderBufferBase>& buffer) { + // Create a dummy message to calculate first the message size. + scoped_ptr<MediaMessage> dummy_msg( + MediaMessage::CreateDummyMessage(FrameMediaMsg)); + DecoderBufferBaseMarshaller::Write(buffer, dummy_msg.get()); + + // Create the real message and write the actual content. + scoped_ptr<MediaMessage> msg( + MediaMessage::CreateMessage( + FrameMediaMsg, + base::Bind(&MediaMessageFifo::ReserveMemory, + base::Unretained(fifo_.get())), + dummy_msg->content_size())); + if (!msg) + return false; + + DecoderBufferBaseMarshaller::Write(buffer, msg.get()); + return true; +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/ipc_streamer/av_streamer_proxy.h b/chromium/chromecast/media/cma/ipc_streamer/av_streamer_proxy.h new file mode 100644 index 00000000000..e750c85ae8c --- /dev/null +++ b/chromium/chromecast/media/cma/ipc_streamer/av_streamer_proxy.h @@ -0,0 +1,88 @@ +// 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_IPC_STREAMER_AV_STREAMER_PROXY_H_ +#define CHROMECAST_MEDIA_CMA_IPC_STREAMER_AV_STREAMER_PROXY_H_ + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/video_decoder_config.h" + +namespace chromecast { +namespace media { +class CodedFrameProvider; +class DecoderBufferBase; +class MediaMessageFifo; + +// AvStreamerProxy streams audio/video data from a coded frame provider +// to a media message fifo. +class AvStreamerProxy { + public: + AvStreamerProxy(); + ~AvStreamerProxy(); + + // AvStreamerProxy gets frames and audio/video config + // from the |frame_provider| and feed them into the |fifo|. + void SetCodedFrameProvider(scoped_ptr<CodedFrameProvider> frame_provider); + void SetMediaMessageFifo(scoped_ptr<MediaMessageFifo> fifo); + + // Starts fetching A/V buffers. + void Start(); + + // Stops fetching A/V buffers and flush the pending buffers, + // invoking |flush_cb| when done. + void StopAndFlush(const base::Closure& flush_cb); + + // Event invoked when some data have been read from the fifo. + // This means some data can now possibly be written into the fifo. + void OnFifoReadEvent(); + + private: + void RequestBufferIfNeeded(); + + void OnNewBuffer(const scoped_refptr<DecoderBufferBase>& buffer, + const ::media::AudioDecoderConfig& audio_config, + const ::media::VideoDecoderConfig& video_config); + + void ProcessPendingData(); + + bool SendAudioDecoderConfig(const ::media::AudioDecoderConfig& config); + bool SendVideoDecoderConfig(const ::media::VideoDecoderConfig& config); + bool SendBuffer(const scoped_refptr<DecoderBufferBase>& buffer); + + base::ThreadChecker thread_checker_; + + scoped_ptr<CodedFrameProvider> frame_provider_; + + // Fifo used to convey A/V configs and buffers. + scoped_ptr<MediaMessageFifo> fifo_; + + // State. + bool is_running_; + + // Indicates if there is a pending request to the coded frame provider. + bool pending_read_; + + // Pending config & buffer. + // |pending_av_data_| is set as long as one of the pending audio/video + // config or buffer is valid. + bool pending_av_data_; + ::media::AudioDecoderConfig pending_audio_config_; + ::media::VideoDecoderConfig pending_video_config_; + scoped_refptr<DecoderBufferBase> pending_buffer_; + + base::WeakPtr<AvStreamerProxy> weak_this_; + base::WeakPtrFactory<AvStreamerProxy> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(AvStreamerProxy); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_IPC_STREAMER_AV_STREAMER_PROXY_H_ diff --git a/chromium/chromecast/media/cma/ipc_streamer/av_streamer_unittest.cc b/chromium/chromecast/media/cma/ipc_streamer/av_streamer_unittest.cc new file mode 100644 index 00000000000..54a9e14ba07 --- /dev/null +++ b/chromium/chromecast/media/cma/ipc_streamer/av_streamer_unittest.cc @@ -0,0 +1,253 @@ +// 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 <list> +#include <vector> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread.h" +#include "base/time/time.h" +#include "chromecast/media/cma/base/decoder_buffer_base.h" +#include "chromecast/media/cma/ipc/media_memory_chunk.h" +#include "chromecast/media/cma/ipc/media_message_fifo.h" +#include "chromecast/media/cma/ipc_streamer/av_streamer_proxy.h" +#include "chromecast/media/cma/ipc_streamer/coded_frame_provider_host.h" +#include "chromecast/media/cma/test/frame_generator_for_test.h" +#include "chromecast/media/cma/test/mock_frame_consumer.h" +#include "chromecast/media/cma/test/mock_frame_provider.h" +#include "media/base/audio_decoder_config.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 { + +namespace { + +class FifoMemoryChunk : public MediaMemoryChunk { + public: + FifoMemoryChunk(void* mem, size_t size) + : mem_(mem), size_(size) {} + virtual ~FifoMemoryChunk() {} + + virtual void* data() const override { return mem_; } + virtual size_t size() const override { return size_; } + virtual bool valid() const override { return true; } + + private: + void* mem_; + size_t size_; + + DISALLOW_COPY_AND_ASSIGN(FifoMemoryChunk); +}; + +} // namespace + +class AvStreamerTest : public testing::Test { + public: + AvStreamerTest(); + virtual ~AvStreamerTest(); + + // Setups the test. + void Configure( + size_t frame_count, + const std::vector<bool>& provider_delayed_pattern, + const std::vector<bool>& consumer_delayed_pattern); + + // Starts the test. + void Start(); + + protected: + scoped_ptr<uint64[]> fifo_mem_; + + scoped_ptr<AvStreamerProxy> av_buffer_proxy_; + scoped_ptr<CodedFrameProviderHost> coded_frame_provider_host_; + scoped_ptr<MockFrameConsumer> frame_consumer_; + + private: + void OnTestTimeout(); + void OnTestCompleted(); + + void OnFifoRead(); + void OnFifoWrite(); + + DISALLOW_COPY_AND_ASSIGN(AvStreamerTest); +}; + +AvStreamerTest::AvStreamerTest() { +} + +AvStreamerTest::~AvStreamerTest() { +} + +void AvStreamerTest::Configure( + size_t frame_count, + const std::vector<bool>& provider_delayed_pattern, + const std::vector<bool>& consumer_delayed_pattern) { + // Frame generation on the producer and consumer side. + std::vector<FrameGeneratorForTest::FrameSpec> frame_specs; + frame_specs.resize(frame_count); + 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 = ((k % 3) == 0); + } + frame_specs[frame_specs.size() - 1].is_eos = true; + + scoped_ptr<FrameGeneratorForTest> frame_generator_provider( + new FrameGeneratorForTest(frame_specs)); + scoped_ptr<FrameGeneratorForTest> frame_generator_consumer( + new FrameGeneratorForTest(frame_specs)); + + scoped_ptr<MockFrameProvider> frame_provider(new MockFrameProvider()); + frame_provider->Configure(provider_delayed_pattern, + frame_generator_provider.Pass()); + + size_t fifo_size_div_8 = 512; + fifo_mem_.reset(new uint64[fifo_size_div_8]); + scoped_ptr<MediaMessageFifo> producer_fifo( + new MediaMessageFifo( + scoped_ptr<MediaMemoryChunk>( + new FifoMemoryChunk(&fifo_mem_[0], fifo_size_div_8 * 8)), + true)); + scoped_ptr<MediaMessageFifo> consumer_fifo( + new MediaMessageFifo( + scoped_ptr<MediaMemoryChunk>( + new FifoMemoryChunk(&fifo_mem_[0], fifo_size_div_8 * 8)), + false)); + producer_fifo->ObserveWriteActivity( + base::Bind(&AvStreamerTest::OnFifoWrite, base::Unretained(this))); + consumer_fifo->ObserveReadActivity( + base::Bind(&AvStreamerTest::OnFifoRead, base::Unretained(this))); + + av_buffer_proxy_.reset( + new AvStreamerProxy()); + av_buffer_proxy_->SetCodedFrameProvider( + scoped_ptr<CodedFrameProvider>(frame_provider.release())); + av_buffer_proxy_->SetMediaMessageFifo(producer_fifo.Pass()); + + coded_frame_provider_host_.reset( + new CodedFrameProviderHost(consumer_fifo.Pass())); + + frame_consumer_.reset( + new MockFrameConsumer(coded_frame_provider_host_.get())); + frame_consumer_->Configure( + consumer_delayed_pattern, + false, + frame_generator_consumer.Pass()); +} + +void AvStreamerTest::Start() { + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&AvStreamerProxy::Start, + base::Unretained(av_buffer_proxy_.get()))); + + frame_consumer_->Start( + base::Bind(&AvStreamerTest::OnTestCompleted, + base::Unretained(this))); +} + +void AvStreamerTest::OnTestTimeout() { + ADD_FAILURE() << "Test timed out"; + if (base::MessageLoop::current()) + base::MessageLoop::current()->QuitWhenIdle(); +} + +void AvStreamerTest::OnTestCompleted() { + base::MessageLoop::current()->QuitWhenIdle(); +} + +void AvStreamerTest::OnFifoWrite() { + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&CodedFrameProviderHost::OnFifoWriteEvent, + base::Unretained(coded_frame_provider_host_.get()))); +} + +void AvStreamerTest::OnFifoRead() { + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&AvStreamerProxy::OnFifoReadEvent, + base::Unretained(av_buffer_proxy_.get()))); +} + +TEST_F(AvStreamerTest, FastProviderSlowConsumer) { + bool provider_delayed_pattern[] = { false }; + bool consumer_delayed_pattern[] = { true }; + + const size_t frame_count = 100u; + Configure( + frame_count, + std::vector<bool>( + provider_delayed_pattern, + provider_delayed_pattern + arraysize(provider_delayed_pattern)), + std::vector<bool>( + consumer_delayed_pattern, + consumer_delayed_pattern + arraysize(consumer_delayed_pattern))); + + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + message_loop->PostTask( + FROM_HERE, + base::Bind(&AvStreamerTest::Start, base::Unretained(this))); + message_loop->Run(); +}; + +TEST_F(AvStreamerTest, SlowProviderFastConsumer) { + bool provider_delayed_pattern[] = { true }; + bool consumer_delayed_pattern[] = { false }; + + const size_t frame_count = 100u; + Configure( + frame_count, + std::vector<bool>( + provider_delayed_pattern, + provider_delayed_pattern + arraysize(provider_delayed_pattern)), + std::vector<bool>( + consumer_delayed_pattern, + consumer_delayed_pattern + arraysize(consumer_delayed_pattern))); + + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + message_loop->PostTask( + FROM_HERE, + base::Bind(&AvStreamerTest::Start, base::Unretained(this))); + message_loop->Run(); +}; + +TEST_F(AvStreamerTest, SlowFastProducerConsumer) { + // Pattern lengths are prime between each other + // so that a lot of combinations can be tested. + bool provider_delayed_pattern[] = { + true, true, true, true, true, + false, false, false, false + }; + bool consumer_delayed_pattern[] = { + true, true, true, true, true, true, true, + false, false, false, false, false, false, false + }; + + const size_t frame_count = 100u; + Configure( + frame_count, + std::vector<bool>( + provider_delayed_pattern, + provider_delayed_pattern + arraysize(provider_delayed_pattern)), + std::vector<bool>( + consumer_delayed_pattern, + consumer_delayed_pattern + arraysize(consumer_delayed_pattern))); + + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + message_loop->PostTask( + FROM_HERE, + base::Bind(&AvStreamerTest::Start, base::Unretained(this))); + message_loop->Run(); +}; + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/ipc_streamer/coded_frame_provider_host.cc b/chromium/chromecast/media/cma/ipc_streamer/coded_frame_provider_host.cc new file mode 100644 index 00000000000..97f372c5851 --- /dev/null +++ b/chromium/chromecast/media/cma/ipc_streamer/coded_frame_provider_host.cc @@ -0,0 +1,96 @@ +// 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/ipc_streamer/coded_frame_provider_host.h" + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "chromecast/media/cma/base/decoder_buffer_base.h" +#include "chromecast/media/cma/ipc/media_message.h" +#include "chromecast/media/cma/ipc/media_message_fifo.h" +#include "chromecast/media/cma/ipc/media_message_type.h" +#include "chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.h" +#include "chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.h" +#include "chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.h" +#include "media/base/buffers.h" +#include "media/base/decrypt_config.h" + +namespace chromecast { +namespace media { + +CodedFrameProviderHost::CodedFrameProviderHost( + scoped_ptr<MediaMessageFifo> media_message_fifo) + : fifo_(media_message_fifo.Pass()), + weak_factory_(this) { + weak_this_ = weak_factory_.GetWeakPtr(); + thread_checker_.DetachFromThread(); +} + +CodedFrameProviderHost::~CodedFrameProviderHost() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void CodedFrameProviderHost::Read(const ReadCB& read_cb) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Cannot be called if there is already a pending read. + DCHECK(read_cb_.is_null()); + read_cb_ = read_cb; + + ReadMessages(); +} + +void CodedFrameProviderHost::Flush(const base::Closure& flush_cb) { + DCHECK(thread_checker_.CalledOnValidThread()); + audio_config_ = ::media::AudioDecoderConfig(); + video_config_ = ::media::VideoDecoderConfig(); + read_cb_.Reset(); + fifo_->Flush(); + flush_cb.Run(); +} + +void CodedFrameProviderHost::OnFifoWriteEvent() { + DCHECK(thread_checker_.CalledOnValidThread()); + ReadMessages(); +} + +base::Closure CodedFrameProviderHost::GetFifoWriteEventCb() { + return base::Bind(&CodedFrameProviderHost::OnFifoWriteEvent, weak_this_); +} + +void CodedFrameProviderHost::ReadMessages() { + // Read messages until a frame is provided (i.e. not just the audio/video + // configurations). + while (!read_cb_.is_null()) { + scoped_ptr<MediaMessage> msg(fifo_->Pop()); + if (!msg) + break; + + if (msg->type() == PaddingMediaMsg) { + // Ignore the message. + } else if (msg->type() == AudioConfigMediaMsg) { + audio_config_ = AudioDecoderConfigMarshaller::Read(msg.get()); + } else if (msg->type() == VideoConfigMediaMsg) { + video_config_ = VideoDecoderConfigMarshaller::Read(msg.get()); + } else if (msg->type() == FrameMediaMsg) { + scoped_refptr<DecoderBufferBase> buffer = + DecoderBufferBaseMarshaller::Read(msg.Pass()); + base::ResetAndReturn(&read_cb_).Run( + buffer, audio_config_, video_config_); + audio_config_ = ::media::AudioDecoderConfig(); + video_config_ = ::media::VideoDecoderConfig(); + } else { + // Receiving an unexpected message. + // Possible use case (except software bugs): the renderer process has + // been compromised and an invalid message value has been written to + // the fifo. Crash the browser process in this case to avoid further + // security implications (so do not use NOTREACHED which crashes only + // in debug builds). + LOG(FATAL) << "Unknown media message"; + } + } +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/ipc_streamer/coded_frame_provider_host.h b/chromium/chromecast/media/cma/ipc_streamer/coded_frame_provider_host.h new file mode 100644 index 00000000000..9dc9471bc5b --- /dev/null +++ b/chromium/chromecast/media/cma/ipc_streamer/coded_frame_provider_host.h @@ -0,0 +1,61 @@ +// 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_IPC_STREAMER_CODED_FRAME_PROVIDER_HOST_H_ +#define CHROMECAST_MEDIA_CMA_IPC_STREAMER_CODED_FRAME_PROVIDER_HOST_H_ + +#include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" +#include "chromecast/media/cma/base/coded_frame_provider.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/video_decoder_config.h" + +namespace chromecast { +namespace media { +class MediaMessageFifo; + +// CodedFrameProviderHost is a frame provider that gets the frames +// from a media message fifo. +class CodedFrameProviderHost : public CodedFrameProvider { + public: + // Note: if the media message fifo is located into shared memory, + // the caller must make sure the shared memory segment is valid + // during the whole lifetime of this object. + explicit CodedFrameProviderHost( + scoped_ptr<MediaMessageFifo> media_message_fifo); + virtual ~CodedFrameProviderHost(); + + // CodedFrameProvider implementation. + virtual void Read(const ReadCB& read_cb) override; + virtual void Flush(const base::Closure& flush_cb) override; + + // Invoked when some data has been written into the fifo. + void OnFifoWriteEvent(); + base::Closure GetFifoWriteEventCb(); + + private: + void ReadMessages(); + + base::ThreadChecker thread_checker_; + + // Fifo holding the frames. + scoped_ptr<MediaMessageFifo> fifo_; + + ReadCB read_cb_; + + // Audio/video configuration for the next A/V buffer. + ::media::AudioDecoderConfig audio_config_; + ::media::VideoDecoderConfig video_config_; + + base::WeakPtr<CodedFrameProviderHost> weak_this_; + base::WeakPtrFactory<CodedFrameProviderHost> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(CodedFrameProviderHost); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_IPC_STREAMER_CODED_FRAME_PROVIDER_HOST_H_ diff --git a/chromium/chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.cc b/chromium/chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.cc new file mode 100644 index 00000000000..dc1f8f5714e --- /dev/null +++ b/chromium/chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.cc @@ -0,0 +1,177 @@ +// 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/ipc_streamer/decoder_buffer_base_marshaller.h" + +#include "base/logging.h" +#include "chromecast/media/cma/base/decoder_buffer_base.h" +#include "chromecast/media/cma/ipc/media_message.h" +#include "chromecast/media/cma/ipc/media_message_type.h" +#include "chromecast/media/cma/ipc_streamer/decrypt_config_marshaller.h" +#include "media/base/decrypt_config.h" + +namespace chromecast { +namespace media { + +namespace { +const size_t kMaxFrameSize = 4 * 1024 * 1024; + +class DecoderBufferFromMsg : public DecoderBufferBase { + public: + explicit DecoderBufferFromMsg(scoped_ptr<MediaMessage> msg); + + void Initialize(); + + // DecoderBufferBase implementation. + virtual base::TimeDelta timestamp() const override; + virtual const uint8* data() const override; + virtual uint8* writable_data() const override; + virtual int data_size() const override; + virtual const ::media::DecryptConfig* decrypt_config() const override; + virtual bool end_of_stream() const override; + + private: + virtual ~DecoderBufferFromMsg(); + + // Indicates whether this is an end of stream frame. + bool is_eos_; + + // Frame timestamp. + base::TimeDelta pts_; + + // CENC parameters. + scoped_ptr< ::media::DecryptConfig> decrypt_config_; + + // Size of the frame. + int data_size_; + + // Keeps the message since frame data is not copied. + scoped_ptr<MediaMessage> msg_; + uint8* data_; + + DISALLOW_COPY_AND_ASSIGN(DecoderBufferFromMsg); +}; + +DecoderBufferFromMsg::DecoderBufferFromMsg( + scoped_ptr<MediaMessage> msg) + : msg_(msg.Pass()), + is_eos_(true), + data_(NULL) { + CHECK(msg_); +} + +DecoderBufferFromMsg::~DecoderBufferFromMsg() { +} + +void DecoderBufferFromMsg::Initialize() { + CHECK_EQ(msg_->type(), FrameMediaMsg); + + CHECK(msg_->ReadPod(&is_eos_)); + if (is_eos_) + return; + + int64 pts_internal = 0; + CHECK(msg_->ReadPod(&pts_internal)); + pts_ = base::TimeDelta::FromInternalValue(pts_internal); + + bool has_decrypt_config = false; + CHECK(msg_->ReadPod(&has_decrypt_config)); + if (has_decrypt_config) + decrypt_config_.reset(DecryptConfigMarshaller::Read(msg_.get()).release()); + + CHECK(msg_->ReadPod(&data_size_)); + CHECK_GT(data_size_, 0); + CHECK_LT(data_size_, kMaxFrameSize); + + // Get a pointer to the frame data inside the message. + // Avoid copying the frame data here. + data_ = static_cast<uint8*>(msg_->GetWritableBuffer(data_size_)); + CHECK(data_); + + if (decrypt_config_) { + uint32 subsample_total_size = 0; + for (size_t k = 0; k < decrypt_config_->subsamples().size(); k++) { + subsample_total_size += decrypt_config_->subsamples()[k].clear_bytes; + subsample_total_size += decrypt_config_->subsamples()[k].cypher_bytes; + } + CHECK_EQ(subsample_total_size, data_size_); + } +} + +base::TimeDelta DecoderBufferFromMsg::timestamp() const { + return pts_; +} + +const uint8* DecoderBufferFromMsg::data() const { + CHECK(msg_->IsSerializedMsgAvailable()); + return data_; +} + +uint8* DecoderBufferFromMsg::writable_data() const { + CHECK(msg_->IsSerializedMsgAvailable()); + return data_; +} + +int DecoderBufferFromMsg::data_size() const { + return data_size_; +} + +const ::media::DecryptConfig* DecoderBufferFromMsg::decrypt_config() const { + return decrypt_config_.get(); +} + +bool DecoderBufferFromMsg::end_of_stream() const { + return is_eos_; +} + +} // namespace + +// static +void DecoderBufferBaseMarshaller::Write( + const scoped_refptr<DecoderBufferBase>& buffer, + MediaMessage* msg) { + CHECK(msg->WritePod(buffer->end_of_stream())); + if (buffer->end_of_stream()) + return; + + CHECK(msg->WritePod(buffer->timestamp().ToInternalValue())); + + bool has_decrypt_config = + (buffer->decrypt_config() != NULL && + buffer->decrypt_config()->iv().size() > 0); + CHECK(msg->WritePod(has_decrypt_config)); + + if (has_decrypt_config) { + // DecryptConfig may contain 0 subsamples if all content is encrypted. + // Map this case to a single fully-encrypted "subsample" for more consistent + // backend handling. + if (buffer->decrypt_config()->subsamples().empty()) { + std::vector< ::media::SubsampleEntry> encrypted_subsample_list(1); + encrypted_subsample_list[0].clear_bytes = 0; + encrypted_subsample_list[0].cypher_bytes = buffer->data_size(); + ::media::DecryptConfig full_sample_config( + buffer->decrypt_config()->key_id(), + buffer->decrypt_config()->iv(), + encrypted_subsample_list); + DecryptConfigMarshaller::Write(full_sample_config, msg); + } else { + DecryptConfigMarshaller::Write(*buffer->decrypt_config(), msg); + } + } + + CHECK(msg->WritePod(buffer->data_size())); + CHECK(msg->WriteBuffer(buffer->data(), buffer->data_size())); +} + +// static +scoped_refptr<DecoderBufferBase> DecoderBufferBaseMarshaller::Read( + scoped_ptr<MediaMessage> msg) { + scoped_refptr<DecoderBufferFromMsg> buffer( + new DecoderBufferFromMsg(msg.Pass())); + buffer->Initialize(); + return buffer; +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.h b/chromium/chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.h new file mode 100644 index 00000000000..bd5dbfe9b11 --- /dev/null +++ b/chromium/chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.h @@ -0,0 +1,29 @@ +// 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_IPC_STREAMER_DECODER_BUFFER_BASE_MARSHALLER_H_ +#define CHROMECAST_MEDIA_CMA_IPC_STREAMER_DECODER_BUFFER_BASE_MARSHALLER_H_ + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" + +namespace chromecast { +namespace media { +class DecoderBufferBase; +class MediaMessage; + +class DecoderBufferBaseMarshaller { + public: + // Writes the serialized structure of |config| into |msg|. + static void Write( + const scoped_refptr<DecoderBufferBase>& buffer, MediaMessage* msg); + + // Returns a decoder buffer from its serialized structure. + static scoped_refptr<DecoderBufferBase> Read(scoped_ptr<MediaMessage> msg); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_IPC_STREAMER_DECODER_BUFFER_BASE_MARSHALLER_H_ diff --git a/chromium/chromecast/media/cma/ipc_streamer/decrypt_config_marshaller.cc b/chromium/chromecast/media/cma/ipc_streamer/decrypt_config_marshaller.cc new file mode 100644 index 00000000000..9e3de954ccb --- /dev/null +++ b/chromium/chromecast/media/cma/ipc_streamer/decrypt_config_marshaller.cc @@ -0,0 +1,75 @@ +// 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/ipc_streamer/decrypt_config_marshaller.h" + +#include "base/logging.h" +#include "chromecast/media/cma/ipc/media_message.h" +#include "media/base/decrypt_config.h" + +namespace chromecast { +namespace media { + +namespace { +const size_t kMaxKeyIdSize = 256; +const size_t kMaxIvSize = 256; +const size_t kMaxSubsampleCount = 1024; +} + +// static +void DecryptConfigMarshaller::Write( + const ::media::DecryptConfig& config, MediaMessage* msg) { + CHECK_GT(config.key_id().size(), 0); + CHECK_GT(config.iv().size(), 0); + CHECK_GT(config.subsamples().size(), 0); + + CHECK(msg->WritePod(config.key_id().size())); + CHECK(msg->WriteBuffer(config.key_id().data(), config.key_id().size())); + CHECK(msg->WritePod(config.iv().size())); + CHECK(msg->WriteBuffer(config.iv().data(), config.iv().size())); + CHECK(msg->WritePod(config.subsamples().size())); + for (size_t k = 0; k < config.subsamples().size(); k++) { + CHECK(msg->WritePod(config.subsamples()[k].clear_bytes)); + CHECK(msg->WritePod(config.subsamples()[k].cypher_bytes)); + } +} + +// static +scoped_ptr< ::media::DecryptConfig> DecryptConfigMarshaller::Read( + MediaMessage* msg) { + size_t key_id_size = 0; + CHECK(msg->ReadPod(&key_id_size)); + CHECK_GT(key_id_size, 0); + CHECK_LT(key_id_size, kMaxKeyIdSize); + scoped_ptr<char[]> key_id(new char[key_id_size]); + CHECK(msg->ReadBuffer(key_id.get(), key_id_size)); + + size_t iv_size = 0; + CHECK(msg->ReadPod(&iv_size)); + CHECK_GT(iv_size, 0); + CHECK_LT(iv_size, kMaxIvSize); + scoped_ptr<char[]> iv(new char[iv_size]); + CHECK(msg->ReadBuffer(iv.get(), iv_size)); + + size_t subsample_count = 0; + CHECK(msg->ReadPod(&subsample_count)); + CHECK_GT(subsample_count, 0); + CHECK_LT(subsample_count, kMaxSubsampleCount); + std::vector< ::media::SubsampleEntry> subsamples(subsample_count); + for (size_t k = 0; k < subsample_count; k++) { + subsamples[k].clear_bytes = 0; + subsamples[k].cypher_bytes = 0; + CHECK(msg->ReadPod(&subsamples[k].clear_bytes)); + CHECK(msg->ReadPod(&subsamples[k].cypher_bytes)); + } + + return scoped_ptr< ::media::DecryptConfig>( + new ::media::DecryptConfig( + std::string(key_id.get(), key_id_size), + std::string(iv.get(), iv_size), + subsamples)); +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/ipc_streamer/decrypt_config_marshaller.h b/chromium/chromecast/media/cma/ipc_streamer/decrypt_config_marshaller.h new file mode 100644 index 00000000000..b600ba41c14 --- /dev/null +++ b/chromium/chromecast/media/cma/ipc_streamer/decrypt_config_marshaller.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_IPC_STREAMER_DECRYPT_CONFIG_MARSHALLER_H_ +#define CHROMECAST_MEDIA_CMA_IPC_STREAMER_DECRYPT_CONFIG_MARSHALLER_H_ + +#include "base/memory/scoped_ptr.h" + +namespace media { +class DecryptConfig; +} + +namespace chromecast { +namespace media { +class MediaMessage; + +class DecryptConfigMarshaller { + public: + // Writes the serialized structure of |config| into |msg|. + static void Write( + const ::media::DecryptConfig& config, MediaMessage* msg); + + // Returns a DecryptConfig from its serialized structure. + static scoped_ptr< ::media::DecryptConfig> Read(MediaMessage* msg); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_IPC_STREAMER_DECRYPT_CONFIG_MARSHALLER_H_ diff --git a/chromium/chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.cc b/chromium/chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.cc new file mode 100644 index 00000000000..645056ce22d --- /dev/null +++ b/chromium/chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.cc @@ -0,0 +1,113 @@ +// 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/ipc_streamer/video_decoder_config_marshaller.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "chromecast/media/cma/ipc/media_message.h" +#include "media/base/video_decoder_config.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/size.h" + +namespace chromecast { +namespace media { + +namespace { +const size_t kMaxExtraDataSize = 16 * 1024; + +class SizeMarshaller { + public: + static void Write(const gfx::Size& size, MediaMessage* msg) { + CHECK(msg->WritePod(size.width())); + CHECK(msg->WritePod(size.height())); + } + + static gfx::Size Read(MediaMessage* msg) { + int w, h; + CHECK(msg->ReadPod(&w)); + CHECK(msg->ReadPod(&h)); + return gfx::Size(w, h); + } +}; + +class RectMarshaller { + public: + static void Write(const gfx::Rect& rect, MediaMessage* msg) { + CHECK(msg->WritePod(rect.x())); + CHECK(msg->WritePod(rect.y())); + CHECK(msg->WritePod(rect.width())); + CHECK(msg->WritePod(rect.height())); + } + + static gfx::Rect Read(MediaMessage* msg) { + int x, y, w, h; + CHECK(msg->ReadPod(&x)); + CHECK(msg->ReadPod(&y)); + CHECK(msg->ReadPod(&w)); + CHECK(msg->ReadPod(&h)); + return gfx::Rect(x, y, w, h); + } +}; + +} // namespace + +// static +void VideoDecoderConfigMarshaller::Write( + const ::media::VideoDecoderConfig& config, MediaMessage* msg) { + CHECK(msg->WritePod(config.codec())); + CHECK(msg->WritePod(config.profile())); + CHECK(msg->WritePod(config.format())); + SizeMarshaller::Write(config.coded_size(), msg); + RectMarshaller::Write(config.visible_rect(), msg); + SizeMarshaller::Write(config.natural_size(), msg); + CHECK(msg->WritePod(config.is_encrypted())); + CHECK(msg->WritePod(config.extra_data_size())); + if (config.extra_data_size() > 0) + CHECK(msg->WriteBuffer(config.extra_data(), config.extra_data_size())); +} + +// static +::media::VideoDecoderConfig VideoDecoderConfigMarshaller::Read( + MediaMessage* msg) { + ::media::VideoCodec codec; + ::media::VideoCodecProfile profile; + ::media::VideoFrame::Format format; + gfx::Size coded_size; + gfx::Rect visible_rect; + gfx::Size natural_size; + bool is_encrypted; + size_t extra_data_size; + scoped_ptr<uint8[]> extra_data; + + CHECK(msg->ReadPod(&codec)); + CHECK(msg->ReadPod(&profile)); + CHECK(msg->ReadPod(&format)); + coded_size = SizeMarshaller::Read(msg); + visible_rect = RectMarshaller::Read(msg); + natural_size = SizeMarshaller::Read(msg); + CHECK(msg->ReadPod(&is_encrypted)); + CHECK(msg->ReadPod(&extra_data_size)); + + CHECK_GE(codec, ::media::kUnknownVideoCodec); + CHECK_LE(codec, ::media::kVideoCodecMax); + CHECK_GE(profile, ::media::VIDEO_CODEC_PROFILE_UNKNOWN); + CHECK_LE(profile, ::media::VIDEO_CODEC_PROFILE_MAX); + CHECK_GE(format, ::media::VideoFrame::UNKNOWN); + CHECK_LE(format, ::media::VideoFrame::FORMAT_MAX); + CHECK_LT(extra_data_size, kMaxExtraDataSize); + if (extra_data_size > 0) { + extra_data.reset(new uint8[extra_data_size]); + CHECK(msg->ReadBuffer(extra_data.get(), extra_data_size)); + } + + return ::media::VideoDecoderConfig( + codec, profile, format, + coded_size, visible_rect, natural_size, + extra_data.get(), extra_data_size, + is_encrypted); +} + +} // namespace media +} // namespace chromecast diff --git a/chromium/chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.h b/chromium/chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.h new file mode 100644 index 00000000000..5ba7baec067 --- /dev/null +++ b/chromium/chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.h @@ -0,0 +1,27 @@ +// 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_IPC_STREAMER_VIDEO_DECODER_CONFIG_MARSHALLER_H_ +#define CHROMECAST_MEDIA_CMA_IPC_STREAMER_VIDEO_DECODER_CONFIG_MARSHALLER_H_ + +#include "media/base/video_decoder_config.h" + +namespace chromecast { +namespace media { +class MediaMessage; + +class VideoDecoderConfigMarshaller { + public: + // Writes the serialized structure of |config| into |msg|. + static void Write( + const ::media::VideoDecoderConfig& config, MediaMessage* msg); + + // Returns a VideoDecoderConfig from its serialized structure. + static ::media::VideoDecoderConfig Read(MediaMessage* msg); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_IPC_STREAMER_VIDEO_DECODER_CONFIG_MARSHALLER_H_ diff --git a/chromium/chromecast/media/media.gyp b/chromium/chromecast/media/media.gyp new file mode 100644 index 00000000000..9eaf8d1be75 --- /dev/null +++ b/chromium/chromecast/media/media.gyp @@ -0,0 +1,208 @@ +# 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. + +{ + 'variables': { + 'chromecast_branding%': 'Chromium', + }, + 'targets': [ + { + 'target_name': 'media_base', + 'type': '<(component)', + 'dependencies': [ + '../../base/base.gyp:base', + '../../crypto/crypto.gyp:crypto', + '../../third_party/widevine/cdm/widevine_cdm.gyp:widevine_cdm_version_h', + ], + 'sources': [ + 'base/decrypt_context.cc', + 'base/decrypt_context.h', + 'base/decrypt_context_clearkey.cc', + 'base/decrypt_context_clearkey.h', + 'base/key_systems_common.cc', + 'base/key_systems_common.h', + ], + 'conditions': [ + ['chromecast_branding=="Chrome"', { + 'dependencies': [ + '<(cast_internal_gyp):media_base_internal', + ], + }, { + 'sources': [ + 'base/key_systems_common_simple.cc', + ], + }], + ], + }, + { + 'target_name': 'cma_base', + 'type': '<(component)', + 'dependencies': [ + '../chromecast.gyp:cast_base', + '../../base/base.gyp:base', + '../../media/media.gyp:media', + ], + 'include_dirs': [ + '../..', + ], + 'sources': [ + 'cma/base/balanced_media_task_runner_factory.cc', + 'cma/base/balanced_media_task_runner_factory.h', + 'cma/base/buffering_controller.cc', + 'cma/base/buffering_controller.h', + 'cma/base/buffering_frame_provider.cc', + 'cma/base/buffering_frame_provider.h', + 'cma/base/buffering_state.cc', + 'cma/base/buffering_state.h', + 'cma/base/cma_logging.h', + 'cma/base/coded_frame_provider.cc', + 'cma/base/coded_frame_provider.h', + 'cma/base/decoder_buffer_adapter.cc', + 'cma/base/decoder_buffer_adapter.h', + 'cma/base/decoder_buffer_base.cc', + 'cma/base/decoder_buffer_base.h', + 'cma/base/media_task_runner.cc', + 'cma/base/media_task_runner.h', + ], + }, + { + 'target_name': 'cma_backend', + 'type': '<(component)', + 'dependencies': [ + 'cma_base', + 'media_base', + '../../base/base.gyp:base', + '../../media/media.gyp:media', + ], + 'include_dirs': [ + '../..', + ], + 'sources': [ + 'cma/backend/audio_pipeline_device.cc', + 'cma/backend/audio_pipeline_device.h', + 'cma/backend/media_clock_device.cc', + 'cma/backend/media_clock_device.h', + 'cma/backend/media_component_device.cc', + 'cma/backend/media_component_device.h', + 'cma/backend/media_pipeline_device.cc', + 'cma/backend/media_pipeline_device.h', + 'cma/backend/media_pipeline_device_fake.cc', + 'cma/backend/media_pipeline_device_fake.h', + 'cma/backend/media_pipeline_device_params.cc', + 'cma/backend/media_pipeline_device_params.h', + 'cma/backend/video_pipeline_device.cc', + 'cma/backend/video_pipeline_device.h', + ], + 'conditions': [ + ['chromecast_branding=="Chrome"', { + 'dependencies': [ + '<(cast_internal_gyp):cma_backend_internal', + ], + }, { + 'sources': [ + 'cma/backend/media_pipeline_device_fake_factory.cc', + ], + }], + ], + }, + { + 'target_name': 'cma_ipc', + 'type': '<(component)', + 'dependencies': [ + '../../base/base.gyp:base', + ], + 'sources': [ + 'cma/ipc/media_memory_chunk.cc', + 'cma/ipc/media_memory_chunk.h', + 'cma/ipc/media_message.cc', + 'cma/ipc/media_message.h', + 'cma/ipc/media_message_fifo.cc', + 'cma/ipc/media_message_fifo.h', + ], + }, + { + 'target_name': 'cma_ipc_streamer', + 'type': '<(component)', + 'dependencies': [ + '../../base/base.gyp:base', + '../../media/media.gyp:media', + 'cma_base', + ], + 'sources': [ + 'cma/ipc_streamer/audio_decoder_config_marshaller.cc', + 'cma/ipc_streamer/audio_decoder_config_marshaller.h', + 'cma/ipc_streamer/av_streamer_proxy.cc', + 'cma/ipc_streamer/av_streamer_proxy.h', + 'cma/ipc_streamer/coded_frame_provider_host.cc', + 'cma/ipc_streamer/coded_frame_provider_host.h', + 'cma/ipc_streamer/decoder_buffer_base_marshaller.cc', + 'cma/ipc_streamer/decoder_buffer_base_marshaller.h', + 'cma/ipc_streamer/decrypt_config_marshaller.cc', + 'cma/ipc_streamer/decrypt_config_marshaller.h', + 'cma/ipc_streamer/video_decoder_config_marshaller.cc', + 'cma/ipc_streamer/video_decoder_config_marshaller.h', + ], + }, + { + 'target_name': 'cma_filters', + 'type': '<(component)', + 'dependencies': [ + '../../base/base.gyp:base', + '../../media/media.gyp:media', + 'cma_base', + ], + 'sources': [ + 'cma/filters/demuxer_stream_adapter.cc', + 'cma/filters/demuxer_stream_adapter.h', + ], + }, + { + 'target_name': 'cast_media', + 'type': 'none', + 'dependencies': [ + 'cma_backend', + 'cma_base', + 'cma_filters', + 'cma_ipc', + 'cma_ipc_streamer', + ], + }, + { + 'target_name': 'cast_media_unittests', + 'type': '<(gtest_target_type)', + 'dependencies': [ + 'cast_media', + '../../base/base.gyp:base', + '../../base/base.gyp:base_i18n', + '../../base/base.gyp:test_support_base', + '../../chromecast/chromecast.gyp:cast_metrics_test_support', + '../../media/media.gyp:media_test_support', + '../../testing/gmock.gyp:gmock', + '../../testing/gtest.gyp:gtest', + '../../testing/gtest.gyp:gtest_main', + ], + 'sources': [ + 'cma/backend/audio_video_pipeline_device_unittest.cc', + 'cma/base/balanced_media_task_runner_unittest.cc', + 'cma/base/buffering_controller_unittest.cc', + 'cma/base/buffering_frame_provider_unittest.cc', + 'cma/filters/demuxer_stream_adapter_unittest.cc', + 'cma/ipc/media_message_fifo_unittest.cc', + 'cma/ipc/media_message_unittest.cc', + 'cma/ipc_streamer/av_streamer_unittest.cc', + 'cma/test/frame_generator_for_test.cc', + 'cma/test/frame_generator_for_test.h', + 'cma/test/frame_segmenter_for_test.cc', + 'cma/test/frame_segmenter_for_test.h', + 'cma/test/media_component_device_feeder_for_test.cc', + 'cma/test/media_component_device_feeder_for_test.h', + 'cma/test/mock_frame_consumer.cc', + 'cma/test/mock_frame_consumer.h', + 'cma/test/mock_frame_provider.cc', + 'cma/test/mock_frame_provider.h', + 'cma/test/run_all_unittests.cc', + ], + }, + ], +} |