blob: 2198db33401a88e57dfc3e15906428ee252358c3 [file] [log] [blame]
// Copyright 2020 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 "media/audio/cras/cras_util.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/time.h"
#include "media/audio/audio_device_description.h"
#include "media/audio/cras/audio_manager_cras_base.h"
namespace media {
namespace {
constexpr char kInternalInputVirtualDevice[] = "Built-in mic";
constexpr char kInternalOutputVirtualDevice[] = "Built-in speaker";
constexpr char kHeadphoneLineOutVirtualDevice[] = "Headphone/Line Out";
// Names below are from the node_type_to_str function in CRAS server.
// https://chromium.googlesource.com/chromiumos/third_party/adhd/+/refs/heads/main/cras/src/server/cras_iodev_list.c
constexpr char kInternalSpeaker[] = "INTERNAL_SPEAKER";
constexpr char kHeadphone[] = "HEADPHONE";
constexpr char kHDMI[] = "HDMI";
constexpr char kLineout[] = "LINEOUT";
constexpr char kMic[] = "MIC";
constexpr char kInternalMic[] = "INTERNAL_MIC";
constexpr char kFrontMic[] = "FRONT_MIC";
constexpr char kRearMic[] = "REAR_MIC";
constexpr char kKeyBoardMic[] = "KEYBOARD_MIC";
constexpr char kBluetoothNBMic[] = "BLUETOOTH_NB_MIC";
constexpr char kUSB[] = "USB";
constexpr char kBluetooth[] = "BLUETOOTH";
constexpr char kAlsaLoopback[] = "ALSA_LOOPBACK";
// Returns if that an input or output audio device is for simple usage like
// playback or recording for user. In contrast, audio device such as loopback,
// always on keyword recognition (HOTWORD), and keyboard mic are not for simple
// usage.
// One special case is ALSA loopback device, which will only exist under
// testing. We want it visible to users for e2e tests.
bool IsForSimpleUsage(std::string type) {
return type == kInternalMic || type == kHeadphone || type == kHDMI ||
type == kLineout || type == kMic || type == kInternalMic ||
type == kFrontMic || type == kRearMic || type == kBluetoothNBMic ||
type == kUSB || type == kBluetooth || type == kAlsaLoopback;
}
bool IsInternalMic(std::string type) {
return type == kInternalMic || type == kFrontMic || type == kRearMic;
}
// Connects to the CRAS server.
libcras_client* CrasConnect() {
libcras_client* client;
client = libcras_client_create();
if (!client) {
LOG(ERROR) << "Couldn't create CRAS client.\n";
return nullptr;
}
if (libcras_client_connect(client)) {
LOG(ERROR) << "Couldn't connect CRAS client.\n";
libcras_client_destroy(client);
return nullptr;
}
return client;
}
// Disconnects from the CRAS server.
void CrasDisconnect(libcras_client** client) {
if (*client) {
libcras_client_stop(*client);
libcras_client_destroy(*client);
*client = nullptr;
}
}
} // namespace
CrasDevice::CrasDevice() = default;
CrasDevice::CrasDevice(struct libcras_node_info* node, DeviceType type)
: type(type) {
int rc;
rc = libcras_node_info_get_id(node, &id);
if (rc) {
LOG(ERROR) << "Failed to get the node id: " << rc;
id = 0;
}
rc = libcras_node_info_get_dev_idx(node, &dev_idx);
if (rc) {
LOG(ERROR) << "Failed to get the dev idx: " << rc;
dev_idx = 0;
}
rc = libcras_node_info_is_plugged(node, &plugged);
if (rc) {
LOG(ERROR) << "Failed to get if the node is plugged: " << rc;
plugged = false;
}
rc = libcras_node_info_is_active(node, &active);
if (rc) {
LOG(ERROR) << "Failed to get if the node is active: " << rc;
active = false;
}
char* type_str;
rc = libcras_node_info_get_type(node, &type_str);
if (rc) {
LOG(ERROR) << "Failed to get the node type: " << rc;
node_type = nullptr;
}
node_type = type_str;
char* node_name;
rc = libcras_node_info_get_node_name(node, &node_name);
if (rc) {
LOG(ERROR) << "Failed to get the node name: " << rc;
node_name = nullptr;
}
char* device_name;
rc = libcras_node_info_get_dev_name(node, &device_name);
if (rc) {
LOG(ERROR) << "Failed to get the dev name: " << rc;
device_name = nullptr;
}
name = std::string(node_name);
if (name.empty() || name == "(default)")
name = device_name;
dev_name = device_name;
}
void mergeDevices(CrasDevice& old_dev, CrasDevice& new_dev) {
if (old_dev.node_type == kLineout || new_dev.node_type == kLineout) {
old_dev.name = kHeadphoneLineOutVirtualDevice;
old_dev.node_type = "";
} else if (old_dev.node_type == kInternalSpeaker ||
new_dev.node_type == kInternalSpeaker) {
old_dev.name = kInternalOutputVirtualDevice;
old_dev.node_type = "";
} else if (IsInternalMic(old_dev.node_type) ||
IsInternalMic(new_dev.node_type)) {
old_dev.name = kInternalInputVirtualDevice;
old_dev.node_type = "";
} else {
LOG(WARNING) << "Failed to create virtual device for " << old_dev.name;
}
old_dev.active |= new_dev.active;
}
std::vector<CrasDevice> CrasGetAudioDevices(DeviceType type) {
std::vector<CrasDevice> devices;
libcras_client* client = CrasConnect();
if (!client)
return devices;
int rc;
struct libcras_node_info** nodes;
size_t num_nodes;
if (type == DeviceType::kInput) {
rc =
libcras_client_get_nodes(client, CRAS_STREAM_INPUT, &nodes, &num_nodes);
} else {
rc = libcras_client_get_nodes(client, CRAS_STREAM_OUTPUT, &nodes,
&num_nodes);
}
if (rc < 0) {
LOG(ERROR) << "Failed to get devices: " << std::strerror(rc);
CrasDisconnect(&client);
return devices;
}
for (size_t i = 0; i < num_nodes; i++) {
auto new_dev = CrasDevice(nodes[i], type);
if (!new_dev.plugged || !IsForSimpleUsage(new_dev.node_type))
continue;
bool added = false;
for (auto& dev : devices) {
if (dev.dev_idx == new_dev.dev_idx) {
mergeDevices(dev, new_dev);
added = true;
break;
}
}
if (!added)
devices.emplace_back(new_dev);
}
libcras_node_info_array_destroy(nodes, num_nodes);
CrasDisconnect(&client);
return devices;
}
bool CrasHasKeyboardMic() {
libcras_client* client = CrasConnect();
if (!client)
return false;
struct libcras_node_info** nodes;
size_t num_nodes;
int rc =
libcras_client_get_nodes(client, CRAS_STREAM_INPUT, &nodes, &num_nodes);
int ret = false;
if (rc < 0) {
LOG(ERROR) << "Failed to get devices: " << std::strerror(rc);
CrasDisconnect(&client);
return false;
}
for (size_t i = 0; i < num_nodes; i++) {
auto device = CrasDevice(nodes[i], DeviceType::kInput);
if (device.node_type == kKeyBoardMic)
ret = true;
}
libcras_node_info_array_destroy(nodes, num_nodes);
CrasDisconnect(&client);
return ret;
}
int CrasGetAecSupported() {
libcras_client* client = CrasConnect();
if (!client)
return 0;
int supported;
libcras_client_get_aec_supported(client, &supported);
CrasDisconnect(&client);
return supported;
}
int CrasGetAecGroupId() {
libcras_client* client = CrasConnect();
if (!client)
return -1;
int id;
int rc = libcras_client_get_aec_group_id(client, &id);
CrasDisconnect(&client);
return rc < 0 ? rc : id;
}
int CrasGetDefaultOutputBufferSize() {
libcras_client* client = CrasConnect();
if (!client)
return -1;
int size;
int rc = libcras_client_get_default_output_buffer_size(client, &size);
CrasDisconnect(&client);
return rc < 0 ? rc : size;
}
} // namespace media