blob: 08c0d02dc8254d2eff0a2777fb7b2b35b31cf330 [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_delegate_impl.h"
#include <stdint.h>
#include <limits>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/optional.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/sync_socket.h"
#include "build/build_config.h"
#include "content/browser/media/capture/audio_mirroring_manager.h"
#include "content/browser/media/media_internals.h"
#include "content/browser/renderer_host/media/media_stream_manager.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/media_observer.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "media/audio/audio_output_controller.h"
#include "media/audio/audio_system_impl.h"
#include "media/audio/fake_audio_input_stream.h"
#include "media/audio/mock_audio_manager.h"
#include "media/audio/test_audio_thread.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/gmock_callback_support.h"
#include "media/base/media_switches.h"
#include "media/base/user_input_monitor.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::Mock;
using ::testing::InSequence;
using ::testing::DoubleEq;
using ::testing::AtLeast;
using ::testing::NotNull;
using ::testing::StrictMock;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::InvokeWithoutArgs;
namespace content {
namespace {
const int kRenderProcessId = 1;
const int kRenderFrameId = 5;
const int kStreamId = 50;
const uint32_t kDefaultSharedMemoryCount = 10;
const double kNewVolume = 0.618;
const double kVolumeScale = 2.71828;
const bool kDoEnableAGC = true;
const bool kDoNotEnableAGC = false;
const char* kDefaultDeviceId = "default";
const char* kDefaultDeviceName = "default";
media::AudioParameters ValidAudioParameters() {
return media::AudioParameters::UnavailableDeviceParams();
}
media::AudioOutputStream* ExpectNoOutputStreamCreation(
const media::AudioParameters&,
const std::string&) {
CHECK(false);
return nullptr;
}
media::AudioInputStream* ExpectNoInputStreamCreation(
const media::AudioParameters&,
const std::string&) {
CHECK(false);
return nullptr;
}
media::AudioInputStream* MakeInputStreamCallback(
media::AudioInputStream* stream,
bool* created,
const media::AudioParameters&,
const std::string&) {
CHECK(!*created);
*created = true;
return stream;
}
} // namespace
class MockEventHandler : public media::AudioInputDelegate::EventHandler {
public:
void OnStreamCreated(int stream_id,
base::ReadOnlySharedMemoryRegion,
std::unique_ptr<base::CancelableSyncSocket>,
bool initially_muted) override {
MockOnStreamCreated(stream_id, initially_muted);
}
MOCK_METHOD2(MockOnStreamCreated, void(int, bool));
MOCK_METHOD2(OnMuted, void(int, bool));
MOCK_METHOD1(OnStreamError, void(int));
};
class MockUserInputMonitor : public media::UserInputMonitor {
public:
MockUserInputMonitor() {}
uint32_t GetKeyPressCount() const override { return 0; }
MOCK_METHOD0(EnableKeyPressMonitoring, void());
MOCK_METHOD0(DisableKeyPressMonitoring, void());
};
class MockAudioInputStream : public media::AudioInputStream {
public:
MockAudioInputStream() {}
~MockAudioInputStream() override {}
MOCK_METHOD0(Open, bool());
MOCK_METHOD1(Start, void(AudioInputCallback*));
MOCK_METHOD0(Stop, void());
MOCK_METHOD0(Close, void());
MOCK_METHOD0(GetMaxVolume, double());
MOCK_METHOD1(SetVolume, void(double));
MOCK_METHOD0(GetVolume, double());
MOCK_METHOD1(SetAutomaticGainControl, bool(bool));
MOCK_METHOD0(GetAutomaticGainControl, bool());
MOCK_METHOD0(IsMuted, bool());
MOCK_METHOD1(SetOutputDeviceForAec, void(const std::string&));
};
class MockMediaStreamProviderListener : public MediaStreamProviderListener {
public:
MockMediaStreamProviderListener() {}
~MockMediaStreamProviderListener() override {}
MOCK_METHOD2(Opened,
void(MediaStreamType stream_type, int capture_session_id));
void Closed(MediaStreamType stream_type, int capture_session_id) override {}
void Aborted(MediaStreamType stream_type, int capture_session_id) override {}
};
class AudioInputDelegateTest : public testing::Test {
public:
AudioInputDelegateTest()
: thread_bundle_(base::in_place),
audio_manager_(std::make_unique<media::TestAudioThread>(true)),
audio_system_(&audio_manager_),
media_stream_manager_(&audio_system_, audio_manager_.GetTaskRunner()) {
audio_manager_.SetMakeInputStreamCB(
base::BindRepeating(&ExpectNoInputStreamCreation));
audio_manager_.SetMakeOutputStreamCB(
base::BindRepeating(&ExpectNoOutputStreamCreation));
}
~AudioInputDelegateTest() override {
audio_manager_.Shutdown();
// MediaStreamManager expects to outlive the IO thread.
thread_bundle_.reset();
}
protected:
// Streams must be opened with AudioInputDeviceManager before
// AudioInputDelegateImpl will allow them to be used.
int MakeDeviceAvailable(const std::string& device_id,
const std::string& name) {
// Authorize device for use and wait for completion.
MockMediaStreamProviderListener listener;
media_stream_manager_.audio_input_device_manager()->RegisterListener(
&listener);
int session_id = media_stream_manager_.audio_input_device_manager()->Open(
MediaStreamDevice(MEDIA_DEVICE_AUDIO_CAPTURE, device_id, name));
// Block for completion.
base::RunLoop loop;
EXPECT_CALL(listener, Opened(MEDIA_DEVICE_AUDIO_CAPTURE, session_id))
.WillOnce(InvokeWithoutArgs(&loop, &base::RunLoop::Quit));
loop.Run();
media_stream_manager_.audio_input_device_manager()->UnregisterListener(
&listener);
return session_id;
}
std::unique_ptr<media::AudioInputDelegate> CreateDelegate(
uint32_t shared_memory_count,
int session_id,
bool enable_agc) {
return AudioInputDelegateImpl::Create(
&audio_manager_, AudioMirroringManager::GetInstance(),
&user_input_monitor_, kRenderProcessId, kRenderFrameId,
media_stream_manager_.audio_input_device_manager(),
MediaInternals::GetInstance()->CreateMojoAudioLog(
media::AudioLogFactory::AUDIO_INPUT_CONTROLLER, kStreamId),
AudioInputDeviceManager::KeyboardMicRegistration(), shared_memory_count,
kStreamId, session_id, enable_agc, ValidAudioParameters(),
&event_handler_);
}
void SyncWithAudioThread() {
base::RunLoop().RunUntilIdle();
base::RunLoop run_loop;
audio_manager_.GetTaskRunner()->PostTask(
FROM_HERE, media::BindToCurrentLoop(run_loop.QuitClosure()));
run_loop.Run();
}
base::Optional<TestBrowserThreadBundle> thread_bundle_;
media::MockAudioManager audio_manager_;
media::AudioSystemImpl audio_system_;
MediaStreamManager media_stream_manager_;
NiceMock<MockUserInputMonitor> user_input_monitor_;
StrictMock<MockEventHandler> event_handler_;
private:
DISALLOW_COPY_AND_ASSIGN(AudioInputDelegateTest);
};
TEST_F(AudioInputDelegateTest,
CreateWithoutAuthorization_FailsDelegateCreation) {
EXPECT_EQ(CreateDelegate(kDefaultSharedMemoryCount, 0, kDoNotEnableAGC),
nullptr);
// Ensure |event_handler_| didn't get any notifications.
base::RunLoop().RunUntilIdle();
}
TEST_F(AudioInputDelegateTest,
CreateWithTooManySegments_FailsDelegateCreation) {
int session_id = MakeDeviceAvailable(kDefaultDeviceId, kDefaultDeviceName);
EXPECT_EQ(CreateDelegate(std::numeric_limits<uint32_t>::max(), session_id,
kDoNotEnableAGC),
nullptr);
// Ensure |event_handler_| didn't get any notifications. Also verifies that
// no AudioManager stream is created.
base::RunLoop().RunUntilIdle();
}
TEST_F(AudioInputDelegateTest, CreateWebContentsCaptureStream) {
int session_id = MakeDeviceAvailable(
base::StringPrintf("web-contents-media-stream://%d:%d", kRenderProcessId,
kRenderFrameId),
"Web contents stream");
EXPECT_NE(
CreateDelegate(kDefaultSharedMemoryCount, session_id, kDoNotEnableAGC),
nullptr);
base::RunLoop().RunUntilIdle();
}
TEST_F(AudioInputDelegateTest, CreateOrdinaryCaptureStream) {
int session_id = MakeDeviceAvailable(kDefaultDeviceId, kDefaultDeviceName);
StrictMock<MockAudioInputStream> stream;
EXPECT_CALL(stream, Open()).WillOnce(Return(true));
EXPECT_CALL(stream, SetAutomaticGainControl(false))
.WillOnce(Return(true));
EXPECT_CALL(stream, IsMuted()).WillOnce(Return(false));
EXPECT_CALL(event_handler_, MockOnStreamCreated(kStreamId, false));
bool created = false;
audio_manager_.SetMakeInputStreamCB(
base::BindRepeating(&MakeInputStreamCallback, &stream, &created));
auto delegate =
CreateDelegate(kDefaultSharedMemoryCount, session_id, kDoNotEnableAGC);
EXPECT_NE(delegate, nullptr);
SyncWithAudioThread();
EXPECT_CALL(stream, Close());
delegate.reset();
SyncWithAudioThread();
}
TEST_F(AudioInputDelegateTest, CreateOrdinaryStreamWithAGC_AGCPropagates) {
int session_id = MakeDeviceAvailable(kDefaultDeviceId, kDefaultDeviceName);
StrictMock<MockAudioInputStream> stream;
EXPECT_CALL(stream, Open()).WillOnce(Return(true));
EXPECT_CALL(stream, SetAutomaticGainControl(true))
.WillOnce(Return(true));
EXPECT_CALL(stream, IsMuted()).WillOnce(Return(false));
EXPECT_CALL(event_handler_, MockOnStreamCreated(kStreamId, false));
bool created = false;
audio_manager_.SetMakeInputStreamCB(
base::BindRepeating(&MakeInputStreamCallback, &stream, &created));
auto delegate =
CreateDelegate(kDefaultSharedMemoryCount, session_id, kDoEnableAGC);
EXPECT_NE(delegate, nullptr);
SyncWithAudioThread();
EXPECT_CALL(stream, Close());
delegate.reset();
SyncWithAudioThread();
}
TEST_F(AudioInputDelegateTest, Record) {
int session_id = MakeDeviceAvailable(kDefaultDeviceId, kDefaultDeviceName);
StrictMock<MockAudioInputStream> stream;
EXPECT_CALL(stream, Open()).WillOnce(Return(true));
EXPECT_CALL(stream, SetAutomaticGainControl(false))
.WillOnce(Return(true));
EXPECT_CALL(stream, IsMuted()).WillOnce(Return(false));
EXPECT_CALL(event_handler_, MockOnStreamCreated(kStreamId, false));
bool created = false;
audio_manager_.SetMakeInputStreamCB(
base::BindRepeating(&MakeInputStreamCallback, &stream, &created));
auto delegate =
CreateDelegate(kDefaultSharedMemoryCount, session_id, kDoNotEnableAGC);
EXPECT_NE(delegate, nullptr);
SyncWithAudioThread();
EXPECT_CALL(stream, Start(NotNull()));
delegate->OnRecordStream();
SyncWithAudioThread();
EXPECT_CALL(stream, Stop());
EXPECT_CALL(stream, Close());
delegate.reset();
SyncWithAudioThread();
}
TEST_F(AudioInputDelegateTest, SetVolume) {
int session_id = MakeDeviceAvailable(kDefaultDeviceId, kDefaultDeviceName);
StrictMock<MockAudioInputStream> stream;
EXPECT_CALL(stream, Open()).WillOnce(Return(true));
EXPECT_CALL(stream, SetAutomaticGainControl(false))
.WillOnce(Return(true));
EXPECT_CALL(stream, IsMuted()).WillOnce(Return(false));
EXPECT_CALL(event_handler_, MockOnStreamCreated(kStreamId, false));
bool created = false;
audio_manager_.SetMakeInputStreamCB(
base::BindRepeating(&MakeInputStreamCallback, &stream, &created));
auto delegate =
CreateDelegate(kDefaultSharedMemoryCount, session_id, kDoNotEnableAGC);
EXPECT_NE(delegate, nullptr);
SyncWithAudioThread();
// Note: The AudioInputController is supposed to access the max volume of the
// stream and map [0, 1] to [0, max_volume].
EXPECT_CALL(stream, GetMaxVolume()).WillOnce(Return(kVolumeScale));
EXPECT_CALL(stream, SetVolume(DoubleEq(kNewVolume * kVolumeScale)));
delegate->OnSetVolume(kNewVolume);
SyncWithAudioThread();
EXPECT_CALL(stream, Close());
delegate.reset();
SyncWithAudioThread();
}
} // namespace content