| 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..9ae9cd1d826dc626c509c86aa9e7a082ccc65898
|
| --- /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[] = "";
|
| +} // 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(kSampleFormatPlanarS16, 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
|
|
|