blob: 4438dfd09c1b653026062269b80edc29d4914ba5 [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/test/task_environment.h"
#include "base/time/time.h"
#include "chromecast/media/audio/cast_audio_manager.h"
#include "chromecast/media/audio/cast_audio_output_stream.h"
#include "chromecast/media/cma/backend/cma_backend_factory.h"
#include "media/audio/audio_io.h"
#include "media/audio/test_audio_thread.h"
#include "services/service_manager/public/cpp/connector.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
std::unique_ptr<service_manager::Connector> CreateConnector() {
service_manager::mojom::ConnectorRequest request;
return service_manager::Connector::Create(&request);
}
std::string DummyGetSessionId(std::string /* audio_group_id */) {
return "";
}
} // namespace
namespace chromecast {
namespace media {
namespace {
using testing::_;
using testing::Assign;
using testing::Invoke;
using testing::Return;
using testing::SaveArg;
using testing::StrictMock;
// Utility functions
::media::AudioParameters GetAudioParams() {
return ::media::AudioParameters(
::media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
::media::CHANNEL_LAYOUT_STEREO, 48000, 1024);
}
void SignalPull(
::media::AudioOutputStream::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(
::media::AudioOutputStream::AudioSourceCallback* source_callback) {
source_callback->OnError();
}
// 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_METHOD0(OnError, void());
private:
int OnMoreDataImpl(base::TimeDelta /* delay */,
base::TimeTicks /* delay_timestamp */,
int /* prior_frames_skipped */,
::media::AudioBus* dest) {
dest->Zero();
return dest->frames();
}
};
class MockMediaAudioOutputStream : public ::media::AudioOutputStream {
public:
MockMediaAudioOutputStream() {}
MOCK_METHOD0(Open, bool());
MOCK_METHOD0(Close, void());
MOCK_METHOD0(Flush, void());
MOCK_METHOD1(Start, void(AudioSourceCallback* source_callback));
MOCK_METHOD0(Stop, void());
MOCK_METHOD1(SetVolume, void(double volume));
MOCK_METHOD1(GetVolume, void(double* volume));
};
class MockCastAudioManager : public CastAudioManager {
public:
explicit MockCastAudioManager(
service_manager::Connector* connector,
scoped_refptr<base::SingleThreadTaskRunner> media_task_runner)
: CastAudioManager(
std::make_unique<::media::TestAudioThread>(),
nullptr,
base::BindRepeating(&MockCastAudioManager::GetCmaBackendFactory,
base::Unretained(this)),
base::BindRepeating(&DummyGetSessionId),
media_task_runner,
media_task_runner,
connector,
true /* use_mixer */) {
ON_CALL(*this, ReleaseOutputStream(_))
.WillByDefault(
Invoke(this, &MockCastAudioManager::ReleaseOutputStreamConcrete));
}
media::CmaBackendFactory* GetCmaBackendFactory() { return nullptr; }
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);
}
};
// Generates StrictMocks of Mixer, Manager, and Mixer OutputStream.
class CastAudioMixerTest : public ::testing::Test {
public:
CastAudioMixerTest()
: task_environment_(base::test::TaskEnvironment::MainThreadType::UI),
connector_(CreateConnector()),
source_callback_(nullptr) {}
~CastAudioMixerTest() override {}
protected:
void SetUp() override {
mock_manager_.reset(new StrictMock<MockCastAudioManager>(
connector_.get(), task_environment_.GetMainThreadTaskRunner()));
mock_mixer_stream_.reset(new StrictMock<MockMediaAudioOutputStream>());
ON_CALL(*mock_manager_, MakeMixerOutputStream(_))
.WillByDefault(Return(mock_mixer_stream_.get()));
ON_CALL(*mock_mixer_stream_, Start(_))
.WillByDefault(SaveArg<0>(&source_callback_));
ON_CALL(*mock_mixer_stream_, Stop())
.WillByDefault(Assign(&source_callback_, nullptr));
}
void TearDown() override { mock_manager_->Shutdown(); }
MockCastAudioManager& mock_manager() { return *mock_manager_; }
MockMediaAudioOutputStream& mock_mixer_stream() {
return *mock_mixer_stream_;
}
::media::AudioOutputStream* CreateMixerStream() {
return mock_manager_->MakeAudioOutputStream(
GetAudioParams(), "", ::media::AudioManager::LogCallback());
}
base::test::TaskEnvironment task_environment_;
std::unique_ptr<service_manager::Connector> connector_;
std::unique_ptr<MockCastAudioManager> mock_manager_;
std::unique_ptr<MockMediaAudioOutputStream> mock_mixer_stream_;
// Saved params passed to |mock_mixer_stream_|.
::media::AudioOutputStream::AudioSourceCallback* source_callback_;
};
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(_));
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(_)).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(), [] {
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(_));
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(_, _, _, _));
SignalPull(source_callback_, 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(_));
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, OnError) {
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(_));
streams.front()->Start(&source);
// Note that error will only be triggered on the first stream because that
// is the only stream that has been started.
EXPECT_CALL(source, OnError());
SignalError(source_callback_);
base::RunLoop().RunUntilIdle();
// Try to add another stream.
streams.push_back(CreateMixerStream());
ASSERT_TRUE(streams.back());
ASSERT_FALSE(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();
}
streams.clear();
// Now that the state has been refreshed, attempt to open a stream.
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(_));
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(_));
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, _));
SignalPull(source_callback_, 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