#include "media/audio/pulse/pulse_util.h"
#include <stdint.h>
#include <string.h>
#include <memory>
#include <type_traits>
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/notreached.h"
#include "base/synchronization/waitable_event.h"
#include "build/branding_buildflags.h"
#include "media/audio/audio_device_description.h"
#include "media/base/audio_timestamp_helper.h"
#if defined(DLOPEN_PULSEAUDIO)
#include "media/audio/pulse/pulse_stubs.h"
using media_audio_pulse::kModulePulse;
using media_audio_pulse::InitializeStubs;
using media_audio_pulse::StubPathMap;
#endif
namespace media {
namespace pulse {
namespace {
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
constexpr char kBrowserDisplayName[] = "google-chrome";
#define PRODUCT_STRING "Google Chrome"
#else
constexpr char kBrowserDisplayName[] = "chromium-browser";
#define PRODUCT_STRING "Chromium"
#endif
#if defined(DLOPEN_PULSEAUDIO)
static const base::FilePath::CharType kPulseLib[] =
FILE_PATH_LITERAL("libpulse.so.0");
#endif
void DestroyMainloop(pa_threaded_mainloop* mainloop) {
pa_threaded_mainloop_stop(mainloop);
pa_threaded_mainloop_free(mainloop);
}
void DestroyContext(pa_context* context) {
pa_context_set_state_callback(context, nullptr, nullptr);
pa_context_disconnect(context);
pa_context_unref(context);
}
pa_channel_position ChromiumToPAChannelPosition(Channels channel) {
switch (channel) {
case LEFT:
return PA_CHANNEL_POSITION_FRONT_LEFT;
case RIGHT:
return PA_CHANNEL_POSITION_FRONT_RIGHT;
case CENTER:
return PA_CHANNEL_POSITION_FRONT_CENTER;
case LFE:
return PA_CHANNEL_POSITION_LFE;
case BACK_LEFT:
return PA_CHANNEL_POSITION_REAR_LEFT;
case BACK_RIGHT:
return PA_CHANNEL_POSITION_REAR_RIGHT;
case LEFT_OF_CENTER:
return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
case RIGHT_OF_CENTER:
return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
case BACK_CENTER:
return PA_CHANNEL_POSITION_REAR_CENTER;
case SIDE_LEFT:
return PA_CHANNEL_POSITION_SIDE_LEFT;
case SIDE_RIGHT:
return PA_CHANNEL_POSITION_SIDE_RIGHT;
default:
NOTREACHED() << "Invalid channel: " << channel;
}
}
class ScopedPropertyList {
public:
ScopedPropertyList() : property_list_(pa_proplist_new()) {}
ScopedPropertyList(const ScopedPropertyList&) = delete;
ScopedPropertyList& operator=(const ScopedPropertyList&) = delete;
pa_proplist* get() const { return property_list_.get(); }
private:
using deleter =
std::integral_constant<decltype(pa_proplist_free)*, pa_proplist_free>;
std::unique_ptr<pa_proplist, deleter> property_list_;
};
struct InputBusData {
InputBusData(pa_threaded_mainloop* loop, const std::string& name)
: loop_(loop), name_(name), bus_() {}
const raw_ptr<pa_threaded_mainloop> loop_;
const raw_ref<const std::string> name_;
std::string bus_;
};
struct OutputBusData {
OutputBusData(pa_threaded_mainloop* loop, const std::string& bus)
: loop_(loop), name_(), bus_(bus) {}
const raw_ptr<pa_threaded_mainloop> loop_;
std::string name_;
const raw_ref<const std::string> bus_;
};
void InputBusCallback(pa_context* context,
const pa_source_info* info,
int error,
void* user_data) {
InputBusData* data = static_cast<InputBusData*>(user_data);
if (error) {
pa_threaded_mainloop_signal(data->loop_, 0);
return;
}
if (info->name == *data->name_ &&
pa_proplist_contains(info->proplist, PA_PROP_DEVICE_BUS_PATH)) {
data->bus_ = pa_proplist_gets(info->proplist, PA_PROP_DEVICE_BUS_PATH);
}
}
void OutputBusCallback(pa_context* context,
const pa_sink_info* info,
int error,
void* user_data) {
OutputBusData* data = static_cast<OutputBusData*>(user_data);
if (error) {
pa_threaded_mainloop_signal(data->loop_, 0);
return;
}
if (pa_proplist_contains(info->proplist, PA_PROP_DEVICE_BUS_PATH) &&
pa_proplist_gets(info->proplist, PA_PROP_DEVICE_BUS_PATH) ==
*data->bus_) {
data->name_ = info->name;
}
}
struct DefaultDevicesData {
explicit DefaultDevicesData(pa_threaded_mainloop* loop) : loop_(loop) {}
std::string input_;
std::string output_;
const raw_ptr<pa_threaded_mainloop> loop_;
};
void GetDefaultDeviceIdCallback(pa_context* c,
const pa_server_info* info,
void* userdata) {
DefaultDevicesData* data = static_cast<DefaultDevicesData*>(userdata);
if (info->default_source_name)
data->input_ = info->default_source_name;
if (info->default_sink_name)
data->output_ = info->default_sink_name;
pa_threaded_mainloop_signal(data->loop_, 0);
}
struct MonitorSourceData {
explicit MonitorSourceData(pa_threaded_mainloop* loop) : loop_(loop) {}
const raw_ptr<pa_threaded_mainloop> loop_;
std::string monitor_source_name_;
};
void GetMonitorSourceNameForSinkCallback(pa_context* context,
const pa_sink_info* info,
int eol,
void* userdata) {
MonitorSourceData* data = static_cast<MonitorSourceData*>(userdata);
if (!eol) {
data->monitor_source_name_ = info->monitor_source_name;
}
pa_threaded_mainloop_signal(data->loop_, 0);
}
struct ContextStartupData {
raw_ptr<base::WaitableEvent> context_wait;
raw_ptr<pa_threaded_mainloop, DanglingUntriaged> pa_mainloop;
};
void SignalReadyOrErrorStateCallback(pa_context* context, void* context_data) {
auto context_state = pa_context_get_state(context);
auto* data = static_cast<ContextStartupData*>(context_data);
if (!PA_CONTEXT_IS_GOOD(context_state) || context_state == PA_CONTEXT_READY)
data->context_wait->Signal();
pa_threaded_mainloop_signal(data->pa_mainloop, 0);
}
}
bool InitPulse(pa_threaded_mainloop** mainloop, pa_context** context) {
#if defined(DLOPEN_PULSEAUDIO)
StubPathMap paths;
paths[kModulePulse].push_back(kPulseLib);
if (!InitializeStubs(paths)) {
VLOG(1) << "Failed on loading the Pulse library and symbols";
return false;
}
#endif
pa_threaded_mainloop* pa_mainloop = pa_threaded_mainloop_new();
if (!pa_mainloop)
return false;
pa_mainloop_api* pa_mainloop_api = pa_threaded_mainloop_get_api(pa_mainloop);
pa_context* pa_context =
pa_context_new(pa_mainloop_api, PRODUCT_STRING " input");
if (!pa_context) {
pa_threaded_mainloop_free(pa_mainloop);
return false;
}
base::WaitableEvent context_wait;
ContextStartupData data = {&context_wait, pa_mainloop};
pa_context_set_state_callback(pa_context, &SignalReadyOrErrorStateCallback,
&data);
if (pa_context_connect(pa_context, nullptr, PA_CONTEXT_NOAUTOSPAWN,
nullptr)) {
VLOG(1) << "Failed to connect to the context. Error: "
<< pa_strerror(pa_context_errno(pa_context));
DestroyContext(pa_context);
data = {nullptr, nullptr};
pa_threaded_mainloop_free(pa_mainloop);
return false;
}
auto mainloop_lock = std::make_unique<AutoPulseLock>(pa_mainloop);
if (pa_threaded_mainloop_start(pa_mainloop)) {
DestroyContext(pa_context);
mainloop_lock.reset();
data = {nullptr, nullptr};
DestroyMainloop(pa_mainloop);
return false;
}
mainloop_lock.reset();
constexpr base::TimeDelta kStartupTimeout = base::Seconds(5);
const bool was_signaled = context_wait.TimedWait(kStartupTimeout);
mainloop_lock = std::make_unique<AutoPulseLock>(pa_mainloop);
auto context_state = pa_context_get_state(pa_context);
if (context_state != PA_CONTEXT_READY) {
if (!was_signaled)
VLOG(1) << "Timed out trying to connect to PulseAudio.";
else
VLOG(1) << "Failed to connect to PulseAudio: " << context_state;
DestroyContext(pa_context);
mainloop_lock.reset();
data = {nullptr, nullptr};
DestroyMainloop(pa_mainloop);
return false;
}
pa_context_set_state_callback(pa_context, &pulse::ContextStateCallback,
pa_mainloop);
*mainloop = pa_mainloop;
*context = pa_context;
return true;
}
void DestroyPulse(pa_threaded_mainloop* mainloop, pa_context* context) {
DCHECK(mainloop);
DCHECK(context);
{
AutoPulseLock auto_lock(mainloop);
DestroyContext(context);
}
DestroyMainloop(mainloop);
}
void StreamSuccessCallback(pa_stream* s, int error, void* mainloop) {
pa_threaded_mainloop* pa_mainloop =
static_cast<pa_threaded_mainloop*>(mainloop);
pa_threaded_mainloop_signal(pa_mainloop, 0);
}
void ContextSuccessCallback(pa_context* context, int success, void* mainloop) {
pa_threaded_mainloop* pa_mainloop =
static_cast<pa_threaded_mainloop*>(mainloop);
if (!success) {
LOG(ERROR) << "Context operation failed.";
}
pa_threaded_mainloop_signal(pa_mainloop, 0);
}
void ContextStateCallback(pa_context* context, void* mainloop) {
pa_threaded_mainloop* pa_mainloop =
static_cast<pa_threaded_mainloop*>(mainloop);
pa_threaded_mainloop_signal(pa_mainloop, 0);
}
pa_channel_map ChannelLayoutToPAChannelMap(ChannelLayout channel_layout) {
pa_channel_map channel_map;
if (channel_layout == CHANNEL_LAYOUT_MONO) {
pa_channel_map_init_mono(&channel_map);
} else {
pa_channel_map_init(&channel_map);
channel_map.channels = ChannelLayoutToChannelCount(channel_layout);
base::span channel_map_span(channel_map.map);
for (Channels ch = LEFT; ch <= CHANNELS_MAX;
ch = static_cast<Channels>(ch + 1)) {
int channel_index = ChannelOrder(channel_layout, ch);
if (channel_index < 0)
continue;
channel_map_span[channel_index] = ChromiumToPAChannelPosition(ch);
}
}
return channel_map;
}
bool WaitForOperationCompletion(pa_threaded_mainloop* mainloop,
pa_operation* operation,
pa_context* optional_context,
pa_stream* optional_stream) {
if (!operation) {
LOG(ERROR) << "pa_operation is nullptr.";
return false;
}
while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING) {
if (optional_context) {
pa_context_state_t context_state = pa_context_get_state(optional_context);
if (!PA_CONTEXT_IS_GOOD(context_state)) {
LOG(ERROR) << "pa_context went bad while waiting: state="
<< context_state << ", error="
<< pa_strerror(pa_context_errno(optional_context));
pa_operation_cancel(operation);
pa_operation_unref(operation);
return false;
}
}
if (optional_stream) {
pa_stream_state_t stream_state = pa_stream_get_state(optional_stream);
if (!PA_STREAM_IS_GOOD(stream_state)) {
LOG(ERROR) << "pa_stream went bad while waiting: " << stream_state;
pa_operation_cancel(operation);
pa_operation_unref(operation);
return false;
}
}
pa_threaded_mainloop_wait(mainloop);
}
pa_operation_unref(operation);
return true;
}
base::TimeDelta GetHardwareLatency(pa_stream* stream) {
DCHECK(stream);
int negative = 0;
pa_usec_t latency_micros = 0;
if (pa_stream_get_latency(stream, &latency_micros, &negative) != 0)
return base::TimeDelta();
if (negative)
return base::TimeDelta();
return base::Microseconds(latency_micros);
}
#define RETURN_ON_FAILURE(expression, message) do { \
if (!(expression)) { \
DLOG(ERROR) << message; \
return false; \
} \
} while (0)
bool CreateInputStream(pa_threaded_mainloop* mainloop,
pa_context* context,
raw_ptr<pa_stream>* stream,
const AudioParameters& params,
const std::string& device_id,
pa_stream_notify_cb_t stream_callback,
void* user_data) {
DCHECK(mainloop);
DCHECK(context);
pa_sample_spec sample_specifications;
static_assert(kInputSampleFormat == kSampleFormatS16,
"Only 16-bit input supported.");
sample_specifications.format = PA_SAMPLE_S16LE;
sample_specifications.rate = params.sample_rate();
sample_specifications.channels = params.channels();
pa_channel_map source_channel_map = ChannelLayoutToPAChannelMap(
params.channel_layout());
pa_channel_map* map =
(source_channel_map.channels != 0) ? &source_channel_map : nullptr;
ScopedPropertyList property_list;
pa_proplist_sets(property_list.get(), PA_PROP_APPLICATION_ICON_NAME,
kBrowserDisplayName);
*stream = pa_stream_new_with_proplist(context, "RecordStream",
&sample_specifications, map,
property_list.get());
RETURN_ON_FAILURE(*stream, "failed to create PA recording stream");
pa_stream_set_state_callback(*stream, stream_callback, user_data);
pa_buffer_attr buffer_attributes;
const unsigned int buffer_size = params.GetBytesPerBuffer(kInputSampleFormat);
buffer_attributes.maxlength = static_cast<uint32_t>(-1);
buffer_attributes.tlength = buffer_size;
buffer_attributes.minreq = buffer_size;
buffer_attributes.prebuf = static_cast<uint32_t>(-1);
buffer_attributes.fragsize = buffer_size;
int flags = PA_STREAM_AUTO_TIMING_UPDATE |
PA_STREAM_INTERPOLATE_TIMING |
PA_STREAM_ADJUST_LATENCY |
PA_STREAM_START_CORKED;
RETURN_ON_FAILURE(
pa_stream_connect_record(
*stream,
device_id == AudioDeviceDescription::kDefaultDeviceId
? nullptr
: device_id.c_str(),
&buffer_attributes, static_cast<pa_stream_flags_t>(flags)) == 0,
"pa_stream_connect_record FAILED ");
while (true) {
pa_stream_state_t stream_state = pa_stream_get_state(*stream);
RETURN_ON_FAILURE(
PA_STREAM_IS_GOOD(stream_state), "Invalid PulseAudio stream state");
if (stream_state == PA_STREAM_READY)
break;
pa_threaded_mainloop_wait(mainloop);
}
return true;
}
bool CreateOutputStream(raw_ptr<pa_threaded_mainloop>* mainloop,
raw_ptr<pa_context>* context,
raw_ptr<pa_stream>* stream,
const AudioParameters& params,
const std::string& device_id,
const std::string& app_name,
pa_stream_notify_cb_t stream_callback,
pa_stream_request_cb_t write_callback,
void* user_data) {
DCHECK(!*mainloop);
DCHECK(!*context);
*mainloop = pa_threaded_mainloop_new();
RETURN_ON_FAILURE(*mainloop, "Failed to create PulseAudio main loop.");
pa_mainloop_api* pa_mainloop_api = pa_threaded_mainloop_get_api(*mainloop);
*context = pa_context_new(
pa_mainloop_api, app_name.empty() ? PRODUCT_STRING : app_name.c_str());
RETURN_ON_FAILURE(*context, "Failed to create PulseAudio context.");
pa_context_set_state_callback(*context, &ContextStateCallback, *mainloop);
AutoPulseLock auto_lock(*mainloop);
RETURN_ON_FAILURE(pa_threaded_mainloop_start(*mainloop) == 0,
"Failed to start PulseAudio main loop.");
RETURN_ON_FAILURE(pa_context_connect(*context, nullptr,
PA_CONTEXT_NOAUTOSPAWN, nullptr) == 0,
"Failed to connect PulseAudio context.");
while (true) {
pa_context_state_t context_state = pa_context_get_state(*context);
RETURN_ON_FAILURE(PA_CONTEXT_IS_GOOD(context_state),
"Invalid PulseAudio context state.");
if (context_state == PA_CONTEXT_READY)
break;
pa_threaded_mainloop_wait(*mainloop);
}
pa_sample_spec sample_specifications;
sample_specifications.format = PA_SAMPLE_FLOAT32;
sample_specifications.rate = params.sample_rate();
sample_specifications.channels = params.channels();
pa_channel_map* map = nullptr;
pa_channel_map source_channel_map = ChannelLayoutToPAChannelMap(
params.channel_layout());
if (source_channel_map.channels != 0) {
map = &source_channel_map;
}
ScopedPropertyList property_list;
pa_proplist_sets(property_list.get(), PA_PROP_APPLICATION_ICON_NAME,
kBrowserDisplayName);
*stream = pa_stream_new_with_proplist(
*context, "Playback", &sample_specifications, map, property_list.get());
RETURN_ON_FAILURE(*stream, "failed to create PA playback stream");
pa_stream_set_state_callback(*stream, stream_callback, user_data);
pa_stream_set_write_callback(*stream, write_callback, user_data);
size_t buffer_size = params.GetBytesPerBuffer(kSampleFormatF32);
pa_buffer_attr pa_buffer_attributes;
pa_buffer_attributes.maxlength = static_cast<uint32_t>(-1);
pa_buffer_attributes.minreq = buffer_size / 2;
pa_buffer_attributes.prebuf = static_cast<uint32_t>(-1);
pa_buffer_attributes.tlength = buffer_size * 3;
pa_buffer_attributes.fragsize = static_cast<uint32_t>(-1);
RETURN_ON_FAILURE(
pa_stream_connect_playback(
*stream,
device_id == AudioDeviceDescription::kDefaultDeviceId
? nullptr
: device_id.c_str(),
&pa_buffer_attributes,
static_cast<pa_stream_flags_t>(
PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY |
PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_NOT_MONOTONIC |
PA_STREAM_START_CORKED),
nullptr, nullptr) == 0,
"pa_stream_connect_playback FAILED ");
while (true) {
pa_stream_state_t stream_state = pa_stream_get_state(*stream);
RETURN_ON_FAILURE(
PA_STREAM_IS_GOOD(stream_state), "Invalid PulseAudio stream state");
if (stream_state == PA_STREAM_READY)
break;
pa_threaded_mainloop_wait(*mainloop);
}
return true;
}
void MuteAllSinksExcept(pa_threaded_mainloop* mainloop,
pa_context* context,
const std::string& exclude_sink_name) {
CHECK(mainloop);
CHECK(context);
AutoPulseLock lock(mainloop);
pa_operation* op = pa_context_get_sink_info_list(
context,
[](pa_context* c, const pa_sink_info* i, int eol, void* userdata) {
if (eol != 0) {
return;
}
if (!eol) {
std::string* exclude_sink_name = static_cast<std::string*>(userdata);
if (i->name != *exclude_sink_name) {
pa_context_set_sink_mute_by_index(
c, i->index, 1, nullptr,
nullptr);
}
}
},
(void*)&exclude_sink_name);
WaitForOperationCompletion(mainloop, op, context);
if (op) {
pa_operation_unref(op);
}
}
void UnmuteAllSinks(pa_threaded_mainloop* mainloop, pa_context* context) {
CHECK(mainloop);
CHECK(context);
AutoPulseLock lock(mainloop);
pa_operation* op = pa_context_get_sink_info_list(
context,
[](pa_context* c, const pa_sink_info* i, int eol, void* userdata) {
if (eol != 0) {
return;
}
pa_operation* unmute_op = pa_context_set_sink_mute_by_index(
c, i->index, 0,
[](pa_context* c, int success, void* userdata) {
pa_threaded_mainloop_signal((pa_threaded_mainloop*)userdata, 0);
},
userdata
);
if (unmute_op) {
pa_operation_unref(unmute_op);
}
},
mainloop
);
WaitForOperationCompletion(mainloop, op, context);
if (op) {
pa_operation_unref(op);
}
}
std::string GetBusOfInput(pa_threaded_mainloop* mainloop,
pa_context* context,
const std::string& name) {
DCHECK(mainloop);
DCHECK(context);
AutoPulseLock auto_lock(mainloop);
InputBusData data(mainloop, name);
pa_operation* operation =
pa_context_get_source_info_list(context, InputBusCallback, &data);
WaitForOperationCompletion(mainloop, operation, context);
return data.bus_;
}
std::string GetOutputCorrespondingTo(pa_threaded_mainloop* mainloop,
pa_context* context,
const std::string& bus) {
DCHECK(mainloop);
DCHECK(context);
AutoPulseLock auto_lock(mainloop);
OutputBusData data(mainloop, bus);
pa_operation* operation =
pa_context_get_sink_info_list(context, OutputBusCallback, &data);
WaitForOperationCompletion(mainloop, operation, context);
return data.name_;
}
std::string GetRealDefaultDeviceId(pa_threaded_mainloop* mainloop,
pa_context* context,
RequestType type) {
DCHECK(mainloop);
DCHECK(context);
AutoPulseLock auto_lock(mainloop);
DefaultDevicesData data(mainloop);
pa_operation* operation =
pa_context_get_server_info(context, &GetDefaultDeviceIdCallback, &data);
WaitForOperationCompletion(mainloop, operation, context);
return (type == RequestType::INPUT) ? data.input_ : data.output_;
}
std::string GetMonitorSourceNameForSink(pa_threaded_mainloop* mainloop,
pa_context* context,
const std::string& sink_name) {
CHECK(mainloop);
CHECK(context);
CHECK(!sink_name.empty());
AutoPulseLock auto_lock(mainloop);
MonitorSourceData data(mainloop);
pa_operation* operation = pa_context_get_sink_info_by_name(
context, sink_name.c_str(), &GetMonitorSourceNameForSinkCallback, &data);
WaitForOperationCompletion(mainloop, operation, context);
return data.monitor_source_name_;
}
#undef RETURN_ON_FAILURE
}
}