blob: c79b1be1cab26deb82385fc7e46e6ecd01f32d63 [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 "services/audio/output_controller.h"
#include <stdint.h>
#include <memory>
#include <vector>
#include "base/barrier_closure.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback_helpers.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/optional.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.h"
#include "base/time/time.h"
#include "base/unguessable_token.h"
#include "media/audio/audio_device_description.h"
#include "media/audio/fake_audio_log_factory.h"
#include "media/audio/fake_audio_manager.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 "services/audio/loopback_group_member.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::AtLeast;
using ::testing::Invoke;
using ::testing::Mock;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::StrictMock;
using media::AudioBus;
using media::AudioManager;
using media::AudioOutputStream;
using media::AudioParameters;
using media::RunClosure;
using media::RunOnceClosure;
namespace audio {
namespace {
constexpr int kSampleRate = AudioParameters::kAudioCDSampleRate;
constexpr media::ChannelLayout kChannelLayout = media::CHANNEL_LAYOUT_STEREO;
constexpr int kSamplesPerPacket = kSampleRate / 1000;
constexpr double kTestVolume = 0.25;
constexpr float kBufferNonZeroData = 1.0f;
AudioParameters GetTestParams() {
// AudioManagerForControllerTest only creates FakeAudioOutputStreams
// behind-the-scenes. So, the use of PCM_LOW_LATENCY won't actually result in
// any real system audio output during these tests.
return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, kChannelLayout,
kSampleRate, kSamplesPerPacket);
}
class MockOutputControllerEventHandler : public OutputController::EventHandler {
public:
MockOutputControllerEventHandler() = default;
MOCK_METHOD0(OnControllerPlaying, void());
MOCK_METHOD0(OnControllerPaused, void());
MOCK_METHOD0(OnControllerError, void());
void OnLog(base::StringPiece) override {}
private:
DISALLOW_COPY_AND_ASSIGN(MockOutputControllerEventHandler);
};
class MockOutputControllerSyncReader : public OutputController::SyncReader {
public:
MockOutputControllerSyncReader() = 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(MockOutputControllerSyncReader);
};
class MockStreamMonitor : public StreamMonitor {
public:
MockStreamMonitor() = default;
MOCK_METHOD1(OnStreamActive, void(Snoopable* snoopable));
MOCK_METHOD1(OnStreamInactive, void(Snoopable* snoopable));
private:
DISALLOW_COPY_AND_ASSIGN(MockStreamMonitor);
};
// Wraps an AudioOutputStream instance, calling DidXYZ() mock methods for test
// verification of controller behavior. If a null AudioOutputStream pointer is
// provided to the constructor, a "data pump" thread will be run between the
// Start() and Stop() calls to simulate an AudioOutputStream not owned by the
// AudioManager.
class MockAudioOutputStream : public AudioOutputStream,
public AudioOutputStream::AudioSourceCallback {
public:
MockAudioOutputStream(AudioOutputStream* impl, AudioParameters::Format format)
: impl_(impl), format_(format) {}
AudioParameters::Format format() const { return format_; }
void set_close_callback(base::OnceClosure callback) {
close_callback_ = std::move(callback);
}
// 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(DidSetVolume, void(double));
bool Open() override {
if (impl_)
impl_->Open();
DidOpen();
return true;
}
void Start(AudioOutputStream::AudioSourceCallback* cb) override {
EXPECT_EQ(nullptr, callback_);
callback_ = cb;
if (impl_) {
impl_->Start(this);
} else {
data_thread_ = std::make_unique<base::Thread>("AudioDataThread");
CHECK(data_thread_->StartAndWaitForTesting());
data_thread_->task_runner()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&MockAudioOutputStream::RunDataLoop,
base::Unretained(this), data_thread_->task_runner()),
GetTestParams().GetBufferDuration());
}
DidStart();
}
void Stop() override {
if (impl_) {
impl_->Stop();
} else {
data_thread_ = nullptr; // Joins/Stops the thread cleanly.
}
callback_ = nullptr;
DidStop();
}
void Close() override {
if (impl_) {
impl_->Close();
impl_ = nullptr;
}
DidClose();
if (close_callback_)
std::move(close_callback_).Run();
delete this;
}
void SetVolume(double volume) override {
volume_ = volume;
if (impl_)
impl_->SetVolume(volume);
DidSetVolume(volume);
}
void GetVolume(double* volume) override { *volume = volume_; }
protected:
~MockAudioOutputStream() override = default;
private:
// Calls OnMoreData() and then posts a delayed task to call itself again soon.
void RunDataLoop(scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
auto bus = AudioBus::Create(GetTestParams());
OnMoreData(base::TimeDelta(), base::TimeTicks::Now(), 0, bus.get());
task_runner->PostDelayedTask(
FROM_HERE,
base::BindOnce(&MockAudioOutputStream::RunDataLoop,
base::Unretained(this), task_runner),
GetTestParams().GetBufferDuration());
}
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();
}
AudioOutputStream* impl_;
const AudioParameters::Format format_;
base::OnceClosure close_callback_;
AudioOutputStream::AudioSourceCallback* callback_ = nullptr;
double volume_ = 1.0;
std::unique_ptr<base::Thread> data_thread_;
DISALLOW_COPY_AND_ASSIGN(MockAudioOutputStream);
};
class MockSnooper : public Snoopable::Snooper {
public:
MockSnooper() = default;
~MockSnooper() override = default;
MOCK_METHOD0(DidProvideData, void());
void OnData(const media::AudioBus& audio_bus,
base::TimeTicks reference_time,
double volume) final {
// Is the AudioBus populated?
EXPECT_EQ(kBufferNonZeroData, audio_bus.channel(0)[0]);
// Are reference timestamps monotonically increasing?
if (!last_reference_time_.is_null()) {
EXPECT_LT(last_reference_time_, reference_time);
}
last_reference_time_ = reference_time;
// Is the correct volume being provided?
EXPECT_EQ(kTestVolume, volume);
DidProvideData();
}
private:
base::TimeTicks last_reference_time_;
DISALLOW_COPY_AND_ASSIGN(MockSnooper);
};
// A FakeAudioManager that produces MockAudioOutputStreams, and tracks the last
// stream that was created and the last stream that was closed.
class AudioManagerForControllerTest : public media::FakeAudioManager {
public:
AudioManagerForControllerTest()
: media::FakeAudioManager(std::make_unique<media::TestAudioThread>(false),
&fake_audio_log_factory_) {}
~AudioManagerForControllerTest() final = default;
MockAudioOutputStream* last_created_stream() const {
return last_created_stream_;
}
MockAudioOutputStream* last_closed_stream() const {
return last_closed_stream_;
}
AudioOutputStream* MakeAudioOutputStream(const AudioParameters& params,
const std::string& device_id,
const LogCallback& cb) final {
last_created_stream_ = new NiceMock<MockAudioOutputStream>(
media::FakeAudioManager::MakeAudioOutputStream(params, device_id, cb),
params.format());
last_created_stream_->set_close_callback(
base::BindOnce(&AudioManagerForControllerTest::SetLastClosedStream,
base::Unretained(this), last_created_stream_));
return last_created_stream_;
}
AudioOutputStream* MakeAudioOutputStreamProxy(
const AudioParameters& params,
const std::string& device_id) final {
last_created_stream_ = new NiceMock<MockAudioOutputStream>(
media::FakeAudioManager::MakeAudioOutputStream(params, device_id,
base::DoNothing()),
params.format());
last_created_stream_->set_close_callback(
base::BindOnce(&AudioManagerForControllerTest::SetLastClosedStream,
base::Unretained(this), last_created_stream_));
return last_created_stream_;
}
private:
void SetLastClosedStream(MockAudioOutputStream* stream) {
last_closed_stream_ = stream;
}
media::FakeAudioLogFactory fake_audio_log_factory_;
MockAudioOutputStream* last_created_stream_ = nullptr;
MockAudioOutputStream* last_closed_stream_ = nullptr;
};
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 OutputControllerTest : public ::testing::Test {
public:
OutputControllerTest()
: group_id_(base::UnguessableToken::Create()),
processing_id_(base::UnguessableToken::Create()) {}
~OutputControllerTest() override { audio_manager_.Shutdown(); }
void SetUp() override {
controller_.emplace(&audio_manager_, &mock_event_handler_, GetTestParams(),
std::string(), &mock_sync_reader_,
&stream_monitor_coordinator_, processing_id_);
controller_->SetVolume(kTestVolume);
}
void TearDown() override { controller_ = base::nullopt; }
protected:
// Returns the last-created or last-closed AudioOuptutStream.
MockAudioOutputStream* last_created_stream() const {
return audio_manager_.last_created_stream();
}
MockAudioOutputStream* last_closed_stream() const {
return audio_manager_.last_closed_stream();
}
void Create() {
controller_->CreateStream();
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->Zero();
data->channel(0)[0] = kBufferNonZeroData;
barrier.Run();
}))
.WillRepeatedly(PopulateBuffer());
controller_->Play();
Mock::VerifyAndClearExpectations(&mock_event_handler_);
// Waits for all gmock expectations to be satisfied.
loop.Run();
}
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 OutputController from the AudioManager.
audio_manager_.GetTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&OutputController::OnDeviceChange,
base::Unretained(&(*controller_))));
// Wait for device change to take effect.
base::RunLoop loop;
audio_manager_.GetTaskRunner()->PostTask(FROM_HERE, loop.QuitClosure());
loop.Run();
Mock::VerifyAndClearExpectations(&mock_event_handler_);
}
void StartMutingBeforePlaying() { controller_->StartMuting(); }
void StartMutingWhilePlaying() {
EXPECT_CALL(mock_event_handler_, OnControllerPlaying());
controller_->StartMuting();
Mock::VerifyAndClearExpectations(&mock_event_handler_);
}
void StopMuting() {
EXPECT_CALL(mock_event_handler_, OnControllerPlaying());
controller_->StopMuting();
Mock::VerifyAndClearExpectations(&mock_event_handler_);
}
void StartSnooping(MockSnooper* snooper, Snoopable::SnoopingMode mode) {
controller_->StartSnooping(snooper, mode);
}
void WaitForSnoopedData(MockSnooper* snooper) {
base::RunLoop loop;
EXPECT_CALL(*snooper, DidProvideData())
.WillOnce(RunOnceClosure(loop.QuitClosure()))
.WillRepeatedly(Return());
loop.Run();
Mock::VerifyAndClearExpectations(snooper);
}
void StopSnooping(MockSnooper* snooper, Snoopable::SnoopingMode mode) {
controller_->StopSnooping(snooper, mode);
}
Snoopable* GetSnoopable() { return &(*controller_); }
void JoinProcessingGroup(StreamMonitor* monitor) {
stream_monitor_coordinator_.RegisterMember(processing_id_, monitor);
}
void LeaveProcessingGroup(StreamMonitor* monitor) {
stream_monitor_coordinator_.UnregisterMember(processing_id_, monitor);
}
void Close() {
EXPECT_CALL(mock_sync_reader_, Close());
controller_->Close();
// Flush any pending tasks (that should have been canceled!).
base::RunLoop loop;
audio_manager_.GetTaskRunner()->PostTask(FROM_HERE, loop.QuitClosure());
loop.Run();
}
void SimulateErrorThenDeviceChange() {
audio_manager_.GetTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&OutputControllerTest::TriggerErrorThenDeviceChange,
base::Unretained(this)));
base::RunLoop loop;
audio_manager_.GetTaskRunner()->PostTask(FROM_HERE, loop.QuitClosure());
loop.Run();
}
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();
Mock::VerifyAndClearExpectations(&mock_event_handler_);
}
private:
base::TestMessageLoop message_loop_;
AudioManagerForControllerTest audio_manager_;
base::UnguessableToken group_id_;
base::UnguessableToken processing_id_;
StrictMock<MockOutputControllerEventHandler> mock_event_handler_;
StrictMock<MockOutputControllerSyncReader> mock_sync_reader_;
base::Optional<OutputController> controller_;
StreamMonitorCoordinator stream_monitor_coordinator_;
DISALLOW_COPY_AND_ASSIGN(OutputControllerTest);
};
TEST_F(OutputControllerTest, CreateAndClose) {
Create();
Close();
}
TEST_F(OutputControllerTest, PlayAndClose) {
Create();
Play();
Close();
}
TEST_F(OutputControllerTest, PlayPauseClose) {
Create();
Play();
Pause();
Close();
}
TEST_F(OutputControllerTest, PlayPausePlayClose) {
Create();
Play();
Pause();
Play();
Close();
}
TEST_F(OutputControllerTest, PlayDeviceChangeClose) {
Create();
Play();
ChangeDevice();
Close();
}
TEST_F(OutputControllerTest, PlayDeviceChangeError) {
Create();
Play();
SimulateErrorThenDeviceChange();
Close();
}
// Syntactic convenience.
double GetStreamVolume(AudioOutputStream* stream) {
double result = NAN;
stream->GetVolume(&result);
return result;
}
// Tests that muting before the stream is created will result in only the
// "muting stream" being created, and not any local playout streams (that might
// possibly cause an audible blip).
TEST_F(OutputControllerTest, MuteCreatePlayClose) {
StartMutingBeforePlaying();
EXPECT_EQ(nullptr, last_created_stream()); // No stream yet.
EXPECT_EQ(nullptr, last_closed_stream()); // No stream yet.
Create();
MockAudioOutputStream* const mute_stream = last_created_stream();
ASSERT_TRUE(mute_stream);
EXPECT_EQ(nullptr, last_closed_stream());
EXPECT_EQ(AudioParameters::AUDIO_FAKE, mute_stream->format());
Play();
ASSERT_EQ(mute_stream, last_created_stream());
EXPECT_EQ(nullptr, last_closed_stream());
EXPECT_EQ(AudioParameters::AUDIO_FAKE, mute_stream->format());
Close();
EXPECT_EQ(mute_stream, last_created_stream());
EXPECT_EQ(mute_stream, last_closed_stream());
}
// Tests that a local playout stream is shut-down and replaced with a "muting
// stream" if StartMuting() is called after playback begins.
TEST_F(OutputControllerTest, CreatePlayMuteClose) {
Create();
MockAudioOutputStream* const playout_stream = last_created_stream();
ASSERT_TRUE(playout_stream);
EXPECT_EQ(nullptr, last_closed_stream());
Play();
ASSERT_EQ(playout_stream, last_created_stream());
EXPECT_EQ(nullptr, last_closed_stream());
EXPECT_EQ(GetTestParams().format(), playout_stream->format());
EXPECT_EQ(kTestVolume, GetStreamVolume(playout_stream));
StartMutingWhilePlaying();
MockAudioOutputStream* const mute_stream = last_created_stream();
ASSERT_TRUE(mute_stream);
EXPECT_EQ(playout_stream, last_closed_stream());
EXPECT_EQ(AudioParameters::AUDIO_FAKE, mute_stream->format());
Close();
EXPECT_EQ(mute_stream, last_created_stream());
EXPECT_EQ(mute_stream, last_closed_stream());
}
// Tests that the "muting stream" is shut down and replaced with the normal
// playout stream after StopMuting() is called.
TEST_F(OutputControllerTest, PlayMuteUnmuteClose) {
StartMutingBeforePlaying();
Create();
Play();
MockAudioOutputStream* const mute_stream = last_created_stream();
ASSERT_TRUE(mute_stream);
EXPECT_EQ(nullptr, last_closed_stream());
EXPECT_EQ(AudioParameters::AUDIO_FAKE, mute_stream->format());
StopMuting();
MockAudioOutputStream* const playout_stream = last_created_stream();
ASSERT_TRUE(playout_stream);
EXPECT_EQ(mute_stream, last_closed_stream());
EXPECT_EQ(GetTestParams().format(), playout_stream->format());
EXPECT_EQ(kTestVolume, GetStreamVolume(playout_stream));
Close();
EXPECT_EQ(playout_stream, last_created_stream());
EXPECT_EQ(playout_stream, last_closed_stream());
}
class WithSnoopingMode
: public OutputControllerTest,
public ::testing::WithParamInterface<Snoopable::SnoopingMode> {};
TEST_P(WithSnoopingMode, SnoopCreatePlayStopClose) {
NiceMock<MockSnooper> snooper;
StartSnooping(&snooper, GetParam());
Create();
Play();
WaitForSnoopedData(&snooper);
StopSnooping(&snooper, GetParam());
Close();
}
TEST_P(WithSnoopingMode, CreatePlaySnoopStopClose) {
NiceMock<MockSnooper> snooper;
Create();
Play();
StartSnooping(&snooper, GetParam());
WaitForSnoopedData(&snooper);
StopSnooping(&snooper, GetParam());
Close();
}
TEST_P(WithSnoopingMode, CreatePlaySnoopCloseStop) {
NiceMock<MockSnooper> snooper;
Create();
Play();
StartSnooping(&snooper, GetParam());
WaitForSnoopedData(&snooper);
Close();
StopSnooping(&snooper, GetParam());
}
TEST_P(WithSnoopingMode, TwoSnoopers_StartAtDifferentTimes) {
NiceMock<MockSnooper> snooper1;
NiceMock<MockSnooper> snooper2;
StartSnooping(&snooper1, GetParam());
Create();
Play();
WaitForSnoopedData(&snooper1);
StartSnooping(&snooper2, GetParam());
WaitForSnoopedData(&snooper2);
WaitForSnoopedData(&snooper1);
WaitForSnoopedData(&snooper2);
Close();
StopSnooping(&snooper1, GetParam());
StopSnooping(&snooper2, GetParam());
}
TEST_P(WithSnoopingMode, TwoSnoopers_StopAtDifferentTimes) {
NiceMock<MockSnooper> snooper1;
NiceMock<MockSnooper> snooper2;
Create();
Play();
StartSnooping(&snooper1, GetParam());
WaitForSnoopedData(&snooper1);
StartSnooping(&snooper2, GetParam());
WaitForSnoopedData(&snooper2);
StopSnooping(&snooper1, GetParam());
WaitForSnoopedData(&snooper2);
Close();
StopSnooping(&snooper2, GetParam());
}
TEST_P(WithSnoopingMode, SnoopWhileMuting) {
NiceMock<MockSnooper> snooper;
StartMutingBeforePlaying();
EXPECT_EQ(nullptr, last_created_stream()); // No stream yet.
EXPECT_EQ(nullptr, last_closed_stream()); // No stream yet.
Create();
MockAudioOutputStream* const mute_stream = last_created_stream();
ASSERT_TRUE(mute_stream);
EXPECT_EQ(nullptr, last_closed_stream());
Play();
ASSERT_EQ(mute_stream, last_created_stream());
EXPECT_EQ(nullptr, last_closed_stream());
EXPECT_EQ(AudioParameters::AUDIO_FAKE, mute_stream->format());
StartSnooping(&snooper, GetParam());
ASSERT_EQ(mute_stream, last_created_stream());
EXPECT_EQ(nullptr, last_closed_stream());
EXPECT_EQ(AudioParameters::AUDIO_FAKE, mute_stream->format());
WaitForSnoopedData(&snooper);
StopSnooping(&snooper, GetParam());
ASSERT_EQ(mute_stream, last_created_stream());
EXPECT_EQ(nullptr, last_closed_stream());
EXPECT_EQ(AudioParameters::AUDIO_FAKE, mute_stream->format());
Close();
EXPECT_EQ(mute_stream, last_created_stream());
EXPECT_EQ(mute_stream, last_closed_stream());
}
INSTANTIATE_TEST_SUITE_P(OutputControllerSnoopingTest,
WithSnoopingMode,
::testing::Values(Snoopable::SnoopingMode::kDeferred,
Snoopable::SnoopingMode::kRealtime));
TEST_F(OutputControllerTest, InformsStreamMonitorsAlreadyInGroup) {
MockStreamMonitor monitor;
EXPECT_CALL(monitor, OnStreamActive(GetSnoopable()));
EXPECT_CALL(monitor, OnStreamInactive(GetSnoopable()));
JoinProcessingGroup(&monitor);
Create();
Play();
Close();
LeaveProcessingGroup(&monitor);
}
TEST_F(OutputControllerTest, InformsStreamMonitorsJoiningInGroup) {
MockStreamMonitor monitor;
EXPECT_CALL(monitor, OnStreamActive(GetSnoopable()));
EXPECT_CALL(monitor, OnStreamInactive(GetSnoopable()));
Create();
Play();
JoinProcessingGroup(&monitor);
Close();
LeaveProcessingGroup(&monitor);
}
TEST_F(OutputControllerTest,
DoesNotInformStreamMonitorsJoiningInGroupAfterClose) {
MockStreamMonitor monitor;
EXPECT_CALL(monitor, OnStreamActive(GetSnoopable())).Times(0);
EXPECT_CALL(monitor, OnStreamInactive(GetSnoopable())).Times(0);
Create();
Play();
Close();
JoinProcessingGroup(&monitor);
LeaveProcessingGroup(&monitor);
}
} // namespace
} // namespace audio