2015-07-07 09:55:32 +00:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2015 Andrew Kelley
|
|
|
|
*
|
|
|
|
* This file is part of libsoundio, which is MIT licensed.
|
|
|
|
* See http://opensource.org/licenses/MIT
|
|
|
|
*/
|
|
|
|
|
2015-07-01 08:02:44 +00:00
|
|
|
#include "pulseaudio.hpp"
|
|
|
|
#include "soundio.hpp"
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
#include <math.h>
|
2015-07-21 05:55:30 +00:00
|
|
|
#include <stdio.h>
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-01 09:37:51 +00:00
|
|
|
|
2015-07-01 08:02:44 +00:00
|
|
|
static void subscribe_callback(pa_context *context,
|
|
|
|
pa_subscription_event_type_t event_bits, uint32_t index, void *userdata)
|
|
|
|
{
|
2015-07-13 16:17:20 +00:00
|
|
|
SoundIoPrivate *si = (SoundIoPrivate *)userdata;
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-04 21:20:52 +00:00
|
|
|
sipa->device_scan_queued = true;
|
|
|
|
pa_threaded_mainloop_signal(sipa->main_loop, 0);
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
|
2015-07-30 07:46:13 +00:00
|
|
|
static int subscribe_to_events(SoundIoPrivate *si) {
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-01 08:02:44 +00:00
|
|
|
pa_subscription_mask_t events = (pa_subscription_mask_t)(
|
|
|
|
PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE|PA_SUBSCRIPTION_MASK_SERVER);
|
2015-07-30 07:46:13 +00:00
|
|
|
pa_operation *subscribe_op = pa_context_subscribe(sipa->pulse_context, events, nullptr, si);
|
2015-07-01 08:02:44 +00:00
|
|
|
if (!subscribe_op)
|
2015-07-30 07:46:13 +00:00
|
|
|
return SoundIoErrorNoMem;
|
2015-07-01 08:02:44 +00:00
|
|
|
pa_operation_unref(subscribe_op);
|
2015-07-30 07:46:13 +00:00
|
|
|
return 0;
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void context_state_callback(pa_context *context, void *userdata) {
|
2015-07-13 16:17:20 +00:00
|
|
|
SoundIoPrivate *si = (SoundIoPrivate *)userdata;
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-01 08:02:44 +00:00
|
|
|
|
|
|
|
switch (pa_context_get_state(context)) {
|
|
|
|
case PA_CONTEXT_UNCONNECTED: // The context hasn't been connected yet.
|
|
|
|
return;
|
|
|
|
case PA_CONTEXT_CONNECTING: // A connection is being established.
|
|
|
|
return;
|
|
|
|
case PA_CONTEXT_AUTHORIZING: // The client is authorizing itself to the daemon.
|
|
|
|
return;
|
|
|
|
case PA_CONTEXT_SETTING_NAME: // The client is passing its application name to the daemon.
|
|
|
|
return;
|
|
|
|
case PA_CONTEXT_READY: // The connection is established, the context is ready to execute operations.
|
2015-07-04 21:20:52 +00:00
|
|
|
sipa->ready_flag = true;
|
|
|
|
pa_threaded_mainloop_signal(sipa->main_loop, 0);
|
2015-07-01 08:02:44 +00:00
|
|
|
return;
|
|
|
|
case PA_CONTEXT_TERMINATED: // The connection was terminated cleanly.
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_signal(sipa->main_loop, 0);
|
2015-07-01 08:02:44 +00:00
|
|
|
return;
|
|
|
|
case PA_CONTEXT_FAILED: // The connection failed or was disconnected.
|
2015-07-30 07:46:13 +00:00
|
|
|
sipa->connection_err = SoundIoErrorInitAudioBackend;
|
|
|
|
sipa->ready_flag = true;
|
|
|
|
pa_threaded_mainloop_signal(sipa->main_loop, 0);
|
|
|
|
return;
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
static void destroy_pa(SoundIoPrivate *si) {
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-01 09:37:51 +00:00
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
if (sipa->main_loop)
|
|
|
|
pa_threaded_mainloop_stop(sipa->main_loop);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-07 09:55:32 +00:00
|
|
|
soundio_destroy_devices_info(sipa->current_devices_info);
|
|
|
|
soundio_destroy_devices_info(sipa->ready_devices_info);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_context_disconnect(sipa->pulse_context);
|
|
|
|
pa_context_unref(sipa->pulse_context);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
if (sipa->main_loop)
|
|
|
|
pa_threaded_mainloop_free(sipa->main_loop);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
if (sipa->props)
|
|
|
|
pa_proplist_free(sipa->props);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
free(sipa->default_sink_name);
|
|
|
|
free(sipa->default_source_name);
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
|
2015-07-21 05:55:30 +00:00
|
|
|
static SoundIoFormat from_pulseaudio_format(pa_sample_spec sample_spec) {
|
2015-07-01 08:02:44 +00:00
|
|
|
switch (sample_spec.format) {
|
2015-07-10 09:21:47 +00:00
|
|
|
case PA_SAMPLE_U8: return SoundIoFormatU8;
|
|
|
|
case PA_SAMPLE_S16LE: return SoundIoFormatS16LE;
|
|
|
|
case PA_SAMPLE_S16BE: return SoundIoFormatS16BE;
|
|
|
|
case PA_SAMPLE_FLOAT32LE: return SoundIoFormatFloat32LE;
|
|
|
|
case PA_SAMPLE_FLOAT32BE: return SoundIoFormatFloat32BE;
|
|
|
|
case PA_SAMPLE_S32LE: return SoundIoFormatS32LE;
|
|
|
|
case PA_SAMPLE_S32BE: return SoundIoFormatS32BE;
|
|
|
|
case PA_SAMPLE_S24_32LE: return SoundIoFormatS24LE;
|
|
|
|
case PA_SAMPLE_S24_32BE: return SoundIoFormatS24BE;
|
2015-07-10 07:46:03 +00:00
|
|
|
|
|
|
|
case PA_SAMPLE_MAX:
|
|
|
|
case PA_SAMPLE_INVALID:
|
|
|
|
case PA_SAMPLE_ALAW:
|
|
|
|
case PA_SAMPLE_ULAW:
|
|
|
|
case PA_SAMPLE_S24LE:
|
|
|
|
case PA_SAMPLE_S24BE:
|
2015-07-10 09:21:47 +00:00
|
|
|
return SoundIoFormatInvalid;
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
2015-07-10 09:21:47 +00:00
|
|
|
return SoundIoFormatInvalid;
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static SoundIoChannelId from_pulseaudio_channel_pos(pa_channel_position_t pos) {
|
|
|
|
switch (pos) {
|
|
|
|
case PA_CHANNEL_POSITION_MONO: return SoundIoChannelIdFrontCenter;
|
|
|
|
case PA_CHANNEL_POSITION_FRONT_LEFT: return SoundIoChannelIdFrontLeft;
|
|
|
|
case PA_CHANNEL_POSITION_FRONT_RIGHT: return SoundIoChannelIdFrontRight;
|
|
|
|
case PA_CHANNEL_POSITION_FRONT_CENTER: return SoundIoChannelIdFrontCenter;
|
|
|
|
case PA_CHANNEL_POSITION_REAR_CENTER: return SoundIoChannelIdBackCenter;
|
|
|
|
case PA_CHANNEL_POSITION_REAR_LEFT: return SoundIoChannelIdBackLeft;
|
|
|
|
case PA_CHANNEL_POSITION_REAR_RIGHT: return SoundIoChannelIdBackRight;
|
2015-07-10 06:35:58 +00:00
|
|
|
case PA_CHANNEL_POSITION_LFE: return SoundIoChannelIdLfe;
|
|
|
|
case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: return SoundIoChannelIdFrontLeftCenter;
|
|
|
|
case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: return SoundIoChannelIdFrontRightCenter;
|
2015-07-01 08:02:44 +00:00
|
|
|
case PA_CHANNEL_POSITION_SIDE_LEFT: return SoundIoChannelIdSideLeft;
|
|
|
|
case PA_CHANNEL_POSITION_SIDE_RIGHT: return SoundIoChannelIdSideRight;
|
|
|
|
case PA_CHANNEL_POSITION_TOP_CENTER: return SoundIoChannelIdTopCenter;
|
|
|
|
case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: return SoundIoChannelIdTopFrontLeft;
|
|
|
|
case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: return SoundIoChannelIdTopFrontRight;
|
|
|
|
case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: return SoundIoChannelIdTopFrontCenter;
|
|
|
|
case PA_CHANNEL_POSITION_TOP_REAR_LEFT: return SoundIoChannelIdTopBackLeft;
|
|
|
|
case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: return SoundIoChannelIdTopBackRight;
|
|
|
|
case PA_CHANNEL_POSITION_TOP_REAR_CENTER: return SoundIoChannelIdTopBackCenter;
|
|
|
|
|
2015-08-02 05:40:55 +00:00
|
|
|
case PA_CHANNEL_POSITION_AUX0: return SoundIoChannelIdAux0;
|
|
|
|
case PA_CHANNEL_POSITION_AUX1: return SoundIoChannelIdAux1;
|
|
|
|
case PA_CHANNEL_POSITION_AUX2: return SoundIoChannelIdAux2;
|
|
|
|
case PA_CHANNEL_POSITION_AUX3: return SoundIoChannelIdAux3;
|
|
|
|
case PA_CHANNEL_POSITION_AUX4: return SoundIoChannelIdAux4;
|
|
|
|
case PA_CHANNEL_POSITION_AUX5: return SoundIoChannelIdAux5;
|
|
|
|
case PA_CHANNEL_POSITION_AUX6: return SoundIoChannelIdAux6;
|
|
|
|
case PA_CHANNEL_POSITION_AUX7: return SoundIoChannelIdAux7;
|
|
|
|
case PA_CHANNEL_POSITION_AUX8: return SoundIoChannelIdAux8;
|
|
|
|
case PA_CHANNEL_POSITION_AUX9: return SoundIoChannelIdAux9;
|
|
|
|
case PA_CHANNEL_POSITION_AUX10: return SoundIoChannelIdAux10;
|
|
|
|
case PA_CHANNEL_POSITION_AUX11: return SoundIoChannelIdAux11;
|
|
|
|
case PA_CHANNEL_POSITION_AUX12: return SoundIoChannelIdAux12;
|
|
|
|
case PA_CHANNEL_POSITION_AUX13: return SoundIoChannelIdAux13;
|
|
|
|
case PA_CHANNEL_POSITION_AUX14: return SoundIoChannelIdAux14;
|
|
|
|
case PA_CHANNEL_POSITION_AUX15: return SoundIoChannelIdAux15;
|
|
|
|
|
2015-07-30 07:46:13 +00:00
|
|
|
default: return SoundIoChannelIdInvalid;
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void set_from_pulseaudio_channel_map(pa_channel_map channel_map, SoundIoChannelLayout *channel_layout) {
|
|
|
|
channel_layout->channel_count = channel_map.channels;
|
|
|
|
for (int i = 0; i < channel_map.channels; i += 1) {
|
|
|
|
channel_layout->channels[i] = from_pulseaudio_channel_pos(channel_map.map[i]);
|
|
|
|
}
|
|
|
|
channel_layout->name = nullptr;
|
|
|
|
int builtin_layout_count = soundio_channel_layout_builtin_count();
|
|
|
|
for (int i = 0; i < builtin_layout_count; i += 1) {
|
|
|
|
const SoundIoChannelLayout *builtin_layout = soundio_channel_layout_get_builtin(i);
|
|
|
|
if (soundio_channel_layout_equal(builtin_layout, channel_layout)) {
|
|
|
|
channel_layout->name = builtin_layout->name;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-07-21 05:55:30 +00:00
|
|
|
|
|
|
|
static int set_all_device_channel_layouts(SoundIoDevice *device) {
|
|
|
|
device->layout_count = soundio_channel_layout_builtin_count();
|
|
|
|
device->layouts = allocate<SoundIoChannelLayout>(device->layout_count);
|
|
|
|
if (!device->layouts)
|
|
|
|
return SoundIoErrorNoMem;
|
|
|
|
for (int i = 0; i < device->layout_count; i += 1)
|
|
|
|
device->layouts[i] = *soundio_channel_layout_get_builtin(i);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int set_all_device_formats(SoundIoDevice *device) {
|
|
|
|
device->format_count = 9;
|
|
|
|
device->formats = allocate<SoundIoFormat>(device->format_count);
|
|
|
|
if (!device->formats)
|
|
|
|
return SoundIoErrorNoMem;
|
|
|
|
device->formats[0] = SoundIoFormatU8;
|
|
|
|
device->formats[1] = SoundIoFormatS16LE;
|
|
|
|
device->formats[2] = SoundIoFormatS16BE;
|
|
|
|
device->formats[3] = SoundIoFormatFloat32LE;
|
|
|
|
device->formats[4] = SoundIoFormatFloat32BE;
|
|
|
|
device->formats[5] = SoundIoFormatS32LE;
|
|
|
|
device->formats[6] = SoundIoFormatS32BE;
|
|
|
|
device->formats[7] = SoundIoFormatS24LE;
|
|
|
|
device->formats[8] = SoundIoFormatS24BE;
|
|
|
|
return 0;
|
|
|
|
}
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
static int perform_operation(SoundIoPrivate *si, pa_operation *op) {
|
2015-07-30 07:46:13 +00:00
|
|
|
if (!op)
|
|
|
|
return SoundIoErrorNoMem;
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-01 08:02:44 +00:00
|
|
|
for (;;) {
|
|
|
|
switch (pa_operation_get_state(op)) {
|
|
|
|
case PA_OPERATION_RUNNING:
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_wait(sipa->main_loop);
|
2015-07-01 08:02:44 +00:00
|
|
|
continue;
|
|
|
|
case PA_OPERATION_DONE:
|
|
|
|
pa_operation_unref(op);
|
|
|
|
return 0;
|
|
|
|
case PA_OPERATION_CANCELLED:
|
|
|
|
pa_operation_unref(op);
|
2015-07-30 07:46:13 +00:00
|
|
|
return SoundIoErrorInterrupted;
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
static void finish_device_query(SoundIoPrivate *si) {
|
|
|
|
SoundIo *soundio = &si->pub;
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
if (!sipa->have_sink_list ||
|
|
|
|
!sipa->have_source_list ||
|
|
|
|
!sipa->have_default_sink)
|
2015-07-01 08:02:44 +00:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-07-30 07:46:13 +00:00
|
|
|
if (sipa->device_query_err) {
|
|
|
|
sipa->device_scan_queued.store(true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-07-01 08:02:44 +00:00
|
|
|
// based on the default sink name, figure out the default output index
|
2015-07-22 19:07:20 +00:00
|
|
|
// if the name doesn't match just pick the first one. if there are no
|
|
|
|
// devices then we need to set it to -1.
|
2015-07-04 21:20:52 +00:00
|
|
|
sipa->current_devices_info->default_output_index = -1;
|
|
|
|
sipa->current_devices_info->default_input_index = -1;
|
2015-07-22 19:07:20 +00:00
|
|
|
|
|
|
|
if (sipa->current_devices_info->input_devices.length > 0) {
|
|
|
|
sipa->current_devices_info->default_input_index = 0;
|
|
|
|
for (int i = 0; i < sipa->current_devices_info->input_devices.length; i += 1) {
|
|
|
|
SoundIoDevice *device = sipa->current_devices_info->input_devices.at(i);
|
2015-07-30 17:39:10 +00:00
|
|
|
assert(device->aim == SoundIoDeviceAimInput);
|
2015-07-30 17:26:36 +00:00
|
|
|
if (strcmp(device->id, sipa->default_source_name) == 0) {
|
2015-07-22 19:07:20 +00:00
|
|
|
sipa->current_devices_info->default_input_index = i;
|
|
|
|
}
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
}
|
2015-07-22 19:07:20 +00:00
|
|
|
|
|
|
|
if (sipa->current_devices_info->output_devices.length > 0) {
|
|
|
|
sipa->current_devices_info->default_output_index = 0;
|
|
|
|
for (int i = 0; i < sipa->current_devices_info->output_devices.length; i += 1) {
|
|
|
|
SoundIoDevice *device = sipa->current_devices_info->output_devices.at(i);
|
2015-07-30 17:39:10 +00:00
|
|
|
assert(device->aim == SoundIoDeviceAimOutput);
|
2015-07-30 17:26:36 +00:00
|
|
|
if (strcmp(device->id, sipa->default_sink_name) == 0) {
|
2015-07-22 19:07:20 +00:00
|
|
|
sipa->current_devices_info->default_output_index = i;
|
|
|
|
}
|
2015-07-01 08:20:26 +00:00
|
|
|
}
|
|
|
|
}
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-07 09:55:32 +00:00
|
|
|
soundio_destroy_devices_info(sipa->ready_devices_info);
|
2015-07-04 21:20:52 +00:00
|
|
|
sipa->ready_devices_info = sipa->current_devices_info;
|
|
|
|
sipa->current_devices_info = NULL;
|
|
|
|
sipa->have_devices_flag = true;
|
|
|
|
pa_threaded_mainloop_signal(sipa->main_loop, 0);
|
2015-07-01 08:02:44 +00:00
|
|
|
soundio->on_events_signal(soundio);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sink_info_callback(pa_context *pulse_context, const pa_sink_info *info, int eol, void *userdata) {
|
2015-07-13 16:17:20 +00:00
|
|
|
SoundIoPrivate *si = (SoundIoPrivate *)userdata;
|
|
|
|
SoundIo *soundio = &si->pub;
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-21 05:55:30 +00:00
|
|
|
int err;
|
2015-07-01 08:02:44 +00:00
|
|
|
if (eol) {
|
2015-07-04 21:20:52 +00:00
|
|
|
sipa->have_sink_list = true;
|
2015-07-13 16:17:20 +00:00
|
|
|
finish_device_query(si);
|
2015-07-30 07:46:13 +00:00
|
|
|
} else if (!sipa->device_query_err) {
|
2015-08-04 18:03:19 +00:00
|
|
|
SoundIoDevicePrivate *dev = allocate<SoundIoDevicePrivate>(1);
|
2015-07-30 07:46:13 +00:00
|
|
|
if (!dev) {
|
|
|
|
sipa->device_query_err = SoundIoErrorNoMem;
|
|
|
|
pa_threaded_mainloop_signal(sipa->main_loop, 0);
|
|
|
|
return;
|
|
|
|
}
|
2015-07-28 18:28:07 +00:00
|
|
|
SoundIoDevice *device = &dev->pub;
|
2015-07-01 08:02:44 +00:00
|
|
|
|
|
|
|
device->ref_count = 1;
|
|
|
|
device->soundio = soundio;
|
2015-07-30 17:26:36 +00:00
|
|
|
device->id = strdup(info->name);
|
|
|
|
device->name = strdup(info->description);
|
|
|
|
if (!device->id || !device->name) {
|
2015-07-30 07:46:13 +00:00
|
|
|
soundio_device_unref(device);
|
|
|
|
sipa->device_query_err = SoundIoErrorNoMem;
|
|
|
|
pa_threaded_mainloop_signal(sipa->main_loop, 0);
|
|
|
|
return;
|
|
|
|
}
|
2015-07-21 05:55:30 +00:00
|
|
|
|
|
|
|
device->sample_rate_current = info->sample_spec.rate;
|
|
|
|
// PulseAudio performs resampling, so any value is valid. Let's pick
|
|
|
|
// some reasonable min and max values.
|
2015-08-08 21:44:31 +00:00
|
|
|
device->sample_rate_count = 1;
|
|
|
|
device->sample_rates = &dev->prealloc_sample_rate_range;
|
2015-08-13 01:57:34 +00:00
|
|
|
device->sample_rates[0].min = min(SOUNDIO_MIN_SAMPLE_RATE, device->sample_rate_current);
|
|
|
|
device->sample_rates[0].max = max(SOUNDIO_MAX_SAMPLE_RATE, device->sample_rate_current);
|
2015-07-21 05:55:30 +00:00
|
|
|
|
|
|
|
device->current_format = from_pulseaudio_format(info->sample_spec);
|
|
|
|
// PulseAudio performs sample format conversion, so any PulseAudio
|
|
|
|
// value is valid.
|
2015-07-30 07:46:13 +00:00
|
|
|
if ((err = set_all_device_formats(device))) {
|
|
|
|
soundio_device_unref(device);
|
|
|
|
sipa->device_query_err = SoundIoErrorNoMem;
|
|
|
|
pa_threaded_mainloop_signal(sipa->main_loop, 0);
|
|
|
|
return;
|
|
|
|
}
|
2015-07-21 05:55:30 +00:00
|
|
|
|
|
|
|
set_from_pulseaudio_channel_map(info->channel_map, &device->current_layout);
|
|
|
|
// PulseAudio does channel layout remapping, so any channel layout is valid.
|
2015-07-30 07:46:13 +00:00
|
|
|
if ((err = set_all_device_channel_layouts(device))) {
|
|
|
|
soundio_device_unref(device);
|
|
|
|
sipa->device_query_err = SoundIoErrorNoMem;
|
|
|
|
pa_threaded_mainloop_signal(sipa->main_loop, 0);
|
|
|
|
return;
|
|
|
|
}
|
2015-07-21 05:55:30 +00:00
|
|
|
|
|
|
|
|
2015-07-30 17:39:10 +00:00
|
|
|
device->aim = SoundIoDeviceAimOutput;
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-30 07:46:13 +00:00
|
|
|
if (sipa->current_devices_info->output_devices.append(device)) {
|
|
|
|
soundio_device_unref(device);
|
|
|
|
sipa->device_query_err = SoundIoErrorNoMem;
|
|
|
|
pa_threaded_mainloop_signal(sipa->main_loop, 0);
|
|
|
|
return;
|
|
|
|
}
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_signal(sipa->main_loop, 0);
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void source_info_callback(pa_context *pulse_context, const pa_source_info *info, int eol, void *userdata) {
|
2015-07-13 16:17:20 +00:00
|
|
|
SoundIoPrivate *si = (SoundIoPrivate *)userdata;
|
|
|
|
SoundIo *soundio = &si->pub;
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-21 05:55:30 +00:00
|
|
|
int err;
|
2015-07-01 08:02:44 +00:00
|
|
|
if (eol) {
|
2015-07-04 21:20:52 +00:00
|
|
|
sipa->have_source_list = true;
|
2015-07-13 16:17:20 +00:00
|
|
|
finish_device_query(si);
|
2015-07-30 07:46:13 +00:00
|
|
|
} else if (!sipa->device_query_err) {
|
2015-08-04 18:03:19 +00:00
|
|
|
SoundIoDevicePrivate *dev = allocate<SoundIoDevicePrivate>(1);
|
2015-07-30 07:46:13 +00:00
|
|
|
if (!dev) {
|
|
|
|
sipa->device_query_err = SoundIoErrorNoMem;
|
|
|
|
pa_threaded_mainloop_signal(sipa->main_loop, 0);
|
|
|
|
return;
|
|
|
|
}
|
2015-07-28 18:28:07 +00:00
|
|
|
SoundIoDevice *device = &dev->pub;
|
2015-07-01 08:02:44 +00:00
|
|
|
|
|
|
|
device->ref_count = 1;
|
|
|
|
device->soundio = soundio;
|
2015-07-30 17:26:36 +00:00
|
|
|
device->id = strdup(info->name);
|
|
|
|
device->name = strdup(info->description);
|
|
|
|
if (!device->id || !device->name) {
|
2015-07-30 07:46:13 +00:00
|
|
|
soundio_device_unref(device);
|
|
|
|
sipa->device_query_err = SoundIoErrorNoMem;
|
|
|
|
pa_threaded_mainloop_signal(sipa->main_loop, 0);
|
|
|
|
return;
|
|
|
|
}
|
2015-07-21 05:55:30 +00:00
|
|
|
|
|
|
|
device->sample_rate_current = info->sample_spec.rate;
|
|
|
|
// PulseAudio performs resampling, so any value is valid. Let's pick
|
|
|
|
// some reasonable min and max values.
|
2015-08-08 21:44:31 +00:00
|
|
|
device->sample_rate_count = 1;
|
|
|
|
device->sample_rates = &dev->prealloc_sample_rate_range;
|
|
|
|
device->sample_rates[0].min = min(8000, device->sample_rate_current);
|
|
|
|
device->sample_rates[0].max = max(5644800, device->sample_rate_current);
|
2015-07-21 05:55:30 +00:00
|
|
|
|
|
|
|
device->current_format = from_pulseaudio_format(info->sample_spec);
|
|
|
|
// PulseAudio performs sample format conversion, so any PulseAudio
|
|
|
|
// value is valid.
|
2015-07-30 07:46:13 +00:00
|
|
|
if ((err = set_all_device_formats(device))) {
|
|
|
|
soundio_device_unref(device);
|
|
|
|
sipa->device_query_err = SoundIoErrorNoMem;
|
|
|
|
pa_threaded_mainloop_signal(sipa->main_loop, 0);
|
|
|
|
return;
|
|
|
|
}
|
2015-07-21 05:55:30 +00:00
|
|
|
|
|
|
|
set_from_pulseaudio_channel_map(info->channel_map, &device->current_layout);
|
|
|
|
// PulseAudio does channel layout remapping, so any channel layout is valid.
|
2015-07-30 07:46:13 +00:00
|
|
|
if ((err = set_all_device_channel_layouts(device))) {
|
|
|
|
soundio_device_unref(device);
|
|
|
|
sipa->device_query_err = SoundIoErrorNoMem;
|
|
|
|
pa_threaded_mainloop_signal(sipa->main_loop, 0);
|
|
|
|
return;
|
|
|
|
}
|
2015-07-21 05:55:30 +00:00
|
|
|
|
2015-07-30 17:39:10 +00:00
|
|
|
device->aim = SoundIoDeviceAimInput;
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-30 07:46:13 +00:00
|
|
|
if (sipa->current_devices_info->input_devices.append(device)) {
|
|
|
|
soundio_device_unref(device);
|
|
|
|
sipa->device_query_err = SoundIoErrorNoMem;
|
|
|
|
pa_threaded_mainloop_signal(sipa->main_loop, 0);
|
|
|
|
return;
|
|
|
|
}
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_signal(sipa->main_loop, 0);
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void server_info_callback(pa_context *pulse_context, const pa_server_info *info, void *userdata) {
|
2015-07-13 16:17:20 +00:00
|
|
|
SoundIoPrivate *si = (SoundIoPrivate *)userdata;
|
|
|
|
assert(si);
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
free(sipa->default_sink_name);
|
|
|
|
free(sipa->default_source_name);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
sipa->default_sink_name = strdup(info->default_sink_name);
|
|
|
|
sipa->default_source_name = strdup(info->default_source_name);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-30 07:46:13 +00:00
|
|
|
if (!sipa->default_sink_name || !sipa->default_source_name) {
|
|
|
|
free(sipa->default_sink_name);
|
|
|
|
free(sipa->default_source_name);
|
|
|
|
sipa->device_query_err = SoundIoErrorNoMem;
|
|
|
|
}
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
sipa->have_default_sink = true;
|
2015-07-13 16:17:20 +00:00
|
|
|
finish_device_query(si);
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_signal(sipa->main_loop, 0);
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
|
2015-07-30 07:46:13 +00:00
|
|
|
static int scan_devices(SoundIoPrivate *si) {
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
sipa->have_sink_list = false;
|
|
|
|
sipa->have_default_sink = false;
|
|
|
|
sipa->have_source_list = false;
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-07 09:55:32 +00:00
|
|
|
soundio_destroy_devices_info(sipa->current_devices_info);
|
2015-08-04 18:03:19 +00:00
|
|
|
sipa->current_devices_info = allocate<SoundIoDevicesInfo>(1);
|
2015-07-04 21:20:52 +00:00
|
|
|
if (!sipa->current_devices_info)
|
2015-07-30 07:46:13 +00:00
|
|
|
return SoundIoErrorNoMem;
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_lock(sipa->main_loop);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-30 07:46:13 +00:00
|
|
|
pa_operation *list_sink_op = pa_context_get_sink_info_list(sipa->pulse_context, sink_info_callback, si);
|
|
|
|
pa_operation *list_source_op = pa_context_get_source_info_list(sipa->pulse_context, source_info_callback, si);
|
|
|
|
pa_operation *server_info_op = pa_context_get_server_info(sipa->pulse_context, server_info_callback, si);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-30 07:46:13 +00:00
|
|
|
int err;
|
|
|
|
if ((err = perform_operation(si, list_sink_op))) {
|
|
|
|
pa_threaded_mainloop_unlock(sipa->main_loop);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
if ((err = perform_operation(si, list_source_op))) {
|
|
|
|
pa_threaded_mainloop_unlock(sipa->main_loop);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
if ((err = perform_operation(si, server_info_op))) {
|
|
|
|
pa_threaded_mainloop_unlock(sipa->main_loop);
|
|
|
|
return err;
|
|
|
|
}
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_signal(sipa->main_loop, 0);
|
|
|
|
pa_threaded_mainloop_unlock(sipa->main_loop);
|
2015-07-30 07:46:13 +00:00
|
|
|
|
|
|
|
return 0;
|
2015-07-04 21:20:52 +00:00
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
static void block_until_have_devices(SoundIoPrivate *si) {
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-04 21:20:52 +00:00
|
|
|
if (sipa->have_devices_flag)
|
|
|
|
return;
|
|
|
|
pa_threaded_mainloop_lock(sipa->main_loop);
|
|
|
|
while (!sipa->have_devices_flag) {
|
|
|
|
pa_threaded_mainloop_wait(sipa->main_loop);
|
|
|
|
}
|
|
|
|
pa_threaded_mainloop_unlock(sipa->main_loop);
|
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
static void block_until_ready(SoundIoPrivate *si) {
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-04 21:20:52 +00:00
|
|
|
if (sipa->ready_flag)
|
|
|
|
return;
|
|
|
|
pa_threaded_mainloop_lock(sipa->main_loop);
|
|
|
|
while (!sipa->ready_flag) {
|
|
|
|
pa_threaded_mainloop_wait(sipa->main_loop);
|
|
|
|
}
|
|
|
|
pa_threaded_mainloop_unlock(sipa->main_loop);
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
static void flush_events(SoundIoPrivate *si) {
|
|
|
|
SoundIo *soundio = &si->pub;
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-04 21:20:52 +00:00
|
|
|
|
2015-07-30 07:46:13 +00:00
|
|
|
int err;
|
2015-07-04 21:20:52 +00:00
|
|
|
if (sipa->device_scan_queued) {
|
2015-07-30 07:46:13 +00:00
|
|
|
if (!(err = scan_devices(si)))
|
|
|
|
sipa->device_scan_queued = false;
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
SoundIoDevicesInfo *old_devices_info = nullptr;
|
|
|
|
bool change = false;
|
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_lock(sipa->main_loop);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
if (sipa->ready_devices_info) {
|
2015-07-13 16:17:20 +00:00
|
|
|
old_devices_info = si->safe_devices_info;
|
|
|
|
si->safe_devices_info = sipa->ready_devices_info;
|
2015-07-04 21:20:52 +00:00
|
|
|
sipa->ready_devices_info = nullptr;
|
2015-07-01 08:02:44 +00:00
|
|
|
change = true;
|
|
|
|
}
|
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_unlock(sipa->main_loop);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
|
|
|
if (change)
|
|
|
|
soundio->on_devices_change(soundio);
|
|
|
|
|
2015-07-07 09:55:32 +00:00
|
|
|
soundio_destroy_devices_info(old_devices_info);
|
2015-07-04 21:20:52 +00:00
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
block_until_have_devices(si);
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
static void wait_events(SoundIoPrivate *si) {
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-13 16:17:20 +00:00
|
|
|
flush_events(si);
|
2015-07-29 20:12:04 +00:00
|
|
|
pa_threaded_mainloop_lock(sipa->main_loop);
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_wait(sipa->main_loop);
|
2015-07-29 20:12:04 +00:00
|
|
|
pa_threaded_mainloop_unlock(sipa->main_loop);
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
static void wakeup(SoundIoPrivate *si) {
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_signal(sipa->main_loop, 0);
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
|
2015-07-10 09:21:47 +00:00
|
|
|
static pa_sample_format_t to_pulseaudio_format(SoundIoFormat format) {
|
|
|
|
switch (format) {
|
|
|
|
case SoundIoFormatU8: return PA_SAMPLE_U8;
|
|
|
|
case SoundIoFormatS16LE: return PA_SAMPLE_S16LE;
|
|
|
|
case SoundIoFormatS16BE: return PA_SAMPLE_S16BE;
|
|
|
|
case SoundIoFormatS24LE: return PA_SAMPLE_S24_32LE;
|
|
|
|
case SoundIoFormatS24BE: return PA_SAMPLE_S24_32BE;
|
|
|
|
case SoundIoFormatS32LE: return PA_SAMPLE_S32LE;
|
|
|
|
case SoundIoFormatS32BE: return PA_SAMPLE_S32BE;
|
|
|
|
case SoundIoFormatFloat32LE: return PA_SAMPLE_FLOAT32LE;
|
|
|
|
case SoundIoFormatFloat32BE: return PA_SAMPLE_FLOAT32BE;
|
|
|
|
|
|
|
|
case SoundIoFormatInvalid:
|
|
|
|
case SoundIoFormatS8:
|
|
|
|
case SoundIoFormatU16LE:
|
|
|
|
case SoundIoFormatU16BE:
|
|
|
|
case SoundIoFormatU24LE:
|
|
|
|
case SoundIoFormatU24BE:
|
|
|
|
case SoundIoFormatU32LE:
|
|
|
|
case SoundIoFormatU32BE:
|
|
|
|
case SoundIoFormatFloat64LE:
|
|
|
|
case SoundIoFormatFloat64BE:
|
2015-07-10 07:46:03 +00:00
|
|
|
return PA_SAMPLE_INVALID;
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
2015-07-10 07:46:03 +00:00
|
|
|
return PA_SAMPLE_INVALID;
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static pa_channel_position_t to_pulseaudio_channel_pos(SoundIoChannelId channel_id) {
|
|
|
|
switch (channel_id) {
|
2015-07-10 06:35:58 +00:00
|
|
|
case SoundIoChannelIdFrontLeft: return PA_CHANNEL_POSITION_FRONT_LEFT;
|
|
|
|
case SoundIoChannelIdFrontRight: return PA_CHANNEL_POSITION_FRONT_RIGHT;
|
|
|
|
case SoundIoChannelIdFrontCenter: return PA_CHANNEL_POSITION_FRONT_CENTER;
|
|
|
|
case SoundIoChannelIdLfe: return PA_CHANNEL_POSITION_LFE;
|
|
|
|
case SoundIoChannelIdBackLeft: return PA_CHANNEL_POSITION_REAR_LEFT;
|
|
|
|
case SoundIoChannelIdBackRight: return PA_CHANNEL_POSITION_REAR_RIGHT;
|
|
|
|
case SoundIoChannelIdFrontLeftCenter: return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
|
|
|
|
case SoundIoChannelIdFrontRightCenter: return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
|
|
|
|
case SoundIoChannelIdBackCenter: return PA_CHANNEL_POSITION_REAR_CENTER;
|
|
|
|
case SoundIoChannelIdSideLeft: return PA_CHANNEL_POSITION_SIDE_LEFT;
|
|
|
|
case SoundIoChannelIdSideRight: return PA_CHANNEL_POSITION_SIDE_RIGHT;
|
|
|
|
case SoundIoChannelIdTopCenter: return PA_CHANNEL_POSITION_TOP_CENTER;
|
|
|
|
case SoundIoChannelIdTopFrontLeft: return PA_CHANNEL_POSITION_TOP_FRONT_LEFT;
|
|
|
|
case SoundIoChannelIdTopFrontCenter: return PA_CHANNEL_POSITION_TOP_FRONT_CENTER;
|
|
|
|
case SoundIoChannelIdTopFrontRight: return PA_CHANNEL_POSITION_TOP_FRONT_RIGHT;
|
|
|
|
case SoundIoChannelIdTopBackLeft: return PA_CHANNEL_POSITION_TOP_REAR_LEFT;
|
|
|
|
case SoundIoChannelIdTopBackCenter: return PA_CHANNEL_POSITION_TOP_REAR_CENTER;
|
|
|
|
case SoundIoChannelIdTopBackRight: return PA_CHANNEL_POSITION_TOP_REAR_RIGHT;
|
|
|
|
|
2015-08-02 05:40:55 +00:00
|
|
|
case SoundIoChannelIdAux0: return PA_CHANNEL_POSITION_AUX0;
|
|
|
|
case SoundIoChannelIdAux1: return PA_CHANNEL_POSITION_AUX1;
|
|
|
|
case SoundIoChannelIdAux2: return PA_CHANNEL_POSITION_AUX2;
|
|
|
|
case SoundIoChannelIdAux3: return PA_CHANNEL_POSITION_AUX3;
|
|
|
|
case SoundIoChannelIdAux4: return PA_CHANNEL_POSITION_AUX4;
|
|
|
|
case SoundIoChannelIdAux5: return PA_CHANNEL_POSITION_AUX5;
|
|
|
|
case SoundIoChannelIdAux6: return PA_CHANNEL_POSITION_AUX6;
|
|
|
|
case SoundIoChannelIdAux7: return PA_CHANNEL_POSITION_AUX7;
|
|
|
|
case SoundIoChannelIdAux8: return PA_CHANNEL_POSITION_AUX8;
|
|
|
|
case SoundIoChannelIdAux9: return PA_CHANNEL_POSITION_AUX9;
|
|
|
|
case SoundIoChannelIdAux10: return PA_CHANNEL_POSITION_AUX10;
|
|
|
|
case SoundIoChannelIdAux11: return PA_CHANNEL_POSITION_AUX11;
|
|
|
|
case SoundIoChannelIdAux12: return PA_CHANNEL_POSITION_AUX12;
|
|
|
|
case SoundIoChannelIdAux13: return PA_CHANNEL_POSITION_AUX13;
|
|
|
|
case SoundIoChannelIdAux14: return PA_CHANNEL_POSITION_AUX14;
|
|
|
|
case SoundIoChannelIdAux15: return PA_CHANNEL_POSITION_AUX15;
|
|
|
|
|
|
|
|
default:
|
2015-07-10 06:35:58 +00:00
|
|
|
return PA_CHANNEL_POSITION_INVALID;
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static pa_channel_map to_pulseaudio_channel_map(const SoundIoChannelLayout *channel_layout) {
|
|
|
|
pa_channel_map channel_map;
|
|
|
|
channel_map.channels = channel_layout->channel_count;
|
|
|
|
|
2015-07-30 07:46:13 +00:00
|
|
|
assert((unsigned)channel_layout->channel_count <= PA_CHANNELS_MAX);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
|
|
|
for (int i = 0; i < channel_layout->channel_count; i += 1)
|
|
|
|
channel_map.map[i] = to_pulseaudio_channel_pos(channel_layout->channels[i]);
|
|
|
|
|
|
|
|
return channel_map;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void playback_stream_state_callback(pa_stream *stream, void *userdata) {
|
2015-07-13 16:17:20 +00:00
|
|
|
SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate*) userdata;
|
|
|
|
SoundIoOutStream *outstream = &os->pub;
|
|
|
|
SoundIo *soundio = outstream->device->soundio;
|
|
|
|
SoundIoPrivate *si = (SoundIoPrivate *)soundio;
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoOutStreamPulseAudio *ospa = &os->backend_data.pulseaudio;
|
2015-07-01 08:02:44 +00:00
|
|
|
switch (pa_stream_get_state(stream)) {
|
|
|
|
case PA_STREAM_UNCONNECTED:
|
|
|
|
case PA_STREAM_CREATING:
|
|
|
|
case PA_STREAM_TERMINATED:
|
|
|
|
break;
|
|
|
|
case PA_STREAM_READY:
|
2015-07-10 09:42:29 +00:00
|
|
|
ospa->stream_ready = true;
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_signal(sipa->main_loop, 0);
|
2015-07-01 08:02:44 +00:00
|
|
|
break;
|
|
|
|
case PA_STREAM_FAILED:
|
2015-07-30 07:46:13 +00:00
|
|
|
outstream->error_callback(outstream, SoundIoErrorStreaming);
|
2015-07-01 08:02:44 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void playback_stream_underflow_callback(pa_stream *stream, void *userdata) {
|
2015-07-13 16:17:20 +00:00
|
|
|
SoundIoOutStream *outstream = (SoundIoOutStream*)userdata;
|
2015-07-23 22:04:41 +00:00
|
|
|
outstream->underflow_callback(outstream);
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void playback_stream_write_callback(pa_stream *stream, size_t nbytes, void *userdata) {
|
2015-08-04 07:56:03 +00:00
|
|
|
SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate*)(userdata);
|
|
|
|
SoundIoOutStream *outstream = &os->pub;
|
2015-08-05 04:57:46 +00:00
|
|
|
int frame_count = nbytes / outstream->bytes_per_frame;
|
|
|
|
outstream->write_callback(outstream, 0, frame_count);
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
static void outstream_destroy_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoOutStreamPulseAudio *ospa = &os->backend_data.pulseaudio;
|
2015-07-04 09:57:06 +00:00
|
|
|
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-10 09:42:29 +00:00
|
|
|
pa_stream *stream = ospa->stream;
|
2015-07-04 09:57:06 +00:00
|
|
|
if (stream) {
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_lock(sipa->main_loop);
|
2015-07-04 09:57:06 +00:00
|
|
|
|
|
|
|
pa_stream_set_write_callback(stream, nullptr, nullptr);
|
|
|
|
pa_stream_set_state_callback(stream, nullptr, nullptr);
|
|
|
|
pa_stream_set_underflow_callback(stream, nullptr, nullptr);
|
|
|
|
pa_stream_disconnect(stream);
|
|
|
|
|
|
|
|
pa_stream_unref(stream);
|
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_unlock(sipa->main_loop);
|
2015-07-04 09:57:06 +00:00
|
|
|
|
2015-07-10 09:42:29 +00:00
|
|
|
ospa->stream = nullptr;
|
2015-07-04 09:57:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-14 04:30:37 +00:00
|
|
|
static int outstream_open_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoOutStreamPulseAudio *ospa = &os->backend_data.pulseaudio;
|
2015-07-13 16:17:20 +00:00
|
|
|
SoundIoOutStream *outstream = &os->pub;
|
2015-07-14 04:30:37 +00:00
|
|
|
|
2015-07-30 07:46:13 +00:00
|
|
|
if (outstream->layout.channel_count > SOUNDIO_MAX_CHANNELS)
|
|
|
|
return SoundIoErrorInvalid;
|
|
|
|
if ((unsigned)outstream->layout.channel_count > PA_CHANNELS_MAX)
|
|
|
|
return SoundIoErrorIncompatibleBackend;
|
|
|
|
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-08-04 20:09:45 +00:00
|
|
|
ospa->stream_ready.store(false);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
assert(sipa->pulse_context);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_lock(sipa->main_loop);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
|
|
|
pa_sample_spec sample_spec;
|
2015-07-13 16:17:20 +00:00
|
|
|
sample_spec.format = to_pulseaudio_format(outstream->format);
|
|
|
|
sample_spec.rate = outstream->sample_rate;
|
|
|
|
|
|
|
|
sample_spec.channels = outstream->layout.channel_count;
|
|
|
|
pa_channel_map channel_map = to_pulseaudio_channel_map(&outstream->layout);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-21 03:13:35 +00:00
|
|
|
ospa->stream = pa_stream_new(sipa->pulse_context, outstream->name, &sample_spec, &channel_map);
|
2015-07-10 09:42:29 +00:00
|
|
|
if (!ospa->stream) {
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_unlock(sipa->main_loop);
|
2015-07-13 16:17:20 +00:00
|
|
|
outstream_destroy_pa(si, os);
|
2015-07-01 08:02:44 +00:00
|
|
|
return SoundIoErrorNoMem;
|
|
|
|
}
|
2015-07-13 16:17:20 +00:00
|
|
|
pa_stream_set_state_callback(ospa->stream, playback_stream_state_callback, os);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-21 05:55:30 +00:00
|
|
|
ospa->buffer_attr.maxlength = UINT32_MAX;
|
|
|
|
ospa->buffer_attr.tlength = UINT32_MAX;
|
2015-07-30 03:55:28 +00:00
|
|
|
ospa->buffer_attr.prebuf = 0;
|
2015-07-10 09:42:29 +00:00
|
|
|
ospa->buffer_attr.minreq = UINT32_MAX;
|
|
|
|
ospa->buffer_attr.fragsize = UINT32_MAX;
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-23 23:49:44 +00:00
|
|
|
int bytes_per_second = outstream->bytes_per_frame * outstream->sample_rate;
|
2015-08-14 05:54:15 +00:00
|
|
|
if (outstream->software_latency > 0.0) {
|
2015-07-21 05:55:30 +00:00
|
|
|
int buffer_length = outstream->bytes_per_frame *
|
2015-08-14 05:54:15 +00:00
|
|
|
ceil(outstream->software_latency * bytes_per_second / (double)outstream->bytes_per_frame);
|
2015-07-21 05:55:30 +00:00
|
|
|
|
|
|
|
ospa->buffer_attr.maxlength = buffer_length;
|
|
|
|
ospa->buffer_attr.tlength = buffer_length;
|
|
|
|
}
|
|
|
|
|
2015-08-04 20:09:45 +00:00
|
|
|
pa_stream_flags_t flags = PA_STREAM_START_CORKED;
|
2015-08-14 05:54:15 +00:00
|
|
|
if (outstream->software_latency > 0.0)
|
2015-08-04 20:09:45 +00:00
|
|
|
flags = (pa_stream_flags_t) (flags | PA_STREAM_ADJUST_LATENCY);
|
|
|
|
|
|
|
|
int err = pa_stream_connect_playback(ospa->stream,
|
|
|
|
outstream->device->id, &ospa->buffer_attr,
|
|
|
|
flags, nullptr, nullptr);
|
|
|
|
if (err) {
|
|
|
|
pa_threaded_mainloop_unlock(sipa->main_loop);
|
|
|
|
return SoundIoErrorOpeningDevice;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (!ospa->stream_ready.load())
|
|
|
|
pa_threaded_mainloop_wait(sipa->main_loop);
|
|
|
|
|
|
|
|
size_t writable_size = pa_stream_writable_size(ospa->stream);
|
2015-08-14 05:54:15 +00:00
|
|
|
outstream->software_latency = writable_size / bytes_per_second;
|
|
|
|
|
|
|
|
// TODO get the correct software_latency value
|
2015-08-04 20:09:45 +00:00
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_unlock(sipa->main_loop);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
static int outstream_start_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
|
|
|
|
SoundIoOutStream *outstream = &os->pub;
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoOutStreamPulseAudio *ospa = &os->backend_data.pulseaudio;
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_lock(sipa->main_loop);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-08-05 04:57:46 +00:00
|
|
|
ospa->write_byte_count = pa_stream_writable_size(ospa->stream);
|
|
|
|
int frame_count = ospa->write_byte_count / outstream->bytes_per_frame;
|
|
|
|
outstream->write_callback(outstream, 0, frame_count);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-08-04 20:09:45 +00:00
|
|
|
pa_operation *op = pa_stream_cork(ospa->stream, false, nullptr, nullptr);
|
|
|
|
if (!op) {
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_unlock(sipa->main_loop);
|
2015-08-04 20:09:45 +00:00
|
|
|
return SoundIoErrorStreaming;
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
2015-08-04 20:09:45 +00:00
|
|
|
pa_operation_unref(op);
|
|
|
|
pa_stream_set_write_callback(ospa->stream, playback_stream_write_callback, os);
|
|
|
|
pa_stream_set_underflow_callback(ospa->stream, playback_stream_underflow_callback, outstream);
|
2015-07-22 22:43:45 +00:00
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_unlock(sipa->main_loop);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-07-16 03:57:00 +00:00
|
|
|
static int outstream_begin_write_pa(SoundIoPrivate *si,
|
2015-08-05 04:57:46 +00:00
|
|
|
SoundIoOutStreamPrivate *os, SoundIoChannelArea **out_areas, int *frame_count)
|
2015-07-01 08:02:44 +00:00
|
|
|
{
|
2015-07-13 16:17:20 +00:00
|
|
|
SoundIoOutStream *outstream = &os->pub;
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoOutStreamPulseAudio *ospa = &os->backend_data.pulseaudio;
|
2015-07-10 09:42:29 +00:00
|
|
|
pa_stream *stream = ospa->stream;
|
2015-07-29 20:02:17 +00:00
|
|
|
|
2015-08-05 04:57:46 +00:00
|
|
|
ospa->write_byte_count = *frame_count * outstream->bytes_per_frame;
|
|
|
|
if (pa_stream_begin_write(stream, (void**)&ospa->write_ptr, &ospa->write_byte_count))
|
2015-07-16 03:57:00 +00:00
|
|
|
return SoundIoErrorStreaming;
|
|
|
|
|
|
|
|
for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) {
|
2015-07-21 06:47:04 +00:00
|
|
|
ospa->areas[ch].ptr = ospa->write_ptr + outstream->bytes_per_sample * ch;
|
2015-07-16 03:57:00 +00:00
|
|
|
ospa->areas[ch].step = outstream->bytes_per_frame;
|
|
|
|
}
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-08-05 04:57:46 +00:00
|
|
|
*frame_count = ospa->write_byte_count / outstream->bytes_per_frame;
|
2015-07-16 03:57:00 +00:00
|
|
|
*out_areas = ospa->areas;
|
|
|
|
|
|
|
|
return 0;
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
|
2015-08-04 07:56:03 +00:00
|
|
|
static int outstream_end_write_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoOutStreamPulseAudio *ospa = &os->backend_data.pulseaudio;
|
2015-07-10 09:42:29 +00:00
|
|
|
pa_stream *stream = ospa->stream;
|
2015-08-05 04:57:46 +00:00
|
|
|
if (pa_stream_write(stream, ospa->write_ptr, ospa->write_byte_count, nullptr, 0, PA_SEEK_RELATIVE))
|
2015-07-16 03:57:00 +00:00
|
|
|
return SoundIoErrorStreaming;
|
|
|
|
return 0;
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
|
2015-07-24 21:03:49 +00:00
|
|
|
static int outstream_clear_buffer_pa(SoundIoPrivate *si,
|
2015-07-13 16:17:20 +00:00
|
|
|
SoundIoOutStreamPrivate *os)
|
2015-07-01 08:02:44 +00:00
|
|
|
{
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoOutStreamPulseAudio *ospa = &os->backend_data.pulseaudio;
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-10 09:42:29 +00:00
|
|
|
pa_stream *stream = ospa->stream;
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_lock(sipa->main_loop);
|
2015-07-01 08:02:44 +00:00
|
|
|
pa_operation *op = pa_stream_flush(stream, NULL, NULL);
|
2015-08-04 20:09:45 +00:00
|
|
|
if (!op) {
|
|
|
|
pa_threaded_mainloop_unlock(sipa->main_loop);
|
2015-07-24 21:03:49 +00:00
|
|
|
return SoundIoErrorStreaming;
|
2015-08-04 20:09:45 +00:00
|
|
|
}
|
2015-07-01 08:02:44 +00:00
|
|
|
pa_operation_unref(op);
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_unlock(sipa->main_loop);
|
2015-07-24 21:03:49 +00:00
|
|
|
return 0;
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
|
2015-07-16 20:37:41 +00:00
|
|
|
static int outstream_pause_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os, bool pause) {
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoOutStreamPulseAudio *ospa = &os->backend_data.pulseaudio;
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-21 06:47:04 +00:00
|
|
|
|
|
|
|
pa_threaded_mainloop_lock(sipa->main_loop);
|
|
|
|
|
2015-07-29 08:27:07 +00:00
|
|
|
if (pause != pa_stream_is_corked(ospa->stream)) {
|
2015-07-21 06:47:04 +00:00
|
|
|
pa_operation *op = pa_stream_cork(ospa->stream, pause, NULL, NULL);
|
2015-08-04 20:09:45 +00:00
|
|
|
if (!op) {
|
|
|
|
pa_threaded_mainloop_unlock(sipa->main_loop);
|
2015-07-21 06:47:04 +00:00
|
|
|
return SoundIoErrorStreaming;
|
2015-08-04 20:09:45 +00:00
|
|
|
}
|
2015-07-21 06:47:04 +00:00
|
|
|
pa_operation_unref(op);
|
|
|
|
}
|
|
|
|
|
|
|
|
pa_threaded_mainloop_unlock(sipa->main_loop);
|
|
|
|
|
|
|
|
return 0;
|
2015-07-16 20:37:41 +00:00
|
|
|
}
|
|
|
|
|
2015-07-01 08:02:44 +00:00
|
|
|
static void recording_stream_state_callback(pa_stream *stream, void *userdata) {
|
2015-07-13 16:17:20 +00:00
|
|
|
SoundIoInStreamPrivate *is = (SoundIoInStreamPrivate*)userdata;
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoInStreamPulseAudio *ispa = &is->backend_data.pulseaudio;
|
2015-07-22 22:43:45 +00:00
|
|
|
SoundIoInStream *instream = &is->pub;
|
|
|
|
SoundIo *soundio = instream->device->soundio;
|
|
|
|
SoundIoPrivate *si = (SoundIoPrivate *)soundio;
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-01 08:02:44 +00:00
|
|
|
switch (pa_stream_get_state(stream)) {
|
|
|
|
case PA_STREAM_UNCONNECTED:
|
|
|
|
case PA_STREAM_CREATING:
|
|
|
|
case PA_STREAM_TERMINATED:
|
|
|
|
break;
|
|
|
|
case PA_STREAM_READY:
|
2015-07-10 09:42:29 +00:00
|
|
|
ispa->stream_ready = true;
|
2015-07-22 22:43:45 +00:00
|
|
|
pa_threaded_mainloop_signal(sipa->main_loop, 0);
|
2015-07-01 08:02:44 +00:00
|
|
|
break;
|
|
|
|
case PA_STREAM_FAILED:
|
2015-07-30 07:46:13 +00:00
|
|
|
instream->error_callback(instream, SoundIoErrorStreaming);
|
2015-07-01 08:02:44 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void recording_stream_read_callback(pa_stream *stream, size_t nbytes, void *userdata) {
|
2015-07-13 16:17:20 +00:00
|
|
|
SoundIoInStreamPrivate *is = (SoundIoInStreamPrivate*)userdata;
|
|
|
|
SoundIoInStream *instream = &is->pub;
|
2015-07-22 22:43:45 +00:00
|
|
|
assert(nbytes % instream->bytes_per_frame == 0);
|
|
|
|
assert(nbytes > 0);
|
2015-08-05 04:57:46 +00:00
|
|
|
int available_frame_count = nbytes / instream->bytes_per_frame;
|
|
|
|
instream->read_callback(instream, 0, available_frame_count);
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
|
2015-07-28 00:06:12 +00:00
|
|
|
static void instream_destroy_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
|
|
|
|
SoundIoInStreamPulseAudio *ispa = &is->backend_data.pulseaudio;
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-10 09:42:29 +00:00
|
|
|
pa_stream *stream = ispa->stream;
|
2015-07-06 08:05:22 +00:00
|
|
|
if (stream) {
|
|
|
|
pa_threaded_mainloop_lock(sipa->main_loop);
|
|
|
|
|
|
|
|
pa_stream_set_state_callback(stream, nullptr, nullptr);
|
|
|
|
pa_stream_set_read_callback(stream, nullptr, nullptr);
|
|
|
|
pa_stream_disconnect(stream);
|
|
|
|
pa_stream_unref(stream);
|
|
|
|
|
|
|
|
pa_threaded_mainloop_unlock(sipa->main_loop);
|
|
|
|
|
2015-07-10 09:42:29 +00:00
|
|
|
ispa->stream = nullptr;
|
2015-07-06 08:05:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-14 04:30:37 +00:00
|
|
|
static int instream_open_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoInStreamPulseAudio *ispa = &is->backend_data.pulseaudio;
|
2015-07-13 16:17:20 +00:00
|
|
|
SoundIoInStream *instream = &is->pub;
|
2015-07-21 05:55:30 +00:00
|
|
|
|
2015-07-30 07:46:13 +00:00
|
|
|
if (instream->layout.channel_count > SOUNDIO_MAX_CHANNELS)
|
|
|
|
return SoundIoErrorInvalid;
|
|
|
|
if ((unsigned)instream->layout.channel_count > PA_CHANNELS_MAX)
|
|
|
|
return SoundIoErrorIncompatibleBackend;
|
|
|
|
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-10 09:42:29 +00:00
|
|
|
ispa->stream_ready = false;
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_lock(sipa->main_loop);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
|
|
|
pa_sample_spec sample_spec;
|
2015-07-13 16:17:20 +00:00
|
|
|
sample_spec.format = to_pulseaudio_format(instream->format);
|
|
|
|
sample_spec.rate = instream->sample_rate;
|
|
|
|
sample_spec.channels = instream->layout.channel_count;
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
pa_channel_map channel_map = to_pulseaudio_channel_map(&instream->layout);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-21 03:13:35 +00:00
|
|
|
ispa->stream = pa_stream_new(sipa->pulse_context, instream->name, &sample_spec, &channel_map);
|
2015-07-13 16:17:20 +00:00
|
|
|
if (!ispa->stream) {
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_unlock(sipa->main_loop);
|
2015-07-13 16:17:20 +00:00
|
|
|
instream_destroy_pa(si, is);
|
2015-07-01 08:02:44 +00:00
|
|
|
return SoundIoErrorNoMem;
|
|
|
|
}
|
|
|
|
|
2015-07-10 09:42:29 +00:00
|
|
|
pa_stream *stream = ispa->stream;
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
pa_stream_set_state_callback(stream, recording_stream_state_callback, is);
|
|
|
|
pa_stream_set_read_callback(stream, recording_stream_read_callback, is);
|
2015-08-04 07:56:03 +00:00
|
|
|
// TODO handle overflow callback
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-10 09:42:29 +00:00
|
|
|
ispa->buffer_attr.maxlength = UINT32_MAX;
|
|
|
|
ispa->buffer_attr.tlength = UINT32_MAX;
|
2015-07-30 03:55:28 +00:00
|
|
|
ispa->buffer_attr.prebuf = 0;
|
2015-07-10 09:42:29 +00:00
|
|
|
ispa->buffer_attr.minreq = UINT32_MAX;
|
2015-07-21 05:55:30 +00:00
|
|
|
ispa->buffer_attr.fragsize = UINT32_MAX;
|
|
|
|
|
2015-08-14 05:54:15 +00:00
|
|
|
if (instream->software_latency > 0.0) {
|
|
|
|
int bytes_per_second = instream->bytes_per_frame * instream->sample_rate;
|
2015-07-30 04:50:12 +00:00
|
|
|
int buffer_length = instream->bytes_per_frame *
|
2015-08-14 05:54:15 +00:00
|
|
|
ceil(instream->software_latency * bytes_per_second / (double)instream->bytes_per_frame);
|
2015-07-21 05:55:30 +00:00
|
|
|
ispa->buffer_attr.fragsize = buffer_length;
|
|
|
|
}
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_unlock(sipa->main_loop);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
static int instream_start_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
|
|
|
|
SoundIoInStream *instream = &is->pub;
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoInStreamPulseAudio *ispa = &is->backend_data.pulseaudio;
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_lock(sipa->main_loop);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-08-14 05:54:15 +00:00
|
|
|
pa_stream_flags_t flags = (instream->software_latency > 0.0) ? PA_STREAM_ADJUST_LATENCY : PA_STREAM_NOFLAGS;
|
2015-07-21 05:55:30 +00:00
|
|
|
|
2015-07-10 09:42:29 +00:00
|
|
|
int err = pa_stream_connect_record(ispa->stream,
|
2015-07-30 17:26:36 +00:00
|
|
|
instream->device->id,
|
2015-07-21 05:55:30 +00:00
|
|
|
&ispa->buffer_attr, flags);
|
2015-07-01 08:02:44 +00:00
|
|
|
if (err) {
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_unlock(sipa->main_loop);
|
2015-07-01 08:02:44 +00:00
|
|
|
return SoundIoErrorOpeningDevice;
|
|
|
|
}
|
|
|
|
|
2015-07-22 22:43:45 +00:00
|
|
|
while (!ispa->stream_ready)
|
|
|
|
pa_threaded_mainloop_wait(sipa->main_loop);
|
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_threaded_mainloop_unlock(sipa->main_loop);
|
2015-07-01 08:02:44 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-07-22 22:43:45 +00:00
|
|
|
static int instream_begin_read_pa(SoundIoPrivate *si,
|
2015-08-05 04:57:46 +00:00
|
|
|
SoundIoInStreamPrivate *is, SoundIoChannelArea **out_areas, int *frame_count)
|
2015-07-01 08:02:44 +00:00
|
|
|
{
|
2015-07-13 16:17:20 +00:00
|
|
|
SoundIoInStream *instream = &is->pub;
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoInStreamPulseAudio *ispa = &is->backend_data.pulseaudio;
|
2015-07-10 09:42:29 +00:00
|
|
|
pa_stream *stream = ispa->stream;
|
2015-07-22 22:43:45 +00:00
|
|
|
|
|
|
|
assert(ispa->stream_ready);
|
|
|
|
|
2015-08-05 04:57:46 +00:00
|
|
|
if (!ispa->peek_buf) {
|
2015-08-05 05:19:42 +00:00
|
|
|
if (pa_stream_peek(stream, (const void **)&ispa->peek_buf, &ispa->peek_buf_size))
|
|
|
|
return SoundIoErrorStreaming;
|
2015-07-22 22:43:45 +00:00
|
|
|
|
2015-08-05 04:57:46 +00:00
|
|
|
ispa->peek_buf_frames_left = ispa->peek_buf_size / instream->bytes_per_frame;
|
|
|
|
ispa->peek_buf_index = 0;
|
2015-08-04 07:56:03 +00:00
|
|
|
|
2015-08-05 04:57:46 +00:00
|
|
|
// hole
|
|
|
|
if (!ispa->peek_buf) {
|
|
|
|
*frame_count = ispa->peek_buf_frames_left;
|
|
|
|
*out_areas = nullptr;
|
|
|
|
return 0;
|
2015-07-23 22:04:41 +00:00
|
|
|
}
|
2015-08-05 04:57:46 +00:00
|
|
|
}
|
2015-07-23 22:04:41 +00:00
|
|
|
|
2015-08-05 04:57:46 +00:00
|
|
|
ispa->read_frame_count = min(*frame_count, ispa->peek_buf_frames_left);
|
|
|
|
*frame_count = ispa->read_frame_count;
|
|
|
|
for (int ch = 0; ch < instream->layout.channel_count; ch += 1) {
|
|
|
|
ispa->areas[ch].ptr = ispa->peek_buf + ispa->peek_buf_index + instream->bytes_per_sample * ch;
|
|
|
|
ispa->areas[ch].step = instream->bytes_per_frame;
|
2015-07-23 22:04:41 +00:00
|
|
|
}
|
2015-07-22 22:43:45 +00:00
|
|
|
|
2015-08-05 04:57:46 +00:00
|
|
|
*out_areas = ispa->areas;
|
|
|
|
|
2015-07-22 22:43:45 +00:00
|
|
|
return 0;
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
|
2015-07-22 22:43:45 +00:00
|
|
|
static int instream_end_read_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
|
2015-08-05 04:57:46 +00:00
|
|
|
SoundIoInStream *instream = &is->pub;
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoInStreamPulseAudio *ispa = &is->backend_data.pulseaudio;
|
2015-07-10 09:42:29 +00:00
|
|
|
pa_stream *stream = ispa->stream;
|
2015-08-05 04:57:46 +00:00
|
|
|
|
|
|
|
// hole
|
|
|
|
if (!ispa->peek_buf) {
|
|
|
|
if (pa_stream_drop(stream))
|
|
|
|
return SoundIoErrorStreaming;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t advance_bytes = ispa->read_frame_count * instream->bytes_per_frame;
|
|
|
|
ispa->peek_buf_index += advance_bytes;
|
|
|
|
ispa->peek_buf_frames_left -= ispa->read_frame_count;
|
|
|
|
|
|
|
|
if (ispa->peek_buf_index >= ispa->peek_buf_size) {
|
|
|
|
if (pa_stream_drop(stream))
|
|
|
|
return SoundIoErrorStreaming;
|
|
|
|
ispa->peek_buf = nullptr;
|
|
|
|
}
|
|
|
|
|
2015-07-22 22:43:45 +00:00
|
|
|
return 0;
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
|
2015-07-16 20:37:41 +00:00
|
|
|
static int instream_pause_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is, bool pause) {
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoInStreamPulseAudio *ispa = &is->backend_data.pulseaudio;
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-22 00:43:41 +00:00
|
|
|
|
|
|
|
pa_threaded_mainloop_lock(sipa->main_loop);
|
|
|
|
|
2015-07-29 08:27:07 +00:00
|
|
|
if (pause != pa_stream_is_corked(ispa->stream)) {
|
2015-07-22 00:43:41 +00:00
|
|
|
pa_operation *op = pa_stream_cork(ispa->stream, pause, NULL, NULL);
|
|
|
|
if (!op)
|
|
|
|
return SoundIoErrorStreaming;
|
|
|
|
pa_operation_unref(op);
|
|
|
|
}
|
|
|
|
|
|
|
|
pa_threaded_mainloop_unlock(sipa->main_loop);
|
|
|
|
|
|
|
|
return 0;
|
2015-07-16 20:37:41 +00:00
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
int soundio_pulseaudio_init(SoundIoPrivate *si) {
|
2015-07-21 05:55:30 +00:00
|
|
|
SoundIo *soundio = &si->pub;
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-30 07:46:13 +00:00
|
|
|
sipa->device_scan_queued.store(false);
|
|
|
|
sipa->ready_flag.store(false);
|
|
|
|
sipa->have_devices_flag.store(false);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
sipa->main_loop = pa_threaded_mainloop_new();
|
|
|
|
if (!sipa->main_loop) {
|
2015-07-13 16:17:20 +00:00
|
|
|
destroy_pa(si);
|
2015-07-01 08:02:44 +00:00
|
|
|
return SoundIoErrorNoMem;
|
|
|
|
}
|
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
pa_mainloop_api *main_loop_api = pa_threaded_mainloop_get_api(sipa->main_loop);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
sipa->props = pa_proplist_new();
|
|
|
|
if (!sipa->props) {
|
2015-07-13 16:17:20 +00:00
|
|
|
destroy_pa(si);
|
2015-07-01 08:02:44 +00:00
|
|
|
return SoundIoErrorNoMem;
|
|
|
|
}
|
|
|
|
|
2015-07-21 05:55:30 +00:00
|
|
|
sipa->pulse_context = pa_context_new_with_proplist(main_loop_api, soundio->app_name, sipa->props);
|
2015-07-04 21:20:52 +00:00
|
|
|
if (!sipa->pulse_context) {
|
2015-07-13 16:17:20 +00:00
|
|
|
destroy_pa(si);
|
2015-07-01 08:02:44 +00:00
|
|
|
return SoundIoErrorNoMem;
|
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
pa_context_set_subscribe_callback(sipa->pulse_context, subscribe_callback, si);
|
|
|
|
pa_context_set_state_callback(sipa->pulse_context, context_state_callback, si);
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-04 21:20:52 +00:00
|
|
|
int err = pa_context_connect(sipa->pulse_context, NULL, (pa_context_flags_t)0, NULL);
|
2015-07-01 08:02:44 +00:00
|
|
|
if (err) {
|
2015-07-13 16:17:20 +00:00
|
|
|
destroy_pa(si);
|
2015-07-01 08:02:44 +00:00
|
|
|
return SoundIoErrorInitAudioBackend;
|
|
|
|
}
|
|
|
|
|
2015-07-30 07:46:13 +00:00
|
|
|
if (pa_threaded_mainloop_start(sipa->main_loop)) {
|
2015-07-13 16:17:20 +00:00
|
|
|
destroy_pa(si);
|
2015-07-30 07:46:13 +00:00
|
|
|
return SoundIoErrorNoMem;
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
|
2015-07-30 07:46:13 +00:00
|
|
|
block_until_ready(si);
|
|
|
|
|
|
|
|
if (sipa->connection_err) {
|
2015-07-13 16:17:20 +00:00
|
|
|
destroy_pa(si);
|
2015-07-30 07:46:13 +00:00
|
|
|
return sipa->connection_err;
|
|
|
|
}
|
|
|
|
|
|
|
|
sipa->device_scan_queued.store(true);
|
|
|
|
if ((err = subscribe_to_events(si))) {
|
|
|
|
destroy_pa(si);
|
|
|
|
return err;
|
2015-07-01 08:02:44 +00:00
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
si->destroy = destroy_pa;
|
|
|
|
si->flush_events = flush_events;
|
|
|
|
si->wait_events = wait_events;
|
|
|
|
si->wakeup = wakeup;
|
|
|
|
|
2015-07-14 04:30:37 +00:00
|
|
|
si->outstream_open = outstream_open_pa;
|
2015-07-13 16:17:20 +00:00
|
|
|
si->outstream_destroy = outstream_destroy_pa;
|
|
|
|
si->outstream_start = outstream_start_pa;
|
|
|
|
si->outstream_begin_write = outstream_begin_write_pa;
|
2015-07-22 22:43:45 +00:00
|
|
|
si->outstream_end_write = outstream_end_write_pa;
|
2015-07-13 16:17:20 +00:00
|
|
|
si->outstream_clear_buffer = outstream_clear_buffer_pa;
|
2015-07-16 20:37:41 +00:00
|
|
|
si->outstream_pause = outstream_pause_pa;
|
2015-07-13 16:17:20 +00:00
|
|
|
|
2015-07-14 04:30:37 +00:00
|
|
|
si->instream_open = instream_open_pa;
|
2015-07-13 16:17:20 +00:00
|
|
|
si->instream_destroy = instream_destroy_pa;
|
|
|
|
si->instream_start = instream_start_pa;
|
2015-07-22 22:43:45 +00:00
|
|
|
si->instream_begin_read = instream_begin_read_pa;
|
|
|
|
si->instream_end_read = instream_end_read_pa;
|
2015-07-16 20:37:41 +00:00
|
|
|
si->instream_pause = instream_pause_pa;
|
2015-07-01 08:02:44 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|