blob: 2112bebf2f40f4eb33f385230de000d56d784324 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <linux/input.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <android-base/logging.h>
#include <android-base/strings.h>
#include <gflags/gflags.h>
#include <libyuv.h>
#include "common/libs/fs/shared_buf.h"
#include "common/libs/fs/shared_fd.h"
#include "common/libs/utils/files.h"
#include "host/frontend/webrtc/audio_handler.h"
#include "host/frontend/webrtc/connection_observer.h"
#include "host/frontend/webrtc/display_handler.h"
#include "host/frontend/webrtc/kernel_log_events_handler.h"
#include "host/frontend/webrtc/lib/local_recorder.h"
#include "host/frontend/webrtc/lib/streamer.h"
#include "host/frontend/webrtc/lib/video_sink.h"
#include "host/libs/audio_connector/server.h"
#include "host/libs/config/cuttlefish_config.h"
#include "host/libs/config/logging.h"
#include "host/libs/confui/host_mode_ctrl.h"
#include "host/libs/confui/host_server.h"
#include "host/libs/screen_connector/screen_connector.h"
DEFINE_string(touch_fds, "",
"A list of fds to listen on for touch connections.");
DEFINE_int32(keyboard_fd, -1, "An fd to listen on for keyboard connections.");
DEFINE_int32(switches_fd, -1, "An fd to listen on for switch connections.");
DEFINE_int32(frame_server_fd, -1, "An fd to listen on for frame updates");
DEFINE_int32(kernel_log_events_fd, -1,
"An fd to listen on for kernel log events.");
DEFINE_int32(command_fd, -1, "An fd to listen to for control messages");
DEFINE_string(action_servers, "",
"A comma-separated list of server_name:fd pairs, "
"where each entry corresponds to one custom action server.");
DEFINE_bool(write_virtio_input, true,
"Whether to send input events in virtio format.");
DEFINE_int32(audio_server_fd, -1, "An fd to listen on for audio frames");
using cuttlefish::AudioHandler;
using cuttlefish::CfConnectionObserverFactory;
using cuttlefish::DisplayHandler;
using cuttlefish::KernelLogEventsHandler;
using cuttlefish::webrtc_streaming::LocalRecorder;
using cuttlefish::webrtc_streaming::Streamer;
using cuttlefish::webrtc_streaming::StreamerConfig;
using cuttlefish::webrtc_streaming::VideoSink;
class CfOperatorObserver
: public cuttlefish::webrtc_streaming::OperatorObserver {
public:
virtual ~CfOperatorObserver() = default;
virtual void OnRegistered() override {
LOG(VERBOSE) << "Registered with Operator";
}
virtual void OnClose() override {
LOG(ERROR) << "Connection with Operator unexpectedly closed";
}
virtual void OnError() override {
LOG(ERROR) << "Error encountered in connection with Operator";
}
};
static std::vector<std::pair<std::string, std::string>> ParseHttpHeaders(
const std::string& path) {
auto fd = cuttlefish::SharedFD::Open(path, O_RDONLY);
if (!fd->IsOpen()) {
LOG(WARNING) << "Unable to open operator (signaling server) headers file, "
"connecting to the operator will probably fail: "
<< fd->StrError();
return {};
}
std::string raw_headers;
auto res = cuttlefish::ReadAll(fd, &raw_headers);
if (res < 0) {
LOG(WARNING) << "Unable to open operator (signaling server) headers file, "
"connecting to the operator will probably fail: "
<< fd->StrError();
return {};
}
std::vector<std::pair<std::string, std::string>> headers;
std::size_t raw_index = 0;
while (raw_index < raw_headers.size()) {
auto colon_pos = raw_headers.find(':', raw_index);
if (colon_pos == std::string::npos) {
LOG(ERROR)
<< "Expected to find ':' in each line of the operator headers file";
break;
}
auto eol_pos = raw_headers.find('\n', colon_pos);
if (eol_pos == std::string::npos) {
eol_pos = raw_headers.size();
}
// If the file uses \r\n as line delimiters exclude the \r too.
auto eov_pos = raw_headers[eol_pos - 1] == '\r'? eol_pos - 1: eol_pos;
headers.emplace_back(
raw_headers.substr(raw_index, colon_pos + 1 - raw_index),
raw_headers.substr(colon_pos + 1, eov_pos - colon_pos - 1));
raw_index = eol_pos + 1;
}
return headers;
}
std::unique_ptr<cuttlefish::AudioServer> CreateAudioServer() {
cuttlefish::SharedFD audio_server_fd =
cuttlefish::SharedFD::Dup(FLAGS_audio_server_fd);
close(FLAGS_audio_server_fd);
return std::make_unique<cuttlefish::AudioServer>(audio_server_fd);
}
int main(int argc, char** argv) {
cuttlefish::DefaultSubprocessLogging(argv);
::gflags::ParseCommandLineFlags(&argc, &argv, true);
cuttlefish::InputSockets input_sockets;
auto counter = 0;
for (const auto& touch_fd_str : android::base::Split(FLAGS_touch_fds, ",")) {
auto touch_fd = std::stoi(touch_fd_str);
input_sockets.touch_servers["display_" + std::to_string(counter++)] =
cuttlefish::SharedFD::Dup(touch_fd);
close(touch_fd);
}
input_sockets.keyboard_server = cuttlefish::SharedFD::Dup(FLAGS_keyboard_fd);
input_sockets.switches_server = cuttlefish::SharedFD::Dup(FLAGS_switches_fd);
auto control_socket = cuttlefish::SharedFD::Dup(FLAGS_command_fd);
close(FLAGS_keyboard_fd);
close(FLAGS_switches_fd);
close(FLAGS_command_fd);
// Accepting on these sockets here means the device won't register with the
// operator as soon as it could, but rather wait until crosvm's input display
// devices have been initialized. That's OK though, because without those
// devices there is no meaningful interaction the user can have with the
// device.
for (const auto& touch_entry : input_sockets.touch_servers) {
input_sockets.touch_clients[touch_entry.first] =
cuttlefish::SharedFD::Accept(*touch_entry.second);
}
input_sockets.keyboard_client =
cuttlefish::SharedFD::Accept(*input_sockets.keyboard_server);
input_sockets.switches_client =
cuttlefish::SharedFD::Accept(*input_sockets.switches_server);
std::vector<std::thread> touch_accepters;
for (const auto& touch : input_sockets.touch_servers) {
auto label = touch.first;
touch_accepters.emplace_back([label, &input_sockets]() {
for (;;) {
input_sockets.touch_clients[label] =
cuttlefish::SharedFD::Accept(*input_sockets.touch_servers[label]);
}
});
}
std::thread keyboard_accepter([&input_sockets]() {
for (;;) {
input_sockets.keyboard_client =
cuttlefish::SharedFD::Accept(*input_sockets.keyboard_server);
}
});
std::thread switches_accepter([&input_sockets]() {
for (;;) {
input_sockets.switches_client =
cuttlefish::SharedFD::Accept(*input_sockets.switches_server);
}
});
auto kernel_log_events_client =
cuttlefish::SharedFD::Dup(FLAGS_kernel_log_events_fd);
close(FLAGS_kernel_log_events_fd);
auto cvd_config = cuttlefish::CuttlefishConfig::Get();
auto instance = cvd_config->ForDefaultInstance();
auto& host_mode_ctrl = cuttlefish::HostModeCtrl::Get();
auto screen_connector_ptr = cuttlefish::DisplayHandler::ScreenConnector::Get(
FLAGS_frame_server_fd, host_mode_ctrl);
auto& screen_connector = *(screen_connector_ptr.get());
// create confirmation UI service, giving host_mode_ctrl and
// screen_connector
// keep this singleton object alive until the webRTC process ends
static auto& host_confui_server =
cuttlefish::confui::HostServer::Get(host_mode_ctrl, screen_connector);
StreamerConfig streamer_config;
streamer_config.device_id = instance.webrtc_device_id();
streamer_config.tcp_port_range = cvd_config->webrtc_tcp_port_range();
streamer_config.udp_port_range = cvd_config->webrtc_udp_port_range();
streamer_config.operator_server.addr = cvd_config->sig_server_address();
streamer_config.operator_server.port = cvd_config->sig_server_port();
streamer_config.operator_server.path = cvd_config->sig_server_path();
streamer_config.operator_server.security =
cvd_config->sig_server_strict()
? WsConnection::Security::kStrict
: WsConnection::Security::kAllowSelfSigned;
if (!cvd_config->sig_server_headers_path().empty()) {
streamer_config.operator_server.http_headers =
ParseHttpHeaders(cvd_config->sig_server_headers_path());
}
KernelLogEventsHandler kernel_logs_event_handler(kernel_log_events_client);
auto observer_factory = std::make_shared<CfConnectionObserverFactory>(
input_sockets, &kernel_logs_event_handler, host_confui_server);
auto streamer = Streamer::Create(streamer_config, observer_factory);
CHECK(streamer) << "Could not create streamer";
uint32_t display_index = 0;
std::vector<std::shared_ptr<VideoSink>> displays;
for (const auto& display_config : cvd_config->display_configs()) {
const std::string display_id = "display_" + std::to_string(display_index);
auto display =
streamer->AddDisplay(display_id, display_config.width,
display_config.height, cvd_config->dpi(), true);
displays.push_back(display);
++display_index;
}
auto display_handler =
std::make_shared<DisplayHandler>(std::move(displays), screen_connector);
std::unique_ptr<cuttlefish::webrtc_streaming::LocalRecorder> local_recorder;
if (cvd_config->record_screen()) {
int recording_num = 0;
std::string recording_path;
do {
recording_path = instance.PerInstancePath("recording/recording_");
recording_path += std::to_string(recording_num);
recording_path += ".webm";
recording_num++;
} while (cuttlefish::FileExists(recording_path));
local_recorder = LocalRecorder::Create(recording_path);
CHECK(local_recorder) << "Could not create local recorder";
streamer->RecordDisplays(*local_recorder);
display_handler->IncClientCount();
}
observer_factory->SetDisplayHandler(display_handler);
streamer->SetHardwareSpec("CPUs", cvd_config->cpus());
streamer->SetHardwareSpec("RAM", std::to_string(cvd_config->memory_mb()) + " mb");
std::string user_friendly_gpu_mode;
if (cvd_config->gpu_mode() == cuttlefish::kGpuModeGuestSwiftshader) {
user_friendly_gpu_mode = "SwiftShader (Guest CPU Rendering)";
} else if (cvd_config->gpu_mode() == cuttlefish::kGpuModeDrmVirgl) {
user_friendly_gpu_mode = "VirglRenderer (Accelerated Host GPU Rendering)";
} else if (cvd_config->gpu_mode() == cuttlefish::kGpuModeGfxStream) {
user_friendly_gpu_mode = "Gfxstream (Accelerated Host GPU Rendering)";
} else {
user_friendly_gpu_mode = cvd_config->gpu_mode();
}
streamer->SetHardwareSpec("GPU Mode", user_friendly_gpu_mode);
std::shared_ptr<AudioHandler> audio_handler;
if (cvd_config->enable_audio()) {
auto audio_stream = streamer->AddAudioStream("audio");
auto audio_server = CreateAudioServer();
auto audio_source = streamer->GetAudioSource();
audio_handler = std::make_shared<AudioHandler>(std::move(audio_server),
audio_stream, audio_source);
}
// Parse the -action_servers flag, storing a map of action server name -> fd
std::map<std::string, int> action_server_fds;
for (const std::string& action_server :
android::base::Split(FLAGS_action_servers, ",")) {
if (action_server.empty()) {
continue;
}
const std::vector<std::string> server_and_fd =
android::base::Split(action_server, ":");
CHECK(server_and_fd.size() == 2)
<< "Wrong format for action server flag: " << action_server;
std::string server = server_and_fd[0];
int fd = std::stoi(server_and_fd[1]);
action_server_fds[server] = fd;
}
for (const auto& custom_action : cvd_config->custom_actions()) {
if (custom_action.shell_command) {
if (custom_action.buttons.size() != 1) {
LOG(FATAL) << "Expected exactly one button for custom action command: "
<< *(custom_action.shell_command);
}
const auto button = custom_action.buttons[0];
streamer->AddCustomControlPanelButtonWithShellCommand(
button.command, button.title, button.icon_name,
*(custom_action.shell_command));
} else if (custom_action.server) {
if (action_server_fds.find(*(custom_action.server)) !=
action_server_fds.end()) {
LOG(INFO) << "Connecting to custom action server "
<< *(custom_action.server);
int fd = action_server_fds[*(custom_action.server)];
cuttlefish::SharedFD custom_action_server = cuttlefish::SharedFD::Dup(fd);
close(fd);
if (custom_action_server->IsOpen()) {
std::vector<std::string> commands_for_this_server;
for (const auto& button : custom_action.buttons) {
streamer->AddCustomControlPanelButton(button.command, button.title,
button.icon_name);
commands_for_this_server.push_back(button.command);
}
observer_factory->AddCustomActionServer(custom_action_server,
commands_for_this_server);
} else {
LOG(ERROR) << "Error connecting to custom action server: "
<< *(custom_action.server);
}
} else {
LOG(ERROR) << "Custom action server not provided as command line flag: "
<< *(custom_action.server);
}
} else if (!custom_action.device_states.empty()) {
if (custom_action.buttons.size() != 1) {
LOG(FATAL)
<< "Expected exactly one button for custom action device states.";
}
const auto button = custom_action.buttons[0];
streamer->AddCustomControlPanelButtonWithDeviceStates(
button.command, button.title, button.icon_name,
custom_action.device_states);
}
}
std::shared_ptr<cuttlefish::webrtc_streaming::OperatorObserver> operator_observer(
new CfOperatorObserver());
streamer->Register(operator_observer);
std::thread control_thread([control_socket, &local_recorder]() {
if (!local_recorder) {
return;
}
std::string message = "_";
int read_ret;
while ((read_ret = cuttlefish::ReadExact(control_socket, &message)) > 0) {
LOG(VERBOSE) << "received control message: " << message;
if (message[0] == 'C') {
LOG(DEBUG) << "Finalizing screen recording...";
local_recorder->Stop();
LOG(INFO) << "Finalized screen recording.";
message = "Y";
cuttlefish::WriteAll(control_socket, message);
}
}
LOG(DEBUG) << "control socket closed";
});
if (audio_handler) {
audio_handler->Start();
}
host_confui_server.Start();
display_handler->Loop();
return 0;
}