blob: fdb44aa34d0eeba073f7c1d561ff1648de3835cc [file] [log] [blame]
// 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 "base/bind.h"
#include "base/synchronization/waitable_event.h"
#include "chromecast/base/metrics/cast_metrics_test_helper.cc"
#include "chromecast/media/audio/cast_audio_manager.h"
#include "chromecast/media/audio/cast_audio_output_stream.h"
#include "chromecast/media/base/media_message_loop.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 chromecast {
namespace media {
namespace {
const char kDefaultDeviceId[] = "";
void RunUntilIdle(base::TaskRunner* task_runner) {
base::WaitableEvent completion_event(false, false);
task_runner->PostTask(FROM_HERE,
base::Bind(&base::WaitableEvent::Signal,
base::Unretained(&completion_event)));
completion_event.Wait();
}
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;
default:
NOTREACHED();
}
// This will never be reached but is necessary for compiler warnings.
return kFrameFailed;
}
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 CastAudioManager {
public:
FakeAudioManager()
: CastAudioManager(nullptr), media_pipeline_backend_(nullptr) {}
~FakeAudioManager() override {}
// CastAudioManager overrides.
scoped_ptr<MediaPipelineBackend> CreateMediaPipelineBackend() override {
DCHECK(media::MediaMessageLoop::GetTaskRunner()->BelongsToCurrentThread());
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;
CastAudioManager::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 CastAudioOutputStreamTest : public ::testing::Test {
public:
CastAudioOutputStreamTest()
: 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) {}
~CastAudioOutputStreamTest() override {}
protected:
void SetUp() override {
metrics::InitializeMetricsHelperForTesting();
audio_manager_.reset(new FakeAudioManager);
audio_task_runner_ = audio_manager_->GetTaskRunner();
backend_task_runner_ = media::MediaMessageLoop::GetTaskRunner();
}
void TearDown() override {
audio_manager_.reset();
}
::media::AudioParameters GetAudioParams() {
return ::media::AudioParameters(format_, channel_layout_, sample_rate_,
bits_per_sample_, frames_per_buffer_);
}
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;
}
// Synchronous utility functions.
::media::AudioOutputStream* CreateStream() {
::media::AudioOutputStream* stream = nullptr;
base::WaitableEvent completion_event(false, false);
audio_task_runner_->PostTask(
FROM_HERE,
base::Bind(&CastAudioOutputStreamTest::CreateStreamOnAudioThread,
base::Unretained(this), GetAudioParams(), &stream,
&completion_event));
completion_event.Wait();
return stream;
}
bool OpenStream(::media::AudioOutputStream* stream) {
DCHECK(stream);
bool success = false;
base::WaitableEvent completion_event(false, false);
audio_task_runner_->PostTask(
FROM_HERE,
base::Bind(&CastAudioOutputStreamTest::OpenStreamOnAudioThread,
base::Unretained(this), stream, &success,
&completion_event));
completion_event.Wait();
// Drain the backend task runner so that appropriate states are set on
// the backend pipeline devices.
RunUntilIdle(backend_task_runner_.get());
return success;
}
void CloseStream(::media::AudioOutputStream* stream) {
audio_task_runner_->PostTask(FROM_HERE,
base::Bind(&::media::AudioOutputStream::Close,
base::Unretained(stream)));
RunUntilIdle(audio_task_runner_.get());
RunUntilIdle(backend_task_runner_.get());
// Backend task runner may have posted more tasks to the audio task runner.
// So we need to drain it once more.
RunUntilIdle(audio_task_runner_.get());
}
void StartStream(
::media::AudioOutputStream* stream,
::media::AudioOutputStream::AudioSourceCallback* source_callback) {
audio_task_runner_->PostTask(
FROM_HERE, base::Bind(&::media::AudioOutputStream::Start,
base::Unretained(stream), source_callback));
// Drain the audio task runner twice so that tasks posted by
// media::AudioOutputStream::Start are run as well.
RunUntilIdle(audio_task_runner_.get());
RunUntilIdle(audio_task_runner_.get());
// Drain the backend task runner so that appropriate states are set on
// the backend pipeline devices.
RunUntilIdle(backend_task_runner_.get());
// Drain the audio task runner again to run the tasks posted by the
// backend on audio task runner.
RunUntilIdle(audio_task_runner_.get());
}
void StopStream(::media::AudioOutputStream* stream) {
audio_task_runner_->PostTask(FROM_HERE,
base::Bind(&::media::AudioOutputStream::Stop,
base::Unretained(stream)));
RunUntilIdle(audio_task_runner_.get());
// Drain the backend task runner so that appropriate states are set on
// the backend pipeline devices.
RunUntilIdle(backend_task_runner_.get());
}
void SetStreamVolume(::media::AudioOutputStream* stream, double volume) {
audio_task_runner_->PostTask(
FROM_HERE, base::Bind(&::media::AudioOutputStream::SetVolume,
base::Unretained(stream), volume));
RunUntilIdle(audio_task_runner_.get());
// Drain the backend task runner so that appropriate states are set on
// the backend pipeline devices.
RunUntilIdle(backend_task_runner_.get());
}
double GetStreamVolume(::media::AudioOutputStream* stream) {
double volume = 0.0;
audio_task_runner_->PostTask(
FROM_HERE, base::Bind(&::media::AudioOutputStream::GetVolume,
base::Unretained(stream), &volume));
RunUntilIdle(audio_task_runner_.get());
// No need to drain the backend task runner because getting the volume
// does not involve posting any task to the backend.
return volume;
}
void CreateStreamOnAudioThread(const ::media::AudioParameters& audio_params,
::media::AudioOutputStream** stream,
base::WaitableEvent* completion_event) {
DCHECK(audio_task_runner_->BelongsToCurrentThread());
*stream = audio_manager_->MakeAudioOutputStream(GetAudioParams(),
kDefaultDeviceId);
completion_event->Signal();
}
void OpenStreamOnAudioThread(::media::AudioOutputStream* stream,
bool* success,
base::WaitableEvent* completion_event) {
DCHECK(audio_task_runner_->BelongsToCurrentThread());
*success = stream->Open();
completion_event->Signal();
}
scoped_ptr<FakeAudioManager> audio_manager_;
scoped_refptr<base::SingleThreadTaskRunner> audio_task_runner_;
scoped_refptr<base::SingleThreadTaskRunner> backend_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(CastAudioOutputStreamTest, 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(OpenStream(stream));
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);
CloseStream(stream);
}
}
TEST_F(CastAudioOutputStreamTest, 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(OpenStream(stream));
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);
CloseStream(stream);
}
}
TEST_F(CastAudioOutputStreamTest, SampleRate) {
sample_rate_ = ::media::AudioParameters::kAudioCDSampleRate;
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
EXPECT_TRUE(OpenStream(stream));
FakeAudioPipelineDevice* audio_device = GetAudio();
ASSERT_TRUE(audio_device);
const AudioConfig& audio_config = audio_device->config();
EXPECT_EQ(sample_rate_, audio_config.samples_per_second);
CloseStream(stream);
}
TEST_F(CastAudioOutputStreamTest, BitsPerSample) {
bits_per_sample_ = 16;
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
EXPECT_TRUE(OpenStream(stream));
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);
CloseStream(stream);
}
TEST_F(CastAudioOutputStreamTest, DeviceState) {
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
EXPECT_FALSE(GetAudio());
EXPECT_TRUE(OpenStream(stream));
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);
StartStream(stream, source_callback.get());
EXPECT_EQ(AudioPipelineDevice::kStateRunning, audio_device->GetState());
EXPECT_EQ(MediaClockDevice::kStateRunning, clock_device->GetState());
EXPECT_EQ(1.f, clock_device->rate());
StopStream(stream);
EXPECT_EQ(AudioPipelineDevice::kStateRunning, audio_device->GetState());
EXPECT_EQ(MediaClockDevice::kStateRunning, clock_device->GetState());
EXPECT_EQ(0.f, clock_device->rate());
CloseStream(stream);
EXPECT_FALSE(GetAudio());
}
TEST_F(CastAudioOutputStreamTest, PushFrame) {
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
EXPECT_TRUE(OpenStream(stream));
FakeAudioPipelineDevice* audio_device = GetAudio();
ASSERT_TRUE(audio_device);
// Verify initial state.
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());
scoped_ptr<FakeAudioSourceCallback> source_callback(
new FakeAudioSourceCallback);
StartStream(stream, source_callback.get());
StopStream(stream);
// Verify that the stream pushed frames to the backend.
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());
CloseStream(stream);
}
TEST_F(CastAudioOutputStreamTest, DeviceBusy) {
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
EXPECT_TRUE(OpenStream(stream));
FakeAudioPipelineDevice* audio_device = GetAudio();
ASSERT_TRUE(audio_device);
audio_device->set_pipeline_status(
FakeAudioPipelineDevice::PIPELINE_STATUS_BUSY);
scoped_ptr<FakeAudioSourceCallback> source_callback(
new FakeAudioSourceCallback);
StartStream(stream, source_callback.get());
// 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 and verify that more frames were not pushed
// because the backend device was busy.
::media::AudioParameters audio_params = GetAudioParams();
base::TimeDelta pause = audio_params.GetBufferDuration() * 5;
base::PlatformThread::Sleep(pause);
RunUntilIdle(audio_task_runner_.get());
RunUntilIdle(backend_task_runner_.get());
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);
RunUntilIdle(audio_task_runner_.get());
RunUntilIdle(backend_task_runner_.get());
EXPECT_LT(1u, audio_device->pushed_frame_count());
EXPECT_FALSE(source_callback->error());
StopStream(stream);
CloseStream(stream);
}
TEST_F(CastAudioOutputStreamTest, DeviceError) {
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
EXPECT_TRUE(OpenStream(stream));
FakeAudioPipelineDevice* audio_device = GetAudio();
ASSERT_TRUE(audio_device);
audio_device->set_pipeline_status(
FakeAudioPipelineDevice::PIPELINE_STATUS_ERROR);
scoped_ptr<FakeAudioSourceCallback> source_callback(
new FakeAudioSourceCallback);
StartStream(stream, source_callback.get());
// 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());
StopStream(stream);
CloseStream(stream);
}
TEST_F(CastAudioOutputStreamTest, Volume) {
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
ASSERT_TRUE(OpenStream(stream));
FakeAudioPipelineDevice* audio_device = GetAudio();
ASSERT_TRUE(audio_device);
double volume = GetStreamVolume(stream);
EXPECT_EQ(1.0, volume);
EXPECT_EQ(1.0f, audio_device->volume_multiplier());
SetStreamVolume(stream, 0.5);
volume = GetStreamVolume(stream);
EXPECT_EQ(0.5, volume);
EXPECT_EQ(0.5f, audio_device->volume_multiplier());
CloseStream(stream);
}
} // namespace
} // namespace media
} // namespace chromecast