#include "media/audio/alsa/audio_manager_alsa.h"
#include <stddef.h>
#include <algorithm>
#include <array>
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/logging.h"
#include "base/memory/free_deleter.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_view_util.h"
#include "media/audio/alsa/alsa_input.h"
#include "media/audio/alsa/alsa_output.h"
#include "media/audio/alsa/alsa_wrapper.h"
#include "media/audio/audio_device_description.h"
#include "media/audio/audio_output_dispatcher.h"
#if defined(USE_PULSEAUDIO)
#include "media/audio/pulse/audio_manager_pulse.h"
#endif
#include "media/base/audio_parameters.h"
#include "media/base/channel_layout.h"
#include "media/base/limits.h"
#include "media/base/media_switches.h"
namespace media {
static const int kMaxOutputStreams = 50;
static const int kDefaultSampleRate = 48000;
constexpr auto kInvalidAudioInputDevices = std::to_array<std::string_view>({
"default",
"dmix",
"null",
"pulse",
"surround",
});
AudioManagerAlsa::AudioManagerAlsa(std::unique_ptr<AudioThread> audio_thread,
AudioLogFactory* audio_log_factory)
: AudioManagerBase(std::move(audio_thread), audio_log_factory),
wrapper_(std::make_unique<AlsaWrapper>()) {
SetMaxOutputStreamsAllowed(kMaxOutputStreams);
}
AudioManagerAlsa::~AudioManagerAlsa() = default;
bool AudioManagerAlsa::HasAudioOutputDevices() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kAlsaOutputDevice) ||
HasAnyAlsaAudioDevice(kStreamPlayback);
}
bool AudioManagerAlsa::HasAudioInputDevices() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kAlsaInputDevice) ||
HasAnyAlsaAudioDevice(kStreamCapture);
}
void AudioManagerAlsa::GetAudioInputDeviceNames(
AudioDeviceNames* device_names) {
DCHECK(device_names->empty());
GetAlsaAudioDevices(kStreamCapture, device_names);
AddAlsaDeviceFromSwitch(switches::kAlsaInputDevice, device_names);
}
void AudioManagerAlsa::GetAudioOutputDeviceNames(
AudioDeviceNames* device_names) {
DCHECK(device_names->empty());
GetAlsaAudioDevices(kStreamPlayback, device_names);
AddAlsaDeviceFromSwitch(switches::kAlsaOutputDevice, device_names);
}
AudioParameters AudioManagerAlsa::GetInputStreamParameters(
const std::string& device_id) {
static const int kDefaultInputBufferSize = 1024;
return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
ChannelLayoutConfig::Stereo(), kDefaultSampleRate,
kDefaultInputBufferSize);
}
const std::string_view AudioManagerAlsa::GetName() {
return "ALSA";
}
void AudioManagerAlsa::GetAlsaAudioDevices(StreamType type,
AudioDeviceNames* device_names) {
static const char kPcmInterfaceName[] = "pcm";
int card = -1;
while (!wrapper_->CardNext(&card) && card >= 0) {
void** hints = nullptr;
int error = wrapper_->DeviceNameHint(card, kPcmInterfaceName, &hints);
if (!error) {
GetAlsaDevicesInfo(type, hints, device_names);
wrapper_->DeviceNameFreeHint(hints);
} else {
DLOG(WARNING) << "GetAlsaAudioDevices: unable to get device hints: "
<< wrapper_->StrError(error);
}
}
}
void AudioManagerAlsa::GetAlsaDevicesInfo(AudioManagerAlsa::StreamType type,
void** hints,
AudioDeviceNames* device_names) {
static const char kIoHintName[] = "IOID";
static const char kNameHintName[] = "NAME";
static const char kDescriptionHintName[] = "DESC";
const std::string_view unwanted_device_type =
UnwantedDeviceTypeWhenEnumerating(type);
for (void** hint_iter = hints; *hint_iter != nullptr;
UNSAFE_BUFFERS(++hint_iter)) {
auto io = wrapper_->DeviceNameGetHint(*hint_iter, kIoHintName);
if (base::as_string_view(io) == unwanted_device_type) {
continue;
}
if (device_names->empty())
device_names->push_front(AudioDeviceName::CreateDefault());
auto unique_device_name =
wrapper_->DeviceNameGetHint(*hint_iter, kNameHintName);
if (IsAlsaDeviceAvailable(type, unique_device_name.data())) {
auto desc = wrapper_->DeviceNameGetHint(*hint_iter, kDescriptionHintName);
AudioDeviceName name;
name.unique_id = base::as_string_view(unique_device_name);
if (!desc.empty()) {
std::ranges::replace(desc, '\n', '-');
name.device_name = base::as_string_view(desc);
} else {
name.device_name = base::as_string_view(unique_device_name);
}
device_names->push_back(name);
}
}
}
bool AudioManagerAlsa::IsAlsaDeviceAvailable(AudioManagerAlsa::StreamType type,
std::string_view device_name) {
if (device_name.empty()) {
return false;
}
if (type == kStreamCapture) {
for (const auto kInvalidAudioInputDevice : kInvalidAudioInputDevices) {
if (device_name.starts_with(kInvalidAudioInputDevice)) {
return false;
}
}
return true;
}
DCHECK_EQ(kStreamPlayback, type);
static constexpr std::string_view kDeviceTypeDesired = "plughw";
return device_name.starts_with(kDeviceTypeDesired);
}
void AudioManagerAlsa::AddAlsaDeviceFromSwitch(const char* switch_name,
AudioDeviceNames* device_names) {
if (base::CommandLine::ForCurrentProcess()->HasSwitch(switch_name)) {
if (device_names->empty()) {
device_names->push_front(AudioDeviceName::CreateDefault());
}
std::string switch_device_name =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switch_name);
if (!base::Contains(
*device_names, switch_device_name,
[](const auto& device_name) { return device_name.unique_id; })) {
AudioDeviceName name;
name.unique_id = switch_device_name;
name.device_name = switch_device_name;
device_names->push_back(name);
}
}
}
std::string_view AudioManagerAlsa::UnwantedDeviceTypeWhenEnumerating(
AudioManagerAlsa::StreamType wanted_type) {
return wanted_type == kStreamPlayback ? "Input" : "Output";
}
bool AudioManagerAlsa::HasAnyAlsaAudioDevice(
AudioManagerAlsa::StreamType stream) {
static const char kPcmInterfaceName[] = "pcm";
static const char kIoHintName[] = "IOID";
void** hints = nullptr;
bool has_device = false;
int card = -1;
while (!wrapper_->CardNext(&card) && (card >= 0) && !has_device) {
int error = wrapper_->DeviceNameHint(card, kPcmInterfaceName, &hints);
if (!error) {
const std::string_view unwanted_type =
UnwantedDeviceTypeWhenEnumerating(stream);
for (void** hint_iter = hints; *hint_iter != nullptr;
UNSAFE_BUFFERS(++hint_iter)) {
auto io = wrapper_->DeviceNameGetHint(*hint_iter, kIoHintName);
if (base::as_string_view(io) == unwanted_type) {
continue;
}
has_device = true;
break;
}
wrapper_->DeviceNameFreeHint(hints);
hints = nullptr;
} else {
DLOG(WARNING) << "HasAnyAudioDevice: unable to get device hints: "
<< wrapper_->StrError(error);
}
}
return has_device;
}
AudioOutputStream* AudioManagerAlsa::MakeLinearOutputStream(
const AudioParameters& params,
const LogCallback& log_callback) {
DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
return MakeOutputStream(params);
}
AudioOutputStream* AudioManagerAlsa::MakeLowLatencyOutputStream(
const AudioParameters& params,
const std::string& device_id,
const LogCallback& log_callback) {
DLOG_IF(ERROR, !device_id.empty()) << "Not implemented!";
DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
return MakeOutputStream(params);
}
AudioInputStream* AudioManagerAlsa::MakeLinearInputStream(
const AudioParameters& params,
const std::string& device_id,
const LogCallback& log_callback) {
DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
return MakeInputStream(params, device_id);
}
AudioInputStream* AudioManagerAlsa::MakeLowLatencyInputStream(
const AudioParameters& params,
const std::string& device_id,
const LogCallback& log_callback) {
DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
return MakeInputStream(params, device_id);
}
AudioParameters AudioManagerAlsa::GetPreferredOutputStreamParameters(
const std::string& output_device_id,
const AudioParameters& input_params) {
DLOG_IF(ERROR, !output_device_id.empty()) << "Not implemented!";
static const int kDefaultOutputBufferSize = 2048;
ChannelLayoutConfig channel_layout_config = ChannelLayoutConfig::Stereo();
int sample_rate = kDefaultSampleRate;
int buffer_size = kDefaultOutputBufferSize;
if (input_params.IsValid()) {
sample_rate = input_params.sample_rate();
channel_layout_config = input_params.channel_layout_config();
buffer_size = std::min(input_params.frames_per_buffer(), buffer_size);
}
int user_buffer_size = GetUserBufferSize();
if (user_buffer_size)
buffer_size = user_buffer_size;
return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
channel_layout_config, sample_rate, buffer_size);
}
AudioOutputStream* AudioManagerAlsa::MakeOutputStream(
const AudioParameters& params) {
std::string device_name = AlsaPcmOutputStream::kAutoSelectDevice;
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kAlsaOutputDevice)) {
device_name = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kAlsaOutputDevice);
}
return new AlsaPcmOutputStream(device_name, params, wrapper_.get(), this);
}
AudioInputStream* AudioManagerAlsa::MakeInputStream(
const AudioParameters& params, const std::string& device_id) {
std::string device_name =
(device_id == AudioDeviceDescription::kDefaultDeviceId)
? AlsaPcmInputStream::kAutoSelectDevice
: device_id;
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kAlsaInputDevice)) {
device_name = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kAlsaInputDevice);
}
return new AlsaPcmInputStream(this, device_name, params, wrapper_.get());
}
}