pipewire/pipewire-alsa/alsa-plugins/pcm_pipewire.c
Martin Geier 422c2ad726 alsa-plugin: prevent deadlock when update_active is called from two threads
When there is not enough room for next data to write, alsa calls
snd_pcm_pipewire_poll_descriptors to setup file descriptor. This function
clears io->poll_fd by calling spa_system_eventfd_read and next time the
quantum is processed, update_active sets value in io->poll_fd,
alsa is notified and can continue to push data.

In bad case scenario, update_active is called simultaneously from
both - alsa thread and pw thread. In alsa thread, the check_active(io)
returns false, pw->active is set to false, but spa_system_eventfd_read
isn't called yet as Alsa thread is rescheduled. Pw thread starts to execute
the same code, however this time, check_active(io) returns true, pw->active
is set to true and spa_system_eventfd_write is called. When alsa thread
starts to run again, spa_system_eventfd_read is called and clears any
events from io->poll_fd.
Alsa starts to poll for events, io->poll_fd is clear, pw->active is set
to true and therefore spa_system_eventfd_write is not called ever again.

To fix this deadlock, write to io->poll_fd every time there is some
room for new data. Doing this is safe, as write only increases internal
counter and next read clears the counter.
This code can lead to opposite behavior - spa_system_eventfd_write is
called right after spa_system_eventfd_read, however there is no room for
new data. This would lead to busy loop in alsa thread. To prevent this
scenario, call update_active alsa in snd_pcm_pipewire_poll_revents.

Signed-off-by: Martin Geier <martin.geier@streamunlimited.com>
2022-10-25 11:26:20 +00:00

1374 lines
38 KiB
C

