blob: 9f6688049a39e5f1f1093ed5c02142cf74e38a70 [file] [log] [blame]
// Copyright 2016 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/media/audio/cast_audio_mixer.h"
#include <stdint.h>
#include <algorithm>
#include <memory>
#include <vector>
#include "base/bind.h"
#include "base/run_loop.h"
#include "base/time/time.h"
#include "chromecast/media/audio/cast_audio_manager.h"
#include "chromecast/media/audio/cast_audio_output_stream.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromecast {
namespace media {
namespace {
using ::testing::_;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::StrictMock;
// Utility functions
::media::AudioParameters GetAudioParams() {
return ::media::AudioParameters(
::media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
::media::CHANNEL_LAYOUT_STEREO, 48000, 16, 1024);
}
// Mock implementations
class MockAudioSourceCallback
: public ::media::AudioOutputStream::AudioSourceCallback {
public:
MockAudioSourceCallback() {
ON_CALL(*this, OnMoreData(_, _, _, _))
.WillByDefault(Invoke(this, &MockAudioSourceCallback::OnMoreDataImpl));
}
MOCK_METHOD4(OnMoreData,
int(base::TimeDelta, base::TimeTicks, int, ::media::AudioBus*));
MOCK_METHOD1(OnError, void(::media::AudioOutputStream* stream));
private:
int OnMoreDataImpl(base::TimeDelta /* delay */,
base::TimeTicks /* delay_timestamp */,
int /* prior_frames_skipped */,
::media::AudioBus* dest) {
dest->Zero();
return dest->frames();
}
};
class MockCastAudioOutputStream : public CastAudioOutputStream {
public:
MockCastAudioOutputStream(const ::media::AudioParameters& audio_params,
CastAudioManager* audio_manager)
: CastAudioOutputStream(audio_params, audio_manager) {}
MOCK_METHOD0(Open, bool());
MOCK_METHOD0(Close, void());
MOCK_METHOD1(Start, void(AudioSourceCallback* source_callback));
MOCK_METHOD0(Stop, void());
MOCK_METHOD1(SetVolume, void(double volume));
MOCK_METHOD1(GetVolume, void(double* volume));
void SignalPull(AudioSourceCallback* source_callback, base::TimeDelta delay) {
std::unique_ptr<::media::AudioBus> audio_bus =
::media::AudioBus::Create(GetAudioParams());
source_callback->OnMoreData(delay, base::TimeTicks::Now(), 0,
audio_bus.get());
}
void SignalError(AudioSourceCallback* source_callback) {
source_callback->OnError(this);
}
};
class MockCastAudioManager : public CastAudioManager {
public:
MockCastAudioManager(scoped_refptr<base::SingleThreadTaskRunner> task_runner,
CastAudioMixer* audio_mixer)
: CastAudioManager(task_runner,
task_runner,
nullptr,
nullptr,
audio_mixer) {
ON_CALL(*this, ReleaseOutputStream(_))
.WillByDefault(
Invoke(this, &MockCastAudioManager::ReleaseOutputStreamConcrete));
}
MOCK_METHOD1(
MakeMixerOutputStream,
::media::AudioOutputStream*(const ::media::AudioParameters& params));
MOCK_METHOD1(ReleaseOutputStream, void(::media::AudioOutputStream* stream));
private:
void ReleaseOutputStreamConcrete(::media::AudioOutputStream* stream) {
CastAudioManager::ReleaseOutputStream(stream);
}
};
class MockCastAudioMixer : public CastAudioMixer {
public:
explicit MockCastAudioMixer(const RealStreamFactory& real_stream_factory)
: CastAudioMixer(real_stream_factory) {
ON_CALL(*this, MakeStream(_, _))
.WillByDefault(Invoke(this, &MockCastAudioMixer::MakeStreamConcrete));
}
MOCK_METHOD2(
MakeStream,
::media::AudioOutputStream*(const ::media::AudioParameters& params,
CastAudioManager* audio_manager));
private:
::media::AudioOutputStream* MakeStreamConcrete(
const ::media::AudioParameters& params,
CastAudioManager* audio_manager) {
return CastAudioMixer::MakeStream(params, audio_manager);
}
};
// Generates StrictMocks of Mixer, Manager, and Mixer OutputStream.
class CastAudioMixerTest : public ::testing::Test {
public:
CastAudioMixerTest() {}
~CastAudioMixerTest() override {}
protected:
void SetUp() override {
// |this| will outlive |mock_mixer_|
mock_mixer_ = new StrictMock<MockCastAudioMixer>(
base::Bind(&CastAudioMixerTest::MakeMixerOutputStreamProxy,
base::Unretained(this)));
mock_manager_.reset(new StrictMock<MockCastAudioManager>(
message_loop_.task_runner(), mock_mixer_));
mock_mixer_stream_.reset(new StrictMock<MockCastAudioOutputStream>(
GetAudioParams(), mock_manager_.get()));
}
void TearDown() override { mock_manager_.reset(); }
MockCastAudioManager& mock_manager() { return *mock_manager_; }
MockCastAudioMixer& mock_mixer() { return *mock_mixer_; }
MockCastAudioOutputStream& mock_mixer_stream() { return *mock_mixer_stream_; }
::media::AudioOutputStream* CreateMixerStream() {
EXPECT_CALL(*mock_mixer_, MakeStream(_, &mock_manager()));
return mock_manager_->MakeAudioOutputStream(
GetAudioParams(), "", ::media::AudioManager::LogCallback());
}
private:
::media::AudioOutputStream* MakeMixerOutputStreamProxy(
const ::media::AudioParameters& audio_params) {
return mock_manager_ ? mock_manager_->MakeMixerOutputStream(audio_params)
: nullptr;
}
base::MessageLoop message_loop_;
MockCastAudioMixer* mock_mixer_;
std::unique_ptr<MockCastAudioManager> mock_manager_;
std::unique_ptr<MockCastAudioOutputStream> mock_mixer_stream_;
};
TEST_F(CastAudioMixerTest, Volume) {
::media::AudioOutputStream* stream = CreateMixerStream();
ASSERT_TRUE(stream);
double volume;
stream->GetVolume(&volume);
ASSERT_EQ(volume, 1.0);
stream->SetVolume(.56);
stream->GetVolume(&volume);
ASSERT_EQ(volume, .56);
EXPECT_CALL(mock_manager(), ReleaseOutputStream(stream));
stream->Close();
}
TEST_F(CastAudioMixerTest, MixerCallsCloseOnFailedOpen) {
::media::AudioOutputStream* stream = CreateMixerStream();
ASSERT_TRUE(stream);
EXPECT_CALL(mock_manager(), MakeMixerOutputStream(_))
.WillOnce(Return(&mock_mixer_stream()));
EXPECT_CALL(mock_mixer_stream(), Open()).WillOnce(Return(false));
EXPECT_CALL(mock_mixer_stream(), Close());
ASSERT_FALSE(stream->Open());
EXPECT_CALL(mock_manager(), ReleaseOutputStream(stream));
stream->Close();
}
TEST_F(CastAudioMixerTest, StreamControlOrderMisuse) {
MockAudioSourceCallback source;
::media::AudioOutputStream* stream = CreateMixerStream();
ASSERT_TRUE(stream);
// Close stream without first opening
EXPECT_CALL(mock_manager(), ReleaseOutputStream(stream));
stream->Close();
stream = CreateMixerStream();
ASSERT_TRUE(stream);
// Should not trigger mixer actions.
stream->Stop();
stream->Start(&source);
EXPECT_CALL(mock_manager(), MakeMixerOutputStream(_))
.WillOnce(Return(&mock_mixer_stream()));
EXPECT_CALL(mock_mixer_stream(), Open()).WillOnce(Return(false));
EXPECT_CALL(mock_mixer_stream(), Close());
ASSERT_FALSE(stream->Open());
// Should not trigger mixer actions.
stream->Start(&source);
stream->Stop();
EXPECT_CALL(mock_manager(), MakeMixerOutputStream(_))
.WillOnce(Return(&mock_mixer_stream()));
EXPECT_CALL(mock_mixer_stream(), Open()).WillOnce(Return(true));
ASSERT_TRUE(stream->Open());
EXPECT_CALL(mock_mixer_stream(), Start(&mock_mixer()));
stream->Start(&source);
stream->Start(&source);
EXPECT_CALL(mock_mixer_stream(), Stop());
EXPECT_CALL(mock_mixer_stream(), Close());
EXPECT_CALL(mock_manager(), ReleaseOutputStream(stream));
stream->Close(); // Close abruptly without Stop(), should not fail.
}
TEST_F(CastAudioMixerTest, SingleStreamCycle) {
MockAudioSourceCallback source;
::media::AudioOutputStream* stream = CreateMixerStream();
ASSERT_TRUE(stream);
EXPECT_CALL(mock_manager(), MakeMixerOutputStream(_))
.WillOnce(Return(&mock_mixer_stream()));
EXPECT_CALL(mock_mixer_stream(), Open()).WillOnce(Return(true));
ASSERT_TRUE(stream->Open());
EXPECT_CALL(mock_mixer_stream(), Start(&mock_mixer())).Times(2);
EXPECT_CALL(mock_mixer_stream(), Stop()).Times(2);
stream->Start(&source);
stream->Stop();
stream->Start(&source);
stream->Stop();
EXPECT_CALL(mock_mixer_stream(), Close());
EXPECT_CALL(mock_manager(), ReleaseOutputStream(stream));
stream->Close();
}
TEST_F(CastAudioMixerTest, MultiStreamCycle) {
// This test will break if run with < 1 stream.
std::vector<::media::AudioOutputStream*> streams(5);
std::vector<std::unique_ptr<MockAudioSourceCallback>> sources(streams.size());
std::generate(streams.begin(), streams.end(),
[this] { return CreateMixerStream(); });
std::generate(sources.begin(), sources.end(), [this] {
return std::unique_ptr<MockAudioSourceCallback>(
new StrictMock<MockAudioSourceCallback>());
});
EXPECT_CALL(mock_manager(), MakeMixerOutputStream(_))
.WillOnce(Return(&mock_mixer_stream()));
EXPECT_CALL(mock_mixer_stream(), Open()).WillOnce(Return(true));
for (auto* stream : streams)
ASSERT_TRUE(stream->Open());
EXPECT_CALL(mock_mixer_stream(), Start(&mock_mixer()));
for (unsigned int i = 0; i < streams.size(); i++)
streams[i]->Start(sources[i].get());
// Individually pull out streams
while (streams.size() > 1) {
::media::AudioOutputStream* stream = streams.front();
stream->Stop();
streams.erase(streams.begin());
sources.erase(sources.begin());
for (auto& source : sources)
EXPECT_CALL(*source, OnMoreData(_, _, _, _));
mock_mixer_stream().SignalPull(&mock_mixer(), base::TimeDelta());
EXPECT_CALL(mock_manager(), ReleaseOutputStream(stream));
stream->Close();
}
EXPECT_CALL(mock_mixer_stream(), Stop());
EXPECT_CALL(mock_mixer_stream(), Close());
EXPECT_CALL(mock_manager(), ReleaseOutputStream(streams.front()));
streams.front()->Close();
}
TEST_F(CastAudioMixerTest, TwoStreamRestart) {
MockAudioSourceCallback source;
::media::AudioOutputStream *stream1, *stream2;
for (int i = 0; i < 2; i++) {
stream1 = CreateMixerStream();
stream2 = CreateMixerStream();
ASSERT_TRUE(stream1);
ASSERT_TRUE(stream2);
EXPECT_CALL(mock_manager(), MakeMixerOutputStream(_))
.WillOnce(Return(&mock_mixer_stream()));
EXPECT_CALL(mock_mixer_stream(), Open()).WillOnce(Return(true));
ASSERT_TRUE(stream1->Open());
ASSERT_TRUE(stream2->Open());
EXPECT_CALL(mock_mixer_stream(), Start(&mock_mixer()));
stream1->Start(&source);
stream2->Start(&source);
stream1->Stop();
EXPECT_CALL(mock_mixer_stream(), Stop());
EXPECT_CALL(mock_manager(), ReleaseOutputStream(stream2));
stream2->Close();
EXPECT_CALL(mock_mixer_stream(), Close());
EXPECT_CALL(mock_manager(), ReleaseOutputStream(stream1));
stream1->Close();
}
}
TEST_F(CastAudioMixerTest, OnErrorRecovery) {
MockAudioSourceCallback source;
std::vector<::media::AudioOutputStream*> streams;
streams.push_back(CreateMixerStream());
streams.push_back(CreateMixerStream());
for (auto* stream : streams)
ASSERT_TRUE(stream);
EXPECT_CALL(mock_manager(), MakeMixerOutputStream(_))
.WillOnce(Return(&mock_mixer_stream()));
EXPECT_CALL(mock_mixer_stream(), Open()).WillOnce(Return(true));
for (auto* stream : streams)
ASSERT_TRUE(stream->Open());
EXPECT_CALL(mock_mixer_stream(), Start(&mock_mixer()));
streams.front()->Start(&source);
EXPECT_CALL(mock_mixer_stream(), Stop());
EXPECT_CALL(mock_mixer_stream(), Close());
EXPECT_CALL(mock_manager(), MakeMixerOutputStream(_))
.WillOnce(Return(&mock_mixer_stream()));
EXPECT_CALL(mock_mixer_stream(), Open()).WillOnce(Return(true));
EXPECT_CALL(mock_mixer_stream(), Start(&mock_mixer()));
// The MockAudioSourceCallback should not receive any call OnError.
mock_mixer_stream().SignalError(&mock_mixer());
// Try to add another stream.
streams.push_back(CreateMixerStream());
ASSERT_TRUE(streams.back());
ASSERT_TRUE(streams.back()->Open());
EXPECT_CALL(mock_mixer_stream(), Stop());
EXPECT_CALL(mock_mixer_stream(), Close());
for (auto* stream : streams) {
EXPECT_CALL(mock_manager(), ReleaseOutputStream(stream));
stream->Close();
}
}
TEST_F(CastAudioMixerTest, OnErrorNoRecovery) {
MockAudioSourceCallback source;
std::vector<::media::AudioOutputStream*> streams;
streams.push_back(CreateMixerStream());
streams.push_back(CreateMixerStream());
for (auto* stream : streams)
ASSERT_TRUE(stream);
EXPECT_CALL(mock_manager(), MakeMixerOutputStream(_))
.WillOnce(Return(&mock_mixer_stream()));
EXPECT_CALL(mock_mixer_stream(), Open()).WillOnce(Return(true));
for (auto* stream : streams)
ASSERT_TRUE(stream->Open());
EXPECT_CALL(mock_mixer_stream(), Start(&mock_mixer()));
streams.front()->Start(&source);
EXPECT_CALL(mock_mixer_stream(), Stop());
EXPECT_CALL(mock_manager(), MakeMixerOutputStream(_))
.WillOnce(Return(&mock_mixer_stream()));
EXPECT_CALL(mock_mixer_stream(), Open()).WillOnce(Return(false));
EXPECT_CALL(mock_mixer_stream(), Close()).Times(2);
EXPECT_CALL(source, OnError(streams.front()));
// The MockAudioSourceCallback should receive an OnError call.
mock_mixer_stream().SignalError(&mock_mixer());
// Try to add another stream.
streams.push_back(CreateMixerStream());
ASSERT_TRUE(streams.back());
ASSERT_FALSE(streams.back()->Open());
for (auto* stream : streams) {
EXPECT_CALL(mock_manager(), ReleaseOutputStream(stream));
stream->Close();
}
// Now that the state has been refreshed, attempt to open a stream.
streams.clear();
streams.push_back(CreateMixerStream());
EXPECT_CALL(mock_manager(), MakeMixerOutputStream(_))
.WillOnce(Return(&mock_mixer_stream()));
EXPECT_CALL(mock_mixer_stream(), Open()).WillOnce(Return(true));
ASSERT_TRUE(streams.front()->Open());
EXPECT_CALL(mock_mixer_stream(), Start(&mock_mixer()));
streams.front()->Start(&source);
EXPECT_CALL(mock_mixer_stream(), Stop());
EXPECT_CALL(mock_mixer_stream(), Close());
EXPECT_CALL(mock_manager(), ReleaseOutputStream(streams.front()));
streams.front()->Close();
}
TEST_F(CastAudioMixerTest, Delay) {
MockAudioSourceCallback source;
::media::AudioOutputStream* stream = CreateMixerStream();
EXPECT_CALL(mock_manager(), MakeMixerOutputStream(_))
.WillOnce(Return(&mock_mixer_stream()));
EXPECT_CALL(mock_mixer_stream(), Open()).WillOnce(Return(true));
ASSERT_TRUE(stream->Open());
EXPECT_CALL(mock_mixer_stream(), Start(&mock_mixer()));
stream->Start(&source);
// |delay| is the same because the Mixer and stream are
// using the same AudioParameters.
base::TimeDelta delay = base::TimeDelta::FromMicroseconds(1000);
EXPECT_CALL(source, OnMoreData(delay, _, 0, _));
mock_mixer_stream().SignalPull(&mock_mixer(), delay);
EXPECT_CALL(mock_mixer_stream(), Stop());
EXPECT_CALL(mock_mixer_stream(), Close());
EXPECT_CALL(mock_manager(), ReleaseOutputStream(stream));
stream->Close();
}
} // namespace
} // namespace media
} // namespace chromecast