blob: 41d6ffb367814d179de470a8976fbf5d5f95499b [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 "content/browser/renderer_host/media/audio_renderer_host.h"
#include <stdint.h>
#include <memory>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/sync_socket.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/browser/media/capture/audio_mirroring_manager.h"
#include "content/browser/media/media_internals.h"
#include "content/browser/renderer_host/media/audio_input_device_manager.h"
#include "content/browser/renderer_host/media/media_stream_manager.h"
#include "content/common/media/audio_messages.h"
#include "content/public/browser/media_device_id.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "ipc/ipc_message_utils.h"
#include "media/audio/fake_audio_log_factory.h"
#include "media/audio/fake_audio_manager.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/media_switches.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::Assign;
using ::testing::AtLeast;
using ::testing::DoAll;
using ::testing::NotNull;
namespace content {
namespace {
const int kRenderFrameId = 5;
const int kStreamId = 50;
const char kSecurityOrigin[] = "http://localhost";
const char kBadSecurityOrigin[] = "about:about";
const char kDefaultDeviceId[] = "";
const char kSalt[] = "salt";
const char kNondefaultDeviceId[] =
"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
const char kBadDeviceId[] =
"badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad1";
const char kInvalidDeviceId[] = "invalid-device-id";
void ValidateRenderFrameId(int render_process_id,
int render_frame_id,
const base::Callback<void(bool)>& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
const bool frame_exists = (render_frame_id == kRenderFrameId);
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::Bind(callback, frame_exists));
}
class MockAudioMirroringManager : public AudioMirroringManager {
public:
MockAudioMirroringManager() {}
virtual ~MockAudioMirroringManager() {}
MOCK_METHOD3(AddDiverter,
void(int render_process_id,
int render_frame_id,
Diverter* diverter));
MOCK_METHOD1(RemoveDiverter, void(Diverter* diverter));
private:
DISALLOW_COPY_AND_ASSIGN(MockAudioMirroringManager);
};
class MockRenderProcessHostWithSignaling : public MockRenderProcessHost {
public:
MockRenderProcessHostWithSignaling(BrowserContext* context,
base::RunLoop* auth_run_loop)
: MockRenderProcessHost(context), auth_run_loop_(auth_run_loop) {}
void ShutdownForBadMessage(CrashReportMode crash_report_mode) override {
MockRenderProcessHost::ShutdownForBadMessage(crash_report_mode);
auth_run_loop_->Quit();
}
private:
base::RunLoop* auth_run_loop_;
};
class FakeAudioManagerWithAssociations : public media::FakeAudioManager {
public:
FakeAudioManagerWithAssociations(
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
media::AudioLogFactory* factory)
: FakeAudioManager(task_runner, task_runner, factory) {}
void CreateDeviceAssociation(const std::string& input_device_id,
const std::string& output_device_id) {
// We shouldn't accidentally add hashed ids, since the audio manager
// works with raw ids.
EXPECT_FALSE(IsValidDeviceId(input_device_id));
EXPECT_FALSE(IsValidDeviceId(output_device_id));
associations_[input_device_id] = output_device_id;
}
std::string GetAssociatedOutputDeviceID(
const std::string& input_id) override {
auto it = associations_.find(input_id);
return it == associations_.end() ? "" : it->second;
}
private:
std::map<std::string, std::string> associations_;
};
} // namespace
class MockAudioRendererHost : public AudioRendererHost {
public:
MockAudioRendererHost(base::RunLoop* auth_run_loop,
int render_process_id,
media::AudioManager* audio_manager,
AudioMirroringManager* mirroring_manager,
MediaStreamManager* media_stream_manager,
const std::string& salt)
: AudioRendererHost(render_process_id,
audio_manager,
mirroring_manager,
media_stream_manager,
salt),
shared_memory_length_(0),
auth_run_loop_(auth_run_loop) {
set_render_frame_id_validate_function_for_testing(&ValidateRenderFrameId);
}
// A list of mock methods.
MOCK_METHOD4(OnDeviceAuthorized,
void(int stream_id,
media::OutputDeviceStatus device_status,
const media::AudioParameters& output_params,
const std::string& matched_device_id));
MOCK_METHOD2(WasNotifiedOfCreation, void(int stream_id, int length));
MOCK_METHOD1(WasNotifiedOfError, void(int stream_id));
void ShutdownForBadMessage() override { bad_msg_count++; }
int bad_msg_count = 0;
private:
virtual ~MockAudioRendererHost() {
// Make sure all audio streams have been deleted.
EXPECT_TRUE(delegates_.empty());
}
// This method is used to dispatch IPC messages to the renderer. We intercept
// these messages here and dispatch to our mock methods to verify the
// conversation between this object and the renderer.
// Note: this means that file descriptors won't be duplicated,
// leading to double-close errors from SyncSocket.
// See crbug.com/647659.
bool Send(IPC::Message* message) override {
CHECK(message);
// In this method we dispatch the messages to the according handlers as if
// we are the renderer.
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(MockAudioRendererHost, *message)
IPC_MESSAGE_HANDLER(AudioMsg_NotifyDeviceAuthorized,
OnNotifyDeviceAuthorized)
IPC_MESSAGE_HANDLER(AudioMsg_NotifyStreamCreated,
OnNotifyStreamCreated)
IPC_MESSAGE_HANDLER(AudioMsg_NotifyStreamError, OnNotifyStreamError)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
EXPECT_TRUE(handled);
delete message;
return true;
}
void OnNotifyDeviceAuthorized(int stream_id,
media::OutputDeviceStatus device_status,
const media::AudioParameters& output_params,
const std::string& matched_device_id) {
// Make sure we didn't leak a raw device id.
EXPECT_TRUE(IsValidDeviceId(matched_device_id));
OnDeviceAuthorized(stream_id, device_status, output_params,
matched_device_id);
auth_run_loop_->Quit();
}
void OnNotifyStreamCreated(
int stream_id,
base::SharedMemoryHandle handle,
base::SyncSocket::TransitDescriptor socket_descriptor,
uint32_t length) {
// Maps the shared memory.
shared_memory_.reset(new base::SharedMemory(handle, false));
CHECK(shared_memory_->Map(length));
CHECK(shared_memory_->memory());
shared_memory_length_ = length;
// Create the SyncSocket using the handle.
base::SyncSocket::Handle sync_socket_handle =
base::SyncSocket::UnwrapHandle(socket_descriptor);
sync_socket_.reset(new base::SyncSocket(sync_socket_handle));
// And then delegate the call to the mock method.
WasNotifiedOfCreation(stream_id, length);
}
void OnNotifyStreamError(int stream_id) { WasNotifiedOfError(stream_id); }
std::unique_ptr<base::SharedMemory> shared_memory_;
std::unique_ptr<base::SyncSocket> sync_socket_;
uint32_t shared_memory_length_;
base::RunLoop* auth_run_loop_; // Used to wait for authorization.
DISALLOW_COPY_AND_ASSIGN(MockAudioRendererHost);
};
class AudioRendererHostTest : public testing::Test {
public:
AudioRendererHostTest()
: log_factory(base::MakeUnique<media::FakeAudioLogFactory>()),
audio_manager_(base::MakeUnique<FakeAudioManagerWithAssociations>(
base::ThreadTaskRunnerHandle::Get(),
log_factory.get())),
render_process_host_(&browser_context_, &auth_run_loop_) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kUseFakeDeviceForMediaStream);
media_stream_manager_.reset(new MediaStreamManager(audio_manager_.get()));
host_ = new MockAudioRendererHost(
&auth_run_loop_, render_process_host_.GetID(), audio_manager_.get(),
&mirroring_manager_, media_stream_manager_.get(), kSalt);
// Simulate IPC channel connected.
host_->set_peer_process_for_testing(base::Process::Current());
}
~AudioRendererHostTest() override {
// Simulate closing the IPC channel and give the audio thread time to close
// the underlying streams.
host_->OnChannelClosing();
SyncWithAudioThread();
// To correctly clean up the audio manager, we first put it in a
// ScopedAudioManagerPtr. It will immediately destruct, cleaning up the
// audio manager correctly.
media::ScopedAudioManagerPtr(audio_manager_.release());
// Release the reference to the mock object. The object will be destructed
// on message_loop_.
host_ = nullptr;
}
protected:
void OverrideDevicePermissions(bool has_permissions) {
host_->OverrideDevicePermissionsForTesting(has_permissions);
}
std::string GetNondefaultIdExpectedToPassPermissionsCheck() {
std::string nondefault_id;
MediaDevicesManager::BoolDeviceTypes devices_to_enumerate;
devices_to_enumerate[MEDIA_DEVICE_TYPE_AUDIO_OUTPUT] = true;
media_stream_manager_->media_devices_manager()->EnumerateDevices(
devices_to_enumerate,
base::Bind(
[](std::string* out, const MediaDeviceEnumeration& result) {
// Index 0 is default, so use 1. Always exists because we use
// fake devices.
CHECK(result[MediaDeviceType::MEDIA_DEVICE_TYPE_AUDIO_OUTPUT]
.size() > 1)
<< "Expected to have a nondefault device.";
*out = result[MediaDeviceType::MEDIA_DEVICE_TYPE_AUDIO_OUTPUT][1]
.device_id;
},
base::Unretained(&nondefault_id)));
// Make sure nondefault_id is set before returning.
base::RunLoop().RunUntilIdle();
return nondefault_id;
}
std::string GetNondefaultInputId() {
std::string nondefault_id;
MediaDevicesManager::BoolDeviceTypes devices_to_enumerate;
devices_to_enumerate[MEDIA_DEVICE_TYPE_AUDIO_INPUT] = true;
media_stream_manager_->media_devices_manager()->EnumerateDevices(
devices_to_enumerate,
base::Bind(
// Index 0 is default, so use 1. Always exists because we use
// fake devices.
[](std::string* out, const MediaDeviceEnumeration& result) {
CHECK(result[MediaDeviceType::MEDIA_DEVICE_TYPE_AUDIO_INPUT]
.size() > 1)
<< "Expected to have a nondefault device.";
*out = result[MediaDeviceType::MEDIA_DEVICE_TYPE_AUDIO_INPUT][1]
.device_id;
},
base::Unretained(&nondefault_id)));
base::RunLoop().RunUntilIdle();
return nondefault_id;
}
void Create() {
Create(kDefaultDeviceId, url::Origin(GURL(kSecurityOrigin)), true, true);
}
void Create(const std::string& device_id,
const url::Origin& security_origin,
bool wait_for_auth,
bool expect_onauthorized) {
media::OutputDeviceStatus expected_device_status =
device_id == kDefaultDeviceId ||
device_id ==
MediaStreamManager::GetHMACForMediaDeviceID(
kSalt, url::Origin(GURL(kSecurityOrigin)),
GetNondefaultIdExpectedToPassPermissionsCheck())
? media::OUTPUT_DEVICE_STATUS_OK
: device_id == kBadDeviceId
? media::OUTPUT_DEVICE_STATUS_ERROR_NOT_AUTHORIZED
: media::OUTPUT_DEVICE_STATUS_ERROR_NOT_FOUND;
if (expect_onauthorized)
EXPECT_CALL(*host_.get(),
OnDeviceAuthorized(kStreamId, expected_device_status, _, _));
if (expected_device_status == media::OUTPUT_DEVICE_STATUS_OK) {
EXPECT_CALL(*host_.get(), WasNotifiedOfCreation(kStreamId, _));
EXPECT_CALL(mirroring_manager_, AddDiverter(render_process_host_.GetID(),
kRenderFrameId, NotNull()))
.RetiresOnSaturation();
}
// Send a create stream message to the audio output stream and wait until
// we receive the created message.
media::AudioParameters params(
media::AudioParameters::AUDIO_FAKE, media::CHANNEL_LAYOUT_STEREO,
media::AudioParameters::kAudioCDSampleRate, 16,
media::AudioParameters::kAudioCDSampleRate / 10);
int session_id = 0;
host_->OnRequestDeviceAuthorization(kStreamId, kRenderFrameId, session_id,
device_id, security_origin);
if (wait_for_auth)
auth_run_loop_.Run();
if (!wait_for_auth ||
expected_device_status == media::OUTPUT_DEVICE_STATUS_OK)
host_->OnCreateStream(kStreamId, kRenderFrameId, params);
if (expected_device_status == media::OUTPUT_DEVICE_STATUS_OK)
// At some point in the future, a corresponding RemoveDiverter() call must
// be made.
EXPECT_CALL(mirroring_manager_, RemoveDiverter(NotNull()))
.RetiresOnSaturation();
SyncWithAudioThread();
}
void RequestDeviceAuthorizationWithBadOrigin(const std::string& device_id) {
int session_id = 0;
host_->OnRequestDeviceAuthorization(kStreamId, kRenderFrameId, session_id,
device_id,
url::Origin(GURL(kBadSecurityOrigin)));
SyncWithAudioThread();
}
void CreateWithoutWaitingForAuth(const std::string& device_id) {
Create(device_id, url::Origin(GURL(kSecurityOrigin)), false, false);
}
void CreateWithInvalidRenderFrameId() {
// When creating a stream with an invalid render frame ID, the host will
// reply with a stream error message.
EXPECT_CALL(*host_, WasNotifiedOfError(kStreamId));
// However, validation does not block stream creation, so these method calls
// might be made:
EXPECT_CALL(*host_, WasNotifiedOfCreation(kStreamId, _)).Times(AtLeast(0));
EXPECT_CALL(mirroring_manager_, AddDiverter(_, _, _)).Times(AtLeast(0));
EXPECT_CALL(mirroring_manager_, RemoveDiverter(_)).Times(AtLeast(0));
// Provide a seemingly-valid render frame ID; and it should be rejected when
// AudioRendererHost calls ValidateRenderFrameId().
const int kInvalidRenderFrameId = kRenderFrameId + 1;
const media::AudioParameters params(
media::AudioParameters::AUDIO_FAKE, media::CHANNEL_LAYOUT_STEREO,
media::AudioParameters::kAudioCDSampleRate, 16,
media::AudioParameters::kAudioCDSampleRate / 10);
host_->OnCreateStream(kStreamId, kInvalidRenderFrameId, params);
base::RunLoop().RunUntilIdle();
}
void CreateUnifiedStream(const url::Origin& security_origin) {
std::string output_id = GetNondefaultIdExpectedToPassPermissionsCheck();
std::string input_id = GetNondefaultInputId();
std::string hashed_output_id = MediaStreamManager::GetHMACForMediaDeviceID(
kSalt, url::Origin(GURL(kSecurityOrigin)), output_id);
// Set up association between input and output so that the output
// device gets selected when using session id:
audio_manager_->CreateDeviceAssociation(input_id, output_id);
int session_id = media_stream_manager_->audio_input_device_manager()->Open(
StreamDeviceInfo(MEDIA_DEVICE_AUDIO_CAPTURE, "Fake input device",
input_id));
base::RunLoop().RunUntilIdle();
// Send a create stream message to the audio output stream and wait until
// we receive the created message.
media::AudioParameters params(
media::AudioParameters::AUDIO_FAKE, media::CHANNEL_LAYOUT_STEREO,
media::AudioParameters::kAudioCDSampleRate, 16,
media::AudioParameters::kAudioCDSampleRate / 10);
EXPECT_CALL(*host_.get(),
OnDeviceAuthorized(kStreamId, media::OUTPUT_DEVICE_STATUS_OK, _,
hashed_output_id))
.Times(1);
EXPECT_CALL(*host_.get(), WasNotifiedOfCreation(kStreamId, _));
EXPECT_CALL(mirroring_manager_, AddDiverter(render_process_host_.GetID(),
kRenderFrameId, NotNull()))
.RetiresOnSaturation();
EXPECT_CALL(mirroring_manager_, RemoveDiverter(NotNull()))
.RetiresOnSaturation();
host_->OnRequestDeviceAuthorization(kStreamId, kRenderFrameId, session_id,
/*device id*/ std::string(),
security_origin);
auth_run_loop_.Run();
host_->OnCreateStream(kStreamId, kRenderFrameId, params);
SyncWithAudioThread();
}
void Close() {
// Send a message to AudioRendererHost to tell it we want to close the
// stream.
host_->OnCloseStream(kStreamId);
SyncWithAudioThread();
}
void Play() {
host_->OnPlayStream(kStreamId);
SyncWithAudioThread();
}
void Pause() {
host_->OnPauseStream(kStreamId);
SyncWithAudioThread();
}
void SetVolume(double volume) {
host_->OnSetVolume(kStreamId, volume);
SyncWithAudioThread();
}
void SimulateError() {
EXPECT_EQ(1u, host_->delegates_.size())
<< "Calls Create() before calling this method";
// Expect an error signal sent through IPC.
EXPECT_CALL(*host_.get(), WasNotifiedOfError(kStreamId));
// Simulate an error sent from the audio device.
host_->OnStreamError(kStreamId);
SyncWithAudioThread();
// Expect the audio stream record is removed.
EXPECT_EQ(0u, host_->delegates_.size());
}
// SyncWithAudioThread() waits until all pending tasks on the audio thread
// are executed while also processing pending task in message_loop_ on the
// current thread. It is used to synchronize with the audio thread when we are
// closing an audio stream.
void SyncWithAudioThread() {
base::RunLoop().RunUntilIdle();
base::RunLoop run_loop;
audio_manager_->GetTaskRunner()->PostTask(
FROM_HERE, media::BindToCurrentLoop(run_loop.QuitClosure()));
run_loop.Run();
}
void AssertBadMsgReported() {
// Bad messages can be reported either directly to the RPH or through the
// ARH, so we check both of them.
EXPECT_EQ(render_process_host_.bad_msg_count() + host_->bad_msg_count, 1);
}
private:
// MediaStreamManager uses a DestructionObserver, so it must outlive the
// TestBrowserThreadBundle.
std::unique_ptr<MediaStreamManager> media_stream_manager_;
TestBrowserThreadBundle thread_bundle_;
TestBrowserContext browser_context_;
std::unique_ptr<media::FakeAudioLogFactory> log_factory;
std::unique_ptr<FakeAudioManagerWithAssociations> audio_manager_;
MockAudioMirroringManager mirroring_manager_;
base::RunLoop auth_run_loop_;
MockRenderProcessHostWithSignaling render_process_host_;
scoped_refptr<MockAudioRendererHost> host_;
DISALLOW_COPY_AND_ASSIGN(AudioRendererHostTest);
};
TEST_F(AudioRendererHostTest, CreateAndClose) {
Create();
Close();
}
// Simulate the case where a stream is not properly closed.
TEST_F(AudioRendererHostTest, CreateAndShutdown) {
Create();
}
TEST_F(AudioRendererHostTest, CreatePlayAndClose) {
Create();
Play();
Close();
}
TEST_F(AudioRendererHostTest, CreatePlayPauseAndClose) {
Create();
Play();
Pause();
Close();
}
TEST_F(AudioRendererHostTest, SetVolume) {
Create();
SetVolume(0.5);
Play();
Pause();
Close();
}
// Simulate the case where a stream is not properly closed.
TEST_F(AudioRendererHostTest, CreatePlayAndShutdown) {
Create();
Play();
}
// Simulate the case where a stream is not properly closed.
TEST_F(AudioRendererHostTest, CreatePlayPauseAndShutdown) {
Create();
Play();
Pause();
}
TEST_F(AudioRendererHostTest, SimulateError) {
Create();
Play();
SimulateError();
}
// Simulate the case when an error is generated on the browser process,
// the audio device is closed but the render process try to close the
// audio stream again.
TEST_F(AudioRendererHostTest, SimulateErrorAndClose) {
Create();
Play();
SimulateError();
Close();
}
TEST_F(AudioRendererHostTest, CreateUnifiedStreamAndClose) {
CreateUnifiedStream(url::Origin(GURL(kSecurityOrigin)));
Close();
}
TEST_F(AudioRendererHostTest, CreateUnauthorizedDevice) {
Create(kBadDeviceId, url::Origin(GURL(kSecurityOrigin)), true, true);
Close();
}
TEST_F(AudioRendererHostTest, CreateAuthorizedDevice) {
OverrideDevicePermissions(true);
std::string id = GetNondefaultIdExpectedToPassPermissionsCheck();
std::string hashed_id = MediaStreamManager::GetHMACForMediaDeviceID(
kSalt, url::Origin(GURL(kSecurityOrigin)), id);
Create(hashed_id, url::Origin(GURL(kSecurityOrigin)), true, true);
Close();
}
TEST_F(AudioRendererHostTest, CreateDeviceWithAuthorizationPendingIsError) {
CreateWithoutWaitingForAuth(kBadDeviceId);
Close();
AssertBadMsgReported();
}
TEST_F(AudioRendererHostTest, CreateDeviceWithBadSecurityOrigin) {
RequestDeviceAuthorizationWithBadOrigin(kNondefaultDeviceId);
Close();
AssertBadMsgReported();
}
TEST_F(AudioRendererHostTest, CreateInvalidDevice) {
Create(kInvalidDeviceId, url::Origin(GURL(kSecurityOrigin)), true, true);
Close();
}
TEST_F(AudioRendererHostTest, CreateFailsForInvalidRenderFrame) {
CreateWithInvalidRenderFrameId();
Close();
}
// TODO(hclam): Add tests for data conversation in low latency mode.
} // namespace content