// Copyright 2020 The Chromium Authors
// 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"
#include "media/base/media_switches.h"
namespace media {
namespace {
constexpr char kInternalInputVirtualDevice[] = "Built-in mic";
constexpr char kInternalOutputVirtualDevice[] = "Built-in speaker/headphone";
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 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 == kInternalSpeaker || 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;
}
rc = libcras_node_info_get_max_supported_channels(node,
&max_supported_channels);
if (rc) {
LOG(ERROR) << "Failed to get max supported channels: " << rc;
max_supported_channels = 0;
}
name = std::string(node_name);
if (name.empty() || name == "(default)") {
name = device_name;
}
dev_name = device_name;
}
CrasDevice::CrasDevice(DeviceType type,
uint64_t id,
uint32_t dev_idx,
uint32_t max_supported_channels,
bool plugged,
bool active,
std::string node_type,
std::string name,
std::string dev_name)
: type(type),
id(id),
dev_idx(dev_idx),
max_supported_channels(max_supported_channels),
plugged(plugged),
active(active),
node_type(node_type),
name(name),
dev_name(dev_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;
}
CrasUtil::CrasUtil() = default;
CrasUtil::~CrasUtil() = default;
bool CrasUtil::CacheEffects() {
libcras_client* client = CrasConnect();
if (!client) {
LOG(ERROR) << "Failed to cache effects";
return false;
}
if (libcras_client_get_aec_supported(client, &aec_supported_) < 0) {
LOG(ERROR) << "Fail to query AEC supported";
aec_supported_ = false;
}
if (libcras_client_get_agc_supported(client, &agc_supported_) < 0) {
LOG(ERROR) << "Fail to query AGC supported";
agc_supported_ = false;
}
if (libcras_client_get_ns_supported(client, &ns_supported_) < 0) {
LOG(ERROR) << "Fail to query NS supported";
ns_supported_ = false;
}
if (base::FeatureList::IsEnabled(media::kCrOSSystemVoiceIsolationOption)) {
if (libcras_client_get_voice_isolation_supported(
client, &voice_isolation_supported_) < 0) {
LOG(ERROR) << "Fail to query VoiceIsolation supported";
voice_isolation_supported_ = false;
}
}
if (libcras_client_get_aec_group_id(client, &aec_group_id_) < 0) {
LOG(ERROR) << "Fail to query AEC group ID";
aec_group_id_ = -1; // The default group ID is -1
}
if (libcras_client_get_default_output_buffer_size(
client, &default_output_buffer_size_) < 0) {
LOG(ERROR) << "Fail to query default output buffer size";
default_output_buffer_size_ = 0;
}
CrasDisconnect(&client);
return true;
}
std::vector<CrasDevice> CrasUtil::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;
}
int CrasUtil::CrasGetAecSupported() {
if (!cras_effects_cached_) {
cras_effects_cached_ = CacheEffects();
}
return aec_supported_;
}
int CrasUtil::CrasGetAgcSupported() {
if (!cras_effects_cached_) {
cras_effects_cached_ = CacheEffects();
}
return agc_supported_;
}
int CrasUtil::CrasGetNsSupported() {
if (!cras_effects_cached_) {
cras_effects_cached_ = CacheEffects();
}
return ns_supported_;
}
int CrasUtil::CrasGetVoiceIsolationSupported() {
if (!cras_effects_cached_) {
cras_effects_cached_ = CacheEffects();
}
return voice_isolation_supported_;
}
int CrasUtil::CrasGetAecGroupId() {
if (!cras_effects_cached_) {
cras_effects_cached_ = CacheEffects();
}
return aec_group_id_;
}
int CrasUtil::CrasGetDefaultOutputBufferSize() {
if (!cras_effects_cached_) {
cras_effects_cached_ = CacheEffects();
}
return default_output_buffer_size_;
}
} // namespace media