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.
This commit is contained in:
Wim Taymans 2023-09-11 11:04:49 +02:00
parent 4568d90565
commit 20db9e2d70
3 changed files with 83 additions and 12 deletions

View file

@ -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_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, "clock.name", "api.alsa.%u", index);
pa_proplist_setf(m->output_proplist, "device.profile.pro", "true"); 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); pa_alsa_close(&m->output_pcm);
m->supported = true; m->supported = true;
pa_channel_map_init_auto(&m->channel_map, m->sample_spec.channels, PA_CHANNEL_MAP_AUX); 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_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, "clock.name", "api.alsa.%u", index);
pa_proplist_setf(m->input_proplist, "device.profile.pro", "true"); 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); pa_alsa_close(&m->input_pcm);
m->supported = true; m->supported = true;
pa_channel_map_init_auto(&m->channel_map, m->sample_spec.channels, PA_CHANNEL_MAP_AUX); pa_channel_map_init_auto(&m->channel_map, m->sample_spec.channels, PA_CHANNEL_MAP_AUX);

View file

@ -512,6 +512,7 @@ int spa_alsa_init(struct state *state, const struct spa_dict *info)
state->multi_rate = true; state->multi_rate = true;
state->htimestamp = false; state->htimestamp = false;
state->disable_tsched = state->is_pro;
for (i = 0; info && i < info->n_items; i++) { for (i = 0; info && i < info->n_items; i++) {
const char *k = info->items[i].key; const char *k = info->items[i].key;
const char *s = info->items[i].value; const char *s = info->items[i].value;
@ -627,6 +628,27 @@ error:
return err; 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 spa_alsa_open(struct state *state, const char *params)
{ {
int err; 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 /* ALSA pollfds may only be ready after setting swparams, so
* these are initialised in spa_alsa_start() */ * these are initialised in spa_alsa_start() */
} }
state->opened = true; state->opened = true;
state->sample_count = 0; state->sample_count = 0;
state->sample_time = 0; state->sample_time = 0;
@ -678,6 +701,26 @@ error_exit_close:
return err; 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 spa_alsa_close(struct state *state)
{ {
int err = 0; int err = 0;
@ -685,6 +728,8 @@ int spa_alsa_close(struct state *state)
if (!state->opened) if (!state->opened)
return 0; return 0;
try_unlink(state);
spa_alsa_pause(state); spa_alsa_pause(state);
spa_log_info(state->log, "%p: Device '%s' closing", state, state->name); 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->have_format = false;
state->opened = false; state->opened = false;
state->linked = false;
if (state->pitch_elem) { if (state->pitch_elem) {
snd_ctl_elem_value_free(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; int res;
if (SPA_UNLIKELY(!state->alsa_started)) { if (SPA_UNLIKELY(!state->alsa_started)) {
spa_log_trace(state->log, "%p: snd_pcm_start", state); spa_log_trace(state->log, "%p: snd_pcm_start %u", state, state->linked);
if ((res = snd_pcm_start(state->hndl)) < 0) { if (!state->linked && (res = snd_pcm_start(state->hndl)) < 0) {
spa_log_error(state->log, "%s: snd_pcm_start: %s", spa_log_error(state->log, "%s: snd_pcm_start: %s",
state->name, snd_strerror(res)); state->name, snd_strerror(res));
return res; return res;
@ -1986,10 +2032,14 @@ recover:
state->name, snd_strerror(res)); state->name, snd_strerror(res));
return res; return res;
} }
if (state->driver && state->linked) {
spa_alsa_prepare(state); spa_alsa_prepare(state->driver);
res = spa_alsa_start(state->driver);
return spa_alsa_start(state); } else {
spa_alsa_prepare(state);
res = spa_alsa_start(state);
}
return res;
} }
static inline snd_pcm_sframes_t alsa_avail(struct state *state) 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)) if (SPA_UNLIKELY((res = update_time(state, current_time, delay, target, following)) < 0))
return res; return res;
if (following) { if (following && !state->linked) {
if (SPA_UNLIKELY(state->alsa_sync)) { if (SPA_UNLIKELY(state->alsa_sync)) {
enum spa_log_level lev; enum spa_log_level lev;
@ -2318,7 +2368,6 @@ static int alsa_write_frames(struct state *state)
total_written = 0; total_written = 0;
again: again:
frames = state->buffer_frames; frames = state->buffer_frames;
if (state->use_mmap && frames > 0) { if (state->use_mmap && frames > 0) {
if (SPA_UNLIKELY((res = snd_pcm_mmap_begin(hndl, &my_areas, &offset, &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; return total_frames;
} }
static int alsa_read_sync(struct state *state, uint64_t current_time) static int alsa_read_sync(struct state *state, uint64_t current_time)
{ {
int res, suppressed; 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) int spa_alsa_prepare(struct state *state)
{ {
struct state *follower;
int err; int err;
spa_alsa_pause(state); spa_alsa_pause(state);
@ -2935,6 +2984,14 @@ int spa_alsa_prepare(struct state *state)
state->alsa_recovering = false; state->alsa_recovering = false;
state->alsa_started = 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; state->prepared = true;
return 0; return 0;
@ -2942,6 +2999,7 @@ int spa_alsa_prepare(struct state *state)
int spa_alsa_start(struct state *state) int spa_alsa_start(struct state *state)
{ {
struct state *follower;
int err; int err;
if (state->started) 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 /* start capture now. We should have some data when the timer or IRQ
* goes off later */ * goes off later */
if (state->stream == SND_PCM_STREAM_CAPTURE) { 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); spa_log_debug(state->log, "%p: reassign driver %p->%p", state, state->driver, driver);
if (state->driver != NULL) if (state->driver != NULL)
spa_list_remove(&state->driver_link); spa_list_remove(&state->driver_link);
if (driver) if (driver != NULL) {
spa_list_append(&driver->followers, &state->driver_link); spa_list_append(&driver->followers, &state->driver_link);
}
state->driver = driver; state->driver = driver;
} }
if (following != state->following) { 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); 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); freewheel = pos != NULL && SPA_FLAG_IS_SET(pos->clock.flags, SPA_IO_CLOCK_FLAG_FREEWHEEL);
if (state->freewheel != freewheel) { if (state->freewheel != freewheel) {
spa_log_debug(state->log, "%p: freewheel %d->%d", state, state->freewheel, freewheel); spa_log_debug(state->log, "%p: freewheel %d->%d", state, state->freewheel, freewheel);
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 spa_alsa_pause(struct state *state)
{ {
int err; int err;
struct state *follower;
if (!state->started) if (!state->started)
return 0; return 0;
@ -3076,7 +3139,10 @@ int spa_alsa_pause(struct state *state)
state->started = false; state->started = false;
spa_loop_invoke(state->data_loop, do_state_sync, 0, NULL, 0, true, state); 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, spa_log_error(state->log, "%s: snd_pcm_drop %s", state->name,
snd_strerror(err)); snd_strerror(err));

View file

@ -213,6 +213,7 @@ struct state {
unsigned int htimestamp:1; unsigned int htimestamp:1;
unsigned int is_pro:1; unsigned int is_pro:1;
unsigned int sources_added:1; unsigned int sources_added:1;
unsigned int linked:1;
uint64_t iec958_codecs; uint64_t iec958_codecs;