blob: c441897609acac36cd0a56ffa93b6a6dd1bc87bd [file] [log] [blame]
// Copyright 2016 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_input_renderer_host.h"
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/browser/bad_message.h"
#include "content/browser/media/capture/audio_mirroring_manager.h"
#include "content/browser/renderer_host/media/audio_input_device_manager.h"
#include "content/browser/renderer_host/media/media_stream_manager.h"
#include "content/browser/renderer_host/media/media_stream_ui_proxy.h"
#include "content/public/browser/browser_thread.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.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "media/audio/audio_device_description.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/media_switches.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
using ::media::AudioInputController;
using ::testing::_;
using ::testing::Assign;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::Mock;
using ::testing::NotNull;
using ::testing::SaveArg;
using ::testing::StrictMock;
namespace content {
namespace {
const int kStreamId = 100;
const int kRenderProcessId = 42;
const int kRendererPid = 21718;
const int kRenderFrameId = 31415;
const int kSharedMemoryCount = 11;
const char kSecurityOrigin[] = "http://localhost";
#if BUILDFLAG(ENABLE_WEBRTC)
#if defined(OS_WIN)
const wchar_t kBaseFileName[] = L"some_file_name";
#else
const char kBaseFileName[] = "some_file_name";
#endif // defined(OS_WIN)
#endif // BUILDFLAG(ENABLE_WEBRTC)
url::Origin SecurityOrigin() {
return url::Origin(GURL(kSecurityOrigin));
}
AudioInputHostMsg_CreateStream_Config DefaultConfig() {
AudioInputHostMsg_CreateStream_Config config;
config.params = media::AudioParameters::UnavailableDeviceParams();
config.automatic_gain_control = false;
config.shared_memory_count = kSharedMemoryCount;
return config;
}
class MockRenderer {
public:
MOCK_METHOD6(NotifyStreamCreated,
void(int /*stream_id*/,
base::SharedMemoryHandle /*handle*/,
base::SyncSocket::TransitDescriptor /*socket_desriptor*/,
uint32_t /*length*/,
uint32_t /*total_segments*/,
bool initially_muted));
MOCK_METHOD1(NotifyStreamError, void(int /*stream_id*/));
MOCK_METHOD0(WasShutDown, void());
};
// This class overrides Send to intercept the messages that the
// AudioInputRendererHost sends to the renderer. They are sent to
// the provided MockRenderer instead.
class AudioInputRendererHostWithInterception : public AudioInputRendererHost {
public:
AudioInputRendererHostWithInterception(
int render_process_id,
int32_t renderer_pid,
media::AudioManager* audio_manager,
MediaStreamManager* media_stream_manager,
AudioMirroringManager* audio_mirroring_manager,
media::UserInputMonitor* user_input_monitor,
MockRenderer* renderer)
: AudioInputRendererHost(render_process_id,
audio_manager,
media_stream_manager,
audio_mirroring_manager,
user_input_monitor),
renderer_(renderer) {
set_peer_process_for_testing(base::Process::Current());
set_renderer_pid(renderer_pid);
}
protected:
~AudioInputRendererHostWithInterception() override = default;
private:
bool Send(IPC::Message* message) override {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(AudioInputRendererHostWithInterception, *message)
IPC_MESSAGE_HANDLER(AudioInputMsg_NotifyStreamCreated,
NotifyRendererStreamCreated)
IPC_MESSAGE_HANDLER(AudioInputMsg_NotifyStreamError,
NotifyRendererStreamError)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
EXPECT_TRUE(handled);
delete message;
return true;
}
void ShutdownForBadMessage() override { renderer_->WasShutDown(); }
void NotifyRendererStreamCreated(
int stream_id,
base::SharedMemoryHandle handle,
base::SyncSocket::TransitDescriptor socket_descriptor,
uint32_t length,
uint32_t total_segments,
bool initially_muted) {
// It's difficult to check that the sync socket and shared memory is
// valid in the gmock macros, so we check them here.
EXPECT_NE(base::SyncSocket::UnwrapHandle(socket_descriptor),
base::SyncSocket::kInvalidHandle);
base::SharedMemory memory(handle, /*read_only*/ true);
EXPECT_TRUE(memory.Map(length));
renderer_->NotifyStreamCreated(stream_id, handle, socket_descriptor, length,
total_segments, initially_muted);
EXPECT_TRUE(memory.Unmap());
memory.Close();
}
void NotifyRendererStreamError(int stream_id) {
renderer_->NotifyStreamError(stream_id);
}
MockRenderer* renderer_;
};
class MockAudioInputController : public AudioInputController {
public:
MockAudioInputController(
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
AudioInputController::SyncWriter* writer,
media::AudioManager* audio_manager,
AudioInputController::EventHandler* event_handler,
media::UserInputMonitor* user_input_monitor,
const media::AudioParameters& params,
StreamType type)
: AudioInputController(std::move(task_runner),
event_handler,
writer,
user_input_monitor,
params,
type) {
GetTaskRunnerForTesting()->PostTask(
FROM_HERE,
base::BindOnce(&AudioInputController::EventHandler::OnCreated,
base::Unretained(event_handler), base::Unretained(this),
false));
ON_CALL(*this, EnableDebugRecording(_))
.WillByDefault(SaveArg<0>(&file_name));
}
EventHandler* handler() { return GetHandlerForTesting(); }
// File name that we pretend to do a debug recording to, if any.
base::FilePath debug_file_name() { return file_name; }
void Close(base::OnceClosure cl) override {
DidClose();
// Hop to audio manager thread before calling task, since this is the real
// behavior.
GetTaskRunnerForTesting()->PostTaskAndReply(
FROM_HERE, base::BindOnce([]() {}), std::move(cl));
}
MOCK_METHOD0(Record, void());
MOCK_METHOD1(SetVolume, void(double));
MOCK_METHOD1(EnableDebugRecording, void(const base::FilePath&));
MOCK_METHOD0(DisableDebugRecording, void());
MOCK_METHOD0(DidClose, void());
// AudioInputCallback impl, irrelevant to us.
MOCK_METHOD4(
OnData,
void(media::AudioInputStream*, const media::AudioBus*, uint32_t, double));
MOCK_METHOD1(OnError, void(media::AudioInputStream*));
protected:
~MockAudioInputController() override = default;
private:
base::FilePath file_name;
};
class MockControllerFactory : public AudioInputController::Factory {
public:
using MockController = StrictMock<MockAudioInputController>;
MockControllerFactory() {
AudioInputController::set_factory_for_testing(this);
}
~MockControllerFactory() override {
AudioInputController::set_factory_for_testing(nullptr);
}
// AudioInputController::Factory implementation:
AudioInputController* Create(
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
AudioInputController::SyncWriter* sync_writer,
media::AudioManager* audio_manager,
AudioInputController::EventHandler* event_handler,
media::AudioParameters params,
media::UserInputMonitor* user_input_monitor,
AudioInputController::StreamType type) override {
ControllerCreated();
scoped_refptr<MockController> controller =
new MockController(task_runner, sync_writer, audio_manager,
event_handler, user_input_monitor, params, type);
controller_list_.push_back(controller);
return controller.get();
}
MockController* controller(size_t i) {
EXPECT_GT(controller_list_.size(), i);
return controller_list_[i].get();
}
MOCK_METHOD0(ControllerCreated, void());
private:
std::vector<scoped_refptr<MockController>> controller_list_;
};
} // namespace
class AudioInputRendererHostTest : public testing::Test {
public:
AudioInputRendererHostTest() {
base::CommandLine* flags = base::CommandLine::ForCurrentProcess();
flags->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
flags->AppendSwitch(switches::kUseFakeUIForMediaStream);
audio_manager_.reset(new media::FakeAudioManager(
base::MakeUnique<media::TestAudioThread>(), &log_factory_));
audio_system_ = media::AudioSystemImpl::Create(audio_manager_.get());
media_stream_manager_ =
base::MakeUnique<MediaStreamManager>(audio_system_.get());
airh_ = new AudioInputRendererHostWithInterception(
kRenderProcessId, kRendererPid, media::AudioManager::Get(),
media_stream_manager_.get(), AudioMirroringManager::GetInstance(),
nullptr, &renderer_);
}
~AudioInputRendererHostTest() override {
airh_->OnChannelClosing();
base::RunLoop().RunUntilIdle();
audio_manager_->Shutdown();
}
protected:
// Makes device |device_id| with name |name| available to open with the
// session id returned.
int Open(const std::string& device_id, const std::string& name) {
int session_id = media_stream_manager_->audio_input_device_manager()->Open(
MediaStreamDevice(MEDIA_DEVICE_AUDIO_CAPTURE, device_id, name));
base::RunLoop().RunUntilIdle();
return session_id;
}
std::string GetRawNondefaultId() {
std::string 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(
[](std::string* id, const MediaDeviceEnumeration& result) {
// Index 0 is default, so use 1.
CHECK(result[MediaDeviceType::MEDIA_DEVICE_TYPE_AUDIO_INPUT]
.size() > 1)
<< "Expected to have a nondefault device.";
*id = result[MediaDeviceType::MEDIA_DEVICE_TYPE_AUDIO_INPUT][1]
.device_id;
},
base::Unretained(&id)));
base::RunLoop().RunUntilIdle();
return id;
}
media::FakeAudioLogFactory log_factory_;
StrictMock<MockControllerFactory> controller_factory_;
std::unique_ptr<MediaStreamManager> media_stream_manager_;
TestBrowserThreadBundle thread_bundle_;
std::unique_ptr<media::AudioManager> audio_manager_;
std::unique_ptr<media::AudioSystem> audio_system_;
StrictMock<MockRenderer> renderer_;
scoped_refptr<AudioInputRendererHost> airh_;
private:
DISALLOW_COPY_AND_ASSIGN(AudioInputRendererHostTest);
};
// Checks that a controller is created and a reply is sent when creating a
// stream.
TEST_F(AudioInputRendererHostTest, CreateWithDefaultDevice) {
int session_id =
Open("Default device", media::AudioDeviceDescription::kDefaultDeviceId);
EXPECT_CALL(renderer_,
NotifyStreamCreated(kStreamId, _, _, _, kSharedMemoryCount, _));
EXPECT_CALL(controller_factory_, ControllerCreated());
airh_->OnMessageReceived(AudioInputHostMsg_CreateStream(
kStreamId, kRenderFrameId, session_id, DefaultConfig()));
base::RunLoop().RunUntilIdle();
EXPECT_CALL(*controller_factory_.controller(0), DidClose());
}
// If authorization hasn't been granted, only reply with and error and do
// nothing else.
TEST_F(AudioInputRendererHostTest, CreateWithoutAuthorization_Error) {
EXPECT_CALL(renderer_, NotifyStreamError(kStreamId));
int session_id = 0;
airh_->OnMessageReceived(AudioInputHostMsg_CreateStream(
kStreamId, kRenderFrameId, session_id, DefaultConfig()));
base::RunLoop().RunUntilIdle();
}
// Like CreateWithDefaultDevice but with a nondefault device.
TEST_F(AudioInputRendererHostTest, CreateWithNonDefaultDevice) {
int session_id = Open("Nondefault device", GetRawNondefaultId());
EXPECT_CALL(renderer_,
NotifyStreamCreated(kStreamId, _, _, _, kSharedMemoryCount, _));
EXPECT_CALL(controller_factory_, ControllerCreated());
airh_->OnMessageReceived(AudioInputHostMsg_CreateStream(
kStreamId, kRenderFrameId, session_id, DefaultConfig()));
base::RunLoop().RunUntilIdle();
EXPECT_CALL(*controller_factory_.controller(0), DidClose());
}
// Checks that stream is started when calling record.
TEST_F(AudioInputRendererHostTest, CreateRecordClose) {
int session_id =
Open("Default device", media::AudioDeviceDescription::kDefaultDeviceId);
EXPECT_CALL(renderer_,
NotifyStreamCreated(kStreamId, _, _, _, kSharedMemoryCount, _));
EXPECT_CALL(controller_factory_, ControllerCreated());
airh_->OnMessageReceived(AudioInputHostMsg_CreateStream(
kStreamId, kRenderFrameId, session_id, DefaultConfig()));
base::RunLoop().RunUntilIdle();
EXPECT_CALL(*controller_factory_.controller(0), Record());
EXPECT_CALL(*controller_factory_.controller(0), DidClose());
airh_->OnMessageReceived(AudioInputHostMsg_RecordStream(kStreamId));
base::RunLoop().RunUntilIdle();
airh_->OnMessageReceived(AudioInputHostMsg_CloseStream(kStreamId));
base::RunLoop().RunUntilIdle();
}
// In addition to the above, also check that a SetVolume message is propagated
// to the controller.
TEST_F(AudioInputRendererHostTest, CreateSetVolumeRecordClose) {
int session_id =
Open("Default device", media::AudioDeviceDescription::kDefaultDeviceId);
EXPECT_CALL(renderer_,
NotifyStreamCreated(kStreamId, _, _, _, kSharedMemoryCount, _));
EXPECT_CALL(controller_factory_, ControllerCreated());
airh_->OnMessageReceived(AudioInputHostMsg_CreateStream(
kStreamId, kRenderFrameId, session_id, DefaultConfig()));
base::RunLoop().RunUntilIdle();
EXPECT_CALL(*controller_factory_.controller(0), SetVolume(0.5));
EXPECT_CALL(*controller_factory_.controller(0), Record());
EXPECT_CALL(*controller_factory_.controller(0), DidClose());
airh_->OnMessageReceived(AudioInputHostMsg_SetVolume(kStreamId, 0.5));
airh_->OnMessageReceived(AudioInputHostMsg_RecordStream(kStreamId));
airh_->OnMessageReceived(AudioInputHostMsg_CloseStream(kStreamId));
base::RunLoop().RunUntilIdle();
}
// Check that a too large volume is treated like a bad message and doesn't
// reach the controller.
TEST_F(AudioInputRendererHostTest, SetVolumeTooLarge_BadMessage) {
int session_id =
Open("Default device", media::AudioDeviceDescription::kDefaultDeviceId);
EXPECT_CALL(renderer_,
NotifyStreamCreated(kStreamId, _, _, _, kSharedMemoryCount, _));
EXPECT_CALL(controller_factory_, ControllerCreated());
airh_->OnMessageReceived(AudioInputHostMsg_CreateStream(
kStreamId, kRenderFrameId, session_id, DefaultConfig()));
base::RunLoop().RunUntilIdle();
EXPECT_CALL(*controller_factory_.controller(0), DidClose());
EXPECT_CALL(renderer_, WasShutDown());
airh_->OnMessageReceived(AudioInputHostMsg_SetVolume(kStreamId, 5));
base::RunLoop().RunUntilIdle();
}
// Like above.
TEST_F(AudioInputRendererHostTest, SetVolumeNegative_BadMessage) {
int session_id =
Open("Default device", media::AudioDeviceDescription::kDefaultDeviceId);
EXPECT_CALL(renderer_,
NotifyStreamCreated(kStreamId, _, _, _, kSharedMemoryCount, _));
EXPECT_CALL(controller_factory_, ControllerCreated());
airh_->OnMessageReceived(AudioInputHostMsg_CreateStream(
kStreamId, kRenderFrameId, session_id, DefaultConfig()));
base::RunLoop().RunUntilIdle();
EXPECT_CALL(*controller_factory_.controller(0), DidClose());
EXPECT_CALL(renderer_, WasShutDown());
airh_->OnMessageReceived(AudioInputHostMsg_SetVolume(kStreamId, -0.5));
base::RunLoop().RunUntilIdle();
}
// Checks that a stream_id cannot be reused.
TEST_F(AudioInputRendererHostTest, CreateTwice_Error) {
int session_id =
Open("Default device", media::AudioDeviceDescription::kDefaultDeviceId);
EXPECT_CALL(renderer_,
NotifyStreamCreated(kStreamId, _, _, _, kSharedMemoryCount, _));
EXPECT_CALL(renderer_, NotifyStreamError(kStreamId));
EXPECT_CALL(controller_factory_, ControllerCreated());
airh_->OnMessageReceived(AudioInputHostMsg_CreateStream(
kStreamId, kRenderFrameId, session_id, DefaultConfig()));
airh_->OnMessageReceived(AudioInputHostMsg_CreateStream(
kStreamId, kRenderFrameId, session_id, DefaultConfig()));
base::RunLoop().RunUntilIdle();
EXPECT_CALL(*controller_factory_.controller(0), DidClose());
}
// Checks that when two streams are created, messages are routed to the correct
// stream. Also checks that when enabling debug recording, the streams get
// different file names.
TEST_F(AudioInputRendererHostTest, TwoStreams) {
int session_id =
Open("Default device", media::AudioDeviceDescription::kDefaultDeviceId);
EXPECT_CALL(renderer_,
NotifyStreamCreated(kStreamId, _, _, _, kSharedMemoryCount, _));
EXPECT_CALL(renderer_, NotifyStreamCreated(kStreamId + 1, _, _, _,
kSharedMemoryCount, _));
EXPECT_CALL(controller_factory_, ControllerCreated()).Times(2);
airh_->OnMessageReceived(AudioInputHostMsg_CreateStream(
kStreamId, kRenderFrameId, session_id, DefaultConfig()));
airh_->OnMessageReceived(AudioInputHostMsg_CreateStream(
kStreamId + 1, kRenderFrameId, session_id, DefaultConfig()));
base::RunLoop().RunUntilIdle();
#if BUILDFLAG(ENABLE_WEBRTC)
EXPECT_CALL(*controller_factory_.controller(0), EnableDebugRecording(_));
EXPECT_CALL(*controller_factory_.controller(1), EnableDebugRecording(_));
airh_->EnableDebugRecording(base::FilePath(kBaseFileName));
base::RunLoop().RunUntilIdle();
EXPECT_NE(controller_factory_.controller(0)->debug_file_name(),
controller_factory_.controller(1)->debug_file_name());
EXPECT_CALL(*controller_factory_.controller(0), DisableDebugRecording());
EXPECT_CALL(*controller_factory_.controller(1), DisableDebugRecording());
airh_->DisableDebugRecording();
#endif // ENABLE_WEBRTC
EXPECT_CALL(*controller_factory_.controller(0), DidClose());
EXPECT_CALL(*controller_factory_.controller(1), DidClose());
}
// Checks that the stream is properly cleaned up and a notification is sent to
// the renderer when the stream encounters an error.
TEST_F(AudioInputRendererHostTest, Error_ClosesController) {
int session_id =
Open("Default device", media::AudioDeviceDescription::kDefaultDeviceId);
EXPECT_CALL(renderer_,
NotifyStreamCreated(kStreamId, _, _, _, kSharedMemoryCount, _));
EXPECT_CALL(controller_factory_, ControllerCreated());
airh_->OnMessageReceived(AudioInputHostMsg_CreateStream(
kStreamId, kRenderFrameId, session_id, DefaultConfig()));
base::RunLoop().RunUntilIdle();
EXPECT_CALL(*controller_factory_.controller(0), DidClose());
EXPECT_CALL(renderer_, NotifyStreamError(kStreamId));
controller_factory_.controller(0)->handler()->OnError(
controller_factory_.controller(0), AudioInputController::UNKNOWN_ERROR);
// Check Close expectation before the destructor.
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClear(controller_factory_.controller(0));
}
// Checks that tab capture streams can be created.
TEST_F(AudioInputRendererHostTest, TabCaptureStream) {
StreamControls controls(/* request_audio */ true, /* request_video */ false);
controls.audio.device_id = base::StringPrintf(
"web-contents-media-stream://%d:%d", kRenderProcessId, kRenderFrameId);
controls.audio.stream_source = kMediaStreamSourceTab;
std::string request_label = media_stream_manager_->MakeMediaAccessRequest(
kRenderProcessId, kRenderFrameId, 0, controls, SecurityOrigin(),
base::Bind([](const MediaStreamDevices& devices,
std::unique_ptr<MediaStreamUIProxy>) {}));
base::RunLoop().RunUntilIdle();
int session_id = Open("Tab capture", controls.audio.device_id);
EXPECT_CALL(renderer_,
NotifyStreamCreated(kStreamId, _, _, _, kSharedMemoryCount, _));
EXPECT_CALL(controller_factory_, ControllerCreated());
airh_->OnMessageReceived(AudioInputHostMsg_CreateStream(
kStreamId, kRenderFrameId, session_id, DefaultConfig()));
base::RunLoop().RunUntilIdle();
EXPECT_CALL(*controller_factory_.controller(0), DidClose());
}
} // namespace content