From 20db9e2d7004fea80a313bc3839c595fa46e2de0 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 11 Sep 2023 11:04:49 +0200 Subject: [PATCH] alsa: link driver and follower When we are using the same clock (!matching) try to link the two PCM devices together. This starts and stops the devices at the same time and gives better latency. --- spa/plugins/alsa/acp/acp.c | 4 ++ spa/plugins/alsa/alsa-pcm.c | 90 ++++++++++++++++++++++++++++++++----- spa/plugins/alsa/alsa-pcm.h | 1 + 3 files changed, 83 insertions(+), 12 deletions(-) diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index 4efc97c8f..67b1e4654 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -388,6 +388,8 @@ static int add_pro_profile(pa_card *impl, uint32_t index) pa_alsa_init_proplist_pcm(NULL, m->output_proplist, m->output_pcm); pa_proplist_setf(m->output_proplist, "clock.name", "api.alsa.%u", index); pa_proplist_setf(m->output_proplist, "device.profile.pro", "true"); + pa_proplist_setf(m->output_proplist, "node.group", "pro-audio-%u", index); + pa_proplist_setf(m->output_proplist, "node.link-group", "pro-audio-%u", index); pa_alsa_close(&m->output_pcm); m->supported = true; pa_channel_map_init_auto(&m->channel_map, m->sample_spec.channels, PA_CHANNEL_MAP_AUX); @@ -419,6 +421,8 @@ static int add_pro_profile(pa_card *impl, uint32_t index) pa_alsa_init_proplist_pcm(NULL, m->input_proplist, m->input_pcm); pa_proplist_setf(m->input_proplist, "clock.name", "api.alsa.%u", index); pa_proplist_setf(m->input_proplist, "device.profile.pro", "true"); + pa_proplist_setf(m->input_proplist, "node.group", "pro-audio-%u", index); + pa_proplist_setf(m->input_proplist, "node.link-group", "pro-audio-%u", index); pa_alsa_close(&m->input_pcm); m->supported = true; pa_channel_map_init_auto(&m->channel_map, m->sample_spec.channels, PA_CHANNEL_MAP_AUX); diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index d826f8c54..73e93969d 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -512,6 +512,7 @@ int spa_alsa_init(struct state *state, const struct spa_dict *info) state->multi_rate = true; state->htimestamp = false; + state->disable_tsched = state->is_pro; for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; @@ -627,6 +628,27 @@ error: return err; } +static int do_link(struct state *driver, struct state *state) +{ + int res; + snd_pcm_status_t *status; + + snd_pcm_status_alloca(&status); + snd_pcm_status(driver->hndl, status); + snd_pcm_status_dump(status, state->output); + snd_pcm_status(state->hndl, status); + snd_pcm_status_dump(status, state->output); + fflush(state->log_file); + + res = snd_pcm_link(driver->hndl, state->hndl); + if (res >= 0 || res == -EALREADY) + state->linked = true; + + spa_log_info(state->log, "%p: linked to driver %p: %u (%s)", + state, driver, state->linked, snd_strerror(res)); + return 0; +} + int spa_alsa_open(struct state *state, const char *params) { int err; @@ -663,6 +685,7 @@ int spa_alsa_open(struct state *state, const char *params) /* ALSA pollfds may only be ready after setting swparams, so * these are initialised in spa_alsa_start() */ } + state->opened = true; state->sample_count = 0; state->sample_time = 0; @@ -678,6 +701,26 @@ error_exit_close: return err; } +static void try_unlink(struct state *state) +{ + struct state *follower; + + if (state->driver != NULL && state->linked) { + snd_pcm_unlink(state->hndl); + spa_log_info(state->log, "%p: unlinked from driver %p", + state, state->driver); + state->linked = false; + } + spa_list_for_each(follower, &state->followers, driver_link) { + if (follower->opened && follower->linked) { + snd_pcm_unlink(follower->hndl); + spa_log_info(state->log, "%p: follower unlinked from driver %p", + follower, state); + follower->linked = false; + } + } +} + int spa_alsa_close(struct state *state) { int err = 0; @@ -685,6 +728,8 @@ int spa_alsa_close(struct state *state) if (!state->opened) return 0; + try_unlink(state); + spa_alsa_pause(state); spa_log_info(state->log, "%p: Device '%s' closing", state, state->name); @@ -702,6 +747,7 @@ int spa_alsa_close(struct state *state) state->have_format = false; state->opened = false; + state->linked = false; if (state->pitch_elem) { snd_ctl_elem_value_free(state->pitch_elem); @@ -1917,8 +1963,8 @@ static inline int do_start(struct state *state) { int res; if (SPA_UNLIKELY(!state->alsa_started)) { - spa_log_trace(state->log, "%p: snd_pcm_start", state); - if ((res = snd_pcm_start(state->hndl)) < 0) { + spa_log_trace(state->log, "%p: snd_pcm_start %u", state, state->linked); + if (!state->linked && (res = snd_pcm_start(state->hndl)) < 0) { spa_log_error(state->log, "%s: snd_pcm_start: %s", state->name, snd_strerror(res)); return res; @@ -1986,10 +2032,14 @@ recover: state->name, snd_strerror(res)); return res; } - - spa_alsa_prepare(state); - - return spa_alsa_start(state); + if (state->driver && state->linked) { + spa_alsa_prepare(state->driver); + res = spa_alsa_start(state->driver); + } else { + spa_alsa_prepare(state); + res = spa_alsa_start(state); + } + return res; } static inline snd_pcm_sframes_t alsa_avail(struct state *state) @@ -2279,7 +2329,7 @@ static int alsa_write_sync(struct state *state, uint64_t current_time) if (SPA_UNLIKELY((res = update_time(state, current_time, delay, target, following)) < 0)) return res; - if (following) { + if (following && !state->linked) { if (SPA_UNLIKELY(state->alsa_sync)) { enum spa_log_level lev; @@ -2318,7 +2368,6 @@ static int alsa_write_frames(struct state *state) total_written = 0; again: - frames = state->buffer_frames; if (state->use_mmap && frames > 0) { if (SPA_UNLIKELY((res = snd_pcm_mmap_begin(hndl, &my_areas, &offset, &frames)) < 0)) { @@ -2512,7 +2561,6 @@ push_frames(struct state *state, return total_frames; } - static int alsa_read_sync(struct state *state, uint64_t current_time) { int res, suppressed; @@ -2897,6 +2945,7 @@ static int do_state_sync(struct spa_loop *loop, bool async, uint32_t seq, int spa_alsa_prepare(struct state *state) { + struct state *follower; int err; spa_alsa_pause(state); @@ -2935,6 +2984,14 @@ int spa_alsa_prepare(struct state *state) state->alsa_recovering = false; state->alsa_started = false; + spa_list_for_each(follower, &state->followers, driver_link) { + if (follower != state && !follower->matching) { + spa_alsa_prepare(follower); + if (!follower->linked) + do_link(state, follower); + } + } + state->prepared = true; return 0; @@ -2942,6 +2999,7 @@ int spa_alsa_prepare(struct state *state) int spa_alsa_start(struct state *state) { + struct state *follower; int err; if (state->started) @@ -2988,6 +3046,10 @@ int spa_alsa_start(struct state *state) } } + spa_list_for_each(follower, &state->followers, driver_link) + if (follower != state) + spa_alsa_start(follower); + /* start capture now. We should have some data when the timer or IRQ * goes off later */ if (state->stream == SND_PCM_STREAM_CAPTURE) { @@ -3036,8 +3098,9 @@ int spa_alsa_reassign_follower(struct state *state) spa_log_debug(state->log, "%p: reassign driver %p->%p", state, state->driver, driver); if (state->driver != NULL) spa_list_remove(&state->driver_link); - if (driver) + if (driver != NULL) { spa_list_append(&driver->followers, &state->driver_link); + } state->driver = driver; } if (following != state->following) { @@ -3049,7 +3112,6 @@ int spa_alsa_reassign_follower(struct state *state) spa_loop_invoke(state->data_loop, do_state_sync, 0, NULL, 0, true, state); freewheel = pos != NULL && SPA_FLAG_IS_SET(pos->clock.flags, SPA_IO_CLOCK_FLAG_FREEWHEEL); - if (state->freewheel != freewheel) { spa_log_debug(state->log, "%p: freewheel %d->%d", state, state->freewheel, freewheel); state->freewheel = freewheel; @@ -3067,6 +3129,7 @@ int spa_alsa_reassign_follower(struct state *state) int spa_alsa_pause(struct state *state) { int err; + struct state *follower; if (!state->started) return 0; @@ -3076,7 +3139,10 @@ int spa_alsa_pause(struct state *state) state->started = false; spa_loop_invoke(state->data_loop, do_state_sync, 0, NULL, 0, true, state); - if ((err = snd_pcm_drop(state->hndl)) < 0) + spa_list_for_each(follower, &state->followers, driver_link) + spa_alsa_pause(follower); + + if (!state->linked && (err = snd_pcm_drop(state->hndl)) < 0) spa_log_error(state->log, "%s: snd_pcm_drop %s", state->name, snd_strerror(err)); diff --git a/spa/plugins/alsa/alsa-pcm.h b/spa/plugins/alsa/alsa-pcm.h index 229766f3e..ab74fd6a2 100644 --- a/spa/plugins/alsa/alsa-pcm.h +++ b/spa/plugins/alsa/alsa-pcm.h @@ -213,6 +213,7 @@ struct state { unsigned int htimestamp:1; unsigned int is_pro:1; unsigned int sources_added:1; + unsigned int linked:1; uint64_t iec958_codecs;