blob: e147f612b4753f1c59ec2d1ecf48b7082dcfeddb [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_loopback_stream_broker.h"
#include <memory>
#include <utility>
#include "base/memory/raw_ptr.h"
#include "base/sync_socket.h"
#include "base/test/mock_callback.h"
#include "content/public/test/browser_task_environment.h"
#include "media/mojo/mojom/audio_input_stream.mojom.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.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"
using ::testing::_;
using ::testing::Test;
using ::testing::Mock;
using ::testing::NiceMock;
using ::testing::StrictMock;
using ::testing::InSequence;
namespace content {
namespace {
const int kRenderProcessId = 123;
const int kRenderFrameId = 234;
const uint32_t kShMemCount = 10;
media::AudioParameters TestParams() {
return media::AudioParameters::UnavailableDeviceParams();
}
class MockSource : public AudioStreamBroker::LoopbackSource {
public:
MockSource() : group_id_(base::UnguessableToken::Create()) {}
MockSource(const MockSource&) = delete;
MockSource& operator=(const MockSource&) = delete;
~MockSource() override {}
// AudioStreamBrokerFactory::LoopbackSource mocking.
const base::UnguessableToken& GetGroupID() override { return group_id_; }
MOCK_METHOD1(AddLoopbackSink, void(AudioStreamBroker::LoopbackSink*));
MOCK_METHOD1(RemoveLoopbackSink, void(AudioStreamBroker::LoopbackSink*));
private:
base::UnguessableToken group_id_;
};
using MockDeleterCallback = StrictMock<
base::MockCallback<base::OnceCallback<void(AudioStreamBroker*)>>>;
class MockRendererAudioInputStreamFactoryClient
: public blink::mojom::RendererAudioInputStreamFactoryClient {
public:
MockRendererAudioInputStreamFactoryClient() = default;
~MockRendererAudioInputStreamFactoryClient() override {}
mojo::PendingRemote<blink::mojom::RendererAudioInputStreamFactoryClient>
MakeRemote() {
return receiver_.BindNewPipeAndPassRemote();
}
MOCK_METHOD0(OnStreamCreated, void());
void StreamCreated(
mojo::PendingRemote<media::mojom::AudioInputStream> input_stream,
mojo::PendingReceiver<media::mojom::AudioInputStreamClient>
client_receiver,
media::mojom::ReadWriteAudioDataPipePtr data_pipe,
bool initially_muted,
const std::optional<base::UnguessableToken>& stream_id) override {
// Loopback streams have no stream ids.
EXPECT_FALSE(stream_id.has_value());
input_stream_.Bind(std::move(input_stream));
client_receiver_ = std::move(client_receiver);
OnStreamCreated();
}
void CloseReceiver() { receiver_.reset(); }
private:
mojo::Receiver<blink::mojom::RendererAudioInputStreamFactoryClient> receiver_{
this};
mojo::Remote<media::mojom::AudioInputStream> input_stream_;
mojo::PendingReceiver<media::mojom::AudioInputStreamClient> client_receiver_;
};
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. |device_id| and |params| are set
// ahead of time and verified during request. The other fields are filled in
// when the request is received.
struct StreamRequestData {
StreamRequestData(const base::UnguessableToken& group_id,
const media::AudioParameters& params)
: params(params), group_id(group_id) {}
bool requested = false;
mojo::PendingReceiver<media::mojom::AudioInputStream> stream_receiver;
mojo::Remote<media::mojom::AudioInputStreamClient> client;
mojo::Remote<media::mojom::AudioInputStreamObserver> observer;
const media::AudioParameters params;
uint32_t shared_memory_count;
base::UnguessableToken group_id;
mojo::ScopedSharedBufferHandle key_press_count_buffer;
CreateLoopbackStreamCallback created_callback;
mojo::PendingAssociatedReceiver<media::mojom::LocalMuter> muter_receiver;
};
void ExpectStreamCreation(StreamRequestData* ex) {
stream_request_data_ = ex;
}
MOCK_METHOD1(IsMuting, void(const base::UnguessableToken&));
private:
void CreateLoopbackStream(
mojo::PendingReceiver<media::mojom::AudioInputStream> receiver,
mojo::PendingRemote<media::mojom::AudioInputStreamClient> client,
mojo::PendingRemote<media::mojom::AudioInputStreamObserver> observer,
const media::AudioParameters& params,
uint32_t shared_memory_count,
const base::UnguessableToken& group_id,
CreateLoopbackStreamCallback created_callback) override {
// No way to cleanly exit the test here in case of failure, so use CHECK.
CHECK(stream_request_data_);
EXPECT_EQ(stream_request_data_->group_id, group_id);
EXPECT_TRUE(stream_request_data_->params.Equals(params));
stream_request_data_->requested = true;
stream_request_data_->stream_receiver = std::move(receiver);
stream_request_data_->client.Bind(std::move(client));
stream_request_data_->observer.Bind(std::move(observer));
stream_request_data_->shared_memory_count = shared_memory_count;
stream_request_data_->created_callback = std::move(created_callback);
}
void BindMuter(
mojo::PendingAssociatedReceiver<media::mojom::LocalMuter> receiver,
const base::UnguessableToken& group_id) override {
stream_request_data_->muter_receiver = std::move(receiver);
IsMuting(group_id);
}
raw_ptr<StreamRequestData> stream_request_data_;
};
const bool kMuteSource = true;
struct TestEnvironment {
TestEnvironment(const base::UnguessableToken& group_id, bool mute_source) {
// Muting should not start until CreateStream() is called.
EXPECT_CALL(stream_factory, IsMuting(_)).Times(0);
EXPECT_CALL(source, AddLoopbackSink(_));
broker = std::make_unique<AudioLoopbackStreamBroker>(
kRenderProcessId, kRenderFrameId, &source, TestParams(), kShMemCount,
mute_source, deleter.Get(), renderer_factory_client.MakeRemote());
}
void RunUntilIdle() { task_environment.RunUntilIdle(); }
BrowserTaskEnvironment task_environment;
MockDeleterCallback deleter;
MockSource source;
StrictMock<MockRendererAudioInputStreamFactoryClient> renderer_factory_client;
std::unique_ptr<AudioLoopbackStreamBroker> broker;
MockStreamFactory stream_factory;
mojo::Remote<media::mojom::AudioStreamFactory> factory_ptr{
stream_factory.MakeRemote()};
};
} // namespace
TEST(AudioLoopbackStreamBrokerTest, StoresProcessAndFrameId) {
InSequence seq;
BrowserTaskEnvironment task_environment;
MockDeleterCallback deleter;
StrictMock<MockRendererAudioInputStreamFactoryClient> renderer_factory_client;
MockSource source;
EXPECT_CALL(source, AddLoopbackSink(_));
AudioLoopbackStreamBroker broker(
kRenderProcessId, kRenderFrameId, &source, TestParams(), kShMemCount,
!kMuteSource, deleter.Get(), renderer_factory_client.MakeRemote());
EXPECT_EQ(kRenderProcessId, broker.render_process_id());
EXPECT_EQ(kRenderFrameId, broker.render_frame_id());
EXPECT_CALL(source, RemoveLoopbackSink(&broker));
}
TEST(AudioLoopbackStreamBrokerTest, StreamCreationSuccess_Propagates) {
TestEnvironment env(base::UnguessableToken::Create(), !kMuteSource);
MockStreamFactory::StreamRequestData stream_request_data(
env.source.GetGroupID(), TestParams());
env.stream_factory.ExpectStreamCreation(&stream_request_data);
EXPECT_CALL(env.stream_factory, IsMuting(_)).Times(0);
env.broker->CreateStream(env.factory_ptr.get());
env.RunUntilIdle();
EXPECT_TRUE(stream_request_data.requested);
// Set up test IPC primitives.
const size_t shmem_size = 456;
base::SyncSocket socket1, socket2;
base::SyncSocket::CreatePair(&socket1, &socket2);
std::move(stream_request_data.created_callback)
.Run({std::in_place, base::UnsafeSharedMemoryRegion::Create(shmem_size),
mojo::PlatformHandle(socket1.Take())});
EXPECT_CALL(env.renderer_factory_client, OnStreamCreated());
env.RunUntilIdle();
Mock::VerifyAndClear(&env.renderer_factory_client);
env.broker.reset();
}
TEST(AudioLoopbackStreamBrokerTest, MutedStreamCreation_Mutes) {
TestEnvironment env(base::UnguessableToken::Create(), kMuteSource);
MockStreamFactory::StreamRequestData stream_request_data(
env.source.GetGroupID(), TestParams());
env.stream_factory.ExpectStreamCreation(&stream_request_data);
EXPECT_CALL(env.stream_factory, IsMuting(env.source.GetGroupID()));
env.broker->CreateStream(env.factory_ptr.get());
env.RunUntilIdle();
EXPECT_TRUE(stream_request_data.requested);
// Set up test IPC primitives.
const size_t shmem_size = 456;
base::SyncSocket socket1, socket2;
base::SyncSocket::CreatePair(&socket1, &socket2);
std::move(stream_request_data.created_callback)
.Run({std::in_place, base::UnsafeSharedMemoryRegion::Create(shmem_size),
mojo::PlatformHandle(socket1.Take())});
EXPECT_CALL(env.renderer_factory_client, OnStreamCreated());
env.RunUntilIdle();
Mock::VerifyAndClear(&env.renderer_factory_client);
env.broker.reset();
}
TEST(AudioLoopbackStreamBrokerTest, SourceGone_CallsDeleter) {
TestEnvironment env(base::UnguessableToken::Create(), kMuteSource);
MockStreamFactory::StreamRequestData stream_request_data(
env.source.GetGroupID(), TestParams());
env.stream_factory.ExpectStreamCreation(&stream_request_data);
EXPECT_CALL(env.stream_factory, IsMuting(env.source.GetGroupID()));
env.broker->CreateStream(env.factory_ptr.get());
env.RunUntilIdle();
EXPECT_TRUE(stream_request_data.requested);
// Set up test IPC primitives.
const size_t shmem_size = 456;
base::SyncSocket socket1, socket2;
base::SyncSocket::CreatePair(&socket1, &socket2);
std::move(stream_request_data.created_callback)
.Run({std::in_place, base::UnsafeSharedMemoryRegion::Create(shmem_size),
mojo::PlatformHandle(socket1.Take())});
EXPECT_CALL(env.renderer_factory_client, OnStreamCreated());
env.RunUntilIdle();
Mock::VerifyAndClear(&env.renderer_factory_client);
EXPECT_CALL(env.deleter, Run(env.broker.get()));
// Accessing source is not allowed after OnSourceGone.
EXPECT_CALL(env.source, RemoveLoopbackSink(_)).Times(0);
env.broker->OnSourceGone();
env.RunUntilIdle();
}
TEST(AudioLoopbackStreamBrokerTest, StreamCreationFailure_CallsDeleter) {
TestEnvironment env(base::UnguessableToken::Create(), !kMuteSource);
MockStreamFactory::StreamRequestData stream_request_data(
env.source.GetGroupID(), TestParams());
env.stream_factory.ExpectStreamCreation(&stream_request_data);
EXPECT_CALL(env.stream_factory, IsMuting(_)).Times(0);
env.broker->CreateStream(env.factory_ptr.get());
env.RunUntilIdle();
EXPECT_TRUE(stream_request_data.requested);
EXPECT_CALL(env.deleter, Run(env.broker.release()))
.WillOnce(testing::DeleteArg<0>());
std::move(stream_request_data.created_callback).Run(nullptr);
env.RunUntilIdle();
}
TEST(AudioLoopbackStreamBrokerTest,
RendererFactoryClientDisconnect_CallsDeleter) {
TestEnvironment env(base::UnguessableToken::Create(), !kMuteSource);
MockStreamFactory::StreamRequestData stream_request_data(
env.source.GetGroupID(), TestParams());
env.stream_factory.ExpectStreamCreation(&stream_request_data);
EXPECT_CALL(env.stream_factory, IsMuting(_)).Times(0);
env.broker->CreateStream(env.factory_ptr.get());
env.RunUntilIdle();
EXPECT_TRUE(stream_request_data.requested);
EXPECT_CALL(env.deleter, Run(env.broker.release()))
.WillOnce(testing::DeleteArg<0>());
env.renderer_factory_client.CloseReceiver();
env.RunUntilIdle();
Mock::VerifyAndClear(&env.deleter);
env.stream_factory.ResetReceiver();
env.RunUntilIdle();
}
TEST(AudioLoopbackStreamBrokerTest, ObserverDisconnect_CallsDeleter) {
TestEnvironment env(base::UnguessableToken::Create(), !kMuteSource);
MockStreamFactory::StreamRequestData stream_request_data(
env.source.GetGroupID(), TestParams());
env.stream_factory.ExpectStreamCreation(&stream_request_data);
EXPECT_CALL(env.stream_factory, IsMuting(_)).Times(0);
env.broker->CreateStream(env.factory_ptr.get());
env.RunUntilIdle();
EXPECT_TRUE(stream_request_data.requested);
EXPECT_CALL(env.deleter, Run(env.broker.release()))
.WillOnce(testing::DeleteArg<0>());
stream_request_data.observer.reset();
env.RunUntilIdle();
Mock::VerifyAndClear(&env.deleter);
env.stream_factory.ResetReceiver();
env.RunUntilIdle();
}
TEST(AudioLoopbackStreamBrokerTest,
FactoryDisconnectDuringConstruction_CallsDeleter) {
TestEnvironment env(base::UnguessableToken::Create(), !kMuteSource);
env.broker->CreateStream(env.factory_ptr.get());
env.stream_factory.ResetReceiver();
EXPECT_CALL(env.deleter, Run(env.broker.release()))
.WillOnce(testing::DeleteArg<0>());
env.RunUntilIdle();
}
} // namespace content