From 4066bbaf098f6f316ea7f220039af3020b084051 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Mon, 20 Mar 2023 17:47:52 +0100 Subject: [PATCH] alsa-mixer: fix the re-attach code for the mixer control element The new helem must be tracked and old helem must be cleared to make the code work properly. Introduce the pointer to helem as the private value for melem and add the necessary code. Also, add a check for the duplicate mixer elements. The duplicate mixer element invokes the abort check in alsa-lib. Print a warning instead and handle the exit gracefully. Link: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/commit/d1675df0cdd5565aa6823afefe0795816228d06e Signed-off-by: Jaroslav Kysela --- spa/plugins/alsa/acp/acp.c | 12 ++++++-- spa/plugins/alsa/acp/alsa-util.c | 49 +++++++++++++++++++++++++++----- 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index f21eec6c9..7599328a6 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -675,7 +675,7 @@ struct temp_port_avail { static int report_jack_state(snd_mixer_elem_t *melem, unsigned int mask) { pa_card *impl = snd_mixer_elem_get_callback_private(melem); - snd_hctl_elem_t *elem = snd_mixer_elem_get_private(melem); + snd_hctl_elem_t **_elem = snd_mixer_elem_get_private(melem), *elem; snd_ctl_elem_value_t *elem_value; bool plugged_in, any_input_port_available; void *state; @@ -685,6 +685,8 @@ static int report_jack_state(snd_mixer_elem_t *melem, unsigned int mask) enum acp_available active_available = ACP_AVAILABLE_UNKNOWN; size_t size; + pa_assert(_elem); + elem = *_elem; #if 0 /* Changing the jack state may cause a port change, and a port change will * make the sink or source change the mixer settings. If there are multiple @@ -953,13 +955,17 @@ static pa_device_port* find_port_with_eld_device(pa_card *impl, int device) static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask) { pa_card *impl = snd_mixer_elem_get_callback_private(melem); - snd_hctl_elem_t *elem = snd_mixer_elem_get_private(melem); - int device = snd_hctl_elem_get_device(elem); + snd_hctl_elem_t **_elem = snd_mixer_elem_get_private(melem), *elem; + int device; const char *old_monitor_name; pa_device_port *p; pa_hdmi_eld eld; bool changed = false; + pa_assert(_elem); + elem = *_elem; + device = snd_hctl_elem_get_device(elem); + if (mask == SND_CTL_EVENT_MASK_REMOVE) return 0; diff --git a/spa/plugins/alsa/acp/alsa-util.c b/spa/plugins/alsa/acp/alsa-util.c index 38ae934eb..8d53fdd66 100644 --- a/spa/plugins/alsa/acp/alsa-util.c +++ b/spa/plugins/alsa/acp/alsa-util.c @@ -1604,10 +1604,11 @@ static snd_mixer_elem_t *pa_alsa_mixer_find(snd_mixer_t *mixer, snd_mixer_elem_t *elem; for (elem = snd_mixer_first_elem(mixer); elem; elem = snd_mixer_elem_next(elem)) { - snd_hctl_elem_t *helem; + snd_hctl_elem_t **_helem, *helem; if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_PULSEAUDIO) continue; - helem = snd_mixer_elem_get_private(elem); + _helem = snd_mixer_elem_get_private(elem); + helem = *_helem; if (snd_hctl_elem_get_interface(helem) != iface) continue; if (!pa_streq(snd_hctl_elem_get_name(helem), name)) @@ -1635,15 +1636,25 @@ static int mixer_class_compare(const snd_mixer_elem_t *c1, const snd_mixer_elem_ return c1 == c2 ? 0 : (c1 > c2 ? 1 : -1); } +static void mixer_melem_free(snd_mixer_elem_t *elem) +{ + snd_hctl_elem_t **_helem; + _helem = snd_mixer_elem_get_private(elem); + pa_xfree(_helem); +} + static int mixer_class_event(snd_mixer_class_t *class, unsigned int mask, snd_hctl_elem_t *helem, snd_mixer_elem_t *melem) { int err; const char *name = snd_hctl_elem_get_name(helem); + snd_hctl_elem_t **_helem; // NOTE: The remove event defined as '~0U`. if (mask == SND_CTL_EVENT_MASK_REMOVE) { // NOTE: unless remove pointer to melem from link-list at private_data of helem, hits // assersion in alsa-lib since the list is not empty. + _helem = snd_mixer_elem_get_private(melem); + *_helem = NULL; snd_mixer_elem_detach(melem, helem); } else if (mask & SND_CTL_EVENT_MASK_ADD) { snd_ctl_elem_iface_t iface = snd_hctl_elem_get_interface(helem); @@ -1655,24 +1666,48 @@ static int mixer_class_event(snd_mixer_class_t *class, unsigned int mask, const int device = snd_hctl_elem_get_device(helem); snd_mixer_elem_t *new_melem; + bool found = true; + new_melem = pa_alsa_mixer_find(mixer, iface, name, index, device); if (!new_melem) { + _helem = pa_xmalloc(sizeof(snd_hctl_elem_t *)); + *_helem = helem; /* Put the hctl pointer as our private data - it will be useful for callbacks */ - if ((err = snd_mixer_elem_new(&new_melem, SND_MIXER_ELEM_PULSEAUDIO, 0, helem, NULL)) < 0) { + if ((err = snd_mixer_elem_new(&new_melem, SND_MIXER_ELEM_PULSEAUDIO, 0, _helem, mixer_melem_free)) < 0) { pa_log_warn("snd_mixer_elem_new failed: %s", pa_alsa_strerror(err)); return 0; } + found = false; + } else { + _helem = snd_mixer_elem_get_private(new_melem); + if (_helem) { + char *s1, *s2; + snd_ctl_elem_id_t *id1, *id2; + snd_ctl_elem_id_alloca(&id1); + snd_ctl_elem_id_alloca(&id2); + snd_hctl_elem_get_id(helem, id1); + snd_hctl_elem_get_id(*_helem, id2); + s1 = snd_ctl_ascii_elem_id_get(id1); + s2 = snd_ctl_ascii_elem_id_get(id2); + pa_log_warn("mixer_class_event - duplicate mixer controls: %s | %s", s1, s2); + free(s2); + free(s1); + return 0; + } + *_helem = helem; } if ((err = snd_mixer_elem_attach(new_melem, helem)) < 0) { pa_log_warn("snd_mixer_elem_attach failed: %s", pa_alsa_strerror(err)); - snd_mixer_elem_free(melem); + snd_mixer_elem_free(melem); return 0; } - if ((err = snd_mixer_elem_add(new_melem, class)) < 0) { - pa_log_warn("snd_mixer_elem_add failed: %s", pa_alsa_strerror(err)); - return 0; + if (!found) { + if ((err = snd_mixer_elem_add(new_melem, class)) < 0) { + pa_log_warn("snd_mixer_elem_add failed: %s", pa_alsa_strerror(err)); + return 0; + } } } }