blob: 5847d4813964b8e47a1d522f150a36aa0f607c2a [file] [log] [blame]
// Copyright (c) 2012 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 "media/audio/audio_output_controller.h"
#include <stdint.h>
#include <memory>
#include <vector>
#include "base/barrier_closure.h"
#include "base/bind.h"
#include "base/environment.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_piece.h"
#include "base/test/test_message_loop.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "base/unguessable_token.h"
#include "media/audio/audio_device_description.h"
#include "media/audio/audio_source_diverter.h"
#include "media/audio/test_audio_thread.h"
#include "media/base/audio_bus.h"
#include "media/base/audio_parameters.h"
#include "media/base/gmock_callback_support.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::AtLeast;
using ::testing::Bool;
using ::testing::TestWithParam;
using ::testing::DoAll;
using ::testing::Invoke;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::StrictMock;
using ::testing::Mock;
namespace media {
static const int kSampleRate = AudioParameters::kAudioCDSampleRate;
static const ChannelLayout kChannelLayout = CHANNEL_LAYOUT_STEREO;
static const int kSamplesPerPacket = kSampleRate / 1000;
static const double kTestVolume = 0.25;
static const float kBufferNonZeroData = 1.0f;
AudioParameters AOCTestParams() {
return AudioParameters(AudioParameters::AUDIO_FAKE, kChannelLayout,
kSampleRate, kSamplesPerPacket);
}
class MockAudioOutputControllerEventHandler
: public AudioOutputController::EventHandler {
public:
MockAudioOutputControllerEventHandler() = default;
MOCK_METHOD0(OnControllerCreated, void());
MOCK_METHOD0(OnControllerPlaying, void());
MOCK_METHOD0(OnControllerPaused, void());
MOCK_METHOD0(OnControllerError, void());
void OnLog(base::StringPiece) override {}
private:
DISALLOW_COPY_AND_ASSIGN(MockAudioOutputControllerEventHandler);
};
class MockAudioOutputControllerSyncReader
: public AudioOutputController::SyncReader {
public:
MockAudioOutputControllerSyncReader() = default;
MOCK_METHOD3(RequestMoreData,
void(base::TimeDelta delay,
base::TimeTicks delay_timestamp,
int prior_frames_skipped));
MOCK_METHOD1(Read, void(AudioBus* dest));
MOCK_METHOD0(Close, void());
private:
DISALLOW_COPY_AND_ASSIGN(MockAudioOutputControllerSyncReader);
};
class MockAudioOutputStream : public AudioOutputStream,
public AudioOutputStream::AudioSourceCallback {
public:
explicit MockAudioOutputStream(AudioManager* audio_manager)
: audio_manager_(audio_manager) {}
// We forward to a fake stream to get automatic OnMoreData callbacks,
// required by some tests.
MOCK_METHOD0(DidOpen, void());
MOCK_METHOD0(DidStart, void());
MOCK_METHOD0(DidStop, void());
MOCK_METHOD0(DidClose, void());
MOCK_METHOD1(SetVolume, void(double));
MOCK_METHOD1(GetVolume, void(double* volume));
bool Open() override {
EXPECT_EQ(nullptr, impl_);
impl_ =
audio_manager_->MakeAudioOutputStreamProxy(AOCTestParams(), "default");
impl_->Open();
DidOpen();
return true;
}
void Start(AudioOutputStream::AudioSourceCallback* cb) override {
EXPECT_EQ(nullptr, callback_);
callback_ = cb;
impl_->Start(this);
DidStart();
}
void Stop() override {
impl_->Stop();
callback_ = nullptr;
DidStop();
}
void Close() override {
impl_->Close();
impl_ = nullptr;
DidClose();
}
private:
int OnMoreData(base::TimeDelta delay,
base::TimeTicks delay_timestamp,
int prior_frames_skipped,
AudioBus* dest) override {
int res = callback_->OnMoreData(delay, delay_timestamp,
prior_frames_skipped, dest);
EXPECT_EQ(dest->channel(0)[0], kBufferNonZeroData);
return res;
}
void OnError() override {
// Fake stream doesn't send errors.
NOTREACHED();
}
AudioManager* audio_manager_;
AudioOutputStream* impl_ = nullptr;
AudioOutputStream::AudioSourceCallback* callback_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(MockAudioOutputStream);
};
class MockAudioPushSink : public AudioPushSink {
public:
MockAudioPushSink() = default;
MOCK_METHOD0(Close, void());
MOCK_METHOD1(OnDataCheck, void(float));
void OnData(std::unique_ptr<AudioBus> source,
base::TimeTicks reference_time) override {
OnDataCheck(source->channel(0)[0]);
}
private:
DISALLOW_COPY_AND_ASSIGN(MockAudioPushSink);
};
ACTION(PopulateBuffer) {
arg0->Zero();
// Note: To confirm the buffer will be populated in these tests, it's
// sufficient that only the first float in channel 0 is set to the value.
arg0->channel(0)[0] = kBufferNonZeroData;
}
class AudioOutputControllerTest : public TestWithParam<bool> {
public:
AudioOutputControllerTest()
: synchronous_use_(GetParam()),
audio_manager_(AudioManager::CreateForTesting(
// Make sure that the audio manager thread == the main thread with
// synchronous use of the controller.
std::make_unique<TestAudioThread>(!synchronous_use_))),
mock_stream_(audio_manager_.get()) {}
~AudioOutputControllerTest() override { audio_manager_->Shutdown(); }
protected:
void Create() {
EXPECT_CALL(mock_event_handler_, OnControllerCreated());
controller_ = AudioOutputController::Create(
audio_manager_.get(), &mock_event_handler_, AOCTestParams(),
std::string(), base::UnguessableToken(), &mock_sync_reader_);
EXPECT_NE(nullptr, controller_.get());
controller_->SetVolume(kTestVolume);
}
void Play() {
base::RunLoop loop;
// The barrier is used to wait for all of the expectations to be fulfilled.
base::RepeatingClosure barrier =
base::BarrierClosure(3, loop.QuitClosure());
EXPECT_CALL(mock_event_handler_, OnControllerPlaying())
.WillOnce(RunClosure(barrier));
EXPECT_CALL(mock_sync_reader_, RequestMoreData(_, _, _))
.WillOnce(RunClosure(barrier))
.WillRepeatedly(Return());
EXPECT_CALL(mock_sync_reader_, Read(_))
.WillOnce(Invoke([barrier](AudioBus* data) {
data->channel(0)[0] = kBufferNonZeroData;
barrier.Run();
}))
.WillRepeatedly(PopulateBuffer());
controller_->Play();
// Waits for all gmock expectations to be satisfied.
loop.Run();
}
void PlayWhileDiverting() {
base::RunLoop loop;
// The barrier is used to wait for all of the expectations to be fulfilled.
base::RepeatingClosure barrier =
base::BarrierClosure(4, loop.QuitClosure());
EXPECT_CALL(mock_stream_, DidStart()).WillOnce(RunClosure(barrier));
EXPECT_CALL(mock_event_handler_, OnControllerPlaying())
.WillOnce(RunClosure(barrier));
// The mock stream will start pulling data. We verify that the calls are
// forwarded to SyncReader, and write some data to the buffer that we can
// verify later.
EXPECT_CALL(mock_sync_reader_, RequestMoreData(_, _, _))
.WillOnce(RunClosure(barrier))
.WillRepeatedly(Return());
EXPECT_CALL(mock_sync_reader_, Read(_))
.WillOnce(Invoke([barrier](AudioBus* data) {
data->channel(0)[0] = kBufferNonZeroData;
barrier.Run();
}))
.WillRepeatedly(PopulateBuffer());
controller_->Play();
// Waits for all gmock expectations to be satisfied.
loop.Run();
// At some point in the future, the stream must be stopped.
EXPECT_CALL(mock_stream_, DidStop());
}
void Pause() {
base::RunLoop loop;
EXPECT_CALL(mock_event_handler_, OnControllerPaused())
.WillOnce(RunOnceClosure(loop.QuitClosure()));
controller_->Pause();
loop.Run();
Mock::VerifyAndClearExpectations(&mock_event_handler_);
}
void ChangeDevice() {
// Expect the event handler to receive one OnControllerPaying() call and no
// OnControllerPaused() call.
EXPECT_CALL(mock_event_handler_, OnControllerPlaying());
EXPECT_CALL(mock_event_handler_, OnControllerPaused()).Times(0);
// Simulate a device change event to AudioOutputController from the
// AudioManager.
audio_manager_->GetTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&AudioOutputController::OnDeviceChange, controller_));
// Wait for device change to take effect.
base::RunLoop loop;
audio_manager_->GetTaskRunner()->PostTask(FROM_HERE, loop.QuitClosure());
loop.Run();
}
void Divert() {
EXPECT_CALL(mock_stream_, DidOpen());
EXPECT_CALL(mock_stream_, SetVolume(kTestVolume));
controller_->StartDiverting(&mock_stream_);
base::RunLoop loop;
// Wait for controller to start diverting.
audio_manager_->GetTaskRunner()->PostTask(FROM_HERE, loop.QuitClosure());
loop.Run();
}
void DivertWhilePlaying() {
base::RunLoop loop;
// The barrier is used to wait for all of the expectations to be fulfilled.
base::RepeatingClosure barrier =
base::BarrierClosure(4, loop.QuitClosure());
// Expect mock streams to be initialized and started.
EXPECT_CALL(mock_stream_, DidOpen()).WillOnce(RunClosure(barrier));
EXPECT_CALL(mock_stream_, SetVolume(kTestVolume))
.WillOnce(RunClosure(barrier));
EXPECT_CALL(mock_stream_, DidStart()).WillOnce(RunClosure(barrier));
// Expect event handler to be informed.
EXPECT_CALL(mock_event_handler_, OnControllerPlaying())
.WillOnce(RunClosure(barrier));
controller_->StartDiverting(&mock_stream_);
// Wait until callbacks has started.
loop.Run();
// At some point in the future, the stream must be stopped.
EXPECT_CALL(mock_stream_, DidStop());
}
void StartDuplicating(MockAudioPushSink* sink) {
base::RunLoop loop;
EXPECT_CALL(*sink, OnDataCheck(kBufferNonZeroData))
.WillOnce(RunOnceClosure(loop.QuitClosure()))
.WillRepeatedly(Return());
controller_->StartDuplicating(sink);
loop.Run();
}
void Revert(bool was_playing) {
if (was_playing) {
// Expect the handler to receive one OnControllerPlaying() call as a
// result of the stream switching back.
EXPECT_CALL(mock_event_handler_, OnControllerPlaying());
}
EXPECT_CALL(mock_stream_, DidClose());
controller_->StopDiverting();
base::RunLoop loop;
audio_manager_->GetTaskRunner()->PostTask(FROM_HERE, loop.QuitClosure());
loop.Run();
}
void StopDuplicating(MockAudioPushSink* sink) {
{
// First, verify we're still getting callbacks. Must be done on the AM
// task runner, since it may be a separate thread, and EXPECTing from the
// main thread would be racy.
base::RunLoop loop;
audio_manager_->GetTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](MockAudioPushSink* sink, base::RepeatingClosure done_closure) {
Mock::VerifyAndClear(sink);
EXPECT_CALL(*sink, OnDataCheck(kBufferNonZeroData))
.WillOnce(RunClosure(done_closure))
.WillRepeatedly(Return());
},
sink, loop.QuitClosure()));
loop.Run();
}
{
EXPECT_CALL(*sink, Close());
controller_->StopDuplicating(sink);
base::RunLoop loop;
audio_manager_->GetTaskRunner()->PostTask(FROM_HERE, loop.QuitClosure());
loop.Run();
}
}
void Close() {
EXPECT_CALL(mock_sync_reader_, Close());
if (synchronous_use_) {
controller_->Close(base::OnceClosure());
return;
}
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&AudioOutputController::Close, controller_,
run_loop.QuitClosure()));
run_loop.Run();
}
void SimulateErrorThenDeviceChange() {
audio_manager_->GetTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&AudioOutputControllerTest::TriggerErrorThenDeviceChange,
base::Unretained(this)));
base::RunLoop loop;
audio_manager_->GetTaskRunner()->PostTask(FROM_HERE, loop.QuitClosure());
loop.Run();
}
// These help make test sequences more readable.
void RevertWasNotPlaying() { Revert(false); }
void RevertWhilePlaying() { Revert(true); }
void TriggerErrorThenDeviceChange() {
DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
// Errors should be deferred; the device change should ensure it's dropped.
EXPECT_CALL(mock_event_handler_, OnControllerError()).Times(0);
controller_->OnError();
EXPECT_CALL(mock_event_handler_, OnControllerPlaying());
EXPECT_CALL(mock_event_handler_, OnControllerPaused()).Times(0);
controller_->OnDeviceChange();
}
base::TestMessageLoop message_loop_;
const bool synchronous_use_;
std::unique_ptr<AudioManager> audio_manager_;
StrictMock<MockAudioOutputControllerEventHandler> mock_event_handler_;
StrictMock<MockAudioOutputControllerSyncReader> mock_sync_reader_;
StrictMock<MockAudioOutputStream> mock_stream_;
scoped_refptr<AudioOutputController> controller_;
private:
DISALLOW_COPY_AND_ASSIGN(AudioOutputControllerTest);
};
TEST_P(AudioOutputControllerTest, CreateAndClose) {
Create();
Close();
}
TEST_P(AudioOutputControllerTest, PlayAndClose) {
Create();
Play();
Close();
}
TEST_P(AudioOutputControllerTest, PlayPauseClose) {
Create();
Play();
Pause();
Close();
}
TEST_P(AudioOutputControllerTest, PlayPausePlayClose) {
Create();
Play();
Pause();
Play();
Close();
}
TEST_P(AudioOutputControllerTest, PlayDeviceChangeClose) {
Create();
Play();
ChangeDevice();
Close();
}
TEST_P(AudioOutputControllerTest, PlayDeviceChangeError) {
Create();
Play();
SimulateErrorThenDeviceChange();
Close();
}
TEST_P(AudioOutputControllerTest, PlayDivertRevertClose) {
Create();
Play();
DivertWhilePlaying();
RevertWhilePlaying();
Close();
}
TEST_P(AudioOutputControllerTest, PlayDivertRevertDivertRevertClose) {
Create();
Play();
DivertWhilePlaying();
RevertWhilePlaying();
DivertWhilePlaying();
RevertWhilePlaying();
Close();
}
TEST_P(AudioOutputControllerTest, DivertPlayPausePlayRevertClose) {
Create();
Divert();
PlayWhileDiverting();
Pause();
PlayWhileDiverting();
RevertWhilePlaying();
Close();
}
TEST_P(AudioOutputControllerTest, DivertRevertClose) {
Create();
Divert();
RevertWasNotPlaying();
Close();
}
TEST_P(AudioOutputControllerTest, PlayDuplicateStopClose) {
Create();
MockAudioPushSink mock_sink;
Play();
StartDuplicating(&mock_sink);
StopDuplicating(&mock_sink);
Close();
}
TEST_P(AudioOutputControllerTest, TwoDuplicates) {
Create();
MockAudioPushSink mock_sink_1;
MockAudioPushSink mock_sink_2;
Play();
StartDuplicating(&mock_sink_1);
StartDuplicating(&mock_sink_2);
StopDuplicating(&mock_sink_1);
StopDuplicating(&mock_sink_2);
Close();
}
TEST_P(AudioOutputControllerTest, DuplicateDivertInteract) {
Create();
MockAudioPushSink mock_sink;
Play();
StartDuplicating(&mock_sink);
DivertWhilePlaying();
StopDuplicating(&mock_sink);
RevertWhilePlaying();
Close();
}
INSTANTIATE_TEST_CASE_P(AOC, AudioOutputControllerTest, Bool());
} // namespace media