Chromium Code Reviews| 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..555a69613c0e66489c1808e143a19a4da380fdcd |
| --- /dev/null |
| +++ b/chromecast/media/cma/backend/audio_video_pipeline_device_unittest.cc |
| @@ -0,0 +1,400 @@ |
| +// 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/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); |
|
damienv1
2014/10/03 23:33:45
nit: Could go on the previous line.
gunsch
2014/10/04 00:37:40
Done.
|
| + |
| + // Pattern loops, waiting >= pattern[i].delay against media clock between |
| + // pauses, then pausing for >= pattern[i].length against MessageLoop |
|
damienv1
2014/10/03 23:33:45
nit: extra spaces not needed
gunsch
2014/10/04 00:37:40
Done.
|
| + // A pause with delay <0 signals to stop sequence and do not loop |
| + void SetPausePattern(const std::vector<PauseInfo> pattern); |
|
damienv1
2014/10/03 23:33:44
Blank line.
gunsch
2014/10/04 00:37:40
Done.
|
| + // Add 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) { |
| + // Copy pattern |
|
damienv1
2014/10/03 23:33:44
Copy could be removed. Does not help.
gunsch
2014/10/04 00:37:40
Done.
|
| + pause_pattern_ = pattern; |
| +} |
| + |
| +void AudioVideoPipelineDeviceTest::ConfigureForAudioOnly(std::string filename) { |
| + Initialize(); |
| + |
|
damienv1
2014/10/03 23:33:44
nit: No blank line needed.
gunsch
2014/10/04 00:37:40
Done.
|
| + LoadAudioStream(filename); |
| +} |
| + |
| +void AudioVideoPipelineDeviceTest::ConfigureForVideoOnly(std::string filename, |
| + bool raw_h264) { |
| + Initialize(); |
| + |
|
damienv1
2014/10/03 23:33:45
Not blank line needed.
gunsch
2014/10/04 00:37:40
Done.
|
| + LoadVideoStream(filename, raw_h264); |
| +} |
| + |
| +void AudioVideoPipelineDeviceTest::ConfigureForFile(std::string filename) { |
| + Initialize(); |
| + |
| + LoadVideoStream(filename, /* raw_h264 */ false); |
|
damienv1
2014/10/03 23:33:45
Style ?
gunsch
2014/10/04 00:37:40
Done.
|
| + LoadAudioStream(filename); |
| +} |
| + |
| +void AudioVideoPipelineDeviceTest::LoadAudioStream(std::string filename) { |
| + base::FilePath file_path = GetTestDataFilePath(filename); |
| + DemuxResult demux_result = FFmpegDemuxForTest(file_path, /*audio*/ true); |
| + std::list<scoped_refptr<DecoderBufferBase> > frames = demux_result.frames; |
| + |
| + AudioPipelineDevice *audio_pipeline_device = |
| + media_pipeline_device_->GetAudioPipelineDevice(); |
| + |
| + // Set configuration. |
| + 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 = |
|
damienv1
2014/10/03 23:33:45
Style: Test*
gunsch
2014/10/04 00:37:40
Done.
|
| + new MediaComponentDeviceFeederForTest( |
| + audio_pipeline_device, |
| + frames, |
| + base::Bind(&AudioVideoPipelineDeviceTest::OnEos, |
| + base::Unretained(this))); |
| + |
| + device_feeder->Initialize(); |
| + |
| + component_device_feeders_.push_back(device_feeder); |
| +} |
| + |
| +void AudioVideoPipelineDeviceTest::LoadVideoStream(std::string filename, |
| + bool raw_h264) { |
| + std::list<scoped_refptr<DecoderBufferBase> > 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); |
|
damienv1
2014/10/03 23:33:45
Style issue ?
gunsch
2014/10/04 00:37:40
Done.
|
| + 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, |
| + base::Bind(&AudioVideoPipelineDeviceTest::OnEos, |
| + base::Unretained(this))); |
| + |
| + device_feeder->Initialize(); |
| + |
| + 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]))); |
| + } |
| + |
| + base::MessageLoopProxy::current()->PostTask( |
| + FROM_HERE, |
| + base::Bind(&AudioVideoPipelineDeviceTest::MonitorLoop, |
| + base::Unretained(this))); |
| +} |
| + |
| +void AudioVideoPipelineDeviceTest::MonitorLoop() { |
| + // Start the clock if needed. |
| + media_clock_device_->SetState(MediaClockDevice::kStateRunning); |
|
damienv1
2014/10/03 23:33:44
Does not seem the right place. Why not make it par
gunsch
2014/10/04 00:37:40
Done.
|
| + |
| + 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); |
|
damienv1
2014/10/03 23:33:45
nit: Add a return and remove the else.
gunsch
2014/10/04 00:37:40
Done.
|
| + } else { |
| + // 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; |
| + } |
| + } |
|
damienv1
2014/10/03 23:33:44
nit: Add blank line.
gunsch
2014/10/04 00:37:40
Done.
|
| + // 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", /* raw_h264 */ true); |
|
damienv1
2014/10/03 23:33:45
Style issue ? /* raw_h264 */
I don't remember havi
gunsch
2014/10/04 00:37:40
Common, but usually on the other side. Updated all
|
| + Start(); |
| + message_loop->Run(); |
| +} |
| + |
| +TEST_F(AudioVideoPipelineDeviceTest, WebmPlaybackWithPause) { |
| + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); |
| + |
| + ConfigureForVideoOnly("bear-640x360.webm", /* raw_h264 */ false); |
|
damienv1
2014/10/03 23:33:45
Same.
gunsch
2014/10/04 00:37:40
Done.
|
| + |
| + // Setup to pause for 1000ms every 500ms |
| + AddPause(base::TimeDelta::FromMilliseconds(500), |
|
damienv1
2014/10/03 23:33:45
nit: to keep the same style (i.e. group Configure/
gunsch
2014/10/04 00:37:40
Done.
|
| + base::TimeDelta::FromMilliseconds(100)); |
| + |
| + Start(); |
| + message_loop->Run(); |
| +} |
| + |
| +TEST_F(AudioVideoPipelineDeviceTest, Vp8Playback) { |
| + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); |
| + |
| + ConfigureForVideoOnly("bear-vp8a.webm", /* raw_h264 */ false); |
| + 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 |