Chromium Code Reviews| Index: chromecast/media/audio/audio_output_stream_unittest.cc |
| diff --git a/chromecast/media/audio/audio_output_stream_unittest.cc b/chromecast/media/audio/audio_output_stream_unittest.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..d9c52811bda6404bc69049d77543e45ea1a8ae72 |
| --- /dev/null |
| +++ b/chromecast/media/audio/audio_output_stream_unittest.cc |
| @@ -0,0 +1,498 @@ |
| +// Copyright 2015 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/base/task_runner_impl.h" |
| +#include "chromecast/media/audio/audio_manager.h" |
| +#include "chromecast/media/audio/audio_output_stream.h" |
| +#include "chromecast/public/media/audio_pipeline_device.h" |
| +#include "chromecast/public/media/cast_decoder_buffer.h" |
| +#include "chromecast/public/media/decoder_config.h" |
| +#include "chromecast/public/media/decrypt_context.h" |
| +#include "chromecast/public/media/media_clock_device.h" |
| +#include "chromecast/public/media/media_pipeline_backend.h" |
| +#include "testing/gtest/include/gtest/gtest.h" |
| + |
| +namespace { |
| +const char kDefaultDeviceId[] = ""; |
|
gunsch
2015/09/18 21:23:07
can this be inside the another anonymous namespace
alokp
2015/09/18 21:42:15
Done.
|
| +} // namespace |
| + |
| +namespace chromecast { |
| +namespace media { |
| +namespace { |
| + |
| +class FakeClockDevice : public MediaClockDevice { |
| + public: |
| + FakeClockDevice() : state_(kStateUninitialized), rate_(0.f) {} |
| + ~FakeClockDevice() override {} |
| + |
| + State GetState() const override { return state_; } |
| + bool SetState(State new_state) override { |
| + state_ = new_state; |
| + return true; |
| + } |
| + bool ResetTimeline(int64_t time_microseconds) override { return true; } |
| + bool SetRate(float rate) override { |
| + rate_ = rate; |
| + return true; |
| + } |
| + int64_t GetTimeMicroseconds() override { return 0; } |
| + |
| + float rate() const { return rate_; } |
| + |
| + private: |
| + State state_; |
| + float rate_; |
| +}; |
| + |
| +class FakeAudioPipelineDevice : public AudioPipelineDevice { |
| + public: |
| + enum PipelineStatus { |
| + PIPELINE_STATUS_OK, |
| + PIPELINE_STATUS_BUSY, |
| + PIPELINE_STATUS_ERROR |
| + }; |
| + |
| + FakeAudioPipelineDevice() |
| + : state_(kStateUninitialized), |
| + volume_multiplier_(1.0f), |
| + pipeline_status_(PIPELINE_STATUS_OK), |
| + pushed_frame_count_(0) {} |
| + ~FakeAudioPipelineDevice() override {} |
| + |
| + // AudioPipelineDevice overrides. |
| + void SetClient(Client* client) override {} |
| + bool SetState(State new_state) override { |
| + state_ = new_state; |
| + return true; |
| + } |
| + State GetState() const override { return state_; } |
| + bool SetStartPts(int64_t microseconds) override { return false; } |
| + FrameStatus PushFrame(DecryptContext* decrypt_context, |
| + CastDecoderBuffer* buffer, |
| + FrameStatusCB* completion_cb) override { |
| + last_frame_decrypt_context_.reset(decrypt_context); |
| + last_frame_buffer_.reset(buffer); |
| + last_frame_completion_cb_.reset(completion_cb); |
| + ++pushed_frame_count_; |
| + |
| + switch (pipeline_status_) { |
| + case PIPELINE_STATUS_OK: |
| + return kFrameSuccess; |
| + case PIPELINE_STATUS_BUSY: |
| + return kFramePending; |
| + case PIPELINE_STATUS_ERROR: |
| + return kFrameFailed; |
| + } |
| + NOTREACHED(); |
| + } |
| + RenderingDelay GetRenderingDelay() const override { return RenderingDelay(); } |
| + bool GetStatistics(Statistics* stats) const override { return false; } |
| + bool SetConfig(const AudioConfig& config) override { |
| + config_ = config; |
| + return true; |
| + } |
| + void SetStreamVolumeMultiplier(float multiplier) override { |
| + volume_multiplier_ = multiplier; |
| + } |
| + |
| + const AudioConfig& config() const { return config_; } |
| + float volume_multiplier() const { return volume_multiplier_; } |
| + void set_pipeline_status(PipelineStatus status) { pipeline_status_ = status; } |
| + unsigned pushed_frame_count() const { return pushed_frame_count_; } |
| + DecryptContext* last_frame_decrypt_context() { |
| + return last_frame_decrypt_context_.get(); |
| + } |
| + CastDecoderBuffer* last_frame_buffer() { return last_frame_buffer_.get(); } |
| + FrameStatusCB* last_frame_completion_cb() { |
| + return last_frame_completion_cb_.get(); |
| + } |
| + |
| + private: |
| + State state_; |
| + AudioConfig config_; |
| + float volume_multiplier_; |
| + |
| + PipelineStatus pipeline_status_; |
| + unsigned pushed_frame_count_; |
| + scoped_ptr<DecryptContext> last_frame_decrypt_context_; |
| + scoped_ptr<CastDecoderBuffer> last_frame_buffer_; |
| + scoped_ptr<FrameStatusCB> last_frame_completion_cb_; |
| +}; |
| + |
| +class FakeMediaPipelineBackend : public MediaPipelineBackend { |
| + public: |
| + ~FakeMediaPipelineBackend() override {} |
| + |
| + MediaClockDevice* GetClock() override { |
| + if (!clock_device_) |
| + clock_device_.reset(new FakeClockDevice); |
| + return clock_device_.get(); |
| + } |
| + AudioPipelineDevice* GetAudio() override { |
| + if (!audio_device_) |
| + audio_device_.reset(new FakeAudioPipelineDevice); |
| + return audio_device_.get(); |
| + } |
| + VideoPipelineDevice* GetVideo() override { |
| + NOTREACHED(); |
| + return nullptr; |
| + } |
| + |
| + private: |
| + scoped_ptr<FakeClockDevice> clock_device_; |
| + scoped_ptr<FakeAudioPipelineDevice> audio_device_; |
| +}; |
| + |
| +class FakeAudioSourceCallback |
| + : public ::media::AudioOutputStream::AudioSourceCallback { |
| + public: |
| + FakeAudioSourceCallback() : error_(false) {} |
| + |
| + bool error() const { return error_; } |
| + |
| + // ::media::AudioOutputStream::AudioSourceCallback overrides. |
| + int OnMoreData(::media::AudioBus* audio_bus, |
| + uint32 total_bytes_delay) override { |
| + audio_bus->Zero(); |
| + return audio_bus->frames(); |
| + } |
| + void OnError(::media::AudioOutputStream* stream) override { error_ = true; } |
| + |
| + private: |
| + bool error_; |
| +}; |
| + |
| +class FakeAudioManager : public AudioManager { |
| + public: |
| + FakeAudioManager() |
| + : AudioManager(nullptr), media_pipeline_backend_(nullptr) {} |
| + ~FakeAudioManager() override {} |
| + |
| + // AudioManager overrides. |
| + scoped_ptr<MediaPipelineBackend> CreateMediaPipelineBackend() override { |
| + DCHECK(!media_pipeline_backend_); |
| + scoped_ptr<FakeMediaPipelineBackend> backend(new FakeMediaPipelineBackend); |
| + // Cache the backend locally to be used by tests. |
| + media_pipeline_backend_ = backend.get(); |
| + return backend.Pass(); |
| + } |
| + void ReleaseOutputStream(::media::AudioOutputStream* stream) override { |
| + DCHECK(media_pipeline_backend_); |
| + media_pipeline_backend_ = nullptr; |
| + AudioManager::ReleaseOutputStream(stream); |
| + } |
| + |
| + // Returns the MediaPipelineBackend being used by the AudioOutputStream. |
| + // Note: here is a valid MediaPipelineBackend only while the stream is open. |
| + // Returns NULL at all other times. |
| + FakeMediaPipelineBackend* media_pipeline_backend() { |
| + return media_pipeline_backend_; |
| + } |
| + |
| + private: |
| + FakeMediaPipelineBackend* media_pipeline_backend_; |
| +}; |
| + |
| +class AudioOutputStreamTest : public ::testing::Test { |
| + public: |
| + AudioOutputStreamTest() |
| + : format_(::media::AudioParameters::AUDIO_PCM_LINEAR), |
| + channel_layout_(::media::CHANNEL_LAYOUT_MONO), |
| + sample_rate_(::media::AudioParameters::kAudioCDSampleRate), |
| + bits_per_sample_(16), |
| + frames_per_buffer_(256) {} |
| + ~AudioOutputStreamTest() override {} |
| + |
| + protected: |
| + void SetUp() override { |
| + message_loop_.reset(new base::MessageLoop()); |
| + audio_manager_.reset(new FakeAudioManager); |
| + } |
| + |
| + void TearDown() override { |
| + audio_manager_.reset(); |
| + message_loop_.reset(); |
| + } |
| + |
| + ::media::AudioParameters GetAudioParams() { |
| + return ::media::AudioParameters(format_, channel_layout_, sample_rate_, |
| + bits_per_sample_, frames_per_buffer_); |
| + } |
| + ::media::AudioOutputStream* CreateStream() { |
| + return audio_manager_->MakeAudioOutputStream(GetAudioParams(), |
| + kDefaultDeviceId); |
| + } |
| + FakeClockDevice* GetClock() { |
| + MediaPipelineBackend* backend = audio_manager_->media_pipeline_backend(); |
| + return backend ? static_cast<FakeClockDevice*>(backend->GetClock()) |
| + : nullptr; |
| + } |
| + FakeAudioPipelineDevice* GetAudio() { |
| + MediaPipelineBackend* backend = audio_manager_->media_pipeline_backend(); |
| + return backend ? static_cast<FakeAudioPipelineDevice*>(backend->GetAudio()) |
| + : nullptr; |
| + } |
| + |
| + scoped_ptr<base::MessageLoop> message_loop_; |
| + scoped_ptr<FakeAudioManager> audio_manager_; |
| + scoped_ptr<TaskRunnerImpl> audio_task_runner_; |
| + |
| + // AudioParameters used to create AudioOutputStream. |
| + // Tests can modify these parameters before calling CreateStream. |
| + ::media::AudioParameters::Format format_; |
| + ::media::ChannelLayout channel_layout_; |
| + int sample_rate_; |
| + int bits_per_sample_; |
| + int frames_per_buffer_; |
| +}; |
| + |
| +TEST_F(AudioOutputStreamTest, Format) { |
| + ::media::AudioParameters::Format format[] = { |
| + ::media::AudioParameters::AUDIO_PCM_LINEAR, |
| + ::media::AudioParameters::AUDIO_PCM_LOW_LATENCY}; |
| + for (size_t i = 0; i < arraysize(format); ++i) { |
| + format_ = format[i]; |
| + ::media::AudioOutputStream* stream = CreateStream(); |
| + ASSERT_TRUE(stream); |
| + EXPECT_TRUE(stream->Open()); |
| + |
| + FakeAudioPipelineDevice* audio_device = GetAudio(); |
| + ASSERT_TRUE(audio_device); |
| + const AudioConfig& audio_config = audio_device->config(); |
| + EXPECT_EQ(kCodecPCM, audio_config.codec); |
| + EXPECT_EQ(kSampleFormatS16, audio_config.sample_format); |
| + EXPECT_FALSE(audio_config.is_encrypted); |
| + |
| + stream->Close(); |
| + } |
| +} |
| + |
| +TEST_F(AudioOutputStreamTest, ChannelLayout) { |
| + ::media::ChannelLayout layout[] = {::media::CHANNEL_LAYOUT_MONO, |
| + ::media::CHANNEL_LAYOUT_STEREO}; |
| + for (size_t i = 0; i < arraysize(layout); ++i) { |
| + channel_layout_ = layout[i]; |
| + ::media::AudioOutputStream* stream = CreateStream(); |
| + ASSERT_TRUE(stream); |
| + EXPECT_TRUE(stream->Open()); |
| + |
| + FakeAudioPipelineDevice* audio_device = GetAudio(); |
| + ASSERT_TRUE(audio_device); |
| + const AudioConfig& audio_config = audio_device->config(); |
| + EXPECT_EQ(::media::ChannelLayoutToChannelCount(channel_layout_), |
| + audio_config.channel_number); |
| + |
| + stream->Close(); |
| + } |
| +} |
| + |
| +TEST_F(AudioOutputStreamTest, SampleRate) { |
| + sample_rate_ = ::media::AudioParameters::kAudioCDSampleRate; |
| + ::media::AudioOutputStream* stream = CreateStream(); |
| + ASSERT_TRUE(stream); |
| + EXPECT_TRUE(stream->Open()); |
| + |
| + FakeAudioPipelineDevice* audio_device = GetAudio(); |
| + ASSERT_TRUE(audio_device); |
| + const AudioConfig& audio_config = audio_device->config(); |
| + EXPECT_EQ(sample_rate_, audio_config.samples_per_second); |
| + |
| + stream->Close(); |
| +} |
| + |
| +TEST_F(AudioOutputStreamTest, BitsPerSample) { |
| + bits_per_sample_ = 16; |
| + ::media::AudioOutputStream* stream = CreateStream(); |
| + ASSERT_TRUE(stream); |
| + EXPECT_TRUE(stream->Open()); |
| + |
| + FakeAudioPipelineDevice* audio_device = GetAudio(); |
| + ASSERT_TRUE(audio_device); |
| + const AudioConfig& audio_config = audio_device->config(); |
| + EXPECT_EQ(bits_per_sample_ / 8, audio_config.bytes_per_channel); |
| + |
| + stream->Close(); |
| +} |
| + |
| +TEST_F(AudioOutputStreamTest, DeviceState) { |
| + ::media::AudioOutputStream* stream = CreateStream(); |
| + ASSERT_TRUE(stream); |
| + EXPECT_FALSE(GetAudio()); |
| + |
| + EXPECT_TRUE(stream->Open()); |
| + AudioPipelineDevice* audio_device = GetAudio(); |
| + ASSERT_TRUE(audio_device); |
| + FakeClockDevice* clock_device = GetClock(); |
| + ASSERT_TRUE(clock_device); |
| + EXPECT_EQ(AudioPipelineDevice::kStateIdle, audio_device->GetState()); |
| + EXPECT_EQ(MediaClockDevice::kStateIdle, clock_device->GetState()); |
| + EXPECT_EQ(1.f, clock_device->rate()); |
| + |
| + scoped_ptr<FakeAudioSourceCallback> source_callback( |
| + new FakeAudioSourceCallback); |
| + stream->Start(source_callback.get()); |
| + EXPECT_EQ(AudioPipelineDevice::kStateRunning, audio_device->GetState()); |
| + EXPECT_EQ(MediaClockDevice::kStateRunning, clock_device->GetState()); |
| + EXPECT_EQ(1.f, clock_device->rate()); |
| + |
| + stream->Stop(); |
| + EXPECT_EQ(AudioPipelineDevice::kStatePaused, audio_device->GetState()); |
| + EXPECT_EQ(MediaClockDevice::kStateIdle, clock_device->GetState()); |
| + EXPECT_EQ(0.f, clock_device->rate()); |
| + |
| + stream->Close(); |
| + EXPECT_FALSE(GetAudio()); |
| +} |
| + |
| +TEST_F(AudioOutputStreamTest, PushFrame) { |
| + ::media::AudioOutputStream* stream = CreateStream(); |
| + ASSERT_TRUE(stream); |
| + EXPECT_TRUE(stream->Open()); |
| + |
| + scoped_ptr<FakeAudioSourceCallback> source_callback( |
| + new FakeAudioSourceCallback); |
| + stream->Start(source_callback.get()); |
| + |
| + FakeAudioPipelineDevice* audio_device = GetAudio(); |
| + ASSERT_TRUE(audio_device); |
| + |
| + EXPECT_EQ(0u, audio_device->pushed_frame_count()); |
| + EXPECT_FALSE(audio_device->last_frame_decrypt_context()); |
| + EXPECT_FALSE(audio_device->last_frame_buffer()); |
| + EXPECT_FALSE(audio_device->last_frame_completion_cb()); |
| + |
| + // Let the stream push frames. |
| + message_loop_->RunUntilIdle(); |
| + |
| + EXPECT_LT(0u, audio_device->pushed_frame_count()); |
| + // DecryptContext is always NULL becuase of "raw" audio. |
| + EXPECT_FALSE(audio_device->last_frame_decrypt_context()); |
| + EXPECT_TRUE(audio_device->last_frame_buffer()); |
| + EXPECT_TRUE(audio_device->last_frame_completion_cb()); |
| + |
| + // Verify decoder buffer. |
| + ::media::AudioParameters audio_params = GetAudioParams(); |
| + const size_t expected_frame_size = |
| + static_cast<size_t>(audio_params.GetBytesPerBuffer()); |
| + const CastDecoderBuffer* buffer = audio_device->last_frame_buffer(); |
| + EXPECT_TRUE(buffer->data()); |
| + EXPECT_EQ(expected_frame_size, buffer->data_size()); |
| + EXPECT_FALSE(buffer->decrypt_config()); // Null because of raw audio. |
| + EXPECT_FALSE(buffer->end_of_stream()); |
| + |
| + // No error must be reported to source callback. |
| + EXPECT_FALSE(source_callback->error()); |
| + |
| + stream->Stop(); |
| + stream->Close(); |
| +} |
| + |
| +TEST_F(AudioOutputStreamTest, DeviceBusy) { |
| + ::media::AudioOutputStream* stream = CreateStream(); |
| + ASSERT_TRUE(stream); |
| + EXPECT_TRUE(stream->Open()); |
| + |
| + FakeAudioPipelineDevice* audio_device = GetAudio(); |
| + ASSERT_TRUE(audio_device); |
| + audio_device->set_pipeline_status( |
| + FakeAudioPipelineDevice::PIPELINE_STATUS_BUSY); |
| + |
| + scoped_ptr<FakeAudioSourceCallback> source_callback( |
| + new FakeAudioSourceCallback); |
| + stream->Start(source_callback.get()); |
| + |
| + // Let the stream push frames. |
| + message_loop_->RunUntilIdle(); |
| + |
| + // Make sure that one frame was pushed. |
| + EXPECT_EQ(1u, audio_device->pushed_frame_count()); |
| + // No error must be reported to source callback. |
| + EXPECT_FALSE(source_callback->error()); |
| + |
| + // Sleep for a few frames so that when the message loop is drained |
| + // AudioOutputStream would have the opportunity to push more frames. |
| + ::media::AudioParameters audio_params = GetAudioParams(); |
| + base::TimeDelta pause = audio_params.GetBufferDuration() * 5; |
| + base::PlatformThread::Sleep(pause); |
| + |
| + // Let the stream attempt to push more frames. |
| + message_loop_->RunUntilIdle(); |
| + // But since the device was busy, it must not push more frames. |
| + EXPECT_EQ(1u, audio_device->pushed_frame_count()); |
| + |
| + // Unblock the pipeline and verify that PushFrame resumes. |
| + audio_device->set_pipeline_status( |
| + FakeAudioPipelineDevice::PIPELINE_STATUS_OK); |
| + audio_device->last_frame_completion_cb()->Run( |
| + MediaComponentDevice::kFrameSuccess); |
| + base::PlatformThread::Sleep(pause); |
| + message_loop_->RunUntilIdle(); |
| + EXPECT_EQ(2u, audio_device->pushed_frame_count()); |
| + EXPECT_FALSE(source_callback->error()); |
| + |
| + // Make the pipeline busy again, but this time send kFrameFailed. |
| + audio_device->set_pipeline_status( |
| + FakeAudioPipelineDevice::PIPELINE_STATUS_BUSY); |
| + base::PlatformThread::Sleep(pause); |
| + message_loop_->RunUntilIdle(); |
| + EXPECT_EQ(3u, audio_device->pushed_frame_count()); |
| + EXPECT_FALSE(source_callback->error()); |
| + |
| + audio_device->last_frame_completion_cb()->Run( |
| + MediaComponentDevice::kFrameFailed); |
| + EXPECT_TRUE(source_callback->error()); |
| + |
| + stream->Stop(); |
| + stream->Close(); |
| +} |
| + |
| +TEST_F(AudioOutputStreamTest, DeviceError) { |
| + ::media::AudioOutputStream* stream = CreateStream(); |
| + ASSERT_TRUE(stream); |
| + EXPECT_TRUE(stream->Open()); |
| + |
| + FakeAudioPipelineDevice* audio_device = GetAudio(); |
| + ASSERT_TRUE(audio_device); |
| + audio_device->set_pipeline_status( |
| + FakeAudioPipelineDevice::PIPELINE_STATUS_ERROR); |
| + |
| + scoped_ptr<FakeAudioSourceCallback> source_callback( |
| + new FakeAudioSourceCallback); |
| + stream->Start(source_callback.get()); |
| + |
| + // Let the stream push frames. |
| + message_loop_->RunUntilIdle(); |
| + |
| + // Make sure that AudioOutputStream attempted to push the initial frame. |
| + EXPECT_LT(0u, audio_device->pushed_frame_count()); |
| + // AudioOutputStream must report error to source callback. |
| + EXPECT_TRUE(source_callback->error()); |
| + |
| + stream->Stop(); |
| + stream->Close(); |
| +} |
| + |
| +TEST_F(AudioOutputStreamTest, Volume) { |
| + ::media::AudioOutputStream* stream = CreateStream(); |
| + ASSERT_TRUE(stream); |
| + ASSERT_TRUE(stream->Open()); |
| + FakeAudioPipelineDevice* audio_device = GetAudio(); |
| + ASSERT_TRUE(audio_device); |
| + |
| + double volume = 0.0; |
| + stream->GetVolume(&volume); |
| + EXPECT_EQ(1.0, volume); |
| + EXPECT_EQ(1.0f, audio_device->volume_multiplier()); |
| + |
| + stream->SetVolume(0.5); |
| + stream->GetVolume(&volume); |
| + EXPECT_EQ(0.5, volume); |
| + EXPECT_EQ(0.5f, audio_device->volume_multiplier()); |
| + |
| + stream->Close(); |
| +} |
| + |
| +} // namespace |
| +} // namespace media |
| +} // namespace chromecast |