From 13923416e0f418aea83f42a5a0de55dacaa456eb Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 23 Nov 2021 11:28:41 +0100 Subject: [PATCH] alsa: keep track of rate in card object Expose the card object and always obtain one per pcm. Keep the configured format in the card object. Add a api.alsa.multi-rate property. When multi_rate is disabled, only allow the last configured card rate on all PCMs. This works around drivers that can't handle multiple samplerates on their PCMs. With this patch it should be mostly safe to configure multiple sample rates in pipewire.conf See #1547 --- spa/plugins/alsa/alsa-pcm-sink.c | 3 + spa/plugins/alsa/alsa-pcm-source.c | 4 ++ spa/plugins/alsa/alsa-pcm.c | 101 +++++++++++++++-------------- spa/plugins/alsa/alsa-pcm.h | 18 ++++- 4 files changed, 75 insertions(+), 51 deletions(-) diff --git a/spa/plugins/alsa/alsa-pcm-sink.c b/spa/plugins/alsa/alsa-pcm-sink.c index a79fe8bd4..88e407240 100644 --- a/spa/plugins/alsa/alsa-pcm-sink.c +++ b/spa/plugins/alsa/alsa-pcm-sink.c @@ -644,6 +644,7 @@ static int port_set_format(void *object, return 0; spa_log_debug(this->log, "clear format"); + this->card->format_ref--; spa_alsa_pause(this); clear_buffers(this); spa_alsa_close(this); @@ -1020,6 +1021,8 @@ impl_init(const struct spa_handle_factory *factory, this->disable_batch = spa_atob(s); } else if (spa_streq(k, "api.alsa.use-chmap")) { this->props.use_chmap = spa_atob(s); + } else if (spa_streq(k, "api.alsa.multi-rate")) { + this->multi_rate = spa_atob(s); } } diff --git a/spa/plugins/alsa/alsa-pcm-source.c b/spa/plugins/alsa/alsa-pcm-source.c index d5f440bf1..db12467b0 100644 --- a/spa/plugins/alsa/alsa-pcm-source.c +++ b/spa/plugins/alsa/alsa-pcm-source.c @@ -586,6 +586,8 @@ static int port_set_format(void *object, if (!this->have_format) return 0; + spa_log_debug(this->log, "clear format"); + this->card->format_ref--; spa_alsa_pause(this); clear_buffers(this); spa_alsa_close(this); @@ -960,6 +962,8 @@ impl_init(const struct spa_handle_factory *factory, this->disable_batch = spa_atob(s); } else if (spa_streq(k, "api.alsa.use-chmap")) { this->props.use_chmap = spa_atob(s); + } else if (spa_streq(k, "api.alsa.multi-rate")) { + this->multi_rate = spa_atob(s); } } return spa_alsa_init(this); diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 1fc15c125..e39cadce3 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -16,14 +16,6 @@ static struct spa_list cards = SPA_LIST_INIT(&cards); -struct card { - struct spa_list link; - int ref; - uint32_t index; - snd_use_case_mgr_t *ucm; - char *ucm_prefix; -}; - static struct card *find_card(uint32_t index) { struct card *c; @@ -36,7 +28,7 @@ static struct card *find_card(uint32_t index) return NULL; } -static struct card *ensure_card(uint32_t index) +static struct card *ensure_card(uint32_t index, bool ucm) { struct card *c; char card_name[64]; @@ -50,25 +42,26 @@ static struct card *ensure_card(uint32_t index) c->ref = 1; c->index = index; - snprintf(card_name, sizeof(card_name), "hw:%i", index); - err = snd_use_case_mgr_open(&c->ucm, card_name); - if (err < 0) { - char *name; - err = snd_card_get_name(index, &name); - if (err < 0) - goto error; - - snprintf(card_name, sizeof(card_name), "%s", name); - free(name); - + if (ucm) { + snprintf(card_name, sizeof(card_name), "hw:%i", index); err = snd_use_case_mgr_open(&c->ucm, card_name); - if (err < 0) - goto error; - } - if ((snd_use_case_get(c->ucm, "_alibpref", &alibpref) != 0)) - alibpref = NULL; - c->ucm_prefix = (char*)alibpref; + if (err < 0) { + char *name; + err = snd_card_get_name(index, &name); + if (err < 0) + goto error; + snprintf(card_name, sizeof(card_name), "%s", name); + free(name); + + err = snd_use_case_mgr_open(&c->ucm, card_name); + if (err < 0) + goto error; + } + if ((snd_use_case_get(c->ucm, "_alibpref", &alibpref) != 0)) + alibpref = NULL; + c->ucm_prefix = (char*)alibpref; + } spa_list_append(&cards, &c->link); return c; @@ -88,8 +81,10 @@ static void release_card(uint32_t index) return; spa_list_remove(&c->link); - free(c->ucm_prefix); - snd_use_case_mgr_close(c->ucm); + if (c->ucm) { + free(c->ucm_prefix); + snd_use_case_mgr_close(c->ucm); + } free(c); } @@ -103,23 +98,18 @@ int spa_alsa_init(struct state *state) state->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_PCM; } - if (state->open_ucm) { - struct card *c; - - c = ensure_card(state->card_index); - if (c == NULL) { - spa_log_error(state->log, "UCM not available for card %d", state->card_index); - return -errno; - } - state->ucm_prefix = c->ucm_prefix; + state->card = ensure_card(state->card_index, state->open_ucm); + if (state->card == NULL) { + spa_log_error(state->log, "can't create card %d", state->card_index); + return -errno; } return 0; } int spa_alsa_clear(struct state *state) { - state->ucm_prefix = NULL; release_card(state->card_index); + state->card = NULL; return 0; } @@ -138,7 +128,7 @@ int spa_alsa_open(struct state *state, const char *params) CHECK(snd_output_stdio_attach(&state->output, stderr, 0), "attach failed"); spa_scnprintf(device_name, sizeof(device_name), "%s%s%s", - state->ucm_prefix ? state->ucm_prefix : "", + state->card->ucm_prefix ? state->card->ucm_prefix : "", props->device, params ? params : ""); spa_log_info(state->log, "%p: ALSA device open '%s' %s", state, device_name, @@ -163,10 +153,10 @@ int spa_alsa_open(struct state *state, const char *params) /* we would love to use the sync_id but it always returns 0, so use the * card id for now */ - state->card = snd_pcm_info_get_card(pcminfo); + state->pcm_card = snd_pcm_info_get_card(pcminfo); if (state->clock) { snprintf(state->clock->name, sizeof(state->clock->name), - "api.alsa.%d", state->card); + "api.alsa.%d", state->pcm_card); } state->opened = true; state->sample_count = 0; @@ -385,11 +375,15 @@ static int add_rate(struct state *state, uint32_t scale, bool all, uint32_t inde CHECK(snd_pcm_hw_params_get_rate_min(params, &min, &dir), "get_rate_min"); CHECK(snd_pcm_hw_params_get_rate_max(params, &max, &dir), "get_rate_max"); - if (state->default_rate != 0 && !all) { - if (min < state->default_rate) - min = state->default_rate; - if (max > state->default_rate) - max = state->default_rate; + rate = state->default_rate; + if (!state->multi_rate && state->card->format_ref > 0) + rate = state->card->rate; + + if (rate != 0 && !all) { + if (min < rate) + min = rate; + if (max > rate) + max = rate; } spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_rate, 0); @@ -997,6 +991,14 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ match = false; } + if (!state->multi_rate && + state->card->format_ref > 0 && + state->card->rate != rrate) { + spa_log_error(state->log, "%p: card already opened at rate:%i", + state, state->card->rate); + return -EINVAL; + } + /* set the stream rate */ val = rrate; CHECK(snd_pcm_hw_params_set_rate_near(hndl, params, &val, 0), "set_rate_near"); @@ -1020,6 +1022,9 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ else state->frame_size *= rchannels; + if (state->card->format_ref++ == 0) + state->card->rate = rrate; + dir = 0; period_size = state->default_period_size ? state->default_period_size : 1024; is_batch = snd_pcm_hw_params_is_batch(params) && @@ -1351,9 +1356,9 @@ static int setup_matching(struct state *state) if (state->position == NULL) return -ENOTSUP; - spa_log_debug(state->log, "clock:%s card:%d", state->position->clock.name, state->card); + spa_log_debug(state->log, "clock:%s card:%d", state->position->clock.name, state->pcm_card); if (sscanf(state->position->clock.name, "api.alsa.%d", &card) == 1 && - card == state->card) { + card == state->pcm_card) { state->matching = false; } state->resample = ((uint32_t)state->rate != state->rate_denom) || state->matching; diff --git a/spa/plugins/alsa/alsa-pcm.h b/spa/plugins/alsa/alsa-pcm.h index d2a2210d6..b1f294a03 100644 --- a/spa/plugins/alsa/alsa-pcm.h +++ b/spa/plugins/alsa/alsa-pcm.h @@ -88,6 +88,18 @@ struct channel_map { uint32_t channels; uint32_t pos[SPA_AUDIO_MAX_CHANNELS]; }; + + +struct card { + struct spa_list link; + int ref; + uint32_t index; + snd_use_case_mgr_t *ucm; + char *ucm_prefix; + int format_ref; + uint32_t rate; +}; + struct state { struct spa_handle handle; struct spa_node node; @@ -97,6 +109,7 @@ struct state { struct spa_loop *data_loop; int card_index; + struct card *card; snd_pcm_stream_t stream; snd_output_t *output; @@ -115,7 +128,7 @@ struct state { bool opened; snd_pcm_t *hndl; - int card; + int pcm_card; bool have_format; struct spa_audio_info current_format; @@ -188,6 +201,7 @@ struct state { unsigned int open_ucm:1; unsigned int is_iec958:1; unsigned int is_hdmi:1; + unsigned int multi_rate:1; uint64_t iec958_codecs; @@ -205,8 +219,6 @@ struct state { struct spa_latency_info latency[2]; struct spa_process_latency_info process_latency; - - const char *ucm_prefix; }; int