spa: alsa: support also MIDI-1.0 IO for ALSA seq

Support also non-UMP IO with ALSA seq, in case either alsa-lib or the
kernel does not have UMP enabled.

Add configuration option "api.alsa.seq.ump" for optionally turning UMP
I/O off, for easier debugging.
This commit is contained in:
Pauli Virtanen 2025-03-09 19:38:01 +02:00 committed by P V
parent 6620c6cde1
commit 4379cf446f
3 changed files with 163 additions and 85 deletions

View file

@ -931,6 +931,7 @@ impl_init(const struct spa_handle_factory *factory,
this->quantum_limit = 8192;
this->min_pool_size = 500;
this->max_pool_size = 2000;
this->ump = true;
for (i = 0; info && i < info->n_items; i++) {
const char *k = info->items[i].key;
@ -949,6 +950,8 @@ impl_init(const struct spa_handle_factory *factory,
spa_atou32(s, &this->min_pool_size, 0);
} else if (spa_streq(k, "api.alsa.seq.max-pool")) {
spa_atou32(s, &this->max_pool_size, 0);
} else if (spa_streq(k, "api.alsa.seq.ump")) {
this->ump = spa_atob(s);
}
}
@ -992,6 +995,7 @@ static const struct spa_dict_item info_items[] = {
"["SPA_KEY_API_ALSA_DISABLE_LONGNAME"=<bool, default false>] "
"[ api.alsa.seq.min-pool=<min-pool, default 500>] "
"[ api.alsa.seq.max-pool=<max-pool, default 2000>]"
"[ api.alsa.seq.ump = <boolean> ]"
},
};

View file

