jack: safely update the mixer io areas

The buffer io area is used to enable/disable a port and should be
synchronized with the processing loop. Use invoke to safely update the
mixer io area.

Keep track of the number of mixers on a port and clear the global mixer
buffer io when there are none. Check and use the global mixer io when
processing output to avoid using an inactive output without buffers and
crashing.

Fixes #3506
This commit is contained in:
Wim Taymans 2023-09-13 10:59:35 +02:00
parent 18f29bd840
commit 56786aedc4

View file

@ -247,6 +247,7 @@ struct port {
struct spa_io_buffers io;
struct spa_list mix;
uint32_t n_mix;
struct mix *global_mix;
struct port *tied;
@ -526,6 +527,27 @@ static void free_object(struct client *c, struct object *o)
}
struct io_info {
struct mix *mix;
void *data;
};
static int
do_mix_set_io(struct spa_loop *loop, bool async, uint32_t seq,
const void *data, size_t size, void *user_data)
{
struct io_info *info = user_data;
info->mix->io = info->data;
return 0;
}
static inline void mix_set_io(struct mix *mix, void *data)
{
struct io_info info = { .mix = mix, .data = data };
pw_data_loop_invoke(mix->port->client->loop,
do_mix_set_io, SPA_ID_INVALID, NULL, 0, true, &info);
}
static void init_mix(struct mix *mix, uint32_t mix_id, struct port *port, uint32_t peer_id)
{
pw_log_debug("create %p mix:%d peer:%d", port, mix_id, peer_id);
@ -538,6 +560,8 @@ static void init_mix(struct mix *mix, uint32_t mix_id, struct port *port, uint32
spa_list_init(&mix->queue);
if (mix_id == SPA_ID_INVALID)
port->global_mix = mix;
else if (port->n_mix++ == 0 && port->global_mix != NULL)
mix_set_io(port->global_mix, &port->io);
}
static struct mix *find_mix_peer(struct client *c, uint32_t peer_id)
{
@ -618,10 +642,14 @@ static int clear_buffers(struct client *c, struct mix *mix)
static void free_mix(struct client *c, struct mix *mix)
{
struct port *port = mix->port;
clear_buffers(c, mix);
spa_list_remove(&mix->port_link);
if (mix->id == SPA_ID_INVALID)
mix->port->global_mix = NULL;
port->global_mix = NULL;
else if (--port->n_mix == 0 && port->global_mix != NULL)
mix_set_io(port->global_mix, NULL);
spa_list_remove(&mix->link);
spa_list_append(&c->free_mix, &mix->link);
}
@ -1398,6 +1426,7 @@ static inline void *get_buffer_output(struct port *p, uint32_t frames, uint32_t
void *ptr = NULL;
struct buffer *b;
struct spa_data *d;
struct spa_io_buffers *io;
if (frames == 0 || !p->valid)
return NULL;
@ -1405,20 +1434,21 @@ static inline void *get_buffer_output(struct port *p, uint32_t frames, uint32_t
if (SPA_UNLIKELY((mix = p->global_mix) == NULL))
return NULL;
pw_log_trace_fp("%p: port %s %d get buffer %d n_buffers:%d",
c, p->object->port.name, p->port_id, frames, mix->n_buffers);
pw_log_trace_fp("%p: port %s %d get buffer %d n_buffers:%d io:%p",
c, p->object->port.name, p->port_id, frames,
mix->n_buffers, mix->io);
if (SPA_UNLIKELY(mix->n_buffers == 0))
if (SPA_UNLIKELY((io = mix->io) == NULL))
return NULL;
if (p->io.status == SPA_STATUS_HAVE_DATA &&
p->io.buffer_id < mix->n_buffers) {
b = &mix->buffers[p->io.buffer_id];
if (io->status == SPA_STATUS_HAVE_DATA &&
io->buffer_id < mix->n_buffers) {
b = &mix->buffers[io->buffer_id];
d = &b->datas[0];
} else {
if (p->io.buffer_id < mix->n_buffers) {
reuse_buffer(c, mix, p->io.buffer_id);
p->io.buffer_id = SPA_ID_INVALID;
if (io->buffer_id < mix->n_buffers) {
reuse_buffer(c, mix, io->buffer_id);
io->buffer_id = SPA_ID_INVALID;
}
if (SPA_UNLIKELY((b = dequeue_buffer(c, mix)) == NULL)) {
pw_log_warn("port %p: out of buffers", p);
@ -1429,8 +1459,8 @@ static inline void *get_buffer_output(struct port *p, uint32_t frames, uint32_t
d->chunk->size = frames * sizeof(float);
d->chunk->stride = stride;
p->io.status = SPA_STATUS_HAVE_DATA;
p->io.buffer_id = b->id;
io->status = SPA_STATUS_HAVE_DATA;
io->buffer_id = b->id;
}
ptr = d->data;
if (buf)
@ -1472,14 +1502,19 @@ static inline void process_empty(struct port *p, uint32_t frames)
static void prepare_output(struct port *p, uint32_t frames)
{
struct mix *mix;
struct spa_io_buffers *io;
if (SPA_UNLIKELY(p->empty_out || p->tied))
process_empty(p, frames);
if (p->global_mix == NULL || (io = p->global_mix->io) == NULL)
return;
spa_list_for_each(mix, &p->mix, port_link) {
if (SPA_LIKELY(mix->io != NULL))
*mix->io = p->io;
*mix->io = *io;
}
io->status = SPA_STATUS_NEED_DATA;
}
static void complete_process(struct client *c, uint32_t frames)
@ -1495,7 +1530,6 @@ static void complete_process(struct client *c, uint32_t frames)
if (!p->valid)
continue;
prepare_output(p, frames);
p->io.status = SPA_STATUS_NEED_DATA;
}
pw_array_for_each(item, &c->ports[SPA_DIRECTION_INPUT].items) {
if (pw_map_item_is_free(item))
@ -2688,7 +2722,7 @@ static int client_node_port_set_io(void *data,
switch (id) {
case SPA_IO_Buffers:
mix->io = ptr;
mix_set_io(mix, ptr);
break;
default:
break;
@ -2815,6 +2849,9 @@ static int client_node_port_set_mix_info(void *data,
mix = find_mix(c, p, mix_id);
pw_log_debug("%p: port %p mix:%d peer_id:%u info:%p", c, p, mix_id,
peer_id, props);
if (peer_id == SPA_ID_INVALID) {
if (mix == NULL) {
res = -ENOENT;