Index: chromecast/media/cma/backend/audio_video_pipeline_device_unittest.cc |
diff --git a/chromecast/media/cma/backend/audio_video_pipeline_device_unittest.cc b/chromecast/media/cma/backend/audio_video_pipeline_device_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..cf1daca9ab011910d5cd96030476682245b39415 |
--- /dev/null |
+++ b/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 |