@ -24,7 +24,7 @@
#define CHECK(s,msg,...) if ((res = (s)) < 0) { spa_log_error(state->log, msg ": %s", ##__VA_ARGS__, snd_strerror(res)); return res; }
static int seq_open(struct seq_state *state, struct seq_conn *conn, bool with_queue)
static int seq_open(struct seq_state *state, struct seq_conn *conn, bool with_queue, bool probe_ump)
{
struct props *props = &state->props;
int res;
@ -37,14 +37,29 @@ static int seq_open(struct seq_state *state, struct seq_conn *conn, bool with_qu
0)) < 0)
return res;
#ifdef ALSA_UMP
if ((res = snd_seq_set_client_midi_version(conn->hndl, SND_SEQ_CLIENT_UMP_MIDI_2_0)) < 0) {
snd_seq_close(conn->hndl);
spa_log_info(state->log, "%p: ALSA failed to enable UMP MIDI: %s",
state, snd_strerror(res));
return res;
if (!state->ump) {
spa_log_info(state->log, "%p: ALSA UMP MIDI disabled", state);
return 0;
}
#ifdef ALSA_UMP
res = snd_seq_set_client_midi_version(conn->hndl, SND_SEQ_CLIENT_UMP_MIDI_2_0);
#else
res = -EOPNOTSUPP;
#endif
if (res < 0) {
spa_log_lev(state->log, (probe_ump ? SPA_LOG_LEVEL_INFO : SPA_LOG_LEVEL_ERROR),
"%p: ALSA failed to enable UMP MIDI: %s", state, snd_strerror(res));
if (!probe_ump) {
snd_seq_close(conn->hndl);
return res; /* either all are UMP or none are UMP */
}
state->ump = false;
} else {
spa_log_debug(state->log, "%p: ALSA UMP MIDI enabled", state);
state->ump = true;
}
return 0;
}
@ -174,11 +189,7 @@ static void init_ports(struct seq_state *state)
}
}
#ifdef ALSA_UMP
static void debug_event(struct seq_state *state, snd_seq_ump_event_t *ev)
#else
static void debug_event(struct seq_state *state, snd_seq_event_t *ev)
#endif
{
if (SPA_LIKELY(!spa_log_level_topic_enabled(state->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_TRACE)))
return;
@ -205,19 +216,33 @@ static void debug_event(struct seq_state *state, snd_seq_event_t *ev)
static void alsa_seq_on_sys(struct spa_source *source)
{
struct seq_state *state = source->data;
#ifdef ALSA_UMP
snd_seq_ump_event_t *ev;
#else
snd_seq_event_t *ev;
#endif
int res;
#ifdef ALSA_UMP
while (snd_seq_ump_event_input(state->sys.hndl, &ev) > 0) {
while (1) {
const snd_seq_addr_t *addr;
if (state->ump) {
#if ALSA_UMP
snd_seq_ump_event_t *ump_ev;
res = snd_seq_ump_event_input(state->sys.hndl, &ump_ev);
if (res <= 0)
break;
/* Structures are layout-compatible */
ev = (snd_seq_event_t *)ump_ev;
addr = &ump_ev->data.addr;
#else
while (snd_seq_event_input(state->sys.hndl, &ev) > 0) {
spa_assert_not_reached();
#endif
const snd_seq_addr_t *addr = &ev->data.addr;
} else {
res = snd_seq_event_input(state->sys.hndl, &ev);
if (res <= 0)
break;
addr = &ev->data.addr;
}
if (addr->client == state->event.addr.client)
continue;
@ -283,8 +308,8 @@ int spa_alsa_seq_open(struct seq_state *state)
spa_zero(reserve);
for (i = 0; i < 16; i++) {
spa_log_debug(state->log, "close %d", i);
if ((res = seq_open(state, &reserve[i], false)) < 0)
spa_log_debug(state->log, "open %d", i);
if ((res = seq_open(state, &reserve[i], false, (i == 0))) < 0)
break;
}
if (i >= 2) {
@ -543,31 +568,47 @@ static int process_recycle(struct seq_state *state)
static int process_read(struct seq_state *state)
{
#ifdef ALSA_UMP
snd_seq_ump_event_t *ev;
#else
snd_seq_event_t *ev;
#endif
struct seq_stream *stream = &state->streams[SPA_DIRECTION_OUTPUT];
uint32_t i;
#ifdef ALSA_UMP
uint32_t *data;
#else
uint8_t data[MAX_EVENT_SIZE];
#endif
uint8_t midi1_data[MAX_EVENT_SIZE];
uint32_t ump_data[MAX_EVENT_SIZE];
long size;
int res = -1;
/* copy all new midi events into their port buffers */
#ifdef ALSA_UMP
while ((res = snd_seq_ump_event_input(state->event.hndl, &ev)) > 0) {
#else
while (snd_seq_event_input(state->event.hndl, &ev) > 0) {
#endif
const snd_seq_addr_t *addr = &ev->source;
while (1) {
const snd_seq_addr_t *addr;
struct seq_port *port;
uint64_t ev_time, diff;
uint32_t offset;
#ifdef ALSA_UMP
snd_seq_ump_event_t *ump_ev;
#endif
uint8_t *midi1_ptr;
size_t midi1_size;
uint64_t ump_state = 0;
if (state->ump) {
#ifdef ALSA_UMP
res = snd_seq_ump_event_input(state->event.hndl, &ump_ev);
if (res <= 0)
break;
/* Structures are layout-compatible */
ev = (snd_seq_event_t *)ump_ev;
addr = &ump_ev->source;
#else
spa_assert_not_reached();
#endif
} else {
res = snd_seq_event_input(state->event.hndl, &ev);
if (res <= 0)
break;
addr = &ev->source;
}
debug_event(state, ev);
@ -585,16 +626,23 @@ static int process_read(struct seq_state *state)
continue;
}
if (state->ump) {
#ifdef ALSA_UMP
data = (uint32_t*)&ev->ump[0];
size = spa_ump_message_size(snd_ump_msg_hdr_type(ev->ump[0])) * 4;
data = (uint32_t*)&ump_ev->ump[0];
size = spa_ump_message_size(snd_ump_msg_hdr_type(ump_ev->ump[0])) * 4;
#else
snd_midi_event_reset_decode(stream->codec);
if ((size = snd_midi_event_decode(stream->codec, data, MAX_EVENT_SIZE, ev)) < 0) {
spa_log_warn(state->log, "decode failed: %s", snd_strerror(size));
continue;
}
spa_assert_not_reached();
#endif
} else {
snd_midi_event_reset_decode(stream->codec);
if ((size = snd_midi_event_decode(stream->codec, midi1_data, sizeof(midi1_data), ev)) < 0) {
spa_log_warn(state->log, "decode failed: %s", snd_strerror(size));
continue;
}
midi1_ptr = midi1_data;
midi1_size = size;
}
/* queue_time is the estimated current time of the queue as calculated by
* the DLL. Calculate the age of the event. */
@ -611,19 +659,32 @@ static int process_read(struct seq_state *state)
else
offset = 0;
spa_log_trace_fp(state->log, "event %d time:%"PRIu64" offset:%d size:%ld port:%d.%d",
ev->type, ev_time, offset, size, addr->client, addr->port);
do {
if (!state->ump) {
data = ump_data;
size = spa_ump_from_midi(&midi1_ptr, &midi1_size,
ump_data, sizeof(ump_data), 0, &ump_state);
if (size <= 0)
break;
}
spa_pod_builder_control(&port->builder, offset, SPA_CONTROL_UMP);
spa_pod_builder_bytes(&port->builder, data, size);
spa_log_trace_fp(state->log, "event %d time:%"PRIu64" offset:%d size:%ld port:%d.%d",
ev->type, ev_time, offset, size, addr->client, addr->port);
/* make sure we can fit at least one control event of max size otherwise
* we keep the event in the queue and try to copy it in the next cycle */
if (port->builder.state.offset +
sizeof(struct spa_pod_control) +
MAX_EVENT_SIZE > port->buffer->buf->datas[0].maxsize)
break;
spa_pod_builder_control(&port->builder, offset, SPA_CONTROL_UMP);
spa_pod_builder_bytes(&port->builder, data, size);
/* make sure we can fit at least one control event of max size otherwise
* we keep the event in the queue and try to copy it in the next cycle */
if (port->builder.state.offset +
sizeof(struct spa_pod_control) +
MAX_EVENT_SIZE > port->buffer->buf->datas[0].maxsize)
goto done;
} while (!state->ump);
}
done:
if (res < 0 && res != -EAGAIN)
spa_log_warn(state->log, "event read failed: %s", snd_strerror(res));
@ -695,11 +756,6 @@ static int process_write(struct seq_state *state)
struct spa_pod_sequence *pod;
struct spa_data *d;
struct spa_pod_control *c;
#ifdef ALSA_UMP
snd_seq_ump_event_t ev;
#else
snd_seq_event_t ev;
#endif
uint64_t out_time;
snd_seq_real_time_t out_rt;
@ -725,8 +781,12 @@ static int process_write(struct seq_state *state)
}
SPA_POD_SEQUENCE_FOREACH(pod, c) {
size_t body_size;
snd_seq_event_t *ev;
snd_seq_event_t midi1_ev;
#ifdef ALSA_UMP
snd_seq_ump_event_t ump_ev;
#endif
size_t body_size;
uint8_t *body;
if (c->type != SPA_CONTROL_UMP)
@ -734,48 +794,61 @@ static int process_write(struct seq_state *state)
body = SPA_POD_BODY(&c->value);
body_size = SPA_POD_BODY_SIZE(&c->value);
spa_zero(ev);
memcpy(ev.ump, body, SPA_MIN(sizeof(ev.ump), (size_t)body_size));
if (state->ump) {
#ifdef ALSA_UMP
spa_zero(ump_ev);
memcpy(ump_ev.ump, body, SPA_MIN(sizeof(ump_ev.ump), (size_t)body_size));
ev = (snd_seq_event_t *)&ump_ev;
#else
if (c->type != SPA_CONTROL_Midi)
continue;
snd_seq_ev_clear(&ev);
snd_midi_event_reset_encode(stream->codec);
if ((body_size = snd_midi_event_encode(stream->codec,
SPA_POD_BODY(&c->value),
SPA_POD_BODY_SIZE(&c->value), &ev)) <= 0) {
spa_log_warn(state->log, "failed to encode event: %s",
snd_strerror(body_size));
continue;
}
spa_assert_not_reached();
#endif
} else {
uint8_t data[MAX_EVENT_SIZE];
int size;
snd_seq_ev_set_source(&ev, state->event.addr.port);
snd_seq_ev_set_dest(&ev, port->addr.client, port->addr.port);
if ((size = spa_ump_to_midi((uint32_t *)body, body_size, data, sizeof(data))) <= 0)
continue;
snd_seq_ev_clear(&midi1_ev);
snd_midi_event_reset_encode(stream->codec);
if ((size = snd_midi_event_encode(stream->codec, data, size, &midi1_ev)) <= 0) {
spa_log_warn(state->log, "failed to encode event: %s", snd_strerror(size));
continue;
}
ev = &midi1_ev;
body_size = size;
}
snd_seq_ev_set_source(ev, state->event.addr.port);
snd_seq_ev_set_dest(ev, port->addr.client, port->addr.port);
out_time = state->queue_time + NSEC_FROM_CLOCK(&state->rate, c->offset);
out_rt.tv_nsec = out_time % SPA_NSEC_PER_SEC;
out_rt.tv_sec = out_time / SPA_NSEC_PER_SEC;
snd_seq_ev_schedule_real(&ev, state->event.queue_id, 0, &out_rt);
snd_seq_ev_schedule_real(ev, state->event.queue_id, 0, &out_rt);
spa_log_trace_fp(state->log, "event %d time:%"PRIu64" offset:%d size:%zd port:%d.%d",
ev.type, out_time, c->offset, body_size, port->addr.client, port->addr.port);
ev->type, out_time, c->offset, body_size, port->addr.client, port->addr.port);
if (state->ump) {
#ifdef ALSA_UMP
if ((err = snd_seq_ump_event_output(state->event.hndl, &ev)) < 0) {
spa_log_warn(state->log, "failed to output event: %s",
snd_strerror(err));
}
if ((err = snd_seq_ump_event_output(state->event.hndl, &ump_ev)) < 0) {
spa_log_warn(state->log, "failed to output event: %s",
snd_strerror(err));
}
#else
if ((err = snd_seq_event_output(state->event.hndl, &ev)) < 0) {
spa_log_warn(state->log, "failed to output event: %s",
snd_strerror(err));
}
spa_assert_not_reached();
#endif
} else {
if ((err = snd_seq_event_output(state->event.hndl, ev)) < 0) {
spa_log_warn(state->log, "failed to output event: %s",
snd_strerror(err));
}
}
}
}
snd_seq_drain_output(state->event.hndl);

View file

@ -155,6 +155,7 @@ struct seq_state {
unsigned int opened:1;
unsigned int started:1;
unsigned int following:1;
unsigned int ump:1;
struct seq_stream streams[2];