Commit 7bdd7a5e authored by Yuwei Huang's avatar Yuwei Huang Committed by Chromium LUCI CQ

[remoting host][mac] Add HostSettings and capture_audio_for_device_uid

This CL adds a HostSettings class and provides an implementation for Mac
for reading simple key-value pairs, and add a new host setting,
`capture_audio_for_device_uid` to specify which UID the Mac host should
capture the audio from.

This code is still very preliminary. It only loads the file once, and it
doesn't create the file for the user. The user will need to manually
create a file at:
/Library/PrivilegedHelperTools/org.chromium.chromoting.settings.json,
make it readable by both root and the current $USER, and add the
capture_audio_for_device_uid mapping.

Bug: 1161363
Change-Id: I8ee5269908a9a775d003b6349e3c7567d88380b9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2612400
Commit-Queue: Yuwei Huang <yuweih@chromium.org>
Reviewed-by: default avatarJoe Downing <joedow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#841285}
parent 1564a483
...@@ -174,6 +174,9 @@ static_library("common") { ...@@ -174,6 +174,9 @@ static_library("common") {
"host_power_save_blocker.h", "host_power_save_blocker.h",
"host_secret.cc", "host_secret.cc",
"host_secret.h", "host_secret.h",
"host_setting_keys.h",
"host_settings.cc",
"host_settings.h",
"host_status_logger.cc", "host_status_logger.cc",
"host_status_logger.h", "host_status_logger.h",
"host_status_monitor.cc", "host_status_monitor.cc",
...@@ -409,6 +412,8 @@ static_library("common") { ...@@ -409,6 +412,8 @@ static_library("common") {
"desktop_resizer_mac.cc", "desktop_resizer_mac.cc",
"disconnect_window_mac.h", "disconnect_window_mac.h",
"disconnect_window_mac.mm", "disconnect_window_mac.mm",
"host_settings_mac.cc",
"host_settings_mac.h",
"input_injector_mac.cc", "input_injector_mac.cc",
"keyboard_layout_monitor_mac.cc", "keyboard_layout_monitor_mac.cc",
"pairing_registry_delegate_mac.cc", "pairing_registry_delegate_mac.cc",
...@@ -418,9 +423,13 @@ static_library("common") { ...@@ -418,9 +423,13 @@ static_library("common") {
frameworks = [ frameworks = [
"Accelerate.framework", "Accelerate.framework",
"Carbon.framework", "Carbon.framework",
"CoreAudio.framework",
] ]
deps += [ ":remoting_version" ] deps += [
":remoting_version",
"//remoting/host/mac:constants",
]
} }
if (is_win) { if (is_win) {
......
...@@ -10,8 +10,13 @@ ...@@ -10,8 +10,13 @@
#include "base/containers/flat_set.h" #include "base/containers/flat_set.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/no_destructor.h" #include "base/no_destructor.h"
#include "base/strings/sys_string_conversions.h"
#include "base/synchronization/lock.h" #include "base/synchronization/lock.h"
#include "base/threading/sequenced_task_runner_handle.h" #include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread_restrictions.h"
#include "remoting/base/logging.h"
#include "remoting/host/host_setting_keys.h"
#include "remoting/host/host_settings.h"
#include "remoting/proto/audio.pb.h" #include "remoting/proto/audio.pb.h"
namespace remoting { namespace remoting {
...@@ -88,8 +93,10 @@ AudioCapturerInstanceSet* AudioCapturerInstanceSet::Get() { ...@@ -88,8 +93,10 @@ AudioCapturerInstanceSet* AudioCapturerInstanceSet::Get() {
} // namespace } // namespace
AudioCapturerMac::AudioCapturerMac() { AudioCapturerMac::AudioCapturerMac(const std::string& audio_device_uid)
: audio_device_uid_(audio_device_uid) {
DETACH_FROM_SEQUENCE(sequence_checker_); DETACH_FROM_SEQUENCE(sequence_checker_);
DCHECK(!audio_device_uid.empty());
stream_description_.mSampleRate = kSampleRate; stream_description_.mSampleRate = kSampleRate;
stream_description_.mFormatID = kAudioFormatLinearPCM; stream_description_.mFormatID = kAudioFormatLinearPCM;
...@@ -188,9 +195,6 @@ bool AudioCapturerMac::StartInputQueue() { ...@@ -188,9 +195,6 @@ bool AudioCapturerMac::StartInputQueue() {
// This runs on AudioQueue's internal thread. For some reason if we specify // This runs on AudioQueue's internal thread. For some reason if we specify
// inCallbackRunLoop to current thread, then the callback will never get // inCallbackRunLoop to current thread, then the callback will never get
// called. // called.
// TODO(yuweih): Search for the loopback device directly instead of relying on
// the default input device. This would allow the user to keep their
// microphone as the default input device.
OSStatus err = OSStatus err =
AudioQueueNewInput(&stream_description_, &HandleInputBufferOnAQThread, AudioQueueNewInput(&stream_description_, &HandleInputBufferOnAQThread,
/* inUserData= */ this, /* inCallbackRunLoop= */ NULL, /* inUserData= */ this, /* inCallbackRunLoop= */ NULL,
...@@ -200,6 +204,18 @@ bool AudioCapturerMac::StartInputQueue() { ...@@ -200,6 +204,18 @@ bool AudioCapturerMac::StartInputQueue() {
return false; return false;
} }
// Use the loopback device for input.
HOST_LOG << "Using loopback device: " << audio_device_uid_;
base::ScopedCFTypeRef<CFStringRef> device_uid =
base::SysUTF8ToCFStringRef(audio_device_uid_);
CFStringRef unowned_device_uid = device_uid.get();
err = AudioQueueSetProperty(input_queue_, kAudioQueueProperty_CurrentDevice,
&unowned_device_uid, sizeof(unowned_device_uid));
if (HandleError(err,
"AudioQueueSetProperty(kAudioQueueProperty_CurrentDevice)")) {
return false;
}
// Setup buffers. // Setup buffers.
for (int i = 0; i < kNumberBuffers; i++) { for (int i = 0; i < kNumberBuffers; i++) {
// |buffer| will automatically be freed when |input_queue_| is released. // |buffer| will automatically be freed when |input_queue_| is released.
...@@ -216,6 +232,11 @@ bool AudioCapturerMac::StartInputQueue() { ...@@ -216,6 +232,11 @@ bool AudioCapturerMac::StartInputQueue() {
// Start input queue. // Start input queue.
err = AudioQueueStart(input_queue_, NULL); err = AudioQueueStart(input_queue_, NULL);
if (err == kAudioQueueErr_InvalidDevice) {
LOG(ERROR) << "Loopback device " << audio_device_uid_
<< " could not be located";
return false;
}
if (HandleError(err, "AudioQueueStart")) { if (HandleError(err, "AudioQueueStart")) {
return false; return false;
} }
...@@ -260,29 +281,28 @@ bool AudioCapturerMac::HandleError(OSStatus err, const char* function_name) { ...@@ -260,29 +281,28 @@ bool AudioCapturerMac::HandleError(OSStatus err, const char* function_name) {
// AudioCapturer // AudioCapturer
// AudioCapturer support on Mac is still experimental.
#if defined(NDEBUG)
bool AudioCapturer::IsSupported() {
return false;
}
std::unique_ptr<AudioCapturer> AudioCapturer::Create() {
NOTIMPLEMENTED();
return nullptr;
}
#else
bool AudioCapturer::IsSupported() { bool AudioCapturer::IsSupported() {
if (HostSettings::GetInstance()
->GetString(kMacAudioCaptureDeviceUid)
.empty()) {
HOST_LOG << kMacAudioCaptureDeviceUid << " is not set or not a string. "
<< "Audio capturer will be disabled.";
return false;
}
HOST_LOG << kMacAudioCaptureDeviceUid
<< " is set. Audio capturer will be enabled.";
return true; return true;
} }
std::unique_ptr<AudioCapturer> AudioCapturer::Create() { std::unique_ptr<AudioCapturer> AudioCapturer::Create() {
return std::make_unique<AudioCapturerMac>(); std::string device_uid =
HostSettings::GetInstance()->GetString(kMacAudioCaptureDeviceUid);
if (device_uid.empty()) {
// AudioCapturer::Create is still called even when IsSupported() returns
// false.
return nullptr;
}
return std::make_unique<AudioCapturerMac>(device_uid);
} }
#endif
} // namespace remoting } // namespace remoting
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
#include <AudioToolbox/AudioToolbox.h> #include <AudioToolbox/AudioToolbox.h>
#include <string>
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/scoped_refptr.h" #include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
...@@ -23,7 +25,7 @@ namespace remoting { ...@@ -23,7 +25,7 @@ namespace remoting {
// audio on Mac through the loopback device. // audio on Mac through the loopback device.
class AudioCapturerMac : public AudioCapturer { class AudioCapturerMac : public AudioCapturer {
public: public:
AudioCapturerMac(); explicit AudioCapturerMac(const std::string& audio_device_uid);
~AudioCapturerMac() override; ~AudioCapturerMac() override;
// AudioCapturer interface. // AudioCapturer interface.
...@@ -52,6 +54,8 @@ class AudioCapturerMac : public AudioCapturer { ...@@ -52,6 +54,8 @@ class AudioCapturerMac : public AudioCapturer {
SEQUENCE_CHECKER(sequence_checker_); SEQUENCE_CHECKER(sequence_checker_);
std::string audio_device_uid_;
AudioStreamBasicDescription stream_description_; AudioStreamBasicDescription stream_description_;
PacketCapturedCallback callback_; PacketCapturedCallback callback_;
AudioQueueRef input_queue_ = nullptr; AudioQueueRef input_queue_ = nullptr;
......
// Copyright 2021 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.
#ifndef REMOTING_HOST_HOST_SETTING_KEYS_H_
#define REMOTING_HOST_HOST_SETTING_KEYS_H_
#include "remoting/host/host_settings.h"
namespace remoting {
// If setting is provided, the Mac host will capture audio from the audio device
// specified by the UID and stream it to the client. See AudioCapturerMac for
// more information.
constexpr HostSettingKey kMacAudioCaptureDeviceUid = "audio_capture_device_uid";
} // namespace remoting
#endif // REMOTING_HOST_HOST_SETTING_KEYS_H_
// Copyright 2021 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 "remoting/host/host_settings.h"
#include "base/no_destructor.h"
#include "build/build_config.h"
namespace remoting {
namespace {
class EmptyHostSettings : public HostSettings {
public:
std::string GetString(const HostSettingKey key) const override {
return std::string();
}
void InitializeInstance() override {}
};
} // namespace
// static
void HostSettings::Initialize() {
GetInstance()->InitializeInstance();
}
HostSettings::HostSettings() = default;
HostSettings::~HostSettings() = default;
// HostSettings is currently neither implemented nor used on non-Mac platforms.
#if !defined(OS_APPLE)
// static
HostSettings* HostSettings::GetInstance() {
static base::NoDestructor<EmptyHostSettings> instance;
return instance.get();
}
#endif
} // namespace remoting
// Copyright 2021 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.
#ifndef REMOTING_HOST_HOST_SETTINGS_H_
#define REMOTING_HOST_HOST_SETTINGS_H_
#include <string>
#include "base/values.h"
namespace remoting {
using HostSettingKey = char[];
// A class to read host settings, which are simple key-value pairs unrelated to
// the host's identity, like UID of an audio device to capture audio from, or
// whether a feature should be enabled.
class HostSettings {
public:
// Initializes host settings. Must be called on a thread that allows blocking
// before calling GetValue().
static void Initialize();
static HostSettings* GetInstance();
// Gets the value of the setting. Returns empty string if the value is not
// found.
virtual std::string GetString(const HostSettingKey key) const = 0;
HostSettings(const HostSettings&) = delete;
HostSettings& operator=(const HostSettings&) = delete;
protected:
HostSettings();
virtual ~HostSettings();
virtual void InitializeInstance() = 0;
};
} // namespace remoting
#endif // REMOTING_HOST_HOST_SETTINGS_H_
// Copyright 2021 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 "remoting/host/host_settings_mac.h"
#include "base/files/file_util.h"
#include "base/json/json_file_value_serializer.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "remoting/base/logging.h"
#include "remoting/host/mac/constants_mac.h"
namespace remoting {
HostSettingsMac::HostSettingsMac() = default;
HostSettingsMac::~HostSettingsMac() = default;
void HostSettingsMac::InitializeInstance() {
// TODO(yuweih): Make HostSettingsMac detect changes of the settings file.
if (settings_) {
return;
}
base::FilePath settings_file(kHostSettingsFilePath);
if (!base::PathIsReadable(settings_file)) {
HOST_LOG << "Host settings file " << kHostSettingsFilePath
<< " does not exist.";
return;
}
JSONFileValueDeserializer deserializer(settings_file);
int error_code;
std::string error_message;
settings_ = deserializer.Deserialize(&error_code, &error_message);
if (!settings_) {
LOG(WARNING) << "Failed to load " << kHostSettingsFilePath
<< ". Code: " << error_code << ", message: " << error_message;
return;
}
HOST_LOG << "Host settings loaded.";
}
std::string HostSettingsMac::GetString(const HostSettingKey key) const {
if (!settings_) {
VLOG(1) << "Either Initialize() has not been called, or the settings file "
"doesn't exist.";
return std::string();
}
std::string* string_value = settings_->FindStringKey(key);
if (!string_value) {
return std::string();
}
return *string_value;
}
HostSettings* HostSettings::GetInstance() {
static base::NoDestructor<HostSettingsMac> instance;
return instance.get();
}
} // namespace remoting
// Copyright 2021 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.
#ifndef REMOTING_HOST_HOST_SETTINGS_MAC_H_
#define REMOTING_HOST_HOST_SETTINGS_MAC_H_
#include <memory>
#include "remoting/host/host_settings.h"
namespace remoting {
class HostSettingsMac final : public HostSettings {
public:
HostSettingsMac();
~HostSettingsMac() override;
// HostSettings implementation.
void InitializeInstance() override;
std::string GetString(const HostSettingKey key) const override;
HostSettingsMac(const HostSettingsMac&) = delete;
HostSettingsMac& operator=(const HostSettingsMac&) = delete;
private:
// TODO(yuweih): This needs to be guarded with a lock if we detect changes of
// the settings file.
std::unique_ptr<base::Value> settings_;
};
} // namespace remoting
#endif // REMOTING_HOST_HOST_SETTINGS_MAC_H_
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include "remoting/base/breakpad.h" #include "remoting/base/breakpad.h"
#include "remoting/host/chromoting_host_context.h" #include "remoting/host/chromoting_host_context.h"
#include "remoting/host/host_exit_codes.h" #include "remoting/host/host_exit_codes.h"
#include "remoting/host/host_settings.h"
#include "remoting/host/it2me/it2me_native_messaging_host.h" #include "remoting/host/it2me/it2me_native_messaging_host.h"
#include "remoting/host/logging.h" #include "remoting/host/logging.h"
#include "remoting/host/native_messaging/native_messaging_pipe.h" #include "remoting/host/native_messaging/native_messaging_pipe.h"
...@@ -80,6 +81,7 @@ int It2MeNativeMessagingHostMain(int argc, char** argv) { ...@@ -80,6 +81,7 @@ int It2MeNativeMessagingHostMain(int argc, char** argv) {
base::CommandLine::Init(argc, argv); base::CommandLine::Init(argc, argv);
remoting::InitHostLogging(); remoting::InitHostLogging();
remoting::HostSettings::Initialize();
#if defined(OS_APPLE) #if defined(OS_APPLE)
// Needed so we don't leak objects when threads are created. // Needed so we don't leak objects when threads are created.
......
...@@ -20,6 +20,8 @@ const char kServiceName[] = SERVICE_NAME; ...@@ -20,6 +20,8 @@ const char kServiceName[] = SERVICE_NAME;
const char kHostConfigFileName[] = SERVICE_NAME ".json"; const char kHostConfigFileName[] = SERVICE_NAME ".json";
const char kHostConfigFilePath[] = HELPER_TOOLS_DIR SERVICE_NAME ".json"; const char kHostConfigFilePath[] = HELPER_TOOLS_DIR SERVICE_NAME ".json";
const char kHostSettingsFilePath[] =
HELPER_TOOLS_DIR SERVICE_NAME ".settings.json";
const char kHostServiceBinaryPath[] = HELPER_TOOLS_DIR HOST_BUNDLE_NAME const char kHostServiceBinaryPath[] = HELPER_TOOLS_DIR HOST_BUNDLE_NAME
"/Contents/MacOS/remoting_me2me_host_service"; "/Contents/MacOS/remoting_me2me_host_service";
......
...@@ -17,6 +17,8 @@ extern const char kServiceName[]; ...@@ -17,6 +17,8 @@ extern const char kServiceName[];
extern const char kHostConfigFileName[]; extern const char kHostConfigFileName[];
extern const char kHostConfigFilePath[]; extern const char kHostConfigFilePath[];
extern const char kHostSettingsFilePath[];
// This helper script is executed as root to enable/disable/configure the host // This helper script is executed as root to enable/disable/configure the host
// service. // service.
// It is also used (as non-root) to provide version information for the // It is also used (as non-root) to provide version information for the
......
...@@ -67,6 +67,7 @@ ...@@ -67,6 +67,7 @@
#include "remoting/host/host_exit_codes.h" #include "remoting/host/host_exit_codes.h"
#include "remoting/host/host_main.h" #include "remoting/host/host_main.h"
#include "remoting/host/host_power_save_blocker.h" #include "remoting/host/host_power_save_blocker.h"
#include "remoting/host/host_settings.h"
#include "remoting/host/host_status_logger.h" #include "remoting/host/host_status_logger.h"
#include "remoting/host/input_injector.h" #include "remoting/host/input_injector.h"
#include "remoting/host/ipc_desktop_environment.h" #include "remoting/host/ipc_desktop_environment.h"
...@@ -831,6 +832,8 @@ void HostProcess::StartOnUiThread() { ...@@ -831,6 +832,8 @@ void HostProcess::StartOnUiThread() {
return; return;
} }
HostSettings::Initialize();
if (!report_offline_reason_.empty()) { if (!report_offline_reason_.empty()) {
// Don't need to do any UI initialization. // Don't need to do any UI initialization.
context_->network_task_runner()->PostTask( context_->network_task_runner()->PostTask(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment