blob: 45c62d674d13f0a4b3beec773c6db6fc45688857 [file] [log] [blame]
// Copyright 2013 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/pulse/audio_manager_pulse.h"
#include <algorithm>
#include <utility>
#include "base/command_line.h"
#include "base/environment.h"
#include "base/logging.h"
#include "base/nix/xdg_util.h"
#include "build/chromeos_buildflags.h"
#include "media/audio/audio_device_description.h"
#include "media/audio/pulse/pulse_input.h"
#include "media/audio/pulse/pulse_output.h"
#include "media/audio/pulse/pulse_util.h"
#include "media/base/audio_parameters.h"
#include "media/base/channel_layout.h"
namespace media {
using pulse::AutoPulseLock;
using pulse::WaitForOperationCompletion;
// Maximum number of output streams that can be open simultaneously.
constexpr int kMaxOutputStreams = 50;
constexpr int kMinimumOutputBufferSize = 512;
constexpr int kMaximumOutputBufferSize = 8192;
constexpr int kDefaultInputBufferSize = 1024;
constexpr int kDefaultSampleRate = 48000;
constexpr int kDefaultChannelCount = 2;
AudioManagerPulse::AudioManagerPulse(std::unique_ptr<AudioThread> audio_thread,
AudioLogFactory* audio_log_factory,
pa_threaded_mainloop* pa_mainloop,
pa_context* pa_context)
: AudioManagerBase(std::move(audio_thread), audio_log_factory),
input_mainloop_(pa_mainloop),
input_context_(pa_context),
devices_(nullptr),
native_input_sample_rate_(kDefaultSampleRate),
native_channel_count_(kDefaultChannelCount),
default_source_is_monitor_(false) {
DCHECK(input_mainloop_);
DCHECK(input_context_);
SetMaxOutputStreamsAllowed(kMaxOutputStreams);
}
AudioManagerPulse::~AudioManagerPulse() = default;
void AudioManagerPulse::ShutdownOnAudioThread() {
AudioManagerBase::ShutdownOnAudioThread();
// The Pulse objects are the last things to be destroyed since
// AudioManagerBase::ShutdownOnAudioThread() needs them.
pulse::DestroyPulse(input_mainloop_.ExtractAsDangling(),
input_context_.ExtractAsDangling());
}
bool AudioManagerPulse::HasAudioOutputDevices() {
AudioDeviceNames devices;
GetAudioOutputDeviceNames(&devices);
return !devices.empty();
}
bool AudioManagerPulse::HasAudioInputDevices() {
AudioDeviceNames devices;
GetAudioInputDeviceNames(&devices);
return !devices.empty();
}
void AudioManagerPulse::GetAudioDeviceNames(
bool input, media::AudioDeviceNames* device_names) {
DCHECK(device_names->empty());
DCHECK(input_mainloop_);
DCHECK(input_context_);
AutoPulseLock auto_lock(input_mainloop_);
devices_ = device_names;
pa_operation* operation = NULL;
if (input) {
operation = pa_context_get_source_info_list(
input_context_, InputDevicesInfoCallback, this);
} else {
operation = pa_context_get_sink_info_list(
input_context_, OutputDevicesInfoCallback, this);
}
WaitForOperationCompletion(input_mainloop_, operation, input_context_);
// Prepend the default device if the list is not empty.
if (!device_names->empty())
device_names->push_front(AudioDeviceName::CreateDefault());
}
void AudioManagerPulse::GetAudioInputDeviceNames(
AudioDeviceNames* device_names) {
GetAudioDeviceNames(true, device_names);
}
void AudioManagerPulse::GetAudioOutputDeviceNames(
AudioDeviceNames* device_names) {
GetAudioDeviceNames(false, device_names);
}
AudioParameters AudioManagerPulse::GetInputStreamParameters(
const std::string& device_id) {
UpdateNativeAudioHardwareInfo();
{
AutoPulseLock auto_lock(input_mainloop_);
auto* operation = pa_context_get_source_info_by_name(
input_context_, default_source_name_.c_str(), DefaultSourceInfoCallback,
this);
WaitForOperationCompletion(input_mainloop_, operation, input_context_);
}
// We don't want to accidentally open a monitor device, so return invalid
// parameters for those. Note: The value of |default_source_is_monitor_|
// depends on the the call to pa_context_get_source_info_by_name() above.
if (device_id == AudioDeviceDescription::kDefaultDeviceId &&
default_source_is_monitor_) {
return AudioParameters();
}
const int user_buffer_size = GetUserBufferSize();
const int buffer_size =
user_buffer_size ? user_buffer_size : kDefaultInputBufferSize;
return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
ChannelLayoutConfig::Stereo(),
native_input_sample_rate_ ? native_input_sample_rate_
: kDefaultSampleRate,
buffer_size);
}
const char* AudioManagerPulse::GetName() {
return "PulseAudio";
}
AudioOutputStream* AudioManagerPulse::MakeLinearOutputStream(
const AudioParameters& params,
const LogCallback& log_callback) {
DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
return MakeOutputStream(params, AudioDeviceDescription::kDefaultDeviceId,
log_callback);
}
AudioOutputStream* AudioManagerPulse::MakeLowLatencyOutputStream(
const AudioParameters& params,
const std::string& device_id,
const LogCallback& log_callback) {
DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
return MakeOutputStream(
params,
device_id.empty() ? AudioDeviceDescription::kDefaultDeviceId : device_id,
log_callback);
}
AudioInputStream* AudioManagerPulse::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, log_callback);
}
AudioInputStream* AudioManagerPulse::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, log_callback);
}
std::string AudioManagerPulse::GetDefaultInputDeviceID() {
// Do not use the real default input device since it is a fallback
// device rather than a default device. Using the default input device
// reported by Pulse Audio prevents, for example, input redirection
// using the PULSE_SOURCE environment variable.
return AudioManagerBase::GetDefaultInputDeviceID();
}
std::string AudioManagerPulse::GetDefaultOutputDeviceID() {
// Do not use the real default output device since it is a fallback
// device rather than a default device. Using the default output device
// reported by Pulse Audio prevents, for example, output redirection
// using the PULSE_SINK environment variable.
return AudioManagerBase::GetDefaultOutputDeviceID();
}
std::string AudioManagerPulse::GetAssociatedOutputDeviceID(
const std::string& input_device_id) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
return AudioManagerBase::GetAssociatedOutputDeviceID(input_device_id);
#else
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
DCHECK(input_mainloop_);
DCHECK(input_context_);
if (input_device_id == AudioDeviceDescription::kDefaultDeviceId)
return std::string();
std::string input_bus =
pulse::GetBusOfInput(input_mainloop_, input_context_, input_device_id);
return input_bus.empty() ? std::string()
: pulse::GetOutputCorrespondingTo(
input_mainloop_, input_context_, input_bus);
#endif
}
AudioParameters AudioManagerPulse::GetPreferredOutputStreamParameters(
const std::string& output_device_id,
const AudioParameters& input_params) {
// TODO(tommi): Support |output_device_id|.
VLOG_IF(0, !output_device_id.empty()) << "Not implemented!";
int buffer_size = kMinimumOutputBufferSize;
// Query native parameters where applicable; Pulse does not require these to
// be respected though, so prefer the input parameters for channel count.
UpdateNativeAudioHardwareInfo();
int sample_rate = native_input_sample_rate_ ? native_input_sample_rate_
: kDefaultSampleRate;
ChannelLayoutConfig channel_layout_config = ChannelLayoutConfig::Guess(
native_channel_count_ ? native_channel_count_ : 2);
if (input_params.IsValid()) {
// Use the system's output channel count for the DISCRETE layout. This is to
// avoid a crash due to the lack of support on the multi-channel beyond 8 in
// the PulseAudio layer.
if (input_params.channel_layout() != CHANNEL_LAYOUT_DISCRETE)
channel_layout_config = input_params.channel_layout_config();
buffer_size =
std::min(kMaximumOutputBufferSize,
std::max(buffer_size, input_params.frames_per_buffer()));
}
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* AudioManagerPulse::MakeOutputStream(
const AudioParameters& params,
const std::string& device_id,
LogCallback log_callback) {
DCHECK(!device_id.empty());
return new PulseAudioOutputStream(params, device_id, this,
std::move(log_callback));
}
AudioInputStream* AudioManagerPulse::MakeInputStream(
const AudioParameters& params,
const std::string& device_id,
LogCallback log_callback) {
return new PulseAudioInputStream(this, device_id, params, input_mainloop_,
input_context_, std::move(log_callback));
}
void AudioManagerPulse::UpdateNativeAudioHardwareInfo() {
DCHECK(input_mainloop_);
DCHECK(input_context_);
AutoPulseLock auto_lock(input_mainloop_);
pa_operation* operation = pa_context_get_server_info(
input_context_, AudioHardwareInfoCallback, this);
WaitForOperationCompletion(input_mainloop_, operation, input_context_);
// Be careful about adding OS calls to this method.
// GetPreferredOutputStreamParameters() calls this method on a critical path.
// If the OS calls hang they will hang all device authorizations.
}
void AudioManagerPulse::InputDevicesInfoCallback(pa_context* context,
const pa_source_info* info,
int eol,
void* user_data) {
AudioManagerPulse* manager = reinterpret_cast<AudioManagerPulse*>(user_data);
if (eol) {
// Signal the pulse object that it is done.
pa_threaded_mainloop_signal(manager->input_mainloop_, 0);
return;
}
// Exclude output monitor (i.e. loopback) devices.
if (info->monitor_of_sink != PA_INVALID_INDEX)
return;
// If the device has ports, but none of them are available, skip it.
if (info->n_ports > 0) {
uint32_t port = 0;
for (; port != info->n_ports; ++port) {
if (info->ports[port]->available != PA_PORT_AVAILABLE_NO)
break;
}
if (port == info->n_ports)
return;
}
manager->devices_->push_back(AudioDeviceName(info->description, info->name));
}
void AudioManagerPulse::OutputDevicesInfoCallback(pa_context* context,
const pa_sink_info* info,
int eol,
void* user_data) {
AudioManagerPulse* manager = reinterpret_cast<AudioManagerPulse*>(user_data);
if (eol) {
// Signal the pulse object that it is done.
pa_threaded_mainloop_signal(manager->input_mainloop_, 0);
return;
}
manager->devices_->push_back(AudioDeviceName(info->description, info->name));
}
void AudioManagerPulse::AudioHardwareInfoCallback(pa_context* context,
const pa_server_info* info,
void* user_data) {
AudioManagerPulse* manager = reinterpret_cast<AudioManagerPulse*>(user_data);
manager->native_input_sample_rate_ = info->sample_spec.rate;
manager->native_channel_count_ = info->sample_spec.channels;
if (info->default_source_name)
manager->default_source_name_ = info->default_source_name;
pa_threaded_mainloop_signal(manager->input_mainloop_, 0);
}
void AudioManagerPulse::DefaultSourceInfoCallback(pa_context* context,
const pa_source_info* info,
int eol,
void* user_data) {
AudioManagerPulse* manager = reinterpret_cast<AudioManagerPulse*>(user_data);
if (eol) {
// Signal the pulse object that it is done.
pa_threaded_mainloop_signal(manager->input_mainloop_, 0);
return;
}
DCHECK(info);
manager->default_source_is_monitor_ =
info->monitor_of_sink != PA_INVALID_INDEX;
}
} // namespace media