/* PCM - PipeWire plugin
*
* Copyright © 2017 Wim Taymans
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#define __USE_GNU
#include <limits.h>
#if !defined(__FreeBSD__) && !defined(__MidnightBSD__)
#include <byteswap.h>
#endif
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <alsa/asoundlib.h>
#include <alsa/pcm_external.h>
#include <spa/param/audio/format-utils.h>
#include <spa/debug/types.h>
#include <spa/param/props.h>
#include <spa/utils/result.h>
#include <spa/utils/string.h>
#include <pipewire/pipewire.h>
#define ATOMIC_INC(s) __atomic_add_fetch(&(s), 1, __ATOMIC_SEQ_CST)
#define ATOMIC_LOAD(s) __atomic_load_n(&(s), __ATOMIC_SEQ_CST)
#define SEQ_WRITE(s) ATOMIC_INC(s)
#define SEQ_WRITE_SUCCESS(s1,s2) ((s1) + 1 == (s2) && ((s2) & 1) == 0)
#define SEQ_READ(s) ATOMIC_LOAD(s)
#define SEQ_READ_SUCCESS(s1,s2) ((s1) == (s2) && ((s2) & 1) == 0)
PW_LOG_TOPIC_STATIC(alsa_log_topic, "alsa.pcm");
#define PW_LOG_TOPIC_DEFAULT alsa_log_topic
#define MIN_BUFFERS 2u
#define MAX_BUFFERS 64u
#define MAX_CHANNELS 64
#define MAX_RATE (48000*8)
#define MIN_PERIOD 64
#define MIN_PERIOD_BYTES (128)
#define MAX_PERIOD_BYTES (2*1024*1024)
#define MIN_BUFFER_BYTES (2*MIN_PERIOD_BYTES)
#define MAX_BUFFER_BYTES (2*MAX_PERIOD_BYTES)
struct params {
const char *node_name;
const char *server_name;
const char *playback_node;
const char *capture_node;
const char *role;
snd_pcm_format_t format;
int rate;
int channels;
int period_bytes;
int buffer_bytes;
uint32_t flags;
};
typedef struct {
snd_pcm_ioplug_t io;
snd_output_t *output;
FILE *log_file;
char *node_name;
char *target;
char *role;
int fd;
int error;
unsigned int activated:1; /* PipeWire is activated? */
unsigned int drained:1;
unsigned int draining:1;
unsigned int xrun_detected:1;
unsigned int hw_params_changed:1;
unsigned int active:1;
snd_pcm_uframes_t hw_ptr;
snd_pcm_uframes_t boundary;
snd_pcm_uframes_t min_avail;
unsigned int sample_bits;
uint32_t blocks;
uint32_t stride;
struct spa_system *system;
struct pw_thread_loop *main_loop;
struct pw_context *context;
struct pw_core *core;
struct spa_hook core_listener;
uint32_t flags;
struct pw_stream *stream;
struct spa_hook stream_listener;
int64_t delay;
uint64_t now;
uintptr_t seq;
struct spa_audio_info_raw format;
} snd_pcm_pipewire_t;
static int snd_pcm_pipewire_stop(snd_pcm_ioplug_t *io);
static int check_active(snd_pcm_ioplug_t *io)
{
snd_pcm_pipewire_t *pw = io->private_data;
snd_pcm_sframes_t avail;
bool active;
avail = snd_pcm_ioplug_avail(io, pw->hw_ptr, io->appl_ptr);
if (io->state == SND_PCM_STATE_DRAINING) {
active = pw->drained;
}
else if (avail >= 0 && avail < (snd_pcm_sframes_t)pw->min_avail) {
active = false;
}
else if (avail >= (snd_pcm_sframes_t)pw->min_avail) {
active = true;
} else {
active = false;
}
if (pw->active != active) {
pw_log_trace("%p: avail:%lu min-avail:%lu state:%s hw:%lu appl:%lu active:%d->%d state:%s",
pw, avail, pw->min_avail, snd_pcm_state_name(io->state),
pw->hw_ptr, io->appl_ptr, pw->active, active,
snd_pcm_state_name(io->state));
}
return active;
}
static int update_active(snd_pcm_ioplug_t *io)
{
snd_pcm_pipewire_t *pw = io->private_data;
pw->active = check_active(io);
uint64_t val;
if (pw->active)
spa_system_eventfd_write(pw->system, io->poll_fd, 1);
else
spa_system_eventfd_read(pw->system, io->poll_fd, &val);
return pw->active;
}
static void snd_pcm_pipewire_free(snd_pcm_pipewire_t *pw)
{
if (pw == NULL)
return;
pw_log_debug("%p: free", pw);
if (pw->main_loop)
pw_thread_loop_stop(pw->main_loop);
if (pw->stream)
pw_stream_destroy(pw->stream);
if (pw->context)
pw_context_destroy(pw->context);
if (pw->fd >= 0)
spa_system_close(pw->system, pw->fd);
if (pw->main_loop)
pw_thread_loop_destroy(pw->main_loop);
free(pw->node_name);
free(pw->target);
free(pw->role);
snd_output_close(pw->output);
fclose(pw->log_file);
free(pw);
}
static int snd_pcm_pipewire_close(snd_pcm_ioplug_t *io)
{
snd_pcm_pipewire_t *pw = io->private_data;
pw_log_debug("%p: close", pw);
snd_pcm_pipewire_free(pw);
return 0;
}
static int snd_pcm_pipewire_poll_descriptors(snd_pcm_ioplug_t *io, struct pollfd *pfds, unsigned int space)
{
snd_pcm_pipewire_t *pw = io->private_data;
update_active(io);
pfds->fd = pw->fd;
pfds->events = POLLIN | POLLERR | POLLNVAL;
return 1;
}
static int snd_pcm_pipewire_poll_revents(snd_pcm_ioplug_t *io,
struct pollfd *pfds, unsigned int nfds,
unsigned short *revents)
{
snd_pcm_pipewire_t *pw = io->private_data;
assert(pfds && nfds == 1 && revents);
if (pw->error < 0)
return pw->error;
*revents = pfds[0].revents & ~(POLLIN | POLLOUT);
if (pfds[0].revents & POLLIN && check_active(io)) {
*revents |= (io->stream == SND_PCM_STREAM_PLAYBACK) ? POLLOUT : POLLIN;
update_active(io);
}
return 0;
}
static snd_pcm_sframes_t snd_pcm_pipewire_pointer(snd_pcm_ioplug_t *io)
{
snd_pcm_pipewire_t *pw = io->private_data;
if (pw->xrun_detected)
return -EPIPE;
if (pw->error < 0)
return pw->error;
if (io->buffer_size == 0)
return 0;
#ifdef SND_PCM_IOPLUG_FLAG_BOUNDARY_WA
return pw->hw_ptr;
#else
return pw->hw_ptr % io->buffer_size;
#endif
}
static int snd_pcm_pipewire_delay(snd_pcm_ioplug_t *io, snd_pcm_sframes_t *delayp)
{
snd_pcm_pipewire_t *pw = io->private_data;
uintptr_t seq1, seq2;
int64_t elapsed = 0, delay, now, avail;
struct timespec ts;
int64_t diff;
do {
seq1 = SEQ_READ(pw->seq);
delay = pw->delay;
now = pw->now;
if (io->stream == SND_PCM_STREAM_PLAYBACK)
avail = snd_pcm_ioplug_hw_avail(io, pw->hw_ptr, io->appl_ptr);
else
avail = snd_pcm_ioplug_avail(io, pw->hw_ptr, io->appl_ptr);
seq2 = SEQ_READ(pw->seq);
} while (!SEQ_READ_SUCCESS(seq1, seq2));
if (now != 0 && (io->state == SND_PCM_STATE_RUNNING ||
io->state == SND_PCM_STATE_DRAINING)) {
clock_gettime(CLOCK_MONOTONIC, &ts);
diff = SPA_TIMESPEC_TO_NSEC(&ts) - now;
elapsed = (io->rate * diff) / SPA_NSEC_PER_SEC;
if (io->stream == SND_PCM_STREAM_PLAYBACK)
delay -= SPA_MIN(elapsed, delay);
else
delay += SPA_MIN(elapsed, (int64_t)io->buffer_size);
}
*delayp = delay + avail;
pw_log_trace("avail:%"PRIi64" filled %"PRIi64" elapsed:%"PRIi64" delay:%ld hw:%lu appl:%lu",
avail, delay, elapsed, *delayp, pw->hw_ptr, io->appl_ptr);
return 0;
}
static snd_pcm_uframes_t
snd_pcm_pipewire_process(snd_pcm_pipewire_t *pw, struct pw_buffer *b,
snd_pcm_uframes_t *hw_avail,snd_pcm_uframes_t want)
{
snd_pcm_ioplug_t *io = &pw->io;
snd_pcm_channel_area_t *pwareas;
snd_pcm_uframes_t xfer = 0;
snd_pcm_uframes_t nframes;
unsigned int channel;
struct spa_data *d;
void *ptr;
uint32_t bl, offset, size;
d = b->buffer->datas;
pwareas = alloca(io->channels * sizeof(snd_pcm_channel_area_t));
for (bl = 0; bl < pw->blocks; bl++) {
if (io->stream == SND_PCM_STREAM_PLAYBACK) {
size = SPA_MIN(d[bl].maxsize, pw->min_avail * pw->stride);
} else {
offset = SPA_MIN(d[bl].chunk->offset, d[bl].maxsize);
size = SPA_MIN(d[bl].chunk->size, d[bl].maxsize - offset);
}
want = SPA_MIN(want, size / pw->stride);
}
nframes = SPA_MIN(want, *hw_avail);
if (pw->blocks == 1) {
if (io->stream == SND_PCM_STREAM_PLAYBACK) {
d[0].chunk->size = want * pw->stride;
d[0].chunk->offset = offset = 0;
} else {
offset = SPA_MIN(d[0].chunk->offset, d[0].maxsize);
}
ptr = SPA_PTROFF(d[0].data, offset, void);
for (channel = 0; channel < io->channels; channel++) {
pwareas[channel].addr = ptr;
pwareas[channel].first = channel * pw->sample_bits;
pwareas[channel].step = io->channels * pw->sample_bits;
}
} else {
for (channel = 0; channel < io->channels; channel++) {
if (io->stream == SND_PCM_STREAM_PLAYBACK) {
d[channel].chunk->size = want * pw->stride;
d[channel].chunk->offset = offset = 0;
} else {
offset = SPA_MIN(d[channel].chunk->offset, d[channel].maxsize);
}
ptr = SPA_PTROFF(d[channel].data, offset, void);
pwareas[channel].addr = ptr;
pwareas[channel].first = 0;
pwareas[channel].step = pw->sample_bits;
}
}
if (io->state == SND_PCM_STATE_RUNNING ||
io->state == SND_PCM_STATE_DRAINING) {
snd_pcm_uframes_t hw_ptr = pw->hw_ptr;
xfer = nframes;
if (xfer > 0) {
const snd_pcm_channel_area_t *areas = snd_pcm_ioplug_mmap_areas(io);
const snd_pcm_uframes_t offset = hw_ptr % io->buffer_size;
if (io->stream == SND_PCM_STREAM_PLAYBACK)
snd_pcm_areas_copy_wrap(pwareas, 0, nframes,
areas, offset,
io->buffer_size,
io->channels, xfer,
io->format);
else
snd_pcm_areas_copy_wrap(areas, offset,
io->buffer_size,
pwareas, 0, nframes,
io->channels, xfer,
io->format);
hw_ptr += xfer;
if (hw_ptr >= pw->boundary)
hw_ptr -= pw->boundary;
pw->hw_ptr = hw_ptr;
*hw_avail -= xfer;
}
}
/* check if requested frames were copied */
if (xfer < want) {
/* always fill the not yet written PipeWire buffer with silence */
if (io->stream == SND_PCM_STREAM_PLAYBACK) {
const snd_pcm_uframes_t frames = want - xfer;
snd_pcm_areas_silence(pwareas, xfer, io->channels,
frames, io->format);
xfer += frames;
}
if (io->state == SND_PCM_STATE_RUNNING ||
io->state == SND_PCM_STATE_DRAINING) {
/* report Xrun to user application */
pw->xrun_detected = true;
}
}
return xfer;
}
static void on_stream_param_changed(void *data, uint32_t id, const struct spa_pod *param)
{
snd_pcm_pipewire_t *pw = data;
snd_pcm_ioplug_t *io = &pw->io;
const struct spa_pod *params[4];
uint32_t n_params = 0;
uint8_t buffer[4096];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
uint32_t buffers, size;
if (param == NULL || id != SPA_PARAM_Format)
return;
io->period_size = pw->min_avail;
buffers = SPA_CLAMP(io->buffer_size / io->period_size, MIN_BUFFERS, MAX_BUFFERS);
size = io->period_size * pw->stride;
pw_log_info("%p: buffer_size:%lu period_size:%lu buffers:%u size:%u min_avail:%lu",
pw, io->buffer_size, io->period_size, buffers, size, pw->min_avail);
params[n_params++] = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(buffers, MIN_BUFFERS, MAX_BUFFERS),
SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(pw->blocks),
SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(size, size, INT_MAX),
SPA_PARAM_BUFFERS_stride, SPA_POD_Int(pw->stride));
pw_stream_update_params(pw->stream, params, n_params);
}
static void on_stream_drained(void *data)
{
snd_pcm_pipewire_t *pw = data;
pw->drained = true;
pw->draining = false;
pw_log_debug("%p: drained", pw);
pw_thread_loop_signal(pw->main_loop, false);
}
static void on_stream_process(void *data)
{
snd_pcm_pipewire_t *pw = data;
snd_pcm_ioplug_t *io = &pw->io;
struct pw_buffer *b;
snd_pcm_uframes_t hw_avail, before, want, xfer;
struct pw_time pwt;
int64_t delay;
pw_stream_get_time_n(pw->stream, &pwt, sizeof(pwt));
delay = pwt.delay;
if (pwt.rate.num != 0) {
delay = delay * io->rate * pwt.rate.num / pwt.rate.denom;
}
before = hw_avail = snd_pcm_ioplug_hw_avail(io, pw->hw_ptr, io->appl_ptr);
if (pw->drained)
goto done;
b = pw_stream_dequeue_buffer(pw->stream);
if (b == NULL)
return;
want = b->requested ? b->requested : hw_avail;
SEQ_WRITE(pw->seq);
xfer = snd_pcm_pipewire_process(pw, b, &hw_avail, want);
pw->delay = delay;
/* the buffer is now queued in the stream and consumed */
if (io->stream == SND_PCM_STREAM_PLAYBACK)
pw->delay += xfer;
pw->now = pwt.now;
SEQ_WRITE(pw->seq);
pw_log_trace("%p: avail-before:%lu avail:%lu want:%lu xfer:%lu hw:%lu appl:%lu",
pw, before, hw_avail, want, xfer, pw->hw_ptr, io->appl_ptr);
pw_stream_queue_buffer(pw->stream, b);
if (io->state == SND_PCM_STATE_DRAINING && !pw->draining && hw_avail == 0) {
if (io->stream == SND_PCM_STREAM_CAPTURE) {
on_stream_drained (pw); /* since pw_stream does not call drained() for capture */
} else {
pw_stream_flush(pw->stream, true);
pw->draining = true;
pw->drained = false;
}
}
done:
update_active(io);
}
static const struct pw_stream_events stream_events = {
PW_VERSION_STREAM_EVENTS,
.param_changed = on_stream_param_changed,
.process = on_stream_process,
.drained = on_stream_drained,
};
static int pipewire_start(snd_pcm_pipewire_t *pw)
{
if (!pw->activated && pw->stream != NULL) {
pw_stream_set_active(pw->stream, true);
pw->activated = true;
}
return 0;
}
static int snd_pcm_pipewire_drain(snd_pcm_ioplug_t *io)
{
int res;
snd_pcm_pipewire_t *pw = io->private_data;
pw_thread_loop_lock(pw->main_loop);
pw_log_debug("%p: drain", pw);
pw->drained = false;
pw->draining = false;
pipewire_start(pw);
while (!pw->drained && pw->error >= 0 && pw->activated) {
pw_thread_loop_wait(pw->main_loop);
}
res = pw->error;
pw_thread_loop_unlock(pw->main_loop);
return res;
}
static int snd_pcm_pipewire_prepare(snd_pcm_ioplug_t *io)
{
snd_pcm_pipewire_t *pw = io->private_data;
snd_pcm_sw_params_t *swparams;
const struct spa_pod *params[1];
uint8_t buffer[1024];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
struct pw_properties *props;
uint32_t min_period;
pw_thread_loop_lock(pw->main_loop);
snd_pcm_sw_params_alloca(&swparams);
if (snd_pcm_sw_params_current(io->pcm, swparams) == 0) {
snd_pcm_sw_params_get_avail_min(swparams, &pw->min_avail);
snd_pcm_sw_params_get_boundary(swparams, &pw->boundary);
snd_pcm_sw_params_dump(swparams, pw->output);
fflush(pw->log_file);
} else {
pw->min_avail = io->period_size;
pw->boundary = io->buffer_size;
}
min_period = (MIN_PERIOD * io->rate / 48000);
pw->min_avail = SPA_MAX(pw->min_avail, min_period);
pw_log_debug("%p: prepare error:%d stream:%p buffer-size:%lu "
"period-size:%lu min-avail:%ld", pw, pw->error,
pw->stream, io->buffer_size, io->period_size, pw->min_avail);
if (pw->error >= 0 && pw->stream != NULL && !pw->hw_params_changed)
goto done;
pw->hw_params_changed = false;
props = pw_properties_new(NULL, NULL);
if (props == NULL)
goto error;
pw_properties_set(props, PW_KEY_CLIENT_API, "alsa");
pw_properties_setf(props, PW_KEY_APP_NAME, "%s", pw_get_prgname());
if (pw_properties_get(props, PW_KEY_NODE_LATENCY) == NULL)
pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%lu/%u", pw->min_avail, io->rate);
if (pw_properties_get(props, PW_KEY_NODE_RATE) == NULL)
pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", io->rate);
if (pw->target != NULL &&
pw_properties_get(props, PW_KEY_NODE_TARGET) == NULL)
pw_properties_setf(props, PW_KEY_NODE_TARGET, "%s", pw->target);
if (pw_properties_get(props, PW_KEY_MEDIA_TYPE) == NULL)
pw_properties_set(props, PW_KEY_MEDIA_TYPE, "Audio");
if (pw_properties_get(props, PW_KEY_MEDIA_CATEGORY) == NULL)
pw_properties_set(props, PW_KEY_MEDIA_CATEGORY,
io->stream == SND_PCM_STREAM_PLAYBACK ?
"Playback" : "Capture");
if (pw->role != NULL &&
pw_properties_get(props, PW_KEY_MEDIA_ROLE) == NULL)
pw_properties_setf(props, PW_KEY_MEDIA_ROLE, "%s", pw->role);
params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &pw->format);
if (pw->stream != NULL) {
pw_stream_update_properties(pw->stream, &props->dict);
pw_stream_update_params(pw->stream, params, 1);
goto done;
}
pw->stream = pw_stream_new(pw->core, pw->node_name, props);
if (pw->stream == NULL)
goto error;
pw_stream_add_listener(pw->stream, &pw->stream_listener, &stream_events, pw);
pw->error = 0;
pw_stream_connect(pw->stream,
io->stream == SND_PCM_STREAM_PLAYBACK ?
PW_DIRECTION_OUTPUT :
PW_DIRECTION_INPUT,
PW_ID_ANY,
pw->flags |
PW_STREAM_FLAG_AUTOCONNECT |
PW_STREAM_FLAG_MAP_BUFFERS |
PW_STREAM_FLAG_RT_PROCESS,
params, 1);
done:
pw->hw_ptr = 0;
pw->now = 0;
pw->xrun_detected = false;
pw->drained = false;
pw->draining = false;
pw_thread_loop_unlock(pw->main_loop);
return 0;
error:
pw_thread_loop_unlock(pw->main_loop);
return -ENOMEM;
}
static int snd_pcm_pipewire_start(snd_pcm_ioplug_t *io)
{
snd_pcm_pipewire_t *pw = io->private_data;
pw_thread_loop_lock(pw->main_loop);
pw_log_debug("%p: start", pw);
pipewire_start(pw);
pw_thread_loop_unlock(pw->main_loop);
return 0;
}
static int snd_pcm_pipewire_stop(snd_pcm_ioplug_t *io)
{
snd_pcm_pipewire_t *pw = io->private_data;
pw_log_debug("%p: stop", pw);
update_active(io);
pw_thread_loop_lock(pw->main_loop);
if (pw->activated && pw->stream != NULL) {
pw_stream_set_active(pw->stream, false);
pw->activated = false;
}
pw_thread_loop_unlock(pw->main_loop);
return 0;
}
static int snd_pcm_pipewire_pause(snd_pcm_ioplug_t * io, int enable)
{
pw_log_debug("%p: pause", io);
if (enable)
snd_pcm_pipewire_stop(io);
else
snd_pcm_pipewire_start(io);
return 0;
}
#if __BYTE_ORDER == __BIG_ENDIAN
#define _FORMAT_LE(p, fmt) p ? SPA_AUDIO_FORMAT_UNKNOWN : SPA_AUDIO_FORMAT_ ## fmt ## _OE
#define _FORMAT_BE(p, fmt) p ? SPA_AUDIO_FORMAT_ ## fmt ## P : SPA_AUDIO_FORMAT_ ## fmt
#elif __BYTE_ORDER == __LITTLE_ENDIAN
#define _FORMAT_LE(p, fmt) p ? SPA_AUDIO_FORMAT_ ## fmt ## P : SPA_AUDIO_FORMAT_ ## fmt
#define _FORMAT_BE(p, fmt) p ? SPA_AUDIO_FORMAT_UNKNOWN : SPA_AUDIO_FORMAT_ ## fmt ## _OE
#endif
static int set_default_channels(struct spa_audio_info_raw *info)
{
switch (info->channels) {
case 8:
info->position[6] = SPA_AUDIO_CHANNEL_SL;
info->position[7] = SPA_AUDIO_CHANNEL_SR;
SPA_FALLTHROUGH
case 6:
info->position[5] = SPA_AUDIO_CHANNEL_LFE;
SPA_FALLTHROUGH
case 5:
info->position[4] = SPA_AUDIO_CHANNEL_FC;
SPA_FALLTHROUGH
case 4:
info->position[2] = SPA_AUDIO_CHANNEL_RL;
info->position[3] = SPA_AUDIO_CHANNEL_RR;
SPA_FALLTHROUGH
case 2:
info->position[0] = SPA_AUDIO_CHANNEL_FL;
info->position[1] = SPA_AUDIO_CHANNEL_FR;
return 1;
case 1:
info->position[0] = SPA_AUDIO_CHANNEL_MONO;
return 1;
default:
return 0;
}
}
static int snd_pcm_pipewire_hw_params(snd_pcm_ioplug_t * io,
snd_pcm_hw_params_t * params)
{
snd_pcm_pipewire_t *pw = io->private_data;
bool planar;
snd_pcm_hw_params_dump(params, pw->output);
fflush(pw->log_file);
pw_log_debug("%p: hw_params buffer_size:%lu period_size:%lu", pw, io->buffer_size, io->period_size);
switch(io->access) {
case SND_PCM_ACCESS_MMAP_INTERLEAVED:
case SND_PCM_ACCESS_RW_INTERLEAVED:
planar = false;
break;
case SND_PCM_ACCESS_MMAP_NONINTERLEAVED:
case SND_PCM_ACCESS_RW_NONINTERLEAVED:
planar = true;
break;
default:
SNDERR("PipeWire: invalid access: %d\n", io->access);
return -EINVAL;
}
switch(io->format) {
case SND_PCM_FORMAT_U8:
pw->format.format = planar ? SPA_AUDIO_FORMAT_U8P : SPA_AUDIO_FORMAT_U8;
break;
case SND_PCM_FORMAT_S16_LE:
pw->format.format = _FORMAT_LE(planar, S16);
break;
case SND_PCM_FORMAT_S16_BE:
pw->format.format = _FORMAT_BE(planar, S16);
break;
case SND_PCM_FORMAT_S24_LE:
pw->format.format = _FORMAT_LE(planar, S24_32);
break;
case SND_PCM_FORMAT_S24_BE:
pw->format.format = _FORMAT_BE(planar, S24_32);
break;
case SND_PCM_FORMAT_S32_LE:
pw->format.format = _FORMAT_LE(planar, S32);
break;
case SND_PCM_FORMAT_S32_BE:
pw->format.format = _FORMAT_BE(planar, S32);
break;
case SND_PCM_FORMAT_S24_3LE:
pw->format.format = _FORMAT_LE(planar, S24);
break;
case SND_PCM_FORMAT_S24_3BE:
pw->format.format = _FORMAT_BE(planar, S24);
break;
case SND_PCM_FORMAT_FLOAT_LE:
pw->format.format = _FORMAT_LE(planar, F32);
break;
case SND_PCM_FORMAT_FLOAT_BE:
pw->format.format = _FORMAT_BE(planar, F32);
break;
default:
SNDERR("PipeWire: invalid format: %d\n", io->format);
return -EINVAL;
}
pw->format.channels = io->channels;
pw->format.rate = io->rate;
set_default_channels(&pw->format);
pw->sample_bits = snd_pcm_format_physical_width(io->format);
if (planar) {
pw->blocks = io->channels;
pw->stride = pw->sample_bits / 8;
} else {
pw->blocks = 1;
pw->stride = (io->channels * pw->sample_bits) / 8;
}
pw->hw_params_changed = true;
pw_log_info("%p: format:%s channels:%d rate:%d stride:%d blocks:%d", pw,
spa_debug_type_find_name(spa_type_audio_format, pw->format.format),
io->channels, io->rate, pw->stride, pw->blocks);
return 0;
}
static int snd_pcm_pipewire_sw_params(snd_pcm_ioplug_t * io,
snd_pcm_sw_params_t * sw_params)
{
snd_pcm_pipewire_t *pw = io->private_data;
pw_thread_loop_lock(pw->main_loop);
if (pw->stream) {
snd_pcm_uframes_t min_avail;
snd_pcm_sw_params_get_avail_min( sw_params, &min_avail);
snd_pcm_sw_params_get_boundary(sw_params, &pw->boundary);
if (min_avail != pw->min_avail) {
char latency[64];
struct spa_dict_item item[1];
uint32_t min_period = (MIN_PERIOD * io->rate / 48000);
pw->min_avail = SPA_MAX(min_avail, min_period);
spa_scnprintf(latency, sizeof(latency), "%lu/%u", pw->min_avail, io->rate);
item[0] = SPA_DICT_ITEM_INIT(PW_KEY_NODE_LATENCY, latency);
pw_log_debug("%p: sw_params update props %p %ld", pw, pw->stream, pw->min_avail);
pw_stream_update_properties(pw->stream, &SPA_DICT_INIT(item, 1));
}
} else {
pw_log_debug("%p: sw_params pre-prepare noop", pw);
}
pw_thread_loop_unlock(pw->main_loop);
return 0;
}
struct chmap_info {
enum snd_pcm_chmap_position pos;
enum spa_audio_channel channel;
};
static const struct chmap_info chmap_info[] = {
[SND_CHMAP_UNKNOWN] = { SND_CHMAP_UNKNOWN, SPA_AUDIO_CHANNEL_UNKNOWN },
[SND_CHMAP_NA] = { SND_CHMAP_NA, SPA_AUDIO_CHANNEL_NA },
[SND_CHMAP_MONO] = { SND_CHMAP_MONO, SPA_AUDIO_CHANNEL_MONO },
[SND_CHMAP_FL] = { SND_CHMAP_FL, SPA_AUDIO_CHANNEL_FL },
[SND_CHMAP_FR] = { SND_CHMAP_FR, SPA_AUDIO_CHANNEL_FR },
[SND_CHMAP_RL] = { SND_CHMAP_RL, SPA_AUDIO_CHANNEL_RL },
[SND_CHMAP_RR] = { SND_CHMAP_RR, SPA_AUDIO_CHANNEL_RR },
[SND_CHMAP_FC] = { SND_CHMAP_FC, SPA_AUDIO_CHANNEL_FC },
[SND_CHMAP_LFE] = { SND_CHMAP_LFE, SPA_AUDIO_CHANNEL_LFE },
[SND_CHMAP_SL] = { SND_CHMAP_SL, SPA_AUDIO_CHANNEL_SL },
[SND_CHMAP_SR] = { SND_CHMAP_SR, SPA_AUDIO_CHANNEL_SR },
[SND_CHMAP_RC] = { SND_CHMAP_RC, SPA_AUDIO_CHANNEL_RC },
[SND_CHMAP_FLC] = { SND_CHMAP_FLC, SPA_AUDIO_CHANNEL_FLC },
[SND_CHMAP_FRC] = { SND_CHMAP_FRC, SPA_AUDIO_CHANNEL_FRC },
[SND_CHMAP_RLC] = { SND_CHMAP_RLC, SPA_AUDIO_CHANNEL_RLC },
[SND_CHMAP_RRC] = { SND_CHMAP_RRC, SPA_AUDIO_CHANNEL_RRC },
[SND_CHMAP_FLW] = { SND_CHMAP_FLW, SPA_AUDIO_CHANNEL_FLW },
[SND_CHMAP_FRW] = { SND_CHMAP_FRW, SPA_AUDIO_CHANNEL_FRW },
[SND_CHMAP_FLH] = { SND_CHMAP_FLH, SPA_AUDIO_CHANNEL_FLH },
[SND_CHMAP_FCH] = { SND_CHMAP_FCH, SPA_AUDIO_CHANNEL_FCH },
[SND_CHMAP_FRH] = { SND_CHMAP_FRH, SPA_AUDIO_CHANNEL_FRH },
[SND_CHMAP_TC] = { SND_CHMAP_TC, SPA_AUDIO_CHANNEL_TC },
[SND_CHMAP_TFL] = { SND_CHMAP_TFL, SPA_AUDIO_CHANNEL_TFL },
[SND_CHMAP_TFR] = { SND_CHMAP_TFR, SPA_AUDIO_CHANNEL_TFR },
[SND_CHMAP_TFC] = { SND_CHMAP_TFC, SPA_AUDIO_CHANNEL_TFC },
[SND_CHMAP_TRL] = { SND_CHMAP_TRL, SPA_AUDIO_CHANNEL_TRL },
[SND_CHMAP_TRR] = { SND_CHMAP_TRR, SPA_AUDIO_CHANNEL_TRR },
[SND_CHMAP_TRC] = { SND_CHMAP_TRC, SPA_AUDIO_CHANNEL_TRC },
[SND_CHMAP_TFLC] = { SND_CHMAP_TFLC, SPA_AUDIO_CHANNEL_TFLC },
[SND_CHMAP_TFRC] = { SND_CHMAP_TFRC, SPA_AUDIO_CHANNEL_TFRC },
[SND_CHMAP_TSL] = { SND_CHMAP_TSL, SPA_AUDIO_CHANNEL_TSL },
[SND_CHMAP_TSR] = { SND_CHMAP_TSR, SPA_AUDIO_CHANNEL_TSR },
[SND_CHMAP_LLFE] = { SND_CHMAP_LLFE, SPA_AUDIO_CHANNEL_LLFE },
[SND_CHMAP_RLFE] = { SND_CHMAP_RLFE, SPA_AUDIO_CHANNEL_RLFE },
[SND_CHMAP_BC] = { SND_CHMAP_BC, SPA_AUDIO_CHANNEL_BC },
[SND_CHMAP_BLC] = { SND_CHMAP_BLC, SPA_AUDIO_CHANNEL_BLC },
[SND_CHMAP_BRC] = { SND_CHMAP_BRC, SPA_AUDIO_CHANNEL_BRC },
};
static enum snd_pcm_chmap_position channel_to_chmap(enum spa_audio_channel channel)
{
SPA_FOR_EACH_ELEMENT_VAR(chmap_info, info)
if (info->channel == channel)
return info->pos;
return SND_CHMAP_UNKNOWN;
}
static enum spa_audio_channel chmap_to_channel(enum snd_pcm_chmap_position pos)
{
if (pos >= SPA_N_ELEMENTS(chmap_info))
return SPA_AUDIO_CHANNEL_UNKNOWN;
return chmap_info[pos].channel;
}
static int snd_pcm_pipewire_set_chmap(snd_pcm_ioplug_t * io,
const snd_pcm_chmap_t * map)
{
snd_pcm_pipewire_t *pw = io->private_data;
unsigned int i;
pw->format.channels = map->channels;
for (i = 0; i < map->channels; i++) {
pw->format.position[i] = chmap_to_channel(map->pos[i]);
pw_log_debug("map %d: %s / %s", i,
snd_pcm_chmap_name(map->pos[i]),
spa_debug_type_find_short_name(spa_type_audio_channel,
pw->format.position[i]));
}
return 1;
}
static snd_pcm_chmap_t * snd_pcm_pipewire_get_chmap(snd_pcm_ioplug_t * io)
{
snd_pcm_pipewire_t *pw = io->private_data;
snd_pcm_chmap_t *map;
uint32_t i;
map = calloc(1, sizeof(snd_pcm_chmap_t) +
pw->format.channels * sizeof(unsigned int));
map->channels = pw->format.channels;
for (i = 0; i < pw->format.channels; i++)
map->pos[i] = channel_to_chmap(pw->format.position[i]);
return map;
}
static void make_map(snd_pcm_chmap_query_t **maps, int index, int channels, ...)
{
va_list args;
int i;
maps[index] = malloc(sizeof(snd_pcm_chmap_query_t) + (channels * sizeof(unsigned int)));
maps[index]->type = SND_CHMAP_TYPE_FIXED;
maps[index]->map.channels = channels;
va_start(args, channels);
for (i = 0; i < channels; i++)
maps[index]->map.pos[i] = va_arg(args, int);
va_end(args);
}
static snd_pcm_chmap_query_t **snd_pcm_pipewire_query_chmaps(snd_pcm_ioplug_t *io)
{
snd_pcm_chmap_query_t **maps;
maps = calloc(7, sizeof(*maps));
make_map(maps, 0, 1, SND_CHMAP_MONO);
make_map(maps, 1, 2, SND_CHMAP_FL, SND_CHMAP_FR);
make_map(maps, 2, 4, SND_CHMAP_FL, SND_CHMAP_FR, SND_CHMAP_RL, SND_CHMAP_RR);
make_map(maps, 3, 5, SND_CHMAP_FL, SND_CHMAP_FR, SND_CHMAP_RL, SND_CHMAP_RR,
SND_CHMAP_FC);
make_map(maps, 4, 6, SND_CHMAP_FL, SND_CHMAP_FR, SND_CHMAP_RL, SND_CHMAP_RR,
SND_CHMAP_FC, SND_CHMAP_LFE);
make_map(maps, 5, 8, SND_CHMAP_FL, SND_CHMAP_FR, SND_CHMAP_RL, SND_CHMAP_RR,
SND_CHMAP_FC, SND_CHMAP_LFE, SND_CHMAP_SL, SND_CHMAP_SR);
return maps;
}
static snd_pcm_ioplug_callback_t pipewire_pcm_callback = {
.close = snd_pcm_pipewire_close,
.start = snd_pcm_pipewire_start,
.stop = snd_pcm_pipewire_stop,
.pause = snd_pcm_pipewire_pause,
.pointer = snd_pcm_pipewire_pointer,
.delay = snd_pcm_pipewire_delay,
.drain = snd_pcm_pipewire_drain,
.prepare = snd_pcm_pipewire_prepare,
.poll_descriptors = snd_pcm_pipewire_poll_descriptors,
.poll_revents = snd_pcm_pipewire_poll_revents,
.hw_params = snd_pcm_pipewire_hw_params,
.sw_params = snd_pcm_pipewire_sw_params,
.set_chmap = snd_pcm_pipewire_set_chmap,
.get_chmap = snd_pcm_pipewire_get_chmap,
.query_chmaps = snd_pcm_pipewire_query_chmaps,
};
static int pipewire_set_hw_constraint(snd_pcm_pipewire_t *pw, struct params *p)
{
unsigned int access_list[] = {
SND_PCM_ACCESS_MMAP_INTERLEAVED,
SND_PCM_ACCESS_MMAP_NONINTERLEAVED,
SND_PCM_ACCESS_RW_INTERLEAVED,
SND_PCM_ACCESS_RW_NONINTERLEAVED
};
unsigned int format_list[] = {
#if __BYTE_ORDER == __LITTLE_ENDIAN
SND_PCM_FORMAT_FLOAT_LE,
SND_PCM_FORMAT_S32_LE,
SND_PCM_FORMAT_S24_LE,
SND_PCM_FORMAT_S24_3LE,
SND_PCM_FORMAT_S24_3BE,
SND_PCM_FORMAT_S16_LE,
#elif __BYTE_ORDER == __BIG_ENDIAN
SND_PCM_FORMAT_FLOAT_BE,
SND_PCM_FORMAT_S32_BE,
SND_PCM_FORMAT_S24_BE,
SND_PCM_FORMAT_S24_3LE,
SND_PCM_FORMAT_S24_3BE,
SND_PCM_FORMAT_S16_BE,
#endif
SND_PCM_FORMAT_U8,
};
int min_rate;
int max_rate;
int min_channels;
int max_channels;
int min_period_bytes;
int max_period_bytes;
int min_buffer_bytes;
int max_buffer_bytes;
int err;
if (p->rate > 0) {
min_rate = max_rate = SPA_CLAMP(p->rate, 1, MAX_RATE);
} else {
min_rate = 1;
max_rate = MAX_RATE;
}
if (p->channels > 0) {
min_channels = max_channels = SPA_CLAMP(p->channels, 1, MAX_CHANNELS);
} else {
min_channels = 1;
max_channels = MAX_CHANNELS;
}
if (p->period_bytes > 0) {
min_period_bytes = max_period_bytes = SPA_CLAMP(p->period_bytes,
MIN_PERIOD_BYTES, MAX_PERIOD_BYTES);
} else {
min_period_bytes = MIN_PERIOD_BYTES;
max_period_bytes = MAX_PERIOD_BYTES;
}
if (p->buffer_bytes > 0) {
min_buffer_bytes = max_buffer_bytes = SPA_CLAMP(p->buffer_bytes,
MIN_BUFFER_BYTES, MAX_BUFFER_BYTES);
} else {
min_buffer_bytes = MIN_BUFFER_BYTES;
max_buffer_bytes = MAX_BUFFER_BYTES;
}
if (min_period_bytes * 2 > max_buffer_bytes)
min_period_bytes = max_period_bytes = max_buffer_bytes / 2;
if ((err = snd_pcm_ioplug_set_param_list(&pw->io, SND_PCM_IOPLUG_HW_ACCESS,
SPA_N_ELEMENTS(access_list), access_list)) < 0 ||
(err = snd_pcm_ioplug_set_param_minmax(&pw->io, SND_PCM_IOPLUG_HW_CHANNELS,
min_channels, max_channels)) < 0 ||
(err = snd_pcm_ioplug_set_param_minmax(&pw->io, SND_PCM_IOPLUG_HW_RATE,
min_rate, max_rate)) < 0 ||
(err = snd_pcm_ioplug_set_param_minmax(&pw->io, SND_PCM_IOPLUG_HW_BUFFER_BYTES,
min_buffer_bytes,
max_buffer_bytes)) < 0 ||
(err = snd_pcm_ioplug_set_param_minmax(&pw->io,
SND_PCM_IOPLUG_HW_PERIOD_BYTES,
min_period_bytes,
max_period_bytes)) < 0 ||
(err = snd_pcm_ioplug_set_param_minmax(&pw->io, SND_PCM_IOPLUG_HW_PERIODS,
MIN_BUFFERS, 1024)) < 0) {
pw_log_warn("Can't set param list: %s", snd_strerror(err));
return err;
}
if (p->format != SND_PCM_FORMAT_UNKNOWN) {
err = snd_pcm_ioplug_set_param_list(&pw->io,
SND_PCM_IOPLUG_HW_FORMAT,
1, (unsigned int *)&p->format);
if (err < 0) {
pw_log_warn("Can't set param list: %s", snd_strerror(err));
return err;
}
} else {
err = snd_pcm_ioplug_set_param_list(&pw->io,
SND_PCM_IOPLUG_HW_FORMAT,
SPA_N_ELEMENTS(format_list),
format_list);
if (err < 0) {
pw_log_warn("Can't set param list: %s", snd_strerror(err));
return err;
}
}
return 0;
}
static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message)
{
snd_pcm_pipewire_t *pw = data;
pw_log_warn("%p: error id:%u seq:%d res:%d (%s): %s", pw,
id, seq, res, spa_strerror(res), message);
if (id == PW_ID_CORE) {
pw->error = res;
if (pw->fd != -1)
update_active(&pw->io);
}
pw_thread_loop_signal(pw->main_loop, false);
}
static const struct pw_core_events core_events = {
PW_VERSION_CORE_EVENTS,
.error = on_core_error,
};
static ssize_t log_write(void *cookie, const char *buf, size_t size)
{
int len;
while (size > 0) {
len = strcspn(buf, "\n");
if (len > 0)
pw_log_debug("%.*s", (int)len, buf);
buf += len + 1;
size -= len + 1;
}
return size;
}
static cookie_io_functions_t io_funcs = {
.write = log_write,
};
static int snd_pcm_pipewire_open(snd_pcm_t **pcmp,
struct params *p, snd_pcm_stream_t stream, int mode)
{
snd_pcm_pipewire_t *pw;
int err;
const char *str;
struct pw_properties *props = NULL;
struct pw_loop *loop;
uint32_t val;
assert(pcmp);
pw = calloc(1, sizeof(*pw));
if (!pw)
return -ENOMEM;
props = pw_properties_new(NULL, NULL);
if (props == NULL) {
err = -errno;
goto error;
}
str = getenv("PIPEWIRE_ALSA");
if (str != NULL) {
pw_properties_update_string(props, str, strlen(str));
if ((str = pw_properties_get(props, "alsa.format")))
p->format = snd_pcm_format_value(str);
if ((str = pw_properties_get(props, "alsa.rate")) &&
spa_atou32(str, &val, 0))
p->rate = val;
if ((str = pw_properties_get(props, "alsa.channels")) &&
spa_atou32(str, &val, 0))
p->channels = val;
if ((str = pw_properties_get(props, "alsa.period-bytes")) &&
spa_atou32(str, &val, 0))
p->period_bytes = val;
if ((str = pw_properties_get(props, "alsa.buffer-bytes")) &&
spa_atou32(str, &val, 0))
p->buffer_bytes = val;
}
str = getenv("PIPEWIRE_REMOTE");
if (str != NULL && str[0] != '\0')
p->server_name = str;
str = getenv("PIPEWIRE_NODE");
pw_log_debug("%p: open name:%s stream:%s mode:%d flags:%08x rate:%d format:%s "
"channels:%d period-bytes:%d buffer-bytes:%d target:'%s'", pw, p->node_name,
snd_pcm_stream_name(stream), mode, p->flags, p->rate,
snd_pcm_format_name(p->format), p->channels, p->period_bytes,
p->buffer_bytes, str);
pw->fd = -1;
pw->io.poll_fd = -1;
pw->flags = p->flags;
pw->log_file = fopencookie(pw, "w", io_funcs);
if (pw->log_file == NULL) {
pw_log_error("can't create log file: %m");
err = -errno;
goto error;
}
if ((err = snd_output_stdio_attach(&pw->output, pw->log_file, 0)) < 0) {
pw_log_error("can't attach log file: %s", snd_strerror(err));
goto error;
}
if (p->node_name == NULL)
pw->node_name = spa_aprintf("ALSA %s",
stream == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture");
else
pw->node_name = strdup(p->node_name);
if (pw->node_name == NULL) {
err = -errno;
goto error;
}
pw->target = NULL;
if (str != NULL)
pw->target = strdup(str);
else {
if (stream == SND_PCM_STREAM_PLAYBACK)
pw->target = p->playback_node ? strdup(p->playback_node) : NULL;
else
pw->target = p->capture_node ? strdup(p->capture_node) : NULL;
}
pw->role = (p->role && *p->role) ? strdup(p->role) : NULL;
pw->main_loop = pw_thread_loop_new("alsa-pipewire", NULL);
if (pw->main_loop == NULL) {
err = -errno;
goto error;
}
loop = pw_thread_loop_get_loop(pw->main_loop);
pw->system = loop->system;
if ((pw->context = pw_context_new(loop,
pw_properties_new(
PW_KEY_CONFIG_NAME, "client-rt.conf",
NULL),
0)) == NULL) {
err = -errno;
goto error;
}
pw_properties_setf(props, PW_KEY_APP_NAME, "PipeWire ALSA [%s]",
pw_get_prgname());
if (p->server_name)
pw_properties_set(props, PW_KEY_REMOTE_NAME, p->server_name);
if ((err = pw_thread_loop_start(pw->main_loop)) < 0)
goto error;
pw_thread_loop_lock(pw->main_loop);
pw->core = pw_context_connect(pw->context, props, 0);
props = NULL;
if (pw->core == NULL) {
err = -errno;
pw_thread_loop_unlock(pw->main_loop);
goto error;
}
pw_core_add_listener(pw->core, &pw->core_listener, &core_events, pw);
pw_thread_loop_unlock(pw->main_loop);
pw->fd = spa_system_eventfd_create(pw->system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK);
pw->io.version = SND_PCM_IOPLUG_VERSION;
pw->io.name = "ALSA <-> PipeWire PCM I/O Plugin";
pw->io.callback = &pipewire_pcm_callback;
pw->io.private_data = pw;
pw->io.poll_fd = pw->fd;
pw->io.poll_events = POLLIN;
pw->io.mmap_rw = 1;
#ifdef SND_PCM_IOPLUG_FLAG_BOUNDARY_WA
pw->io.flags = SND_PCM_IOPLUG_FLAG_BOUNDARY_WA;
#else
#warning hw_ptr updates of buffer_size will not be recognized by the ALSA library. Consider to update your ALSA library.
#endif
pw->io.flags |= SND_PCM_IOPLUG_FLAG_MONOTONIC;
if ((err = snd_pcm_ioplug_create(&pw->io, p->node_name, stream, mode)) < 0)
goto error;
if ((err = pipewire_set_hw_constraint(pw, p)) < 0)
goto error;
pw_log_debug("%p: opened name:%s stream:%s mode:%d", pw, p->node_name,
snd_pcm_stream_name(pw->io.stream), mode);
*pcmp = pw->io.pcm;
return 0;
error:
pw_log_debug("%p: failed to open %s :%s", pw, p->node_name, spa_strerror(err));
pw_properties_free(props);
snd_pcm_pipewire_free(pw);
return err;
}
SPA_EXPORT
SND_PCM_PLUGIN_DEFINE_FUNC(pipewire)
{
snd_config_iterator_t i, next;
struct params params;
int err;
pw_init(NULL, NULL);
if (strstr(pw_get_library_version(), "0.2") != NULL)
return -ENOTSUP;
PW_LOG_TOPIC_INIT(alsa_log_topic);
spa_zero(params);
params.format = SND_PCM_FORMAT_UNKNOWN;
snd_config_for_each(i, next, conf) {
snd_config_t *n = snd_config_iterator_entry(i);
const char *id;
if (snd_config_get_id(n, &id) < 0)
continue;
if (spa_streq(id, "comment") || spa_streq(id, "type") || spa_streq(id, "hint"))
continue;
if (spa_streq(id, "name")) {
snd_config_get_string(n, &params.node_name);
continue;
}
if (spa_streq(id, "server")) {
snd_config_get_string(n, &params.server_name);
continue;
}
if (spa_streq(id, "playback_node")) {
snd_config_get_string(n, &params.playback_node);
continue;
}
if (spa_streq(id, "capture_node")) {
snd_config_get_string(n, &params.capture_node);
continue;
}
if (spa_streq(id, "role")) {
snd_config_get_string(n, &params.role);
continue;
}
if (spa_streq(id, "exclusive")) {
if (snd_config_get_bool(n))
params.flags |= PW_STREAM_FLAG_EXCLUSIVE;
continue;
}
if (spa_streq(id, "rate")) {
long val;
if (snd_config_get_integer(n, &val) == 0)
params.rate = val;
else
SNDERR("%s: invalid type", id);
continue;
}
if (spa_streq(id, "format")) {
const char *str;
if (snd_config_get_string(n, &str) == 0) {
params.format = snd_pcm_format_value(str);
if (*str && params.format == SND_PCM_FORMAT_UNKNOWN)
SNDERR("%s: invalid value %s", id, str);
} else {
SNDERR("%s: invalid type", id);
}
continue;
}
if (spa_streq(id, "channels")) {
long val;
if (snd_config_get_integer(n, &val) == 0)
params.channels = val;
else
SNDERR("%s: invalid type", id);
continue;
}
if (spa_streq(id, "period_bytes")) {
long val;
if (snd_config_get_integer(n, &val) == 0)
params.period_bytes = val;
else
SNDERR("%s: invalid type", id);
continue;
}
if (spa_streq(id, "buffer_bytes")) {
long val;
if (snd_config_get_integer(n, &val) == 0)
params.buffer_bytes = val;
else
SNDERR("%s: invalid type", id);
continue;
}
SNDERR("Unknown field %s", id);
return -EINVAL;
}
err = snd_pcm_pipewire_open(pcmp, &params, stream, mode);
return err;
}
SPA_EXPORT
SND_PCM_PLUGIN_SYMBOL(pipewire);