blob: 97e90787ce7cb17e183addcc79af894af9733438 [file] [log] [blame]
// Copyright 2017 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 "third_party/blink/renderer/modules/media/audio/mojo_audio_output_ipc.h"
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include "base/optional.h"
#include "base/run_loop.h"
#include "base/test/gtest_util.h"
#include "media/audio/audio_device_description.h"
#include "media/base/audio_parameters.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 "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
using testing::_;
using testing::AtLeast;
using testing::Invoke;
using testing::Mock;
using testing::StrictMock;
namespace blink {
namespace {
const size_t kMemoryLength = 4321;
const char kDeviceId[] = "device_id";
const char kReturnedDeviceId[] = "returned_device_id";
const double kNewVolume = 0.271828;
media::AudioParameters Params() {
return media::AudioParameters::UnavailableDeviceParams();
}
MojoAudioOutputIPC::FactoryAccessorCB NullAccessor() {
return WTF::BindRepeating(
[]() -> blink::mojom::blink::RendererAudioOutputStreamFactory* {
return nullptr;
});
}
// TODO(https://crbug.com/787252): Convert the test away from using std::string.
class TestStreamProvider
: public media::mojom::blink::AudioOutputStreamProvider {
public:
explicit TestStreamProvider(media::mojom::blink::AudioOutputStream* stream)
: stream_(stream) {}
~TestStreamProvider() override {
// If we expected a stream to be acquired, make sure it is so.
if (stream_)
EXPECT_TRUE(receiver_);
}
void Acquire(
const media::AudioParameters& params,
mojo::PendingRemote<media::mojom::blink::AudioOutputStreamProviderClient>
pending_provider_client) override {
EXPECT_EQ(receiver_, base::nullopt);
EXPECT_NE(stream_, nullptr);
provider_client_.reset();
provider_client_.Bind(std::move(pending_provider_client));
mojo::PendingRemote<media::mojom::blink::AudioOutputStream>
stream_pending_remote;
receiver_.emplace(stream_,
stream_pending_remote.InitWithNewPipeAndPassReceiver());
base::CancelableSyncSocket foreign_socket;
EXPECT_TRUE(
base::CancelableSyncSocket::CreatePair(&socket_, &foreign_socket));
provider_client_->Created(
std::move(stream_pending_remote),
{base::in_place, base::UnsafeSharedMemoryRegion::Create(kMemoryLength),
mojo::PlatformHandle(foreign_socket.Take())});
}
void SignalErrorToProviderClient() {
provider_client_.ResetWithReason(
static_cast<uint32_t>(media::mojom::blink::AudioOutputStreamObserver::
DisconnectReason::kPlatformError),
std::string());
}
private:
media::mojom::blink::AudioOutputStream* stream_;
mojo::Remote<media::mojom::blink::AudioOutputStreamProviderClient>
provider_client_;
base::Optional<mojo::Receiver<media::mojom::blink::AudioOutputStream>>
receiver_;
base::CancelableSyncSocket socket_;
};
class TestRemoteFactory
: public blink::mojom::blink::RendererAudioOutputStreamFactory {
public:
TestRemoteFactory()
: expect_request_(false),
receiver_(this, this_remote_.BindNewPipeAndPassReceiver()) {}
~TestRemoteFactory() override {}
void RequestDeviceAuthorization(
mojo::PendingReceiver<media::mojom::blink::AudioOutputStreamProvider>
stream_provider_receiver,
const base::Optional<base::UnguessableToken>& session_id,
const String& device_id,
RequestDeviceAuthorizationCallback callback) override {
EXPECT_EQ(session_id, expected_session_id_);
EXPECT_EQ(device_id.Utf8(), expected_device_id_);
EXPECT_TRUE(expect_request_);
if (provider_) {
std::move(callback).Run(
static_cast<media::mojom::blink::OutputDeviceStatus>(
media::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_OK),
Params(), String(kReturnedDeviceId));
provider_receiver_.emplace(provider_.get(),
std::move(stream_provider_receiver));
} else {
std::move(callback).Run(
static_cast<media::mojom::blink::OutputDeviceStatus>(
media::OutputDeviceStatus::
OUTPUT_DEVICE_STATUS_ERROR_NOT_AUTHORIZED),
Params(), String(""));
}
expect_request_ = false;
}
void PrepareProviderForAuthorization(
const base::UnguessableToken& session_id,
const std::string& device_id,
std::unique_ptr<TestStreamProvider> provider) {
EXPECT_FALSE(expect_request_);
expect_request_ = true;
expected_session_id_ = session_id.is_empty()
? base::Optional<base::UnguessableToken>()
: session_id;
expected_device_id_ = device_id;
provider_receiver_.reset();
std::swap(provider_, provider);
}
void RefuseNextRequest(const base::UnguessableToken& session_id,
const std::string& device_id) {
EXPECT_FALSE(expect_request_);
expect_request_ = true;
expected_session_id_ = session_id;
expected_device_id_ = device_id;
}
void SignalErrorToProviderClient() {
provider_->SignalErrorToProviderClient();
}
void Disconnect() {
receiver_.reset();
this_remote_.reset();
receiver_.Bind(this_remote_.BindNewPipeAndPassReceiver());
provider_receiver_.reset();
provider_.reset();
expect_request_ = false;
}
MojoAudioOutputIPC::FactoryAccessorCB GetAccessor() {
return WTF::BindRepeating(&TestRemoteFactory::get, WTF::Unretained(this));
}
private:
blink::mojom::blink::RendererAudioOutputStreamFactory* get() {
return this_remote_.get();
}
bool expect_request_;
base::Optional<base::UnguessableToken> expected_session_id_;
std::string expected_device_id_;
mojo::Remote<blink::mojom::blink::RendererAudioOutputStreamFactory>
this_remote_;
mojo::Receiver<blink::mojom::blink::RendererAudioOutputStreamFactory>
receiver_{this};
std::unique_ptr<TestStreamProvider> provider_;
base::Optional<mojo::Receiver<media::mojom::blink::AudioOutputStreamProvider>>
provider_receiver_;
};
class MockStream : public media::mojom::blink::AudioOutputStream {
public:
MOCK_METHOD0(Play, void());
MOCK_METHOD0(Pause, void());
MOCK_METHOD0(Flush, void());
MOCK_METHOD1(SetVolume, void(double));
};
class MockDelegate : public media::AudioOutputIPCDelegate {
public:
MockDelegate() = default;
~MockDelegate() override = default;
void OnStreamCreated(base::UnsafeSharedMemoryRegion mem_handle,
base::SyncSocket::ScopedHandle socket_handle,
bool playing_automatically) override {
GotOnStreamCreated();
}
MOCK_METHOD0(OnError, void());
MOCK_METHOD3(OnDeviceAuthorized,
void(media::OutputDeviceStatus device_status,
const media::AudioParameters& output_params,
const std::string& matched_device_id));
MOCK_METHOD0(GotOnStreamCreated, void());
MOCK_METHOD0(OnIPCClosed, void());
};
} // namespace
TEST(MojoAudioOutputIPC, AuthorizeWithoutFactory_CallsAuthorizedWithError) {
const base::UnguessableToken session_id = base::UnguessableToken::Create();
StrictMock<MockDelegate> delegate;
std::unique_ptr<media::AudioOutputIPC> ipc =
std::make_unique<MojoAudioOutputIPC>(
NullAccessor(),
blink::scheduler::GetSingleThreadTaskRunnerForTesting());
ipc->RequestDeviceAuthorization(&delegate, session_id, kDeviceId);
// Don't call OnDeviceAuthorized synchronously, should wait until we run the
// RunLoop.
EXPECT_CALL(delegate,
OnDeviceAuthorized(media::OUTPUT_DEVICE_STATUS_ERROR_INTERNAL, _,
std::string()));
base::RunLoop().RunUntilIdle();
ipc->CloseStream();
}
TEST(MojoAudioOutputIPC,
CreateWithoutAuthorizationWithoutFactory_CallsAuthorizedWithError) {
StrictMock<MockDelegate> delegate;
std::unique_ptr<media::AudioOutputIPC> ipc =
std::make_unique<MojoAudioOutputIPC>(
NullAccessor(),
blink::scheduler::GetSingleThreadTaskRunnerForTesting());
ipc->CreateStream(&delegate, Params(), base::nullopt);
// No call to OnDeviceAuthorized since authotization wasn't explicitly
// requested.
base::RunLoop().RunUntilIdle();
ipc->CloseStream();
}
TEST(MojoAudioOutputIPC, DeviceAuthorized_Propagates) {
const base::UnguessableToken session_id = base::UnguessableToken::Create();
TestRemoteFactory stream_factory;
StrictMock<MockDelegate> delegate;
const std::unique_ptr<media::AudioOutputIPC> ipc =
std::make_unique<MojoAudioOutputIPC>(
stream_factory.GetAccessor(),
blink::scheduler::GetSingleThreadTaskRunnerForTesting());
stream_factory.PrepareProviderForAuthorization(
session_id, kDeviceId, std::make_unique<TestStreamProvider>(nullptr));
ipc->RequestDeviceAuthorization(&delegate, session_id, kDeviceId);
EXPECT_CALL(delegate, OnDeviceAuthorized(
media::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_OK,
_, std::string(kReturnedDeviceId)));
base::RunLoop().RunUntilIdle();
ipc->CloseStream();
base::RunLoop().RunUntilIdle();
}
TEST(MojoAudioOutputIPC, OnDeviceCreated_Propagates) {
const base::UnguessableToken session_id = base::UnguessableToken::Create();
TestRemoteFactory stream_factory;
StrictMock<MockStream> stream;
StrictMock<MockDelegate> delegate;
const std::unique_ptr<media::AudioOutputIPC> ipc =
std::make_unique<MojoAudioOutputIPC>(
stream_factory.GetAccessor(),
blink::scheduler::GetSingleThreadTaskRunnerForTesting());
stream_factory.PrepareProviderForAuthorization(
session_id, kDeviceId, std::make_unique<TestStreamProvider>(&stream));
ipc->RequestDeviceAuthorization(&delegate, session_id, kDeviceId);
ipc->CreateStream(&delegate, Params(), base::nullopt);
EXPECT_CALL(delegate, OnDeviceAuthorized(
media::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_OK,
_, std::string(kReturnedDeviceId)));
EXPECT_CALL(delegate, GotOnStreamCreated());
base::RunLoop().RunUntilIdle();
ipc->CloseStream();
base::RunLoop().RunUntilIdle();
}
TEST(MojoAudioOutputIPC,
CreateWithoutAuthorization_RequestsAuthorizationFirst) {
TestRemoteFactory stream_factory;
StrictMock<MockStream> stream;
StrictMock<MockDelegate> delegate;
const std::unique_ptr<media::AudioOutputIPC> ipc =
std::make_unique<MojoAudioOutputIPC>(
stream_factory.GetAccessor(),
blink::scheduler::GetSingleThreadTaskRunnerForTesting());
// Note: This call implicitly EXPECTs that authorization is requested,
// and constructing the TestStreamProvider with a |&stream| EXPECTs that the
// stream is created. This implicit request should always be for the default
// device and no session id.
stream_factory.PrepareProviderForAuthorization(
base::UnguessableToken(),
std::string(media::AudioDeviceDescription::kDefaultDeviceId),
std::make_unique<TestStreamProvider>(&stream));
ipc->CreateStream(&delegate, Params(), base::nullopt);
EXPECT_CALL(delegate, GotOnStreamCreated());
base::RunLoop().RunUntilIdle();
ipc->CloseStream();
base::RunLoop().RunUntilIdle();
}
TEST(MojoAudioOutputIPC, IsReusable) {
const base::UnguessableToken session_id = base::UnguessableToken::Create();
TestRemoteFactory stream_factory;
StrictMock<MockStream> stream;
StrictMock<MockDelegate> delegate;
const std::unique_ptr<media::AudioOutputIPC> ipc =
std::make_unique<MojoAudioOutputIPC>(
stream_factory.GetAccessor(),
blink::scheduler::GetSingleThreadTaskRunnerForTesting());
for (int i = 0; i < 5; ++i) {
stream_factory.PrepareProviderForAuthorization(
session_id, kDeviceId, std::make_unique<TestStreamProvider>(&stream));
ipc->RequestDeviceAuthorization(&delegate, session_id, kDeviceId);
ipc->CreateStream(&delegate, Params(), base::nullopt);
EXPECT_CALL(
delegate,
OnDeviceAuthorized(media::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_OK,
_, std::string(kReturnedDeviceId)));
EXPECT_CALL(delegate, GotOnStreamCreated());
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(&delegate);
ipc->CloseStream();
base::RunLoop().RunUntilIdle();
}
}
TEST(MojoAudioOutputIPC, IsReusableAfterError) {
const base::UnguessableToken session_id = base::UnguessableToken::Create();
TestRemoteFactory stream_factory;
StrictMock<MockStream> stream;
StrictMock<MockDelegate> delegate;
const std::unique_ptr<media::AudioOutputIPC> ipc =
std::make_unique<MojoAudioOutputIPC>(
stream_factory.GetAccessor(),
blink::scheduler::GetSingleThreadTaskRunnerForTesting());
stream_factory.PrepareProviderForAuthorization(
session_id, kDeviceId, std::make_unique<TestStreamProvider>(nullptr));
ipc->RequestDeviceAuthorization(&delegate, session_id, kDeviceId);
EXPECT_CALL(delegate, OnDeviceAuthorized(
media::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_OK,
_, std::string(kReturnedDeviceId)));
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(&delegate);
stream_factory.Disconnect();
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(&delegate);
ipc->CloseStream();
base::RunLoop().RunUntilIdle();
for (int i = 0; i < 5; ++i) {
stream_factory.PrepareProviderForAuthorization(
session_id, kDeviceId, std::make_unique<TestStreamProvider>(&stream));
ipc->RequestDeviceAuthorization(&delegate, session_id, kDeviceId);
ipc->CreateStream(&delegate, Params(), base::nullopt);
EXPECT_CALL(
delegate,
OnDeviceAuthorized(media::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_OK,
_, std::string(kReturnedDeviceId)));
EXPECT_CALL(delegate, GotOnStreamCreated());
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(&delegate);
EXPECT_CALL(delegate, OnError());
stream_factory.SignalErrorToProviderClient();
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(&delegate);
ipc->CloseStream();
base::RunLoop().RunUntilIdle();
}
}
TEST(MojoAudioOutputIPC, DeviceNotAuthorized_Propagates) {
const base::UnguessableToken session_id = base::UnguessableToken::Create();
TestRemoteFactory stream_factory;
StrictMock<MockDelegate> delegate;
std::unique_ptr<media::AudioOutputIPC> ipc =
std::make_unique<MojoAudioOutputIPC>(
stream_factory.GetAccessor(),
blink::scheduler::GetSingleThreadTaskRunnerForTesting());
stream_factory.RefuseNextRequest(session_id, kDeviceId);
ipc->RequestDeviceAuthorization(&delegate, session_id, kDeviceId);
EXPECT_CALL(
delegate,
OnDeviceAuthorized(
media::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_ERROR_NOT_AUTHORIZED,
_, std::string()))
.WillOnce(Invoke([&](media::OutputDeviceStatus,
const media::AudioParameters&, const std::string&) {
ipc->CloseStream();
ipc.reset();
}));
EXPECT_CALL(delegate, OnError()).Times(AtLeast(0));
base::RunLoop().RunUntilIdle();
}
TEST(MojoAudioOutputIPC,
FactoryDisconnectedBeforeAuthorizationReply_CallsAuthorizedAnyways) {
// The authorization IPC message might be aborted by the remote end
// disconnecting. In this case, the MojoAudioOutputIPC object must still
// send a notification to unblock the AudioOutputIPCDelegate.
const base::UnguessableToken session_id = base::UnguessableToken::Create();
TestRemoteFactory stream_factory;
StrictMock<MockDelegate> delegate;
std::unique_ptr<media::AudioOutputIPC> ipc =
std::make_unique<MojoAudioOutputIPC>(
stream_factory.GetAccessor(),
blink::scheduler::GetSingleThreadTaskRunnerForTesting());
ipc->RequestDeviceAuthorization(&delegate, session_id, kDeviceId);
EXPECT_CALL(
delegate,
OnDeviceAuthorized(
media::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_ERROR_INTERNAL, _,
std::string()))
.WillOnce(Invoke([&](media::OutputDeviceStatus,
const media::AudioParameters&, const std::string&) {
ipc->CloseStream();
ipc.reset();
}));
stream_factory.Disconnect();
base::RunLoop().RunUntilIdle();
}
TEST(MojoAudioOutputIPC,
FactoryDisconnectedAfterAuthorizationReply_CallsAuthorizedOnlyOnce) {
// This test makes sure that the MojoAudioOutputIPC doesn't callback for
// authorization when the factory disconnects if it already got a callback
// for authorization.
const base::UnguessableToken session_id = base::UnguessableToken::Create();
TestRemoteFactory stream_factory;
stream_factory.PrepareProviderForAuthorization(
session_id, kDeviceId, std::make_unique<TestStreamProvider>(nullptr));
StrictMock<MockDelegate> delegate;
const std::unique_ptr<media::AudioOutputIPC> ipc =
std::make_unique<MojoAudioOutputIPC>(
stream_factory.GetAccessor(),
blink::scheduler::GetSingleThreadTaskRunnerForTesting());
ipc->RequestDeviceAuthorization(&delegate, session_id, kDeviceId);
EXPECT_CALL(delegate, OnDeviceAuthorized(
media::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_OK,
_, std::string(kReturnedDeviceId)));
base::RunLoop().RunUntilIdle();
stream_factory.Disconnect();
base::RunLoop().RunUntilIdle();
ipc->CloseStream();
base::RunLoop().RunUntilIdle();
}
TEST(MojoAudioOutputIPC, AuthorizeNoClose_DCHECKs) {
TestRemoteFactory stream_factory;
const base::UnguessableToken session_id = base::UnguessableToken::Create();
StrictMock<MockDelegate> delegate;
stream_factory.PrepareProviderForAuthorization(
session_id, kDeviceId, std::make_unique<TestStreamProvider>(nullptr));
std::unique_ptr<media::AudioOutputIPC> ipc =
std::make_unique<MojoAudioOutputIPC>(
stream_factory.GetAccessor(),
blink::scheduler::GetSingleThreadTaskRunnerForTesting());
ipc->RequestDeviceAuthorization(&delegate, session_id, kDeviceId);
EXPECT_DCHECK_DEATH(ipc.reset());
ipc->CloseStream();
ipc.reset();
base::RunLoop().RunUntilIdle();
}
TEST(MojoAudioOutputIPC, CreateNoClose_DCHECKs) {
TestRemoteFactory stream_factory;
StrictMock<MockDelegate> delegate;
StrictMock<MockStream> stream;
stream_factory.PrepareProviderForAuthorization(
base::UnguessableToken(),
std::string(media::AudioDeviceDescription::kDefaultDeviceId),
std::make_unique<TestStreamProvider>(&stream));
std::unique_ptr<media::AudioOutputIPC> ipc =
std::make_unique<MojoAudioOutputIPC>(
stream_factory.GetAccessor(),
blink::scheduler::GetSingleThreadTaskRunnerForTesting());
ipc->CreateStream(&delegate, Params(), base::nullopt);
EXPECT_DCHECK_DEATH(ipc.reset());
ipc->CloseStream();
ipc.reset();
base::RunLoop().RunUntilIdle();
}
TEST(MojoAudioOutputIPC, Play_Plays) {
TestRemoteFactory stream_factory;
const base::UnguessableToken session_id = base::UnguessableToken::Create();
StrictMock<MockStream> stream;
StrictMock<MockDelegate> delegate;
EXPECT_CALL(delegate, OnDeviceAuthorized(
media::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_OK,
_, std::string(kReturnedDeviceId)));
EXPECT_CALL(delegate, GotOnStreamCreated());
EXPECT_CALL(stream, Play());
const std::unique_ptr<media::AudioOutputIPC> ipc =
std::make_unique<MojoAudioOutputIPC>(
stream_factory.GetAccessor(),
blink::scheduler::GetSingleThreadTaskRunnerForTesting());
stream_factory.PrepareProviderForAuthorization(
session_id, kDeviceId, std::make_unique<TestStreamProvider>(&stream));
ipc->RequestDeviceAuthorization(&delegate, session_id, kDeviceId);
ipc->CreateStream(&delegate, Params(), base::nullopt);
base::RunLoop().RunUntilIdle();
ipc->PlayStream();
base::RunLoop().RunUntilIdle();
ipc->CloseStream();
base::RunLoop().RunUntilIdle();
}
TEST(MojoAudioOutputIPC, Pause_Pauses) {
TestRemoteFactory stream_factory;
const base::UnguessableToken session_id = base::UnguessableToken::Create();
StrictMock<MockStream> stream;
StrictMock<MockDelegate> delegate;
EXPECT_CALL(delegate, OnDeviceAuthorized(
media::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_OK,
_, std::string(kReturnedDeviceId)));
EXPECT_CALL(delegate, GotOnStreamCreated());
EXPECT_CALL(stream, Pause());
const std::unique_ptr<media::AudioOutputIPC> ipc =
std::make_unique<MojoAudioOutputIPC>(
stream_factory.GetAccessor(),
blink::scheduler::GetSingleThreadTaskRunnerForTesting());
stream_factory.PrepareProviderForAuthorization(
session_id, kDeviceId, std::make_unique<TestStreamProvider>(&stream));
ipc->RequestDeviceAuthorization(&delegate, session_id, kDeviceId);
ipc->CreateStream(&delegate, Params(), base::nullopt);
base::RunLoop().RunUntilIdle();
ipc->PauseStream();
base::RunLoop().RunUntilIdle();
ipc->CloseStream();
base::RunLoop().RunUntilIdle();
}
TEST(MojoAudioOutputIPC, SetVolume_SetsVolume) {
TestRemoteFactory stream_factory;
const base::UnguessableToken session_id = base::UnguessableToken::Create();
StrictMock<MockStream> stream;
StrictMock<MockDelegate> delegate;
EXPECT_CALL(delegate, OnDeviceAuthorized(
media::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_OK,
_, std::string(kReturnedDeviceId)));
EXPECT_CALL(delegate, GotOnStreamCreated());
EXPECT_CALL(stream, SetVolume(kNewVolume));
const std::unique_ptr<media::AudioOutputIPC> ipc =
std::make_unique<MojoAudioOutputIPC>(
stream_factory.GetAccessor(),
blink::scheduler::GetSingleThreadTaskRunnerForTesting());
stream_factory.PrepareProviderForAuthorization(
session_id, kDeviceId, std::make_unique<TestStreamProvider>(&stream));
ipc->RequestDeviceAuthorization(&delegate, session_id, kDeviceId);
ipc->CreateStream(&delegate, Params(), base::nullopt);
base::RunLoop().RunUntilIdle();
ipc->SetVolume(kNewVolume);
base::RunLoop().RunUntilIdle();
ipc->CloseStream();
base::RunLoop().RunUntilIdle();
}
} // namespace blink