// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromecast/media/audio/cast_audio_output_stream.h"
#include <stdint.h>
#include <memory>
#include <utility>
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "chromecast/media/api/cma_backend.h"
#include "chromecast/media/api/decoder_buffer_base.h"
#include "chromecast/media/api/test/mock_cma_backend_factory.h"
#include "chromecast/media/audio/cast_audio_manager.h"
#include "chromecast/media/audio/cast_audio_mixer.h"
#include "chromecast/media/audio/mock_cast_audio_manager_helper_delegate.h"
#include "chromecast/media/base/default_monotonic_clock.h"
#include "chromecast/public/task_runner.h"
#include "chromecast/public/volume_control.h"
#include "media/audio/mock_audio_source_callback.h"
#include "media/audio/test_audio_thread.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using testing::Invoke;
using testing::NiceMock;
using testing::Return;
namespace {
constexpr char kSessionId[] = "01234567-89ab-cdef-0123-456789abcdef";
} // namespace
namespace chromecast {
namespace media {
namespace {
const char kDefaultDeviceId[] = "";
const int64_t kDelayUs = 123;
const double kDefaultVolume = 1.0f;
int on_more_data_call_count_ = 0;
int OnMoreData(base::TimeDelta /* delay */,
base::TimeTicks /* delay_timestamp */,
const ::media::AudioGlitchInfo& /* glitch_info */,
::media::AudioBus* dest) {
on_more_data_call_count_++;
dest->Zero();
return dest->frames();
}
} // namespace
class NotifyPushBufferCompleteTask : public chromecast::TaskRunner::Task {
public:
explicit NotifyPushBufferCompleteTask(CmaBackend::Decoder::Delegate* delegate)
: delegate_(delegate) {}
~NotifyPushBufferCompleteTask() override = default;
void Run() override {
delegate_->OnPushBufferComplete(CmaBackend::BufferStatus::kBufferSuccess);
}
private:
CmaBackend::Decoder::Delegate* const delegate_;
};
class FakeAudioDecoder : public CmaBackend::AudioDecoder {
public:
enum TestingPipelineStatus {
PIPELINE_STATUS_OK,
PIPELINE_STATUS_BUSY,
PIPELINE_STATUS_ERROR,
PIPELINE_STATUS_ASYNC_ERROR,
};
explicit FakeAudioDecoder(const MediaPipelineDeviceParams& params)
: params_(params),
volume_(kDefaultVolume),
pipeline_status_(PIPELINE_STATUS_OK),
pending_push_(false),
pushed_buffer_count_(0),
delegate_(nullptr) {}
~FakeAudioDecoder() override {}
// CmaBackend::AudioDecoder implementation:
void SetDelegate(Delegate* delegate) override {
DCHECK(delegate);
delegate_ = delegate;
}
BufferStatus PushBuffer(scoped_refptr<DecoderBufferBase> buffer) override {
last_buffer_ = std::move(buffer);
++pushed_buffer_count_;
switch (pipeline_status_) {
case PIPELINE_STATUS_OK:
return CmaBackend::BufferStatus::kBufferSuccess;
case PIPELINE_STATUS_BUSY:
pending_push_ = true;
return CmaBackend::BufferStatus::kBufferPending;
case PIPELINE_STATUS_ERROR:
return CmaBackend::BufferStatus::kBufferFailed;
case PIPELINE_STATUS_ASYNC_ERROR:
delegate_->OnDecoderError();
return CmaBackend::BufferStatus::kBufferSuccess;
default:
NOTREACHED_IN_MIGRATION();
return CmaBackend::BufferStatus::kBufferFailed;
}
}
void GetStatistics(Statistics* statistics) override {}
bool SetConfig(const AudioConfig& config) override {
config_ = config;
return true;
}
bool SetVolume(float volume) override {
volume_ = volume;
return true;
}
RenderingDelay GetRenderingDelay() override { return rendering_delay_; }
AudioTrackTimestamp GetAudioTrackTimestamp() override {
return AudioTrackTimestamp();
}
int GetStartThresholdInFrames() override {
return 0;
}
bool RequiresDecryption() override { return false; }
const AudioConfig& config() const { return config_; }
float volume() const { return volume_; }
void set_pipeline_status(TestingPipelineStatus status) {
if (status == PIPELINE_STATUS_OK && pending_push_) {
pending_push_ = false;
params_.task_runner->PostTask(new NotifyPushBufferCompleteTask(delegate_),
0);
}
pipeline_status_ = status;
}
void set_rendering_delay(RenderingDelay rendering_delay) {
rendering_delay_ = rendering_delay;
}
unsigned pushed_buffer_count() const { return pushed_buffer_count_; }
const DecoderBufferBase* last_buffer() { return last_buffer_.get(); }
private:
const MediaPipelineDeviceParams params_;
AudioConfig config_;
float volume_;
TestingPipelineStatus pipeline_status_;
bool pending_push_;
int pushed_buffer_count_;
scoped_refptr<DecoderBufferBase> last_buffer_;
Delegate* delegate_;
RenderingDelay rendering_delay_;
};
class FakeCmaBackend : public CmaBackend {
public:
enum State { kStateStopped, kStateRunning, kStatePaused };
explicit FakeCmaBackend(const MediaPipelineDeviceParams& params)
: params_(params), state_(kStateStopped), audio_decoder_(nullptr) {}
~FakeCmaBackend() override {}
// CmaBackend implementation:
AudioDecoder* CreateAudioDecoder() override {
DCHECK(!audio_decoder_);
audio_decoder_ = std::make_unique<FakeAudioDecoder>(params_);
return audio_decoder_.get();
}
VideoDecoder* CreateVideoDecoder() override {
NOTREACHED_IN_MIGRATION();
return nullptr;
}
bool Initialize() override { return true; }
bool Start(int64_t start_pts) override {
EXPECT_EQ(kStateStopped, state_);
state_ = kStateRunning;
return true;
}
void Stop() override {
EXPECT_TRUE(state_ == kStateRunning || state_ == kStatePaused);
state_ = kStateStopped;
}
bool Pause() override {
EXPECT_EQ(kStateRunning, state_);
state_ = kStatePaused;
return true;
}
bool Resume() override {
EXPECT_EQ(kStatePaused, state_);
state_ = kStateRunning;
return true;
}
int64_t GetCurrentPts() override { return 0; }
bool SetPlaybackRate(float rate) override { return true; }
void LogicalPause() override {}
void LogicalResume() override {}
MediaPipelineDeviceParams params() const { return params_; }
State state() const { return state_; }
FakeAudioDecoder* audio_decoder() const { return audio_decoder_.get(); }
private:
const MediaPipelineDeviceParams params_;
State state_;
std::unique_ptr<FakeAudioDecoder> audio_decoder_;
};
class CastAudioOutputStreamTest : public ::testing::Test {
public:
CastAudioOutputStreamTest()
: audio_thread_("CastAudioThread"),
task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
format_(::media::AudioParameters::AUDIO_PCM_LINEAR),
channel_layout_config_(::media::ChannelLayoutConfig::Mono()),
sample_rate_(::media::AudioParameters::kAudioCDSampleRate),
frames_per_buffer_(256) {}
void SetUp() override {
CreateAudioManagerForTesting();
SetUpCmaBackendFactory();
}
void TearDown() override {
RunThreadsUntilIdle();
audio_manager_->Shutdown();
audio_thread_.Stop();
}
protected:
CmaBackendFactory* GetCmaBackendFactory() {
return mock_backend_factory_.get();
}
void CreateAudioManagerForTesting(bool use_mixer = false) {
// Only one AudioManager may exist at a time, so destroy the one we're
// currently holding before creating a new one.
// Flush the message loop to run any shutdown tasks posted by AudioManager.
if (audio_manager_) {
audio_manager_->Shutdown();
audio_manager_.reset();
}
if (audio_thread_.IsRunning())
audio_thread_.Stop();
CHECK(audio_thread_.StartAndWaitForTesting());
mock_backend_factory_ = std::make_unique<MockCmaBackendFactory>();
audio_manager_ = base::WrapUnique(new CastAudioManager(
std::make_unique<::media::TestAudioThread>(), nullptr, &delegate_,
base::BindRepeating(&CastAudioOutputStreamTest::GetCmaBackendFactory,
base::Unretained(this)),
task_environment_.GetMainThreadTaskRunner(),
audio_thread_.task_runner(), use_mixer,
true /* force_use_cma_backend_for_output*/));
// A few AudioManager implementations post initialization tasks to
// audio thread. Flush the thread to ensure that |audio_manager_| is
// initialized and ready to use before returning from this function.
// TODO(alokp): We should perhaps do this in AudioManager::Create().
RunThreadsUntilIdle();
}
void SetUpCmaBackendFactory() {
EXPECT_CALL(*mock_backend_factory_, CreateBackend(_))
.WillRepeatedly(Invoke([this](const MediaPipelineDeviceParams& params) {
auto fake_cma_backend = std::make_unique<FakeCmaBackend>(params);
cma_backend_ = fake_cma_backend.get();
return fake_cma_backend;
}));
EXPECT_EQ(mock_backend_factory_.get(),
audio_manager_->helper_.GetCmaBackendFactory());
}
void RunThreadsUntilIdle() {
task_environment_.RunUntilIdle();
audio_thread_.FlushForTesting();
}
static void PauseAndWait(base::WaitableEvent* pause_event,
base::WaitableEvent* resume_event) {
pause_event->Signal();
resume_event->Wait();
}
// Synchronously pause the audio thread. This function guarantees that
// the audio thread will be paused before it returns.
void PauseAudioThread() {
audio_thread_pause_ = std::make_unique<base::WaitableEvent>(
base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED);
audio_thread_resume_ = std::make_unique<base::WaitableEvent>(
base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED);
audio_thread_.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&PauseAndWait, audio_thread_pause_.get(),
audio_thread_resume_.get()));
audio_thread_pause_->Wait();
}
// Resume a paused audio thread by signalling to it.
void ResumeAudioThreadAsync() {
if (audio_thread_resume_) {
audio_thread_resume_->Signal();
}
}
::media::AudioParameters GetAudioParams() {
return ::media::AudioParameters(format_, channel_layout_config_,
sample_rate_, frames_per_buffer_);
}
FakeAudioDecoder* GetAudioDecoder() {
return (cma_backend_ ? cma_backend_->audio_decoder() : nullptr);
}
::media::AudioOutputStream* CreateStream() {
return audio_manager_->MakeAudioOutputStream(
GetAudioParams(), kDefaultDeviceId,
::media::AudioManager::LogCallback());
}
base::Thread audio_thread_;
base::test::TaskEnvironment task_environment_;
std::unique_ptr<base::WaitableEvent> audio_thread_pause_;
std::unique_ptr<base::WaitableEvent> audio_thread_resume_;
std::unique_ptr<MockCmaBackendFactory> mock_backend_factory_;
FakeCmaBackend* cma_backend_ = nullptr;
MockCastAudioManagerHelperDelegate delegate_;
std::unique_ptr<CastAudioManager> audio_manager_;
// AudioParameters used to create AudioOutputStream.
// Tests can modify these parameters before calling CreateStream.
::media::AudioParameters::Format format_;
::media::ChannelLayoutConfig channel_layout_config_;
int sample_rate_;
int frames_per_buffer_;
};
TEST_F(CastAudioOutputStreamTest, CloseWithoutStart) {
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
ASSERT_TRUE(stream->Open());
RunThreadsUntilIdle();
stream->Close();
}
TEST_F(CastAudioOutputStreamTest, CloseWithoutStop) {
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
ASSERT_TRUE(stream->Open());
RunThreadsUntilIdle();
::media::MockAudioSourceCallback source_callback;
EXPECT_CALL(source_callback, OnMoreData(_, _, _, _))
.WillRepeatedly(Invoke(OnMoreData));
stream->Start(&source_callback);
RunThreadsUntilIdle();
stream->Close();
RunThreadsUntilIdle();
}
TEST_F(CastAudioOutputStreamTest, StartImmediatelyAfterOpen) {
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
ASSERT_TRUE(stream->Open());
::media::MockAudioSourceCallback source_callback;
EXPECT_CALL(source_callback, OnMoreData(_, _, _, _))
.WillRepeatedly(Invoke(OnMoreData));
stream->Start(&source_callback);
RunThreadsUntilIdle();
EXPECT_EQ(FakeCmaBackend::kStateRunning, cma_backend_->state());
stream->Stop();
RunThreadsUntilIdle();
stream->Close();
RunThreadsUntilIdle();
}
TEST_F(CastAudioOutputStreamTest, SetVolumeImmediatelyAfterOpen) {
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
ASSERT_TRUE(stream->Open());
stream->SetVolume(0.5);
RunThreadsUntilIdle();
FakeAudioDecoder* audio_decoder = GetAudioDecoder();
double volume = 0.0;
stream->GetVolume(&volume);
EXPECT_EQ(0.5, volume);
EXPECT_EQ(0.5f, audio_decoder->volume());
stream->Stop();
stream->Close();
}
TEST_F(CastAudioOutputStreamTest, StartStopStart) {
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
ASSERT_TRUE(stream->Open());
RunThreadsUntilIdle();
FakeAudioDecoder* audio_decoder = GetAudioDecoder();
ASSERT_TRUE(audio_decoder);
::media::MockAudioSourceCallback source_callback;
EXPECT_CALL(source_callback, OnMoreData(_, _, _, _))
.WillRepeatedly(Invoke(OnMoreData));
stream->Start(&source_callback);
RunThreadsUntilIdle();
stream->Stop();
EXPECT_CALL(source_callback, OnMoreData(_, _, _, _)).Times(0);
RunThreadsUntilIdle();
testing::Mock::VerifyAndClearExpectations(&source_callback);
// Ensure we fetch new data when restarting.
EXPECT_CALL(source_callback, OnMoreData(_, _, _, _))
.WillRepeatedly(Invoke(OnMoreData));
int last_on_more_data_call_count = on_more_data_call_count_;
stream->Start(&source_callback);
RunThreadsUntilIdle();
EXPECT_GT(on_more_data_call_count_, last_on_more_data_call_count);
EXPECT_EQ(FakeCmaBackend::kStateRunning, cma_backend_->state());
stream->Stop();
stream->Close();
}
TEST_F(CastAudioOutputStreamTest, StopPreventsCallbacks) {
// Stream API details that Stop is synchronous and prevents calls to callback.
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
ASSERT_TRUE(stream->Open());
RunThreadsUntilIdle();
FakeAudioDecoder* audio_decoder = GetAudioDecoder();
ASSERT_TRUE(audio_decoder);
::media::MockAudioSourceCallback source_callback;
EXPECT_CALL(source_callback, OnMoreData(_, _, _, _))
.WillRepeatedly(Invoke(OnMoreData));
stream->Start(&source_callback);
RunThreadsUntilIdle();
stream->SetVolume(0.5);
stream->Stop();
// TODO(steinbock) Make this fail more reliably when stream->Stop() returns
// asynchronously.
stream->Close();
EXPECT_CALL(source_callback, OnMoreData(_, _, _, _)).Times(0);
RunThreadsUntilIdle();
}
TEST_F(CastAudioOutputStreamTest, ClosePreventsCallbacks) {
// Stream API details that Close is synchronous and prevents calls to
// callback.
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
ASSERT_TRUE(stream->Open());
RunThreadsUntilIdle();
::media::MockAudioSourceCallback source_callback;
EXPECT_CALL(source_callback, OnMoreData(_, _, _, _))
.WillRepeatedly(Invoke(OnMoreData));
stream->Start(&source_callback);
RunThreadsUntilIdle();
// Pause the audio thread from running tasks before calling Close() to
// prevent it from processing OnMoreData() calls after setting the
// expectation and before calling Close().
PauseAudioThread();
// Once the audio thread resumes work, push/fill calls posted to the audio
// thread should no longer call OnMoreData().
EXPECT_CALL(source_callback, OnMoreData(_, _, _, _)).Times(0);
stream->Close();
ResumeAudioThreadAsync();
RunThreadsUntilIdle();
}
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 < std::size(format); ++i) {
format_ = format[i];
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
EXPECT_TRUE(stream->Open());
RunThreadsUntilIdle();
FakeAudioDecoder* audio_decoder = GetAudioDecoder();
ASSERT_TRUE(audio_decoder);
const AudioConfig& audio_config = audio_decoder->config();
EXPECT_EQ(kCodecPCM, audio_config.codec);
EXPECT_EQ(kSampleFormatS16, audio_config.sample_format);
EXPECT_EQ(audio_config.encryption_scheme, EncryptionScheme::kUnencrypted);
stream->Close();
}
}
TEST_F(CastAudioOutputStreamTest, ChannelLayout) {
::media::ChannelLayoutConfig layout[] = {
::media::ChannelLayoutConfig::Mono(),
::media::ChannelLayoutConfig::Stereo()};
for (size_t i = 0; i < std::size(layout); ++i) {
channel_layout_config_ = layout[i];
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
EXPECT_TRUE(stream->Open());
RunThreadsUntilIdle();
FakeAudioDecoder* audio_decoder = GetAudioDecoder();
ASSERT_TRUE(audio_decoder);
const AudioConfig& audio_config = audio_decoder->config();
EXPECT_EQ(::media::ChannelLayoutToChannelCount(
channel_layout_config_.channel_layout()),
audio_config.channel_number);
stream->Close();
}
}
TEST_F(CastAudioOutputStreamTest, SampleRate) {
sample_rate_ = ::media::AudioParameters::kAudioCDSampleRate;
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
EXPECT_TRUE(stream->Open());
RunThreadsUntilIdle();
FakeAudioDecoder* audio_decoder = GetAudioDecoder();
ASSERT_TRUE(audio_decoder);
const AudioConfig& audio_config = audio_decoder->config();
EXPECT_EQ(sample_rate_, audio_config.samples_per_second);
stream->Close();
}
TEST_F(CastAudioOutputStreamTest, DeviceState) {
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
EXPECT_TRUE(stream->Open());
RunThreadsUntilIdle();
FakeAudioDecoder* audio_decoder = GetAudioDecoder();
ASSERT_TRUE(audio_decoder);
ASSERT_TRUE(cma_backend_);
EXPECT_EQ(FakeCmaBackend::kStateStopped, cma_backend_->state());
::media::MockAudioSourceCallback source_callback;
EXPECT_CALL(source_callback, OnMoreData(_, _, _, _))
.WillRepeatedly(Invoke(OnMoreData));
stream->Start(&source_callback);
RunThreadsUntilIdle();
EXPECT_EQ(FakeCmaBackend::kStateRunning, cma_backend_->state());
stream->Stop();
RunThreadsUntilIdle();
EXPECT_EQ(FakeCmaBackend::kStatePaused, cma_backend_->state());
stream->Flush();
RunThreadsUntilIdle();
EXPECT_EQ(FakeCmaBackend::kStateStopped, cma_backend_->state());
stream->Close();
}
TEST_F(CastAudioOutputStreamTest, PushFrame) {
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
EXPECT_TRUE(stream->Open());
RunThreadsUntilIdle();
FakeAudioDecoder* audio_decoder = GetAudioDecoder();
ASSERT_TRUE(audio_decoder);
// Verify initial state.
EXPECT_EQ(0u, audio_decoder->pushed_buffer_count());
EXPECT_FALSE(audio_decoder->last_buffer());
::media::MockAudioSourceCallback source_callback;
EXPECT_CALL(source_callback, OnMoreData(_, _, _, _))
.WillRepeatedly(Invoke(OnMoreData));
// No error must be reported to source callback.
EXPECT_CALL(source_callback, OnError(_)).Times(0);
stream->Start(&source_callback);
RunThreadsUntilIdle();
stream->Stop();
// Verify that the stream pushed frames to the backend.
EXPECT_LT(0u, audio_decoder->pushed_buffer_count());
EXPECT_TRUE(audio_decoder->last_buffer());
// Verify decoder buffer.
::media::AudioParameters audio_params = GetAudioParams();
const size_t expected_frame_size =
audio_params.GetBytesPerBuffer(::media::kSampleFormatS16);
const DecoderBufferBase* buffer = audio_decoder->last_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());
stream->Close();
}
TEST_F(CastAudioOutputStreamTest, PushFrameAfterStop) {
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
EXPECT_TRUE(stream->Open());
RunThreadsUntilIdle();
FakeAudioDecoder* audio_decoder = GetAudioDecoder();
ASSERT_TRUE(audio_decoder);
::media::MockAudioSourceCallback source_callback;
EXPECT_CALL(source_callback, OnMoreData(_, _, _, _))
.WillRepeatedly(Invoke(OnMoreData));
// No error must be reported to source callback.
EXPECT_CALL(source_callback, OnError(_)).Times(0);
stream->Start(&source_callback);
RunThreadsUntilIdle();
// Verify that the stream pushed frames to the backend.
EXPECT_LT(0u, audio_decoder->pushed_buffer_count());
EXPECT_TRUE(audio_decoder->last_buffer());
stream->Stop();
ASSERT_TRUE(cma_backend_);
base::TimeDelta duration = GetAudioParams().GetBufferDuration() * 2;
task_environment_.FastForwardBy(duration);
RunThreadsUntilIdle();
stream->Close();
}
TEST_F(CastAudioOutputStreamTest, PushFrameAfterClose) {
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
EXPECT_TRUE(stream->Open());
RunThreadsUntilIdle();
FakeAudioDecoder* audio_decoder = GetAudioDecoder();
ASSERT_TRUE(audio_decoder);
::media::MockAudioSourceCallback source_callback;
EXPECT_CALL(source_callback, OnMoreData(_, _, _, _))
.WillRepeatedly(Invoke(OnMoreData));
// No error must be reported to source callback.
EXPECT_CALL(source_callback, OnError(_)).Times(0);
stream->Start(&source_callback);
RunThreadsUntilIdle();
// Verify that the stream pushed frames to the backend.
EXPECT_LT(0u, audio_decoder->pushed_buffer_count());
EXPECT_TRUE(audio_decoder->last_buffer());
stream->Close();
ASSERT_TRUE(cma_backend_);
base::TimeDelta duration = GetAudioParams().GetBufferDuration() * 2;
task_environment_.FastForwardBy(duration);
RunThreadsUntilIdle();
}
// TODO(steinbock) fix test on server and reenable.
TEST_F(CastAudioOutputStreamTest, DISABLED_DeviceBusy) {
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
EXPECT_TRUE(stream->Open());
RunThreadsUntilIdle();
FakeAudioDecoder* audio_decoder = GetAudioDecoder();
ASSERT_TRUE(audio_decoder);
audio_decoder->set_pipeline_status(FakeAudioDecoder::PIPELINE_STATUS_BUSY);
::media::MockAudioSourceCallback source_callback;
EXPECT_CALL(source_callback, OnMoreData(_, _, _, _))
.WillRepeatedly(Invoke(OnMoreData));
// No error must be reported to source callback.
EXPECT_CALL(source_callback, OnError(_)).Times(0);
stream->Start(&source_callback);
RunThreadsUntilIdle();
// Make sure that one frame was pushed.
EXPECT_EQ(1u, audio_decoder->pushed_buffer_count());
// Sleep for a few frames and verify that more frames were not pushed
// because the backend device was busy.
RunThreadsUntilIdle();
EXPECT_EQ(1u, audio_decoder->pushed_buffer_count());
// Unblock the pipeline and verify that PushFrame resumes.
audio_decoder->set_pipeline_status(FakeAudioDecoder::PIPELINE_STATUS_OK);
RunThreadsUntilIdle();
stream->Stop();
stream->Close();
RunThreadsUntilIdle();
EXPECT_LT(1u, audio_decoder->pushed_buffer_count());
}
TEST_F(CastAudioOutputStreamTest, DeviceError) {
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
EXPECT_TRUE(stream->Open());
RunThreadsUntilIdle();
FakeAudioDecoder* audio_decoder = GetAudioDecoder();
ASSERT_TRUE(audio_decoder);
audio_decoder->set_pipeline_status(FakeAudioDecoder::PIPELINE_STATUS_ERROR);
::media::MockAudioSourceCallback source_callback;
EXPECT_CALL(source_callback, OnMoreData(_, _, _, _))
.WillRepeatedly(Invoke(OnMoreData));
// AudioOutputStream must report error to source callback.
EXPECT_CALL(source_callback, OnError(_));
stream->Start(&source_callback);
RunThreadsUntilIdle();
// Make sure that AudioOutputStream attempted to push the initial frame.
EXPECT_LT(0u, audio_decoder->pushed_buffer_count());
stream->Stop();
stream->Close();
}
TEST_F(CastAudioOutputStreamTest, DeviceAsyncError) {
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
EXPECT_TRUE(stream->Open());
RunThreadsUntilIdle();
FakeAudioDecoder* audio_decoder = GetAudioDecoder();
ASSERT_TRUE(audio_decoder);
audio_decoder->set_pipeline_status(
FakeAudioDecoder::PIPELINE_STATUS_ASYNC_ERROR);
::media::MockAudioSourceCallback source_callback;
EXPECT_CALL(source_callback, OnMoreData(_, _, _, _))
.WillRepeatedly(Invoke(OnMoreData));
// AudioOutputStream must report error to source callback.
EXPECT_CALL(source_callback, OnError(_)).Times(testing::AtLeast(1));
stream->Start(&source_callback);
RunThreadsUntilIdle();
// Make sure that one frame was pushed.
EXPECT_EQ(1u, audio_decoder->pushed_buffer_count());
stream->Stop();
stream->Close();
}
TEST_F(CastAudioOutputStreamTest, Volume) {
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
EXPECT_TRUE(stream->Open());
RunThreadsUntilIdle();
FakeAudioDecoder* audio_decoder = GetAudioDecoder();
ASSERT_TRUE(audio_decoder);
double volume = 0.0;
stream->GetVolume(&volume);
EXPECT_EQ(1.0, volume);
EXPECT_EQ(1.0f, audio_decoder->volume());
stream->SetVolume(0.5);
RunThreadsUntilIdle();
stream->GetVolume(&volume);
EXPECT_EQ(0.5, volume);
EXPECT_EQ(0.5f, audio_decoder->volume());
stream->Close();
}
TEST_F(CastAudioOutputStreamTest, InvalidAudioDelay) {
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
ASSERT_TRUE(stream->Open());
RunThreadsUntilIdle();
FakeAudioDecoder* audio_decoder = GetAudioDecoder();
ASSERT_TRUE(audio_decoder);
audio_decoder->set_rendering_delay(
CmaBackend::AudioDecoder::RenderingDelay(-1, 0));
::media::MockAudioSourceCallback source_callback;
const base::TimeDelta delay = base::TimeDelta();
EXPECT_CALL(source_callback, OnMoreData(delay, _, _, _))
.WillRepeatedly(Invoke(OnMoreData));
stream->Start(&source_callback);
RunThreadsUntilIdle();
stream->Stop();
stream->Close();
}
TEST_F(CastAudioOutputStreamTest, AudioDelay) {
::media::AudioOutputStream* stream = CreateStream();
ASSERT_TRUE(stream);
ASSERT_TRUE(stream->Open());
RunThreadsUntilIdle();
FakeAudioDecoder* audio_decoder = GetAudioDecoder();
ASSERT_TRUE(audio_decoder);
audio_decoder->set_rendering_delay(
CmaBackend::AudioDecoder::RenderingDelay(kDelayUs, MonotonicClockNow()));
::media::MockAudioSourceCallback source_callback;
const base::TimeDelta delay(base::Microseconds(kDelayUs));
// OnMoreData can be called with a shorter delay than the rendering delay in
// order to prefetch audio data faster.
EXPECT_CALL(source_callback, OnMoreData(testing::Le(delay), _, _, _))
.WillRepeatedly(Invoke(OnMoreData));
stream->Start(&source_callback);
RunThreadsUntilIdle();
stream->Stop();
stream->Close();
}
TEST_F(CastAudioOutputStreamTest, SessionId) {
format_ = ::media::AudioParameters::AUDIO_PCM_LOW_LATENCY;
::media::AudioOutputStream* stream = audio_manager_->MakeAudioOutputStream(
GetAudioParams(), "DummyGroupId", ::media::AudioManager::LogCallback());
EXPECT_CALL(delegate_, GetSessionId(_)).WillOnce(Return(kSessionId));
ASSERT_TRUE(stream);
ASSERT_TRUE(stream->Open());
RunThreadsUntilIdle();
// We will start/stop the stream, because as a test, we do not care about
// whether the info was fetched during Open() or Start() so we test across
// both.
::media::MockAudioSourceCallback source_callback;
EXPECT_CALL(source_callback, OnMoreData(_, _, _, _))
.WillRepeatedly(Invoke(OnMoreData));
stream->Start(&source_callback);
RunThreadsUntilIdle();
// TODO(awolter, b/111669896): Verify that the session id is correct after
// piping has been added. For now, we want to verify that the session id is
// empty, so that basic MZ continues to work.
ASSERT_TRUE(cma_backend_);
MediaPipelineDeviceParams params = cma_backend_->params();
EXPECT_EQ(params.session_id, kSessionId);
stream->Stop();
stream->Close();
}
TEST_F(CastAudioOutputStreamTest, CommunicationsDeviceId) {
format_ = ::media::AudioParameters::AUDIO_PCM_LOW_LATENCY;
::media::AudioOutputStream* stream = audio_manager_->MakeAudioOutputStream(
GetAudioParams(),
::media::AudioDeviceDescription::kCommunicationsDeviceId,
::media::AudioManager::LogCallback());
ASSERT_TRUE(stream);
ASSERT_TRUE(stream->Open());
RunThreadsUntilIdle();
ASSERT_TRUE(cma_backend_);
MediaPipelineDeviceParams params = cma_backend_->params();
EXPECT_EQ(params.content_type, AudioContentType::kCommunication);
EXPECT_EQ(params.device_id,
::media::AudioDeviceDescription::kCommunicationsDeviceId);
stream->Stop();
stream->Close();
}
} // namespace media
} // namespace chromecast