blob: b11eaf31d153e8030cb50f40282bf8f7678e61e6 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/media/audio_output_stream_broker.h"
#include <memory>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/sync_socket.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "base/unguessable_token.h"
#include "content/browser/renderer_host/media/media_stream_manager.h"
#include "content/browser/renderer_host/media/mock_preferred_audio_output_device_manager.h"
#include "content/browser/renderer_host/media/preferred_audio_output_device_manager.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/browser_task_environment.h"
#include "media/audio/audio_system_impl.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_parameters.h"
#include "media/mojo/mojom/audio_data_pipe.mojom.h"
#include "media/mojo/mojom/audio_output_stream.mojom.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/system/buffer.h"
#include "mojo/public/cpp/system/platform_handle.h"
#include "services/audio/public/cpp/fake_stream_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/tokens/tokens.h"
using ::testing::_;
using ::testing::InSequence;
using ::testing::Mock;
using ::testing::ReturnRef;
using ::testing::StrictMock;
using ::testing::Test;
namespace content {
namespace {
const int kRenderProcessId = 123;
const int kRenderFrameId = 234;
const GlobalRenderFrameHostToken kMainFrameHostToken{1,
blink::LocalFrameToken()};
const int kStreamId = 345;
const size_t kShMemSize = 456;
const char kDeviceId[] = "testdeviceid";
media::AudioParameters TestParams() {
return media::AudioParameters::UnavailableDeviceParams();
}
using MockDeleterCallback = StrictMock<
base::MockCallback<base::OnceCallback<void(AudioStreamBroker*)>>>;
class MockAudioOutputStreamProviderClient
: public media::mojom::AudioOutputStreamProviderClient {
public:
MockAudioOutputStreamProviderClient() = default;
MockAudioOutputStreamProviderClient(
const MockAudioOutputStreamProviderClient&) = delete;
MockAudioOutputStreamProviderClient& operator=(
const MockAudioOutputStreamProviderClient&) = delete;
~MockAudioOutputStreamProviderClient() override {}
void Created(mojo::PendingRemote<media::mojom::AudioOutputStream>,
media::mojom::ReadWriteAudioDataPipePtr) override {
OnCreated();
}
MOCK_METHOD0(OnCreated, void());
MOCK_METHOD2(ConnectionError, void(uint32_t, const std::string&));
mojo::PendingRemote<media::mojom::AudioOutputStreamProviderClient>
MakePendingRemote() {
mojo::PendingRemote<media::mojom::AudioOutputStreamProviderClient>
pending_remote;
receiver_.Bind(pending_remote.InitWithNewPipeAndPassReceiver());
receiver_.set_disconnect_with_reason_handler(
base::BindOnce(&MockAudioOutputStreamProviderClient::ConnectionError,
base::Unretained(this)));
return pending_remote;
}
void CloseReceiver() { receiver_.reset(); }
private:
mojo::Receiver<media::mojom::AudioOutputStreamProviderClient> receiver_{this};
};
class MockDeviceSwitchInterface : public media::mojom::DeviceSwitchInterface {
public:
MockDeviceSwitchInterface() = default;
MockDeviceSwitchInterface(const MockDeviceSwitchInterface&) = delete;
MockDeviceSwitchInterface& operator=(const MockDeviceSwitchInterface&) =
delete;
~MockDeviceSwitchInterface() override = default;
MOCK_METHOD(void,
SwitchAudioOutputDeviceId,
(const std::string&),
(override));
};
class MockStreamFactory final : public audio::FakeStreamFactory {
public:
MockStreamFactory() = default;
MockStreamFactory(const MockStreamFactory&) = delete;
MockStreamFactory& operator=(const MockStreamFactory&) = delete;
~MockStreamFactory() override = default;
// State of an expected stream creation. |output_device_id|, |params|,
// and |groups_id| are set ahead of time and verified during request.
// The other fields are filled in when the request is received.
struct StreamRequestData {
StreamRequestData(const std::string& output_device_id,
const media::AudioParameters& params,
const base::UnguessableToken& group_id)
: output_device_id(output_device_id),
params(params),
group_id(group_id) {}
bool requested = false;
mojo::PendingReceiver<media::mojom::AudioOutputStream> stream_receiver;
mojo::PendingAssociatedRemote<media::mojom::AudioOutputStreamObserver>
observer_remote;
mojo::Remote<media::mojom::AudioLog> log;
const std::string output_device_id;
const media::AudioParameters params;
const base::UnguessableToken group_id;
CreateOutputStreamCallback created_callback;
};
void ExpectStreamCreation(StreamRequestData* ex) {
stream_request_data_ = ex;
}
private:
void CreateOutputStream(
mojo::PendingReceiver<media::mojom::AudioOutputStream> stream_receiver,
mojo::PendingAssociatedRemote<media::mojom::AudioOutputStreamObserver>
observer,
mojo::PendingRemote<media::mojom::AudioLog> log,
const std::string& output_device_id,
const media::AudioParameters& params,
const base::UnguessableToken& group_id,
CreateOutputStreamCallback created_callback) final {
// No way to cleanly exit the test here in case of failure, so use CHECK.
CHECK(stream_request_data_);
EXPECT_EQ(stream_request_data_->output_device_id, output_device_id);
EXPECT_TRUE(stream_request_data_->params.Equals(params));
EXPECT_EQ(stream_request_data_->group_id, group_id);
stream_request_data_->requested = true;
stream_request_data_->stream_receiver = std::move(stream_receiver);
stream_request_data_->observer_remote = std::move(observer);
stream_request_data_->log.Bind(std ::move(log));
stream_request_data_->created_callback = std::move(created_callback);
}
void CreateSwitchableOutputStream(
mojo::PendingReceiver<media::mojom::AudioOutputStream> stream_receiver,
mojo::PendingReceiver<media::mojom::DeviceSwitchInterface>
device_switch_receiver,
mojo::PendingAssociatedRemote<media::mojom::AudioOutputStreamObserver>
observer,
mojo::PendingRemote<media::mojom::AudioLog> log,
const std::string& output_device_id,
const media::AudioParameters& params,
const base::UnguessableToken& group_id,
CreateOutputStreamCallback created_callback) final {
CreateOutputStream(std::move(stream_receiver), std::move(observer),
std::move(log), output_device_id, params, group_id,
std::move(created_callback));
}
raw_ptr<StreamRequestData> stream_request_data_;
};
// This struct collects test state we need without doing anything fancy.
struct TestEnvironment {
explicit TestEnvironment(const std::string device_id = kDeviceId)
: group(base::UnguessableToken::Create()),
broker(std::make_unique<AudioOutputStreamBroker>(
kRenderProcessId,
kRenderFrameId,
kMainFrameHostToken,
kStreamId,
device_id,
TestParams(),
group,
deleter.Get(),
provider_client.MakePendingRemote())) {
audio_manager = std::make_unique<media::FakeAudioManager>(
std::make_unique<media::TestAudioThread>(), &log_factory);
audio_system =
std::make_unique<media::AudioSystemImpl>(audio_manager.get());
media_stream_manager =
std::make_unique<MediaStreamManager>(audio_system.get());
}
~TestEnvironment() { audio_manager->Shutdown(); }
void RunUntilIdle() { env.RunUntilIdle(); }
void BindAndSetDeviceSwitchInterface() {
// Inject mock into AudioOutputStreamBroker
mojo::PendingRemote<media::mojom::DeviceSwitchInterface> pending_remote;
receiver =
std::make_unique<mojo::Receiver<media::mojom::DeviceSwitchInterface>>(
&device_switch_interface,
pending_remote.InitWithNewPipeAndPassReceiver());
mojo::Remote<media::mojom::DeviceSwitchInterface> remote(
std::move(pending_remote));
broker->SetDeviceSwichInterfaceForTesting(std::move(remote));
}
// MediaInternals RenderProcessHost observation setup asserts being run on the
// UI thread.
media::FakeAudioLogFactory log_factory;
std::unique_ptr<media::FakeAudioManager> audio_manager;
std::unique_ptr<media::AudioSystemImpl> audio_system;
std::unique_ptr<MediaStreamManager> media_stream_manager;
BrowserTaskEnvironment env;
base::UnguessableToken group;
MockDeleterCallback deleter;
StrictMock<MockAudioOutputStreamProviderClient> provider_client;
std::unique_ptr<AudioOutputStreamBroker> broker;
MockStreamFactory stream_factory;
MockDeviceSwitchInterface device_switch_interface;
std::unique_ptr<mojo::Receiver<media::mojom::DeviceSwitchInterface>> receiver;
mojo::Remote<media::mojom::AudioStreamFactory> factory_ptr{
stream_factory.MakeRemote()};
};
} // namespace
TEST(AudioOutputStreamBrokerTest, StoresProcessAndFrameId) {
BrowserTaskEnvironment env;
MockDeleterCallback deleter;
StrictMock<MockAudioOutputStreamProviderClient> provider_client;
AudioOutputStreamBroker broker(
kRenderProcessId, kRenderFrameId, kMainFrameHostToken, kStreamId,
kDeviceId, TestParams(), base::UnguessableToken::Create(), deleter.Get(),
provider_client.MakePendingRemote());
EXPECT_EQ(kRenderProcessId, broker.render_process_id());
EXPECT_EQ(kRenderFrameId, broker.render_frame_id());
}
TEST(AudioOutputStreamBrokerTest, ClientDisconnect_CallsDeleter) {
TestEnvironment env;
EXPECT_CALL(env.deleter, Run(env.broker.release()))
.WillOnce(testing::DeleteArg<0>());
env.provider_client.CloseReceiver();
env.RunUntilIdle();
}
TEST(AudioOutputStreamBrokerTest, StreamCreationSuccess_Propagates) {
TestEnvironment env;
MockStreamFactory::StreamRequestData stream_request_data(
kDeviceId, TestParams(), env.group);
env.stream_factory.ExpectStreamCreation(&stream_request_data);
env.broker->CreateStream(env.factory_ptr.get());
env.RunUntilIdle();
EXPECT_TRUE(stream_request_data.requested);
// Set up test IPC primitives.
base::SyncSocket socket1, socket2;
base::SyncSocket::CreatePair(&socket1, &socket2);
std::move(stream_request_data.created_callback)
.Run({std::in_place, base::UnsafeSharedMemoryRegion::Create(kShMemSize),
mojo::PlatformHandle(socket1.Take())});
EXPECT_CALL(env.provider_client, OnCreated());
env.RunUntilIdle();
Mock::VerifyAndClear(&env.provider_client);
EXPECT_CALL(env.provider_client, ConnectionError(_, _));
env.broker.reset();
}
TEST(AudioOutputStreamBrokerTest,
StreamCreationFailure_PropagatesErrorAndCallsDeleter) {
TestEnvironment env;
MockStreamFactory::StreamRequestData stream_request_data(
kDeviceId, TestParams(), env.group);
env.stream_factory.ExpectStreamCreation(&stream_request_data);
env.broker->CreateStream(env.factory_ptr.get());
env.RunUntilIdle();
EXPECT_TRUE(stream_request_data.requested);
EXPECT_CALL(env.provider_client,
ConnectionError(static_cast<uint32_t>(
media::mojom::AudioOutputStreamObserver::
DisconnectReason::kPlatformError),
std::string()));
EXPECT_CALL(env.deleter, Run(env.broker.release()))
.WillOnce(testing::DeleteArg<0>());
std::move(stream_request_data.created_callback).Run(nullptr);
env.RunUntilIdle();
}
TEST(AudioOutputStreamBrokerTest,
ObserverDisconnect_PropagatesErrorAndCallsDeleter) {
TestEnvironment env;
MockStreamFactory::StreamRequestData stream_request_data(
kDeviceId, TestParams(), env.group);
env.stream_factory.ExpectStreamCreation(&stream_request_data);
env.broker->CreateStream(env.factory_ptr.get());
env.RunUntilIdle();
EXPECT_TRUE(stream_request_data.requested);
EXPECT_CALL(env.provider_client,
ConnectionError(static_cast<uint32_t>(
media::mojom::AudioOutputStreamObserver::
DisconnectReason::kPlatformError),
std::string()));
EXPECT_CALL(env.deleter, Run(env.broker.release()))
.WillOnce(testing::DeleteArg<0>());
// This results in a disconnect.
stream_request_data.observer_remote.PassHandle();
env.RunUntilIdle();
env.stream_factory.ResetReceiver();
env.RunUntilIdle();
}
TEST(AudioOutputStreamBrokerTest,
FactoryDisconnectDuringConstruction_PropagatesErrorAndCallsDeleter) {
TestEnvironment env;
env.broker->CreateStream(env.factory_ptr.get());
env.stream_factory.ResetReceiver();
EXPECT_CALL(env.deleter, Run(env.broker.release()))
.WillOnce(testing::DeleteArg<0>());
EXPECT_CALL(env.provider_client,
ConnectionError(static_cast<uint32_t>(
media::mojom::AudioOutputStreamObserver::
DisconnectReason::kPlatformError),
std::string()));
env.RunUntilIdle();
}
TEST(AudioOutputStreamBrokerTest, SwitchableStreamCreationSuccess) {
TestEnvironment env(media::AudioDeviceDescription::kDefaultDeviceId);
MockStreamFactory::StreamRequestData stream_request_data(
media::AudioDeviceDescription::kDefaultDeviceId, TestParams(), env.group);
env.stream_factory.ExpectStreamCreation(&stream_request_data);
auto mock_preferred_audio_output_device_manager =
std::make_unique<MockPreferredAudioOutputDeviceManager>();
MockPreferredAudioOutputDeviceManager* manager =
mock_preferred_audio_output_device_manager.get();
env.media_stream_manager->SetPreferredAudioOutputDeviceManagerForTesting(
std::move(mock_preferred_audio_output_device_manager));
EXPECT_CALL(*manager, AddSwitcher(_, _)).Times(1);
env.broker->CreateStream(env.factory_ptr.get());
env.RunUntilIdle();
EXPECT_TRUE(env.broker->IsSwitchableStreamCreatedForTesting());
EXPECT_TRUE(stream_request_data.requested);
// Set up device switcher.
env.BindAndSetDeviceSwitchInterface();
constexpr char kRawDeviceId[] = "rawdeviceid";
EXPECT_CALL(*manager,
SetPreferredSinkId(kMainFrameHostToken, kRawDeviceId, _))
.WillOnce([&env](const GlobalRenderFrameHostToken&,
const std::string& device_id,
base::OnceCallback<void(media::OutputDeviceStatus)>) {
env.broker->SwitchAudioOutputDeviceId(device_id);
});
EXPECT_CALL(env.device_switch_interface,
SwitchAudioOutputDeviceId(kRawDeviceId))
.Times(1);
manager->SetPreferredSinkId(kMainFrameHostToken, kRawDeviceId,
base::DoNothing());
// Set up test IPC primitives.
base::SyncSocket socket1, socket2;
base::SyncSocket::CreatePair(&socket1, &socket2);
std::move(stream_request_data.created_callback)
.Run({std::in_place, base::UnsafeSharedMemoryRegion::Create(kShMemSize),
mojo::PlatformHandle(socket1.Take())});
EXPECT_CALL(env.provider_client, OnCreated());
env.RunUntilIdle();
Mock::VerifyAndClear(&env.provider_client);
EXPECT_CALL(env.provider_client, ConnectionError(_, _));
EXPECT_CALL(*manager, RemoveSwitcher(_, _)).Times(1);
env.broker.reset();
}
TEST(AudioOutputStreamBrokerTest,
NonSwitchableStreamCreationForNonPreferredManager) {
// Do not call `AddSwitcher()` and `RemoveSwitcher` if
// `PreferredAudioOutputDeviceManager` is not set.
TestEnvironment env(media::AudioDeviceDescription::kDefaultDeviceId);
MockStreamFactory::StreamRequestData stream_request_data(
media::AudioDeviceDescription::kDefaultDeviceId, TestParams(), env.group);
env.stream_factory.ExpectStreamCreation(&stream_request_data);
auto mock_preferred_audio_output_device_manager =
std::make_unique<MockPreferredAudioOutputDeviceManager>();
MockPreferredAudioOutputDeviceManager* manager =
mock_preferred_audio_output_device_manager.get();
env.media_stream_manager->SetPreferredAudioOutputDeviceManagerForTesting(
nullptr);
EXPECT_CALL(*manager, AddSwitcher(_, _)).Times(0);
env.broker->CreateStream(env.factory_ptr.get());
env.RunUntilIdle();
EXPECT_FALSE(env.broker->IsSwitchableStreamCreatedForTesting());
EXPECT_TRUE(stream_request_data.requested);
// Set up test IPC primitives.
base::SyncSocket socket1, socket2;
base::SyncSocket::CreatePair(&socket1, &socket2);
std::move(stream_request_data.created_callback)
.Run({std::in_place, base::UnsafeSharedMemoryRegion::Create(kShMemSize),
mojo::PlatformHandle(socket1.Take())});
EXPECT_CALL(env.provider_client, OnCreated());
env.RunUntilIdle();
Mock::VerifyAndClear(&env.provider_client);
EXPECT_CALL(env.provider_client, ConnectionError(_, _));
EXPECT_CALL(*manager, RemoveSwitcher(_, _)).Times(0);
env.broker.reset();
}
TEST(AudioOutputStreamBrokerTest,
UnSwitchableStreamCreationWhenNonDefaultDevice) {
// Do not call `AddSwitcher()` if not default device id.
TestEnvironment env(kDeviceId);
MockStreamFactory::StreamRequestData stream_request_data(
kDeviceId, TestParams(), env.group);
env.stream_factory.ExpectStreamCreation(&stream_request_data);
auto mock_preferred_audio_output_device_manager =
std::make_unique<MockPreferredAudioOutputDeviceManager>();
MockPreferredAudioOutputDeviceManager* manager =
mock_preferred_audio_output_device_manager.get();
env.media_stream_manager->SetPreferredAudioOutputDeviceManagerForTesting(
std::move(mock_preferred_audio_output_device_manager));
EXPECT_CALL(*manager, AddSwitcher(_, _)).Times(0);
env.broker->CreateStream(env.factory_ptr.get());
env.RunUntilIdle();
EXPECT_FALSE(env.broker->IsSwitchableStreamCreatedForTesting());
EXPECT_TRUE(stream_request_data.requested);
// Set up test IPC primitives.
base::SyncSocket socket1, socket2;
base::SyncSocket::CreatePair(&socket1, &socket2);
std::move(stream_request_data.created_callback)
.Run({std::in_place, base::UnsafeSharedMemoryRegion::Create(kShMemSize),
mojo::PlatformHandle(socket1.Take())});
EXPECT_CALL(env.provider_client, OnCreated());
env.RunUntilIdle();
Mock::VerifyAndClear(&env.provider_client);
EXPECT_CALL(env.provider_client, ConnectionError(_, _));
EXPECT_CALL(*manager, RemoveSwitcher(_, _)).Times(1);
env.broker.reset();
}
TEST(AudioOutputStreamBrokerTest, SwitchableStreamCreationWithPreferredSinkId) {
// Switchable stream is created when preferred sink id.
TestEnvironment env(media::AudioDeviceDescription::kDefaultDeviceId);
MockStreamFactory::StreamRequestData stream_request_data(
kDeviceId, TestParams(), env.group);
env.stream_factory.ExpectStreamCreation(&stream_request_data);
auto mock_preferred_audio_output_device_manager =
std::make_unique<MockPreferredAudioOutputDeviceManager>();
MockPreferredAudioOutputDeviceManager* manager =
mock_preferred_audio_output_device_manager.get();
env.media_stream_manager->SetPreferredAudioOutputDeviceManagerForTesting(
std::move(mock_preferred_audio_output_device_manager));
EXPECT_CALL(*manager, AddSwitcher(_, _))
.WillOnce([](const GlobalRenderFrameHostToken&,
AudioOutputDeviceSwitcher* switcher) {
switcher->SwitchAudioOutputDeviceId(kDeviceId);
});
env.broker->CreateStream(env.factory_ptr.get());
env.RunUntilIdle();
EXPECT_TRUE(env.broker->IsSwitchableStreamCreatedForTesting());
EXPECT_TRUE(stream_request_data.requested);
// Set up device switcher.
env.BindAndSetDeviceSwitchInterface();
EXPECT_CALL(*manager, SetPreferredSinkId(
kMainFrameHostToken,
media::AudioDeviceDescription::kDefaultDeviceId, _))
.WillOnce([&env](const GlobalRenderFrameHostToken&,
const std::string& device_id,
base::OnceCallback<void(media::OutputDeviceStatus)>) {
env.broker->SwitchAudioOutputDeviceId(device_id);
});
EXPECT_CALL(env.device_switch_interface,
SwitchAudioOutputDeviceId(
media::AudioDeviceDescription::kDefaultDeviceId))
.Times(1);
manager->SetPreferredSinkId(kMainFrameHostToken,
media::AudioDeviceDescription::kDefaultDeviceId,
base::DoNothing());
// Set up test IPC primitives.
base::SyncSocket socket1, socket2;
base::SyncSocket::CreatePair(&socket1, &socket2);
std::move(stream_request_data.created_callback)
.Run({std::in_place, base::UnsafeSharedMemoryRegion::Create(kShMemSize),
mojo::PlatformHandle(socket1.Take())});
EXPECT_CALL(env.provider_client, OnCreated());
env.RunUntilIdle();
Mock::VerifyAndClear(&env.provider_client);
EXPECT_CALL(env.provider_client, ConnectionError(_, _));
EXPECT_CALL(*manager, RemoveSwitcher(_, _)).Times(1);
env.broker.reset();
}
} // namespace content