diff options
Diffstat (limited to 'src/modules/alsa')
23 files changed, 1302 insertions, 433 deletions
diff --git a/src/modules/alsa/90-pulseaudio.rules b/src/modules/alsa/90-pulseaudio.rules index d857639..fa43cb8 100644 --- a/src/modules/alsa/90-pulseaudio.rules +++ b/src/modules/alsa/90-pulseaudio.rules @@ -109,15 +109,23 @@ ATTRS{idVendor}=="0763", ATTRS{idProduct}=="2012", ENV{PULSE_PROFILE_SET}="maudi ATTRS{idVendor}=="045e", ATTRS{idProduct}=="02bb", ENV{PULSE_PROFILE_SET}="kinect-audio.conf" ATTRS{idVendor}=="041e", ATTRS{idProduct}=="322c", ENV{PULSE_PROFILE_SET}="sb-omni-surround-5.1.conf" ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="4014", ENV{PULSE_PROFILE_SET}="dell-dock-tb16-usb-audio.conf" -ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1250", ENV{PULSE_PROFILE_SET}="steelseries-arctis-5-usb-audio.conf" + # ID 1038:12ad is for the 2018 refresh of the Arctis 7. -# ID 1038:1294 is for Arctis Pro Wireless (which works with the Arctis 7 -# configuration). -ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1260", ENV{PULSE_PROFILE_SET}="steelseries-arctis-7-usb-audio.conf" -ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12ad", ENV{PULSE_PROFILE_SET}="steelseries-arctis-7-usb-audio.conf" -ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1294", ENV{PULSE_PROFILE_SET}="steelseries-arctis-7-usb-audio.conf" -ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1730", ENV{PULSE_PROFILE_SET}="steelseries-arctis-7-usb-audio.conf" +# ID 1038:1294 is for Arctis Pro Wireless (which works with the Arctis 7 configuration). +ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1260", ENV{PULSE_PROFILE_SET}="usb-gaming-headset.conf" +ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12ad", ENV{PULSE_PROFILE_SET}="usb-gaming-headset.conf" +ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1294", ENV{PULSE_PROFILE_SET}="usb-gaming-headset.conf" +ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1730", ENV{PULSE_PROFILE_SET}="usb-gaming-headset.conf" +# Lucidsound LS31 +ATTRS{idVendor}=="2f12", ATTRS{idProduct}=="0109", ENV{PULSE_PROFILE_SET}="usb-gaming-headset.conf" + +# ID 1038:1250 is for the Arctis 5 +# ID 1037:12aa is for the Arctis 5 2019 +# ID 1038:1252 is for the Arctis Pro 2019 edition +ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1250", ENV{PULSE_PROFILE_SET}="steelseries-arctis-common-usb-audio.conf" +ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12aa", ENV{PULSE_PROFILE_SET}="steelseries-arctis-common-usb-audio.conf" +ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1252", ENV{PULSE_PROFILE_SET}="steelseries-arctis-common-usb-audio.conf" ATTRS{idVendor}=="147a", ATTRS{idProduct}=="e055", ENV{PULSE_PROFILE_SET}="cmedia-high-speed-true-hdaudio.conf" diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c index cd99a75..d184aec 100644 --- a/src/modules/alsa/alsa-mixer.c +++ b/src/modules/alsa/alsa-mixer.c @@ -107,13 +107,54 @@ struct description_map { const char *description; }; -pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *name) { +static char *alsa_id_str(char *dst, size_t dst_len, pa_alsa_mixer_id *id) { + if (id->index > 0) { + snprintf(dst, dst_len, "'%s',%d", id->name, id->index); + } else { + snprintf(dst, dst_len, "'%s'", id->name); + } + return dst; +} + +static int alsa_id_decode(const char *src, char *name, int *index) { + char *idx, c; + int i; + + *index = 0; + c = src[0]; + /* Strip quotes in entries such as 'Speaker',1 or "Speaker",1 */ + if (c == '\'' || c == '"') { + strcpy(name, src + 1); + for (i = 0; name[i] != '\0' && name[i] != c; i++); + idx = NULL; + if (name[i]) { + name[i] = '\0'; + idx = strchr(name + i + 1, ','); + } + } else { + strcpy(name, src); + idx = strchr(name, ','); + } + if (idx == NULL) + return 0; + *idx = '\0'; + idx++; + if (*idx < '0' || *idx > '9') { + pa_log("Element %s: index value is invalid", src); + return 1; + } + *index = atoi(idx); + return 0; +} + +pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name) { pa_alsa_jack *jack; pa_assert(name); jack = pa_xnew0(pa_alsa_jack, 1); jack->path = path; + jack->mixer_device_name = pa_xstrdup(mixer_device_name); jack->name = pa_xstrdup(name); jack->alsa_name = pa_sprintf_malloc("%s Jack", name); jack->state_unplugged = PA_AVAILABLE_NO; @@ -132,6 +173,7 @@ void pa_alsa_jack_free(pa_alsa_jack *jack) { pa_xfree(jack->alsa_name); pa_xfree(jack->name); + pa_xfree(jack->mixer_device_name); pa_xfree(jack); } @@ -467,7 +509,7 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) { unsigned short revents = 0; int err, ret = 0; - pd = pa_rtpoll_item_get_userdata(i); + pd = pa_rtpoll_item_get_work_userdata(i); pa_assert_fp(pd); pa_assert_fp(i == pd->poll_item); @@ -547,8 +589,7 @@ int pa_alsa_set_mixer_rtpoll(struct pa_alsa_mixer_pdata *pd, snd_mixer_t *mixer, pd->poll_item = i; pd->mixer = mixer; - pa_rtpoll_item_set_userdata(i, pd); - pa_rtpoll_item_set_work_callback(i, rtpoll_work_cb); + pa_rtpoll_item_set_work_callback(i, rtpoll_work_cb, pd); return 0; } @@ -642,6 +683,7 @@ static void decibel_fix_free(pa_alsa_decibel_fix *db_fix) { pa_xfree(db_fix->name); pa_xfree(db_fix->db_values); + pa_xfree(db_fix->key); pa_xfree(db_fix); } @@ -657,7 +699,7 @@ static void element_free(pa_alsa_element *e) { if (e->db_fix) decibel_fix_free(e->db_fix); - pa_xfree(e->alsa_name); + pa_xfree(e->alsa_id.name); pa_xfree(e); } @@ -699,6 +741,12 @@ void pa_alsa_path_set_free(pa_alsa_path_set *ps) { pa_xfree(ps); } +int pa_alsa_path_set_is_empty(pa_alsa_path_set *ps) { + if (ps && !pa_hashmap_isempty(ps->paths)) + return 0; + return 1; +} + static long to_alsa_dB(pa_volume_t v) { return lround(pa_sw_volume_to_dB(v) * 100.0); } @@ -718,11 +766,11 @@ static pa_volume_t from_alsa_volume(long v, long min, long max) { return (pa_volume_t) round(((double) (v - min) * PA_VOLUME_NORM) / (double) (max - min)); } -#define SELEM_INIT(sid, name) \ - do { \ - snd_mixer_selem_id_alloca(&(sid)); \ - snd_mixer_selem_id_set_name((sid), (name)); \ - snd_mixer_selem_id_set_index((sid), 0); \ +#define SELEM_INIT(sid, aid) \ + do { \ + snd_mixer_selem_id_alloca(&(sid)); \ + snd_mixer_selem_id_set_name((sid), (aid)->name); \ + snd_mixer_selem_id_set_index((sid), (aid)->index); \ } while(false) static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v) { @@ -730,6 +778,7 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann snd_mixer_elem_t *me; snd_mixer_selem_channel_id_t c; pa_channel_position_mask_t mask = 0; + char buf[64]; unsigned k; pa_assert(m); @@ -737,9 +786,10 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann pa_assert(cm); pa_assert(v); - SELEM_INIT(sid, e->alsa_name); + SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { - pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); + alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_warn("Element %s seems to have disappeared.", buf); return -1; } @@ -764,14 +814,16 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann if (value < e->db_fix->min_step) { value = e->db_fix->min_step; snd_mixer_selem_set_playback_volume(me, c, value); + alsa_id_str(buf, sizeof(buf), &e->alsa_id); pa_log_debug("Playback volume for element %s channel %i was below the dB fix limit. " - "Volume reset to %0.2f dB.", e->alsa_name, c, + "Volume reset to %0.2f dB.", buf, c, e->db_fix->db_values[value - e->db_fix->min_step] / 100.0); } else if (value > e->db_fix->max_step) { value = e->db_fix->max_step; snd_mixer_selem_set_playback_volume(me, c, value); + alsa_id_str(buf, sizeof(buf), &e->alsa_id); pa_log_debug("Playback volume for element %s channel %i was over the dB fix limit. " - "Volume reset to %0.2f dB.", e->alsa_name, c, + "Volume reset to %0.2f dB.", buf, c, e->db_fix->db_values[value - e->db_fix->min_step] / 100.0); } @@ -792,14 +844,16 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann if (value < e->db_fix->min_step) { value = e->db_fix->min_step; snd_mixer_selem_set_capture_volume(me, c, value); + alsa_id_str(buf, sizeof(buf), &e->alsa_id); pa_log_debug("Capture volume for element %s channel %i was below the dB fix limit. " - "Volume reset to %0.2f dB.", e->alsa_name, c, + "Volume reset to %0.2f dB.", buf, c, e->db_fix->db_values[value - e->db_fix->min_step] / 100.0); } else if (value > e->db_fix->max_step) { value = e->db_fix->max_step; snd_mixer_selem_set_capture_volume(me, c, value); + alsa_id_str(buf, sizeof(buf), &e->alsa_id); pa_log_debug("Capture volume for element %s channel %i was over the dB fix limit. " - "Volume reset to %0.2f dB.", e->alsa_name, c, + "Volume reset to %0.2f dB.", buf, c, e->db_fix->db_values[value - e->db_fix->min_step] / 100.0); } @@ -897,14 +951,16 @@ static int element_get_switch(pa_alsa_element *e, snd_mixer_t *m, bool *b) { snd_mixer_selem_id_t *sid; snd_mixer_elem_t *me; snd_mixer_selem_channel_id_t c; + char buf[64]; pa_assert(m); pa_assert(e); pa_assert(b); - SELEM_INIT(sid, e->alsa_name); + SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { - pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); + alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_warn("Element %s seems to have disappeared.", buf); return -1; } @@ -1058,6 +1114,7 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann snd_mixer_elem_t *me; snd_mixer_selem_channel_id_t c; pa_channel_position_mask_t mask = 0; + char buf[64]; unsigned k; pa_assert(m); @@ -1066,9 +1123,10 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann pa_assert(v); pa_assert(pa_cvolume_compatible_with_channel_map(v, cm)); - SELEM_INIT(sid, e->alsa_name); + SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { - pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); + alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_warn("Element %s seems to have disappeared.", buf); return -1; } @@ -1251,14 +1309,16 @@ int pa_alsa_path_set_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_ma static int element_set_switch(pa_alsa_element *e, snd_mixer_t *m, bool b) { snd_mixer_elem_t *me; snd_mixer_selem_id_t *sid; + char buf[64]; int r; pa_assert(m); pa_assert(e); - SELEM_INIT(sid, e->alsa_name); + SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { - pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); + alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_warn("Element %s seems to have disappeared.", buf); return -1; } @@ -1267,8 +1327,10 @@ static int element_set_switch(pa_alsa_element *e, snd_mixer_t *m, bool b) { else r = snd_mixer_selem_set_capture_switch_all(me, b); - if (r < 0) - pa_log_warn("Failed to set switch of %s: %s", e->alsa_name, pa_alsa_strerror(errno)); + if (r < 0) { + alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_warn("Failed to set switch of %s: %s", buf, pa_alsa_strerror(errno)); + } return r; } @@ -1303,13 +1365,15 @@ static int element_set_constant_volume(pa_alsa_element *e, snd_mixer_t *m) { int r = 0; long volume = -1; bool volume_set = false; + char buf[64]; pa_assert(m); pa_assert(e); - SELEM_INIT(sid, e->alsa_name); + SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { - pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); + alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_warn("Element %s seems to have disappeared.", buf); return -1; } @@ -1352,8 +1416,10 @@ static int element_set_constant_volume(pa_alsa_element *e, snd_mixer_t *m) { r = snd_mixer_selem_set_capture_dB_all(me, 0, -1); } - if (r < 0) - pa_log_warn("Failed to set volume of %s: %s", e->alsa_name, pa_alsa_strerror(errno)); + if (r < 0) { + alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_warn("Failed to set volume of %s: %s", buf, pa_alsa_strerror(errno)); + } return r; } @@ -1531,6 +1597,7 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { int r; bool is_mono; pa_channel_position_t p; + char buf[64]; if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { if (!snd_mixer_selem_has_playback_volume(me)) { @@ -1556,29 +1623,33 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { r = snd_mixer_selem_get_capture_volume_range(me, &e->min_volume, &e->max_volume); if (r < 0) { - pa_log_warn("Failed to get volume range of %s: %s", e->alsa_name, pa_alsa_strerror(r)); + alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_warn("Failed to get volume range of %s: %s", buf, pa_alsa_strerror(r)); return false; } if (e->min_volume >= e->max_volume) { - pa_log_warn("Your kernel driver is broken: it reports a volume range from %li to %li which makes no sense.", - e->min_volume, e->max_volume); + alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_warn("Your kernel driver is broken for element %s: it reports a volume range from %li to %li which makes no sense.", + buf, e->min_volume, e->max_volume); return false; } if (e->volume_use == PA_ALSA_VOLUME_CONSTANT && (e->min_volume > e->constant_volume || e->max_volume < e->constant_volume)) { + alsa_id_str(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Constant volume %li configured for element %s, but the available range is from %li to %li.", - e->constant_volume, e->alsa_name, e->min_volume, e->max_volume); + e->constant_volume, buf, e->min_volume, e->max_volume); return false; } if (e->db_fix && ((e->min_volume > e->db_fix->min_step) || (e->max_volume < e->db_fix->max_step))) { - pa_log_warn("The step range of the decibel fix for element %s (%li-%li) doesn't fit to the " - "real hardware range (%li-%li). Disabling the decibel fix.", e->alsa_name, - e->db_fix->min_step, e->db_fix->max_step, e->min_volume, e->max_volume); + alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_warn("The step range of the decibel fix for element %s (%li-%li) doesn't fit to the " + "real hardware range (%li-%li). Disabling the decibel fix.", buf, + e->db_fix->min_step, e->db_fix->max_step, e->min_volume, e->max_volume); - decibel_fix_free(e->db_fix); - e->db_fix = NULL; + decibel_fix_free(e->db_fix); + e->db_fix = NULL; } if (e->db_fix) { @@ -1599,19 +1670,22 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { long max_dB_checked = 0; if (element_ask_vol_dB(me, e->direction, e->min_volume, &min_dB_checked) < 0) { - pa_log_warn("Failed to query the dB value for %s at volume level %li", e->alsa_name, e->min_volume); + alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_warn("Failed to query the dB value for %s at volume level %li", buf, e->min_volume); return false; } if (element_ask_vol_dB(me, e->direction, e->max_volume, &max_dB_checked) < 0) { - pa_log_warn("Failed to query the dB value for %s at volume level %li", e->alsa_name, e->max_volume); + alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_warn("Failed to query the dB value for %s at volume level %li", buf, e->max_volume); return false; } if (min_dB != min_dB_checked || max_dB != max_dB_checked) { + alsa_id_str(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Your kernel driver is broken: the reported dB range for %s (from %0.2f dB to %0.2f dB) " "doesn't match the dB values at minimum and maximum volume levels: %0.2f dB at level %li, " - "%0.2f dB at level %li.", e->alsa_name, min_dB / 100.0, max_dB / 100.0, + "%0.2f dB at level %li.", buf, min_dB / 100.0, max_dB / 100.0, min_dB_checked / 100.0, e->min_volume, max_dB_checked / 100.0, e->max_volume); return false; } @@ -1630,11 +1704,12 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { } if (e->volume_limit >= 0) { - if (e->volume_limit <= e->min_volume || e->volume_limit > e->max_volume) + if (e->volume_limit <= e->min_volume || e->volume_limit > e->max_volume) { + alsa_id_str(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Volume limit for element %s of path %s is invalid: %li isn't within the valid range " "%li-%li. The volume limit is ignored.", - e->alsa_name, e->path->name, e->volume_limit, e->min_volume + 1, e->max_volume); - else { + buf, e->path->name, e->volume_limit, e->min_volume + 1, e->max_volume); + } else { e->max_volume = e->volume_limit; if (e->has_dB) { @@ -1642,7 +1717,8 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { e->db_fix->max_step = e->max_volume; e->max_dB = ((double) e->db_fix->db_values[e->db_fix->max_step - e->db_fix->min_step]) / 100.0; } else if (element_ask_vol_dB(me, e->direction, e->max_volume, &max_dB) < 0) { - pa_log_warn("Failed to get dB value of %s: %s", e->alsa_name, pa_alsa_strerror(r)); + alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_warn("Failed to get dB value of %s: %s", buf, pa_alsa_strerror(r)); e->has_dB = false; } else e->max_dB = ((double) max_dB) / 100.0; @@ -1682,7 +1758,8 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { } if (e->n_channels <= 0) { - pa_log_warn("Volume element %s with no channels?", e->alsa_name); + alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_warn("Volume element %s with no channels?", buf); return false; } else if (e->n_channels > 2) { /* FIXME: In some places code like this is used: @@ -1696,7 +1773,8 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { * Since the array size is fixed at 2, we obviously * don't support elements with more than two * channels... */ - pa_log_warn("Volume element %s has %u channels. That's too much! I can't handle that!", e->alsa_name, e->n_channels); + alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_warn("Volume element %s has %u channels. That's too much! I can't handle that!", buf, e->n_channels); return false; } @@ -1734,7 +1812,7 @@ static int element_probe(pa_alsa_element *e, snd_mixer_t *m) { pa_assert(e); pa_assert(e->path); - SELEM_INIT(sid, e->alsa_name); + SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { @@ -1837,7 +1915,7 @@ static int jack_probe(pa_alsa_jack *j, pa_alsa_mapping *mapping, snd_mixer_t *m) j->append_pcm_to_name = false; } - has_control = pa_alsa_mixer_find(m, j->alsa_name, 0) != NULL; + has_control = pa_alsa_mixer_find_card(m, j->alsa_name, 0) != NULL; pa_alsa_jack_set_has_control(j, has_control); if (j->has_control) { @@ -1853,8 +1931,10 @@ static int jack_probe(pa_alsa_jack *j, pa_alsa_mapping *mapping, snd_mixer_t *m) return 0; } -static pa_alsa_element* element_get(pa_alsa_path *p, const char *section, bool prefixed) { +pa_alsa_element * pa_alsa_element_get(pa_alsa_path *p, const char *section, bool prefixed) { pa_alsa_element *e; + char *name; + int index; pa_assert(p); pa_assert(section); @@ -1870,16 +1950,22 @@ static pa_alsa_element* element_get(pa_alsa_path *p, const char *section, bool p if (strchr(section, ':')) return NULL; - if (p->last_element && pa_streq(p->last_element->alsa_name, section)) + name = alloca(strlen(section) + 1); + if (alsa_id_decode(section, name, &index)) + return NULL; + + if (p->last_element && pa_streq(p->last_element->alsa_id.name, name) && + p->last_element->alsa_id.index == index) return p->last_element; PA_LLIST_FOREACH(e, p->elements) - if (pa_streq(e->alsa_name, section)) + if (pa_streq(e->alsa_id.name, name) && e->alsa_id.index == index) goto finish; e = pa_xnew0(pa_alsa_element, 1); e->path = p; - e->alsa_name = pa_xstrdup(section); + e->alsa_id.name = pa_xstrdup(name); + e->alsa_id.index = index; e->direction = p->direction; e->volume_limit = -1; @@ -1904,7 +1990,7 @@ static pa_alsa_jack* jack_get(pa_alsa_path *p, const char *section) { if (pa_streq(j->name, section)) goto finish; - j = pa_alsa_jack_new(p, section); + j = pa_alsa_jack_new(p, NULL, section); PA_LLIST_INSERT_AFTER(pa_alsa_jack, p->jacks, p->last_jack, j); finish: @@ -1913,10 +1999,12 @@ finish: } static pa_alsa_option* option_get(pa_alsa_path *p, const char *section) { - char *en; + char *en, *name; const char *on; pa_alsa_option *o; pa_alsa_element *e; + size_t len; + int index; if (!pa_startswith(section, "Option ")) return NULL; @@ -1927,18 +2015,25 @@ static pa_alsa_option* option_get(pa_alsa_path *p, const char *section) { if (!(on = strchr(section, ':'))) return NULL; - en = pa_xstrndup(section, on - section); + len = on - section; + en = alloca(len + 1); + strncpy(en, section, len); + en[len] = '\0'; + + name = alloca(strlen(en) + 1); + if (alsa_id_decode(en, name, &index)) + return NULL; + on++; if (p->last_option && - pa_streq(p->last_option->element->alsa_name, en) && + pa_streq(p->last_option->element->alsa_id.name, name) && + p->last_option->element->alsa_id.index == index && pa_streq(p->last_option->alsa_name, on)) { - pa_xfree(en); return p->last_option; } - pa_assert_se(e = element_get(p, en, false)); - pa_xfree(en); + pa_assert_se(e = pa_alsa_element_get(p, en, false)); PA_LLIST_FOREACH(o, e->options) if (pa_streq(o->alsa_name, on)) @@ -1967,7 +2062,7 @@ static int element_parse_switch(pa_config_parser_state *state) { p = state->userdata; - if (!(e = element_get(p, state->section, true))) { + if (!(e = pa_alsa_element_get(p, state->section, true))) { pa_log("[%s:%u] Switch makes no sense in '%s'", state->filename, state->lineno, state->section); return -1; } @@ -1998,7 +2093,7 @@ static int element_parse_volume(pa_config_parser_state *state) { p = state->userdata; - if (!(e = element_get(p, state->section, true))) { + if (!(e = pa_alsa_element_get(p, state->section, true))) { pa_log("[%s:%u] Volume makes no sense in '%s'", state->filename, state->lineno, state->section); return -1; } @@ -2034,7 +2129,7 @@ static int element_parse_enumeration(pa_config_parser_state *state) { p = state->userdata; - if (!(e = element_get(p, state->section, true))) { + if (!(e = pa_alsa_element_get(p, state->section, true))) { pa_log("[%s:%u] Enumeration makes no sense in '%s'", state->filename, state->lineno, state->section); return -1; } @@ -2126,7 +2221,7 @@ static int element_parse_required(pa_config_parser_state *state) { p = state->userdata; - e = element_get(p, state->section, true); + e = pa_alsa_element_get(p, state->section, true); o = option_get(p, state->section); j = jack_get(p, state->section); if (!e && !o && !j) { @@ -2192,7 +2287,7 @@ static int element_parse_direction(pa_config_parser_state *state) { p = state->userdata; - if (!(e = element_get(p, state->section, true))) { + if (!(e = pa_alsa_element_get(p, state->section, true))) { pa_log("[%s:%u] Direction makes no sense in '%s'", state->filename, state->lineno, state->section); return -1; } @@ -2218,7 +2313,7 @@ static int element_parse_direction_try_other(pa_config_parser_state *state) { p = state->userdata; - if (!(e = element_get(p, state->section, true))) { + if (!(e = pa_alsa_element_get(p, state->section, true))) { pa_log("[%s:%u] Direction makes no sense in '%s'", state->filename, state->lineno, state->section); return -1; } @@ -2241,7 +2336,7 @@ static int element_parse_volume_limit(pa_config_parser_state *state) { p = state->userdata; - if (!(e = element_get(p, state->section, true))) { + if (!(e = pa_alsa_element_get(p, state->section, true))) { pa_log("[%s:%u] volume-limit makes no sense in '%s'", state->filename, state->lineno, state->section); return -1; } @@ -2299,7 +2394,7 @@ static int element_parse_override_map(pa_config_parser_state *state) { p = state->userdata; - if (!(e = element_get(p, state->section, true))) { + if (!(e = pa_alsa_element_get(p, state->section, true))) { pa_log("[%s:%u] Override map makes no sense in '%s'", state->filename, state->lineno, state->section); return -1; } @@ -2394,14 +2489,16 @@ static int jack_parse_append_pcm_to_name(pa_config_parser_state *state) { static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx) { snd_mixer_selem_id_t *sid; snd_mixer_elem_t *me; + char buf[64]; int r; pa_assert(e); pa_assert(m); - SELEM_INIT(sid, e->alsa_name); + SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { - pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); + alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_warn("Element %s seems to have disappeared.", buf); return -1; } @@ -2412,14 +2509,18 @@ static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx) else r = snd_mixer_selem_set_capture_switch_all(me, alsa_idx); - if (r < 0) - pa_log_warn("Failed to set switch of %s: %s", e->alsa_name, pa_alsa_strerror(errno)); + if (r < 0) { + alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_warn("Failed to set switch of %s: %s", buf, pa_alsa_strerror(errno)); + } } else { pa_assert(e->enumeration_use == PA_ALSA_ENUMERATION_SELECT); - if ((r = snd_mixer_selem_set_enum_item(me, 0, alsa_idx)) < 0) - pa_log_warn("Failed to set enumeration of %s: %s", e->alsa_name, pa_alsa_strerror(errno)); + if ((r = snd_mixer_selem_set_enum_item(me, 0, alsa_idx)) < 0) { + alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_warn("Failed to set enumeration of %s: %s", buf, pa_alsa_strerror(errno)); + } } return r; @@ -2463,6 +2564,7 @@ static int option_verify(pa_alsa_option *o) { { "output-speaker", N_("Speaker") }, { "output-headphones", N_("Headphones") } }; + char buf[64]; pa_assert(o); @@ -2473,14 +2575,16 @@ static int option_verify(pa_alsa_option *o) { if (o->element->enumeration_use != PA_ALSA_ENUMERATION_SELECT && o->element->switch_use != PA_ALSA_SWITCH_SELECT) { - pa_log("Element %s of option %s not set for select.", o->element->alsa_name, o->name); + alsa_id_str(buf, sizeof(buf), &o->element->alsa_id); + pa_log("Element %s of option %s not set for select.", buf, o->name); return -1; } if (o->element->switch_use == PA_ALSA_SWITCH_SELECT && !pa_streq(o->alsa_name, "on") && !pa_streq(o->alsa_name, "off")) { - pa_log("Switch %s options need be named off or on ", o->element->alsa_name); + alsa_id_str(buf, sizeof(buf), &o->element->alsa_id); + pa_log("Switch %s options need be named off or on ", buf); return -1; } @@ -2496,6 +2600,7 @@ static int option_verify(pa_alsa_option *o) { static int element_verify(pa_alsa_element *e) { pa_alsa_option *o; + char buf[64]; pa_assert(e); @@ -2504,12 +2609,14 @@ static int element_verify(pa_alsa_element *e) { (e->required_any != PA_ALSA_REQUIRED_IGNORE && e->required_any == e->required_absent) || (e->required_absent == PA_ALSA_REQUIRED_ANY && e->required_any != PA_ALSA_REQUIRED_IGNORE) || (e->required_absent == PA_ALSA_REQUIRED_ANY && e->required != PA_ALSA_REQUIRED_IGNORE)) { - pa_log("Element %s cannot be required and absent at the same time.", e->alsa_name); + alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log("Element %s cannot be required and absent at the same time.", buf); return -1; } if (e->switch_use == PA_ALSA_SWITCH_SELECT && e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) { - pa_log("Element %s cannot set select for both switch and enumeration.", e->alsa_name); + alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log("Element %s cannot set select for both switch and enumeration.", buf); return -1; } @@ -2661,9 +2768,15 @@ fail: pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t direction) { pa_alsa_path *p; pa_alsa_element *e; + char *name; + int index; pa_assert(element); + name = alloca(strlen(element) + 1); + if (alsa_id_decode(element, name, &index)) + return NULL; + p = pa_xnew0(pa_alsa_path, 1); p->name = pa_xstrdup(element); p->direction = direction; @@ -2671,7 +2784,8 @@ pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t d e = pa_xnew0(pa_alsa_element, 1); e->path = p; - e->alsa_name = pa_xstrdup(element); + e->alsa_id.name = pa_xstrdup(name); + e->alsa_id.index = index; e->direction = direction; e->volume_limit = -1; @@ -2821,6 +2935,7 @@ int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m double min_dB[PA_CHANNEL_POSITION_MAX], max_dB[PA_CHANNEL_POSITION_MAX]; pa_channel_position_t t; pa_channel_position_mask_t path_volume_channels = 0; + char buf[64]; pa_assert(p); pa_assert(m); @@ -2844,12 +2959,13 @@ int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m } PA_LLIST_FOREACH(e, p->elements) { + alsa_id_str(buf, sizeof(buf), &e->alsa_id); if (element_probe(e, m) < 0) { p->supported = false; - pa_log_debug("Probe of element '%s' failed.", e->alsa_name); + pa_log_debug("Probe of element %s failed.", buf); return -1; } - pa_log_debug("Probe of element '%s' succeeded (volume=%d, switch=%d, enumeration=%d).", e->alsa_name, e->volume_use, e->switch_use, e->enumeration_use); + pa_log_debug("Probe of element %s succeeded (volume=%d, switch=%d, enumeration=%d).", buf, e->volume_use, e->switch_use, e->enumeration_use); if (ignore_dB) e->has_dB = false; @@ -2885,13 +3001,13 @@ int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m * which cannot do dB volumes, so we we need * to 'neutralize' this slider */ e->volume_use = PA_ALSA_VOLUME_ZERO; - pa_log_info("Zeroing volume of '%s' on path '%s'", e->alsa_name, p->name); + pa_log_info("Zeroing volume of %s on path '%s'", buf, p->name); } } } else if (p->has_volume) { /* We can't use this volume, so let's ignore it */ e->volume_use = PA_ALSA_VOLUME_IGNORE; - pa_log_info("Ignoring volume of '%s' on path '%s' (missing dB info)", e->alsa_name, p->name); + pa_log_info("Ignoring volume of %s on path '%s' (missing dB info)", buf, p->name); } p->has_volume = true; } @@ -2955,11 +3071,14 @@ void pa_alsa_option_dump(pa_alsa_option *o) { } void pa_alsa_element_dump(pa_alsa_element *e) { + char buf[64]; + pa_alsa_option *o; pa_assert(e); + alsa_id_str(buf, sizeof(buf), &e->alsa_id); pa_log_debug("Element %s, direction=%i, switch=%i, volume=%i, volume_limit=%li, enumeration=%i, required=%i, required_any=%i, required_absent=%i, mask=0x%llx, n_channels=%u, override_map=%s", - e->alsa_name, + buf, e->direction, e->switch_use, e->volume_use, @@ -3009,14 +3128,16 @@ void pa_alsa_path_dump(pa_alsa_path *p) { static void element_set_callback(pa_alsa_element *e, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata) { snd_mixer_selem_id_t *sid; snd_mixer_elem_t *me; + char buf[64]; pa_assert(e); pa_assert(m); pa_assert(cb); - SELEM_INIT(sid, e->alsa_name); + SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { - pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); + alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_warn("Element %s seems to have disappeared.", buf); return; } @@ -3082,6 +3203,8 @@ pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t d char **pn = NULL, **en = NULL, **ie; pa_alsa_decibel_fix *db_fix; void *state, *state2; + char name[64]; + int index; pa_assert(m); pa_assert(m->profile_set); @@ -3161,9 +3284,18 @@ pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t d if (je == ie) continue; + if (strlen(*je) + 1 >= sizeof(name)) { + pa_log("Element identifier %s is too long!", *je); + continue; + } + + if (alsa_id_decode(*je, name, &index)) + continue; + e = pa_xnew0(pa_alsa_element, 1); e->path = p; - e->alsa_name = pa_xstrdup(*je); + e->alsa_id.name = pa_xstrdup(name); + e->alsa_id.index = index; e->direction = direction; e->required_absent = PA_ALSA_REQUIRED_ANY; e->volume_limit = -1; @@ -3184,7 +3316,8 @@ finish: pa_alsa_element *e; PA_LLIST_FOREACH(e, p->elements) { - if (e->volume_use != PA_ALSA_VOLUME_IGNORE && pa_streq(db_fix->name, e->alsa_name)) { + if (e->volume_use != PA_ALSA_VOLUME_IGNORE && pa_streq(db_fix->name, e->alsa_id.name) && + db_fix->index == e->alsa_id.index) { /* The profile set that contains the dB fix may be freed * before the element, so we have to copy the dB fix * object. */ @@ -3257,6 +3390,8 @@ static bool enumeration_is_subset(pa_alsa_option *a_options, pa_alsa_option *b_o * Compares two elements to see if a is a subset of b */ static bool element_is_subset(pa_alsa_element *a, pa_alsa_element *b, snd_mixer_t *m) { + char buf[64]; + pa_assert(a); pa_assert(b); pa_assert(m); @@ -3294,9 +3429,10 @@ static bool element_is_subset(pa_alsa_element *a, pa_alsa_element *b, snd_mixer_ snd_mixer_selem_id_t *sid; snd_mixer_elem_t *me; - SELEM_INIT(sid, a->alsa_name); + SELEM_INIT(sid, &a->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { - pa_log_warn("Element %s seems to have disappeared.", a->alsa_name); + alsa_id_str(buf, sizeof(buf), &a->alsa_id); + pa_log_warn("Element %s seems to have disappeared.", buf); return false; } @@ -3326,8 +3462,9 @@ static bool element_is_subset(pa_alsa_element *a, pa_alsa_element *b, snd_mixer_ return false; for (s = 0; s <= SND_MIXER_SCHN_LAST; s++) if (a->masks[s][a->n_channels-1] != b->masks[s][b->n_channels-1]) { + alsa_id_str(buf, sizeof(buf), &a->alsa_id); pa_log_debug("Element %s is not a subset - mask a: 0x%" PRIx64 ", mask b: 0x%" PRIx64 ", at channel %d", - a->alsa_name, a->masks[s][a->n_channels-1], b->masks[s][b->n_channels-1], s); + buf, a->masks[s][a->n_channels-1], b->masks[s][b->n_channels-1], s); return false; } } @@ -3424,7 +3561,8 @@ static void path_set_condense(pa_alsa_path_set *ps, snd_mixer_t *m) { break; PA_LLIST_FOREACH(eb, p2->elements) { - if (pa_streq(ea->alsa_name, eb->alsa_name)) { + if (pa_streq(ea->alsa_id.name, eb->alsa_id.name) && + ea->alsa_id.index == eb->alsa_id.index) { found_matching_element = true; is_subset = element_is_subset(ea, eb, m); break; @@ -3601,22 +3739,30 @@ static pa_alsa_profile *profile_get(pa_alsa_profile_set *ps, const char *name) { return p; } -static pa_alsa_decibel_fix *decibel_fix_get(pa_alsa_profile_set *ps, const char *name) { +static pa_alsa_decibel_fix *decibel_fix_get(pa_alsa_profile_set *ps, const char *alsa_id) { pa_alsa_decibel_fix *db_fix; + char *name; + int index; - if (!pa_startswith(name, "DecibelFix ")) + if (!pa_startswith(alsa_id, "DecibelFix ")) return NULL; - name += 11; + alsa_id += 11; - if ((db_fix = pa_hashmap_get(ps->decibel_fixes, name))) + if ((db_fix = pa_hashmap_get(ps->decibel_fixes, alsa_id))) return db_fix; + name = alloca(strlen(alsa_id) + 1); + if (alsa_id_decode(alsa_id, name, &index)) + return NULL; + db_fix = pa_xnew0(pa_alsa_decibel_fix, 1); db_fix->profile_set = ps; db_fix->name = pa_xstrdup(name); + db_fix->index = index; + db_fix->key = pa_xstrdup(alsa_id); - pa_hashmap_put(ps->decibel_fixes, db_fix->name, db_fix); + pa_hashmap_put(ps->decibel_fixes, db_fix->key, db_fix); return db_fix; } @@ -3840,6 +3986,24 @@ static int mapping_parse_fallback(pa_config_parser_state *state) { return 0; } +static int mapping_parse_intended_roles(pa_config_parser_state *state) { + pa_alsa_profile_set *ps; + pa_alsa_mapping *m; + + pa_assert(state); + + ps = state->userdata; + + if (!(m = pa_alsa_mapping_get(ps, state->section))) { + pa_log("[%s:%u] %s invalid in section %s", state->filename, state->lineno, state->lvalue, state->section); + return -1; + } + + pa_proplist_sets(m->proplist, PA_PROP_DEVICE_INTENDED_ROLES, state->rvalue); + + return 0; +} + static int profile_parse_mappings(pa_config_parser_state *state) { pa_alsa_profile_set *ps; @@ -4004,7 +4168,8 @@ fail: } static void mapping_paths_probe(pa_alsa_mapping *m, pa_alsa_profile *profile, - pa_alsa_direction_t direction, pa_hashmap *used_paths) { + pa_alsa_direction_t direction, pa_hashmap *used_paths, + pa_hashmap *mixers) { pa_alsa_path *p; void *state; @@ -4029,7 +4194,7 @@ static void mapping_paths_probe(pa_alsa_mapping *m, pa_alsa_profile *profile, pa_assert(pcm_handle); - mixer_handle = pa_alsa_open_mixer_for_pcm(pcm_handle, NULL); + mixer_handle = pa_alsa_open_mixer_for_pcm(mixers, pcm_handle, true); if (!mixer_handle) { /* Cannot open mixer, remove all entries */ pa_hashmap_remove_all(ps->paths); @@ -4047,9 +4212,6 @@ static void mapping_paths_probe(pa_alsa_mapping *m, pa_alsa_profile *profile, path_set_condense(ps, mixer_handle); path_set_make_path_descriptions_unique(ps); - if (mixer_handle) - snd_mixer_close(mixer_handle); - PA_HASHMAP_FOREACH(p, ps->paths, state) pa_hashmap_put(used_paths, p, p); @@ -4431,6 +4593,7 @@ pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel { "element-output", mapping_parse_element, NULL, NULL }, { "direction", mapping_parse_direction, NULL, NULL }, { "exact-channels", mapping_parse_exact_channels, NULL, NULL }, + { "intended-roles", mapping_parse_intended_roles, NULL, NULL }, /* Shared by [Mapping ...] and [Profile ...] */ { "description", mapping_parse_description, NULL, NULL }, @@ -4628,6 +4791,7 @@ static void mapping_query_hw_device(pa_alsa_mapping *mapping, snd_pcm_t *pcm) { void pa_alsa_profile_set_probe( pa_alsa_profile_set *ps, + pa_hashmap *mixers, const char *dev_id, const pa_sample_spec *ss, unsigned default_n_fragments, @@ -4757,14 +4921,14 @@ void pa_alsa_profile_set_probe( PA_IDXSET_FOREACH(m, p->output_mappings, idx) if (m->output_pcm) { found_output |= !p->fallback_output; - mapping_paths_probe(m, p, PA_ALSA_DIRECTION_OUTPUT, used_paths); + mapping_paths_probe(m, p, PA_ALSA_DIRECTION_OUTPUT, used_paths, mixers); } if (p->input_mappings) PA_IDXSET_FOREACH(m, p->input_mappings, idx) if (m->input_pcm) { found_input |= !p->fallback_input; - mapping_paths_probe(m, p, PA_ALSA_DIRECTION_INPUT, used_paths); + mapping_paths_probe(m, p, PA_ALSA_DIRECTION_INPUT, used_paths, mixers); } } diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h index 65b0711..df739cc 100644 --- a/src/modules/alsa/alsa-mixer.h +++ b/src/modules/alsa/alsa-mixer.h @@ -32,8 +32,10 @@ #include <pulsecore/rtpoll.h> typedef struct pa_alsa_fdlist pa_alsa_fdlist; +typedef struct pa_alsa_mixer pa_alsa_mixer; typedef struct pa_alsa_mixer_pdata pa_alsa_mixer_pdata; typedef struct pa_alsa_setting pa_alsa_setting; +typedef struct pa_alsa_mixer_id pa_alsa_mixer_id; typedef struct pa_alsa_option pa_alsa_option; typedef struct pa_alsa_element pa_alsa_element; typedef struct pa_alsa_jack pa_alsa_jack; @@ -97,6 +99,20 @@ struct pa_alsa_setting { unsigned priority; }; +/* An entry for one ALSA mixer */ +struct pa_alsa_mixer { + snd_mixer_t *mixer_handle; + int card_index; + pa_alsa_fdlist *fdl; + bool used_for_probe_only:1; +}; + +/* ALSA mixer element identifier */ +struct pa_alsa_mixer_id { + char *name; + int index; +}; + /* An option belongs to an element and refers to one enumeration item * of the element is an enumeration item, or a switch status if the * element is a switch item. */ @@ -123,7 +139,7 @@ struct pa_alsa_element { pa_alsa_path *path; PA_LLIST_FIELDS(pa_alsa_element); - char *alsa_name; + struct pa_alsa_mixer_id alsa_id; pa_alsa_direction_t direction; pa_alsa_switch_use_t switch_use; @@ -158,6 +174,9 @@ struct pa_alsa_jack { pa_alsa_path *path; PA_LLIST_FIELDS(pa_alsa_jack); + snd_mixer_t *mixer_handle; + char *mixer_device_name; + char *name; /* E g "Headphone" */ char *alsa_name; /* E g "Headphone Jack" */ bool has_control; /* is the jack itself present? */ @@ -175,7 +194,7 @@ struct pa_alsa_jack { bool append_pcm_to_name; }; -pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *name); +pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name); void pa_alsa_jack_free(pa_alsa_jack *jack); void pa_alsa_jack_set_has_control(pa_alsa_jack *jack, bool has_control); void pa_alsa_jack_set_plugged_in(pa_alsa_jack *jack, bool plugged_in); @@ -194,6 +213,7 @@ struct pa_alsa_path { char *description; unsigned priority; bool autodetect_eld_device; + pa_alsa_mixer *eld_mixer_handle; int eld_device; pa_proplist *proplist; @@ -237,6 +257,7 @@ void pa_alsa_element_dump(pa_alsa_element *e); pa_alsa_path *pa_alsa_path_new(const char *paths_dir, const char *fname, pa_alsa_direction_t direction); pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t direction); +pa_alsa_element *pa_alsa_element_get(pa_alsa_path *p, const char *section, bool prefixed); int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m, bool ignore_dB); void pa_alsa_path_dump(pa_alsa_path *p); int pa_alsa_path_get_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v); @@ -251,6 +272,7 @@ pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t d void pa_alsa_path_set_dump(pa_alsa_path_set *s); void pa_alsa_path_set_set_callback(pa_alsa_path_set *ps, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata); void pa_alsa_path_set_free(pa_alsa_path_set *s); +int pa_alsa_path_set_is_empty(pa_alsa_path_set *s); struct pa_alsa_mapping { pa_alsa_profile_set *profile_set; @@ -315,9 +337,12 @@ struct pa_alsa_profile { }; struct pa_alsa_decibel_fix { + char *key; + pa_alsa_profile_set *profile_set; char *name; /* Alsa volume element name. */ + int index; /* Alsa volume element index. */ long min_step; long max_step; @@ -348,13 +373,11 @@ void pa_alsa_decibel_fix_dump(pa_alsa_decibel_fix *db_fix); pa_alsa_mapping *pa_alsa_mapping_get(pa_alsa_profile_set *ps, const char *name); pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel_map *bonus); -void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, const pa_sample_spec *ss, unsigned default_n_fragments, unsigned default_fragment_size_msec); +void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, pa_hashmap *mixers, const char *dev_id, const pa_sample_spec *ss, unsigned default_n_fragments, unsigned default_fragment_size_msec); void pa_alsa_profile_set_free(pa_alsa_profile_set *s); void pa_alsa_profile_set_dump(pa_alsa_profile_set *s); void pa_alsa_profile_set_drop_unsupported(pa_alsa_profile_set *s); -snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device); - pa_alsa_fdlist *pa_alsa_fdlist_new(void); void pa_alsa_fdlist_free(pa_alsa_fdlist *fdl); int pa_alsa_fdlist_set_handle(pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, snd_hctl_t *hctl_handle, pa_mainloop_api* m); diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c index 4b46708..042d4df 100644 --- a/src/modules/alsa/alsa-sink.c +++ b/src/modules/alsa/alsa-sink.c @@ -105,6 +105,7 @@ struct userdata { char *paths_dir; pa_alsa_fdlist *mixer_fdl; pa_alsa_mixer_pdata *mixer_pd; + pa_hashmap *mixers; snd_mixer_t *mixer_handle; pa_alsa_path_set *mixer_path_set; pa_alsa_path *mixer_path; @@ -1266,7 +1267,7 @@ static void sync_mixer(struct userdata *u, pa_device_port *port) { /* port may be NULL, because if we use a synthesized mixer path, then the * sink has no ports. */ - if (port) { + if (port && !u->ucm_context) { pa_alsa_port_data *data; data = PA_DEVICE_PORT_DATA(port); @@ -1598,7 +1599,7 @@ static void sink_set_mute_cb(pa_sink *s) { static void mixer_volume_init(struct userdata *u) { pa_assert(u); - if (!u->mixer_path->has_volume) { + if (!u->mixer_path || !u->mixer_path->has_volume) { pa_sink_set_write_volume_callback(u->sink, NULL); pa_sink_set_get_volume_callback(u->sink, NULL); pa_sink_set_set_volume_callback(u->sink, NULL); @@ -1633,7 +1634,7 @@ static void mixer_volume_init(struct userdata *u) { pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported"); } - if (!u->mixer_path->has_mute) { + if (!u->mixer_path || !u->mixer_path->has_mute) { pa_sink_set_get_mute_callback(u->sink, NULL); pa_sink_set_set_mute_callback(u->sink, NULL); pa_log_info("Driver does not support hardware mute control, falling back to software mute control."); @@ -1646,11 +1647,21 @@ static void mixer_volume_init(struct userdata *u) { static int sink_set_port_ucm_cb(pa_sink *s, pa_device_port *p) { struct userdata *u = s->userdata; + pa_alsa_ucm_port_data *data; pa_assert(u); pa_assert(p); pa_assert(u->ucm_context); + data = PA_DEVICE_PORT_DATA(p); + pa_assert_se(u->mixer_path = data->path); + mixer_volume_init(u); + + if (s->flags & PA_SINK_DEFERRED_VOLUME) + pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_SYNC_MIXER, p, 0, NULL); + else + sync_mixer(u, p); + return pa_alsa_ucm_set_port(u->ucm_context, p, true); } @@ -1661,6 +1672,7 @@ static int sink_set_port_cb(pa_sink *s, pa_device_port *p) { pa_assert(u); pa_assert(p); pa_assert(u->mixer_handle); + pa_assert(!u->ucm_context); data = PA_DEVICE_PORT_DATA(p); pa_assert_se(u->mixer_path = data->path); @@ -2071,10 +2083,24 @@ static void set_sink_name(pa_sink_new_data *data, pa_modargs *ma, const char *de } static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char *element, bool ignore_dB) { + const char *mdev; + if (!mapping && !element) return; - if (!(u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device))) { + if (!element && mapping && pa_alsa_path_set_is_empty(mapping->output_path_set)) + return; + + u->mixers = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, + NULL, (pa_free_cb_t) pa_alsa_mixer_free); + + mdev = pa_proplist_gets(mapping->proplist, "alsa.mixer_device"); + if (mdev) { + u->mixer_handle = pa_alsa_open_mixer_by_name(u->mixers, mdev, true); + } else { + u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->mixers, u->pcm_handle, true); + } + if (!u->mixer_handle) { pa_log_info("Failed to find a working mixer device."); return; } @@ -2089,8 +2115,9 @@ static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char pa_log_debug("Probed mixer path %s:", u->mixer_path->name); pa_alsa_path_dump(u->mixer_path); - } else if (!(u->mixer_path_set = mapping->output_path_set)) - goto fail; + } else { + u->mixer_path_set = mapping->output_path_set; + } return; @@ -2101,10 +2128,9 @@ fail: u->mixer_path = NULL; } - if (u->mixer_handle) { - snd_mixer_close(u->mixer_handle); - u->mixer_handle = NULL; - } + u->mixer_handle = NULL; + pa_hashmap_free(u->mixers); + u->mixers = NULL; } static int setup_mixer(struct userdata *u, bool ignore_dB) { @@ -2116,16 +2142,31 @@ static int setup_mixer(struct userdata *u, bool ignore_dB) { return 0; if (u->sink->active_port) { - pa_alsa_port_data *data; + if (!u->ucm_context) { + pa_alsa_port_data *data; + + /* We have a list of supported paths, so let's activate the + * one that has been chosen as active */ - /* We have a list of supported paths, so let's activate the - * one that has been chosen as active */ + data = PA_DEVICE_PORT_DATA(u->sink->active_port); + u->mixer_path = data->path; - data = PA_DEVICE_PORT_DATA(u->sink->active_port); - u->mixer_path = data->path; + pa_alsa_path_select(data->path, data->setting, u->mixer_handle, u->sink->muted); + } else { + pa_alsa_ucm_port_data *data; + + /* First activate the port on the UCM side */ + if (pa_alsa_ucm_set_port(u->ucm_context, u->sink->active_port, true) < 0) + return -1; - pa_alsa_path_select(data->path, data->setting, u->mixer_handle, u->sink->muted); + data = PA_DEVICE_PORT_DATA(u->sink->active_port); + /* Now activate volume controls, if any */ + if (data->path) { + u->mixer_path = data->path; + pa_alsa_path_select(u->mixer_path, NULL, u->mixer_handle, u->sink->muted); + } + } } else { if (!u->mixer_path && u->mixer_path_set) @@ -2135,7 +2176,6 @@ static int setup_mixer(struct userdata *u, bool ignore_dB) { /* Hmm, we have only a single path, then let's activate it */ pa_alsa_path_select(u->mixer_path, u->mixer_path->settings, u->mixer_handle, u->sink->muted); - } else return 0; } @@ -2466,8 +2506,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca /* ALSA might tweak the sample spec, so recalculate the frame size */ frame_size = pa_frame_size(&ss); - if (!u->ucm_context) - find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB); + find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB); pa_sink_new_data_init(&data); data.driver = driver; @@ -2523,10 +2562,14 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca goto fail; } - if (u->ucm_context) - pa_alsa_ucm_add_ports(&data.ports, data.proplist, u->ucm_context, true, card); - else if (u->mixer_path_set) - pa_alsa_add_ports(&data, u->mixer_path_set, card); + if (u->ucm_context) { + pa_alsa_ucm_add_ports(&data.ports, data.proplist, u->ucm_context, true, card, u->pcm_handle, ignore_dB); + find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB); + } else { + find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB); + if (u->mixer_path_set) + pa_alsa_add_ports(&data, u->mixer_path_set, card); + } u->sink = pa_sink_new(m->core, &data, PA_SINK_HARDWARE | PA_SINK_LATENCY | (u->use_tsched ? PA_SINK_DYNAMIC_LATENCY : 0) | (set_formats ? PA_SINK_SET_FORMATS : 0)); @@ -2598,10 +2641,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca if (update_sw_params(u, false) < 0) goto fail; - if (u->ucm_context) { - if (u->sink->active_port && pa_alsa_ucm_set_port(u->ucm_context, u->sink->active_port, true) < 0) - goto fail; - } else if (setup_mixer(u, ignore_dB) < 0) + if (setup_mixer(u, ignore_dB) < 0) goto fail; pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle); @@ -2666,7 +2706,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca * pa_sink_suspend() between pa_sink_new() and pa_sink_put() would * otherwise work, but currently pa_sink_suspend() will crash if * pa_sink_put() hasn't been called. */ - if (u->sink->active_port) { + if (u->sink->active_port && !u->ucm_context) { pa_alsa_port_data *port_data; port_data = PA_DEVICE_PORT_DATA(u->sink->active_port); @@ -2725,11 +2765,12 @@ static void userdata_free(struct userdata *u) { if (u->mixer_fdl) pa_alsa_fdlist_free(u->mixer_fdl); - if (u->mixer_path && !u->mixer_path_set) + /* Only free the mixer_path if the sink owns it */ + if (u->mixer_path && !u->mixer_path_set && !u->ucm_context) pa_alsa_path_free(u->mixer_path); - if (u->mixer_handle) - snd_mixer_close(u->mixer_handle); + if (u->mixers) + pa_hashmap_free(u->mixers); if (u->smoother) pa_smoother_free(u->smoother); diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c index c8bf649..104de4e 100644 --- a/src/modules/alsa/alsa-source.c +++ b/src/modules/alsa/alsa-source.c @@ -93,6 +93,7 @@ struct userdata { char *paths_dir; pa_alsa_fdlist *mixer_fdl; pa_alsa_mixer_pdata *mixer_pd; + pa_hashmap *mixers; snd_mixer_t *mixer_handle; pa_alsa_path_set *mixer_path_set; pa_alsa_path *mixer_path; @@ -1137,7 +1138,7 @@ static void sync_mixer(struct userdata *u, pa_device_port *port) { /* port may be NULL, because if we use a synthesized mixer path, then the * source has no ports. */ - if (port) { + if (port && !u->ucm_context) { pa_alsa_port_data *data; data = PA_DEVICE_PORT_DATA(port); @@ -1469,7 +1470,7 @@ static void source_set_mute_cb(pa_source *s) { static void mixer_volume_init(struct userdata *u) { pa_assert(u); - if (!u->mixer_path->has_volume) { + if (!u->mixer_path || !u->mixer_path->has_volume) { pa_source_set_write_volume_callback(u->source, NULL); pa_source_set_get_volume_callback(u->source, NULL); pa_source_set_set_volume_callback(u->source, NULL); @@ -1504,7 +1505,7 @@ static void mixer_volume_init(struct userdata *u) { pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported"); } - if (!u->mixer_path->has_mute) { + if (!u->mixer_path || !u->mixer_path->has_mute) { pa_source_set_get_mute_callback(u->source, NULL); pa_source_set_set_mute_callback(u->source, NULL); pa_log_info("Driver does not support hardware mute control, falling back to software mute control."); @@ -1517,11 +1518,21 @@ static void mixer_volume_init(struct userdata *u) { static int source_set_port_ucm_cb(pa_source *s, pa_device_port *p) { struct userdata *u = s->userdata; + pa_alsa_ucm_port_data *data; pa_assert(u); pa_assert(p); pa_assert(u->ucm_context); + data = PA_DEVICE_PORT_DATA(p); + pa_assert_se(u->mixer_path = data->path); + mixer_volume_init(u); + + if (s->flags & PA_SOURCE_DEFERRED_VOLUME) + pa_asyncmsgq_send(u->source->asyncmsgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_SYNC_MIXER, p, 0, NULL); + else + sync_mixer(u, p); + return pa_alsa_ucm_set_port(u->ucm_context, p, false); } @@ -1532,6 +1543,7 @@ static int source_set_port_cb(pa_source *s, pa_device_port *p) { pa_assert(u); pa_assert(p); pa_assert(u->mixer_handle); + pa_assert(!u->ucm_context); data = PA_DEVICE_PORT_DATA(p); pa_assert_se(u->mixer_path = data->path); @@ -1777,10 +1789,24 @@ static void set_source_name(pa_source_new_data *data, pa_modargs *ma, const char } static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char *element, bool ignore_dB) { + const char *mdev; + if (!mapping && !element) return; - if (!(u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device))) { + if (!element && mapping && pa_alsa_path_set_is_empty(mapping->input_path_set)) + return; + + u->mixers = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, + NULL, (pa_free_cb_t) pa_alsa_mixer_free); + + mdev = pa_proplist_gets(mapping->proplist, "alsa.mixer_device"); + if (mdev) { + u->mixer_handle = pa_alsa_open_mixer_by_name(u->mixers, mdev, false); + } else { + u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->mixers, u->pcm_handle, false); + } + if (!u->mixer_handle) { pa_log_info("Failed to find a working mixer device."); return; } @@ -1795,8 +1821,9 @@ static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char pa_log_debug("Probed mixer path %s:", u->mixer_path->name); pa_alsa_path_dump(u->mixer_path); - } else if (!(u->mixer_path_set = mapping->input_path_set)) - goto fail; + } else { + u->mixer_path_set = mapping->input_path_set; + } return; @@ -1807,10 +1834,9 @@ fail: u->mixer_path = NULL; } - if (u->mixer_handle) { - snd_mixer_close(u->mixer_handle); - u->mixer_handle = NULL; - } + u->mixer_handle = NULL; + pa_hashmap_free(u->mixers); + u->mixers = NULL; } static int setup_mixer(struct userdata *u, bool ignore_dB) { @@ -1822,16 +1848,31 @@ static int setup_mixer(struct userdata *u, bool ignore_dB) { return 0; if (u->source->active_port) { - pa_alsa_port_data *data; + if (!u->ucm_context) { + pa_alsa_port_data *data; - /* We have a list of supported paths, so let's activate the - * one that has been chosen as active */ + /* We have a list of supported paths, so let's activate the + * one that has been chosen as active */ - data = PA_DEVICE_PORT_DATA(u->source->active_port); - u->mixer_path = data->path; + data = PA_DEVICE_PORT_DATA(u->source->active_port); + u->mixer_path = data->path; - pa_alsa_path_select(data->path, data->setting, u->mixer_handle, u->source->muted); + pa_alsa_path_select(data->path, data->setting, u->mixer_handle, u->source->muted); + } else { + pa_alsa_ucm_port_data *data; + /* First activate the port on the UCM side */ + if (pa_alsa_ucm_set_port(u->ucm_context, u->source->active_port, false) < 0) + return -1; + + data = PA_DEVICE_PORT_DATA(u->source->active_port); + + /* Now activate volume controls, if any */ + if (data->path) { + u->mixer_path = data->path; + pa_alsa_path_select(u->mixer_path, NULL, u->mixer_handle, u->source->muted); + } + } } else { if (!u->mixer_path && u->mixer_path_set) @@ -2152,8 +2193,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p /* ALSA might tweak the sample spec, so recalculate the frame size */ frame_size = pa_frame_size(&ss); - if (!u->ucm_context) - find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB); + find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB); pa_source_new_data_init(&data); data.driver = driver; @@ -2210,7 +2250,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p } if (u->ucm_context) - pa_alsa_ucm_add_ports(&data.ports, data.proplist, u->ucm_context, false, card); + pa_alsa_ucm_add_ports(&data.ports, data.proplist, u->ucm_context, false, card, u->pcm_handle, ignore_dB); else if (u->mixer_path_set) pa_alsa_add_ports(&data, u->mixer_path_set, card); @@ -2276,10 +2316,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p if (update_sw_params(u) < 0) goto fail; - if (u->ucm_context) { - if (u->source->active_port && pa_alsa_ucm_set_port(u->ucm_context, u->source->active_port, false) < 0) - goto fail; - } else if (setup_mixer(u, ignore_dB) < 0) + if (setup_mixer(u, ignore_dB) < 0) goto fail; pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle); @@ -2368,11 +2405,12 @@ static void userdata_free(struct userdata *u) { if (u->mixer_fdl) pa_alsa_fdlist_free(u->mixer_fdl); - if (u->mixer_path && !u->mixer_path_set) + /* Only free the mixer_path if the sink owns it */ + if (u->mixer_path && !u->mixer_path_set && !u->ucm_context) pa_alsa_path_free(u->mixer_path); - if (u->mixer_handle) - snd_mixer_close(u->mixer_handle); + if (u->mixers) + pa_hashmap_free(u->mixers); if (u->smoother) pa_smoother_free(u->smoother); diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c index 0a40ca8..a57be6d 100644 --- a/src/modules/alsa/alsa-ucm.c +++ b/src/modules/alsa/alsa-ucm.c @@ -81,34 +81,37 @@ static void device_add_hw_mute_jack(pa_alsa_ucm_device *device, pa_alsa_jack *ja static pa_alsa_ucm_device *verb_find_device(pa_alsa_ucm_verb *verb, const char *device_name); -struct ucm_port { - pa_alsa_ucm_config *ucm; - pa_device_port *core_port; - - /* A single port will be associated with multiple devices if it represents - * a combination of devices. */ - pa_dynarray *devices; /* pa_alsa_ucm_device */ -}; -static void ucm_port_init(struct ucm_port *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port, - pa_alsa_ucm_device **devices, unsigned n_devices); -static void ucm_port_free(pa_device_port *port); -static void ucm_port_update_available(struct ucm_port *port); +static void ucm_port_data_init(pa_alsa_ucm_port_data *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port, + pa_alsa_ucm_device **devices, unsigned n_devices); +static void ucm_port_data_free(pa_device_port *port); +static void ucm_port_update_available(pa_alsa_ucm_port_data *port); static struct ucm_items item[] = { {"PlaybackPCM", PA_ALSA_PROP_UCM_SINK}, {"CapturePCM", PA_ALSA_PROP_UCM_SOURCE}, + {"PlaybackCTL", PA_ALSA_PROP_UCM_PLAYBACK_CTL_DEVICE}, {"PlaybackVolume", PA_ALSA_PROP_UCM_PLAYBACK_VOLUME}, {"PlaybackSwitch", PA_ALSA_PROP_UCM_PLAYBACK_SWITCH}, + {"PlaybackMixer", PA_ALSA_PROP_UCM_PLAYBACK_MIXER_DEVICE}, + {"PlaybackMixerElem", PA_ALSA_PROP_UCM_PLAYBACK_MIXER_ELEM}, + {"PlaybackMasterElem", PA_ALSA_PROP_UCM_PLAYBACK_MASTER_ELEM}, + {"PlaybackMasterType", PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE}, {"PlaybackPriority", PA_ALSA_PROP_UCM_PLAYBACK_PRIORITY}, {"PlaybackRate", PA_ALSA_PROP_UCM_PLAYBACK_RATE}, {"PlaybackChannels", PA_ALSA_PROP_UCM_PLAYBACK_CHANNELS}, + {"CaptureCTL", PA_ALSA_PROP_UCM_CAPTURE_CTL_DEVICE}, {"CaptureVolume", PA_ALSA_PROP_UCM_CAPTURE_VOLUME}, {"CaptureSwitch", PA_ALSA_PROP_UCM_CAPTURE_SWITCH}, + {"CaptureMixer", PA_ALSA_PROP_UCM_CAPTURE_MIXER_DEVICE}, + {"CaptureMixerElem", PA_ALSA_PROP_UCM_CAPTURE_MIXER_ELEM}, + {"CaptureMasterElem", PA_ALSA_PROP_UCM_CAPTURE_MASTER_ELEM}, + {"CaptureMasterType", PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE}, {"CapturePriority", PA_ALSA_PROP_UCM_CAPTURE_PRIORITY}, {"CaptureRate", PA_ALSA_PROP_UCM_CAPTURE_RATE}, {"CaptureChannels", PA_ALSA_PROP_UCM_CAPTURE_CHANNELS}, {"TQ", PA_ALSA_PROP_UCM_QOS}, + {"JackCTL", PA_ALSA_PROP_UCM_JACK_DEVICE}, {"JackControl", PA_ALSA_PROP_UCM_JACK_CONTROL}, {"JackHWMute", PA_ALSA_PROP_UCM_JACK_HW_MUTE}, {NULL, NULL}, @@ -143,29 +146,25 @@ static struct ucm_info dev_info[] = { {NULL, 0} }; -/* UCM profile properties - The verb data is store so it can be used to fill - * the new profiles properties */ -static int ucm_get_property(pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr, const char *verb_name) { - const char *value; - char *id; - int i; - - for (i = 0; item[i].id; i++) { - int err; - - id = pa_sprintf_malloc("=%s//%s", item[i].id, verb_name); - err = snd_use_case_get(uc_mgr, id, &value); - pa_xfree(id); - if (err < 0) - continue; - pa_log_debug("Got %s for verb %s: %s", item[i].id, verb_name, value); - pa_proplist_sets(verb->proplist, item[i].property, value); - free((void*)value); - } +static char *ucm_verb_value( + snd_use_case_mgr_t *uc_mgr, + const char *verb_name, + const char *id) { - return 0; -}; + const char *value; + char *_id = pa_sprintf_malloc("=%s//%s", id, verb_name); + int err = snd_use_case_get(uc_mgr, _id, &value); + pa_xfree(_id); + if (err < 0) + return NULL; + pa_log_debug("Got %s for verb %s: %s", id, verb_name, value); + /* Use the cast here to allow free() call without casting for callers. + * The snd_use_case_get() returns mallocated string. + * See the Note: in use-case.h for snd_use_case_get(). + */ + return (char *)value; +} static int ucm_device_exists(pa_idxset *idxset, pa_alsa_ucm_device *dev) { pa_alsa_ucm_device *d; @@ -202,6 +201,139 @@ static void ucm_add_devices_to_idxset( } } +/* Split a string into words. Like pa_split_spaces() but handle '' and "". */ +static char *ucm_split_devnames(const char *c, const char **state) { + const char *current = *state ? *state : c; + char h; + size_t l; + + if (!*current || *c == 0) + return NULL; + + current += strspn(current, "\n\r \t"); + h = *current; + if (h == '\'' || h =='"') { + c = ++current; + for (l = 0; *c && *c != h; l++) c++; + if (*c != h) + return NULL; + *state = c + 1; + } else { + l = strcspn(current, "\n\r \t"); + *state = current+l; + } + + return pa_xstrndup(current, l); +} + + +static void ucm_volume_free(pa_alsa_ucm_volume *vol) { + pa_assert(vol); + pa_xfree(vol->mixer_elem); + pa_xfree(vol->master_elem); + pa_xfree(vol->master_type); + pa_xfree(vol); +} + +/* Get the volume identifier */ +static char *ucm_get_mixer_id( + pa_alsa_ucm_device *device, + const char *mprop, + const char *cprop, + const char *cid) +{ +#if SND_LIB_VERSION >= 0x10201 /* alsa-lib-1.2.1+ check */ + snd_ctl_elem_id_t *ctl; + int err; +#endif + const char *value; + char *value2; + int index; + + /* mixer element as first, if it's found, return it without modifications */ + value = pa_proplist_gets(device->proplist, mprop); + if (value) + return pa_xstrdup(value); + /* fallback, get the control element identifier */ + /* and try to do some heuristic to determine the mixer element name */ + value = pa_proplist_gets(device->proplist, cprop); + if (value == NULL) + return NULL; +#if SND_LIB_VERSION >= 0x10201 /* alsa-lib-1.2.1+ check */ + /* The new parser may return also element index. */ + snd_ctl_elem_id_alloca(&ctl); + err = snd_use_case_parse_ctl_elem_id(ctl, cid, value); + if (err < 0) + return NULL; + value = snd_ctl_elem_id_get_name(ctl); + index = snd_ctl_elem_id_get_index(ctl); +#else +#warning "Upgrade to alsa-lib 1.2.1!" + index = 0; +#endif + if (!(value2 = pa_str_strip_suffix(value, " Playback Volume"))) + if (!(value2 = pa_str_strip_suffix(value, " Capture Volume"))) + if (!(value2 = pa_str_strip_suffix(value, " Volume"))) + value2 = pa_xstrdup(value); + if (index > 0) { + char *mix = pa_sprintf_malloc("'%s',%d", value2, index); + pa_xfree(value2); + return mix; + } + return value2; +} + +/* Get the volume identifier */ +static pa_alsa_ucm_volume *ucm_get_mixer_volume( + pa_alsa_ucm_device *device, + const char *mprop, + const char *cprop, + const char *cid, + const char *masterid, + const char *mastertype) +{ + pa_alsa_ucm_volume *vol; + char *mixer_elem; + + mixer_elem = ucm_get_mixer_id(device, mprop, cprop, cid); + if (mixer_elem == NULL) + return NULL; + vol = pa_xnew0(pa_alsa_ucm_volume, 1); + if (vol == NULL) { + pa_xfree(mixer_elem); + return NULL; + } + vol->mixer_elem = mixer_elem; + vol->master_elem = pa_xstrdup(pa_proplist_gets(device->proplist, masterid)); + vol->master_type = pa_xstrdup(pa_proplist_gets(device->proplist, mastertype)); + return vol; +} + +/* Get the ALSA mixer device for the UCM device */ +static const char *get_mixer_device(pa_alsa_ucm_device *dev, bool is_sink) +{ + const char *dev_name; + + if (is_sink) { + dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_PLAYBACK_MIXER_DEVICE); + if (!dev_name) + dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_PLAYBACK_CTL_DEVICE); + } else { + dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_CAPTURE_MIXER_DEVICE); + if (!dev_name) + dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_CAPTURE_CTL_DEVICE); + } + return dev_name; +} + +/* Get the ALSA mixer device for the UCM jack */ +static const char *get_jack_mixer_device(pa_alsa_ucm_device *dev, bool is_sink) { + const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_JACK_DEVICE); + if (!dev_name) + return get_mixer_device(dev, is_sink); + return dev_name; +} + /* Create a property list for this ucm device */ static int ucm_get_device_property( pa_alsa_ucm_device *device, @@ -216,9 +348,10 @@ static int ucm_get_device_property( int err; uint32_t ui; int n_confdev, n_suppdev; + pa_alsa_ucm_volume *vol; for (i = 0; item[i].id; i++) { - id = pa_sprintf_malloc("=%s/%s", item[i].id, device_name); + id = pa_sprintf_malloc("%s/%s", item[i].id, device_name); err = snd_use_case_get(uc_mgr, id, &value); pa_xfree(id); if (err < 0) @@ -240,14 +373,8 @@ static int ucm_get_device_property( /* get pcm */ value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SINK); - if (!value) { /* take pcm from verb playback default */ - value = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_SINK); - if (value) { - pa_log_debug("UCM playback device %s fetch pcm from verb default %s", device_name, value); - pa_proplist_sets(device->proplist, PA_ALSA_PROP_UCM_SINK, value); - } else - pa_log("UCM playback device %s fetch pcm failed", device_name); - } + if (!value) /* take pcm from verb playback default */ + pa_log("UCM playback device %s fetch pcm failed", device_name); } value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_CAPTURE_CHANNELS); @@ -260,14 +387,8 @@ static int ucm_get_device_property( /* get pcm */ value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SOURCE); - if (!value) { /* take pcm from verb capture default */ - value = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_SOURCE); - if (value) { - pa_log_debug("UCM capture device %s fetch pcm from verb default %s", device_name, value); - pa_proplist_sets(device->proplist, PA_ALSA_PROP_UCM_SOURCE, value); - } else - pa_log("UCM capture device %s fetch pcm failed", device_name); - } + if (!value) /* take pcm from verb capture default */ + pa_log("UCM capture device %s fetch pcm failed", device_name); } if (device->playback_channels == 0 && device->capture_channels == 0) { @@ -280,8 +401,7 @@ static int ucm_get_device_property( /* get rate and priority of device */ if (device->playback_channels) { /* sink device */ /* get rate */ - if ((value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_PLAYBACK_RATE)) || - (value = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_PLAYBACK_RATE))) { + if ((value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_PLAYBACK_RATE))) { if (pa_atou(value, &ui) == 0 && pa_sample_rate_valid(ui)) { pa_log_debug("UCM playback device %s rate %d", device_name, ui); device->playback_rate = ui; @@ -297,12 +417,20 @@ static int ucm_get_device_property( else pa_log_debug("UCM playback priority %s for device %s error", value, device_name); } + + vol = ucm_get_mixer_volume(device, + PA_ALSA_PROP_UCM_PLAYBACK_MIXER_ELEM, + PA_ALSA_PROP_UCM_PLAYBACK_VOLUME, + "PlaybackVolume", + PA_ALSA_PROP_UCM_PLAYBACK_MASTER_ELEM, + PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE); + if (vol) + pa_hashmap_put(device->playback_volumes, pa_xstrdup(pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME)), vol); } if (device->capture_channels) { /* source device */ /* get rate */ - if ((value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_CAPTURE_RATE)) || - (value = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_CAPTURE_RATE))) { + if ((value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_CAPTURE_RATE))) { if (pa_atou(value, &ui) == 0 && pa_sample_rate_valid(ui)) { pa_log_debug("UCM capture device %s rate %d", device_name, ui); device->capture_rate = ui; @@ -318,6 +446,15 @@ static int ucm_get_device_property( else pa_log_debug("UCM capture priority %s for device %s error", value, device_name); } + + vol = ucm_get_mixer_volume(device, + PA_ALSA_PROP_UCM_CAPTURE_MIXER_ELEM, + PA_ALSA_PROP_UCM_CAPTURE_VOLUME, + "CaptureVolume", + PA_ALSA_PROP_UCM_CAPTURE_MASTER_ELEM, + PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE); + if (vol) + pa_hashmap_put(device->capture_volumes, pa_xstrdup(pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME)), vol); } if (PA_UCM_PLAYBACK_PRIORITY_UNSET(device) || PA_UCM_CAPTURE_PRIORITY_UNSET(device)) { @@ -421,6 +558,11 @@ static int ucm_get_devices(pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr) { d->hw_mute_jacks = pa_dynarray_new(NULL); d->available = PA_AVAILABLE_UNKNOWN; + d->playback_volumes = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree, + (pa_free_cb_t) ucm_volume_free); + d->capture_volumes = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree, + (pa_free_cb_t) ucm_volume_free); + PA_LLIST_PREPEND(pa_alsa_ucm_device, verb->devices, d); } @@ -576,17 +718,25 @@ int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index) { const char **verb_list; int num_verbs, i, err = 0; - /* is UCM available for this card ? */ - err = snd_card_get_name(card_index, &card_name); - if (err < 0) { - pa_log("Card can't get card_name from card_index %d", card_index); - goto name_fail; - } - + /* support multiple card instances, address card directly by index */ + card_name = pa_sprintf_malloc("hw:%i", card_index); + if (card_name == NULL) + return -ENOMEM; err = snd_use_case_mgr_open(&ucm->ucm_mgr, card_name); if (err < 0) { - pa_log_info("UCM not available for card %s", card_name); - goto ucm_mgr_fail; + /* fallback longname: is UCM available for this card ? */ + pa_xfree(card_name); + err = snd_card_get_name(card_index, &card_name); + if (err < 0) { + pa_log("Card can't get card_name from card_index %d", card_index); + goto name_fail; + } + + err = snd_use_case_mgr_open(&ucm->ucm_mgr, card_name); + if (err < 0) { + pa_log_info("UCM not available for card %s", card_name); + goto ucm_mgr_fail; + } } pa_log_info("UCM available for card %s", card_name); @@ -626,7 +776,7 @@ ucm_verb_fail: } ucm_mgr_fail: - free(card_name); + pa_xfree(card_name); name_fail: return err; @@ -636,6 +786,8 @@ int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, cons pa_alsa_ucm_device *d; pa_alsa_ucm_modifier *mod; pa_alsa_ucm_verb *verb; + char *value; + unsigned ui; int err = 0; *p_verb = NULL; @@ -650,6 +802,11 @@ int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, cons pa_proplist_sets(verb->proplist, PA_ALSA_PROP_UCM_NAME, pa_strnull(verb_name)); pa_proplist_sets(verb->proplist, PA_ALSA_PROP_UCM_DESCRIPTION, pa_strna(verb_desc)); + value = ucm_verb_value(uc_mgr, verb_name, "Priority"); + if (value && !pa_atou(value, &ui)) + verb->priority = ui > 10000 ? 10000 : ui; + free(value); + err = ucm_get_devices(verb, uc_mgr); if (err < 0) pa_log("No UCM devices for verb %s", verb_name); @@ -658,9 +815,6 @@ int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, cons if (err < 0) pa_log("No UCM modifiers for verb %s", verb_name); - /* Verb properties */ - ucm_get_property(verb, uc_mgr, verb_name); - PA_LLIST_FOREACH(d, verb->devices) { const char *dev_name = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME); @@ -693,6 +847,86 @@ static int pa_alsa_ucm_device_cmp(const void *a, const void *b) { return strcmp(pa_proplist_gets(d1->proplist, PA_ALSA_PROP_UCM_NAME), pa_proplist_gets(d2->proplist, PA_ALSA_PROP_UCM_NAME)); } +static void set_eld_devices(pa_hashmap *hash) +{ + pa_device_port *port; + pa_alsa_ucm_port_data *data; + pa_alsa_ucm_device *dev; + const char *eld_mixer_device_name; + void *state; + int idx, eld_device; + + PA_HASHMAP_FOREACH(port, hash, state) { + data = PA_DEVICE_PORT_DATA(port); + eld_mixer_device_name = NULL; + eld_device = -1; + PA_DYNARRAY_FOREACH(dev, data->devices, idx) { + if (dev->eld_device >= 0 && dev->eld_mixer_device_name) { + if (eld_device >= 0 && eld_device != dev->eld_device) { + pa_log_error("The ELD device is already set!"); + } else if (eld_mixer_device_name && pa_streq(dev->eld_mixer_device_name, eld_mixer_device_name)) { + pa_log_error("The ELD mixer device is already set (%s, %s)!", dev->eld_mixer_device_name, dev->eld_mixer_device_name); + } else { + eld_mixer_device_name = dev->eld_mixer_device_name; + eld_device = dev->eld_device; + } + } + } + data->eld_device = eld_device; + data->eld_mixer_device_name = pa_xstrdup(eld_mixer_device_name); + } +} + +static void probe_volumes(pa_hashmap *hash, bool is_sink, snd_pcm_t *pcm_handle, pa_hashmap *mixers, bool ignore_dB) { + pa_device_port *port; + pa_alsa_path *path; + pa_alsa_ucm_port_data *data; + pa_alsa_ucm_device *dev; + snd_mixer_t *mixer_handle; + const char *profile, *mdev, *mdev2; + void *state, *state2; + int idx; + + PA_HASHMAP_FOREACH(port, hash, state) { + data = PA_DEVICE_PORT_DATA(port); + + mdev = NULL; + PA_DYNARRAY_FOREACH(dev, data->devices, idx) { + mdev2 = get_mixer_device(dev, is_sink); + if (mdev && !pa_streq(mdev, mdev2)) { + pa_log_error("Two mixer device names found ('%s', '%s'), using s/w volume", mdev, mdev2); + goto fail; + } + mdev = mdev2; + } + + if (!(mixer_handle = pa_alsa_open_mixer_by_name(mixers, mdev, true))) { + pa_log_error("Failed to find a working mixer device (%s).", mdev); + goto fail; + } + + PA_HASHMAP_FOREACH_KV(profile, path, data->paths, state2) { + if (pa_alsa_path_probe(path, NULL, mixer_handle, ignore_dB) < 0) { + pa_log_warn("Could not probe path: %s, using s/w volume", data->path->name); + pa_hashmap_remove(data->paths, profile); + } else if (!path->has_volume) { + pa_log_warn("Path %s is not a volume control", data->path->name); + pa_hashmap_remove(data->paths, profile); + } else + pa_log_debug("Set up h/w volume using '%s' for %s:%s", path->name, profile, port->name); + } + } + + return; + +fail: + /* We could not probe the paths we created. Free them and revert to software volumes. */ + PA_HASHMAP_FOREACH(port, hash, state) { + data = PA_DEVICE_PORT_DATA(port); + pa_hashmap_remove_all(data->paths); + } +} + static void ucm_add_port_combination( pa_hashmap *hash, pa_alsa_ucm_mapping_context *context, @@ -710,7 +944,11 @@ static void ucm_add_port_combination( char *name, *desc; const char *dev_name; const char *direction; + const char *profile; pa_alsa_ucm_device *sorted[num], *dev; + pa_alsa_ucm_port_data *data; + pa_alsa_ucm_volume *vol; + void *state; for (i = 0; i < num; i++) sorted[i] = pdevices[i]; @@ -758,8 +996,6 @@ static void ucm_add_port_combination( port = pa_hashmap_get(ports, name); if (!port) { - struct ucm_port *ucm_port; - pa_device_port_new_data port_data; pa_device_port_new_data_init(&port_data); @@ -767,15 +1003,45 @@ static void ucm_add_port_combination( pa_device_port_new_data_set_description(&port_data, desc); pa_device_port_new_data_set_direction(&port_data, is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT); - port = pa_device_port_new(core, &port_data, sizeof(struct ucm_port)); - port->impl_free = ucm_port_free; + port = pa_device_port_new(core, &port_data, sizeof(pa_alsa_ucm_port_data)); pa_device_port_new_data_done(&port_data); - ucm_port = PA_DEVICE_PORT_DATA(port); - ucm_port_init(ucm_port, context->ucm, port, pdevices, num); + data = PA_DEVICE_PORT_DATA(port); + ucm_port_data_init(data, context->ucm, port, pdevices, num); + port->impl_free = ucm_port_data_free; pa_hashmap_put(ports, port->name, port); pa_log_debug("Add port %s: %s", port->name, port->description); + + if (num == 1) { + /* To keep things simple and not worry about stacking controls, we only support hardware volumes on non-combination + * ports. */ + data = PA_DEVICE_PORT_DATA(port); + + PA_HASHMAP_FOREACH_KV(profile, vol, is_sink ? dev->playback_volumes : dev->capture_volumes, state) { + pa_alsa_path *path = pa_alsa_path_synthesize(vol->mixer_elem, + is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT); + + if (!path) + pa_log_warn("Failed to set up volume control: %s", vol->mixer_elem); + else { + if (vol->master_elem) { + pa_alsa_element *e = pa_alsa_element_get(path, vol->master_elem, false); + e->switch_use = PA_ALSA_SWITCH_MUTE; + e->volume_use = PA_ALSA_VOLUME_MERGE; + } + + pa_hashmap_put(data->paths, pa_xstrdup(profile), path); + + /* Add path also to already created empty path set */ + dev = sorted[0]; + if (is_sink) + pa_hashmap_put(dev->playback_mapping->output_path_set->paths, pa_xstrdup(vol->mixer_elem), path); + else + pa_hashmap_put(dev->capture_mapping->input_path_set->paths, pa_xstrdup(vol->mixer_elem), path); + } + } + } } port->priority = priority; @@ -950,6 +1216,9 @@ void pa_alsa_ucm_add_ports_combination( ucm_add_ports_combination(p, context, is_sink, pdevices, 0, PA_IDXSET_INVALID, ports, cp, core); pa_xfree(pdevices); } + + /* ELD devices */ + set_eld_devices(ports); } void pa_alsa_ucm_add_ports( @@ -957,7 +1226,9 @@ void pa_alsa_ucm_add_ports( pa_proplist *proplist, pa_alsa_ucm_mapping_context *context, bool is_sink, - pa_card *card) { + pa_card *card, + snd_pcm_t *pcm_handle, + bool ignore_dB) { uint32_t idx; char *merged_roles; @@ -972,6 +1243,9 @@ void pa_alsa_ucm_add_ports( /* add ports first */ pa_alsa_ucm_add_ports_combination(*p, context, is_sink, card->ports, NULL, card->core); + /* now set up volume paths if any */ + probe_volumes(*p, is_sink, pcm_handle, context->ucm->mixers, ignore_dB); + /* then set property PA_PROP_DEVICE_INTENDED_ROLES */ merged_roles = pa_xstrdup(pa_proplist_gets(proplist, PA_PROP_DEVICE_INTENDED_ROLES)); PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) { @@ -996,10 +1270,13 @@ void pa_alsa_ucm_add_ports( } /* Change UCM verb and device to match selected card profile */ -int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, const char *new_profile, const char *old_profile) { +int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, const char *new_profile, const char *old_profile) { int ret = 0; const char *profile; pa_alsa_ucm_verb *verb; + pa_device_port *port; + pa_alsa_ucm_port_data *data; + void *state; if (new_profile == old_profile) return ret; @@ -1028,6 +1305,12 @@ int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, const char *new_profile, co } } + /* select volume controls on ports */ + PA_HASHMAP_FOREACH(port, card->ports, state) { + data = PA_DEVICE_PORT_DATA(port); + data->path = pa_hashmap_get(data->paths, new_profile); + } + return ret; } @@ -1079,23 +1362,35 @@ int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *p static void ucm_add_mapping(pa_alsa_profile *p, pa_alsa_mapping *m) { + pa_alsa_path_set *ps; + + /* create empty path set for the future path additions */ + ps = pa_xnew0(pa_alsa_path_set, 1); + ps->direction = m->direction; + ps->paths = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + switch (m->direction) { case PA_ALSA_DIRECTION_ANY: pa_idxset_put(p->output_mappings, m, NULL); pa_idxset_put(p->input_mappings, m, NULL); + m->output_path_set = ps; + m->input_path_set = ps; break; case PA_ALSA_DIRECTION_OUTPUT: pa_idxset_put(p->output_mappings, m, NULL); + m->output_path_set = ps; break; case PA_ALSA_DIRECTION_INPUT: pa_idxset_put(p->input_mappings, m, NULL); + m->input_path_set = ps; break; } } static void alsa_mapping_add_ucm_device(pa_alsa_mapping *m, pa_alsa_ucm_device *device) { char *cur_desc; - const char *new_desc; + const char *new_desc, *mdev; + bool is_sink = m->direction == PA_ALSA_DIRECTION_OUTPUT; pa_idxset_put(m->ucm_context.ucm_devices, device, NULL); @@ -1111,10 +1406,14 @@ static void alsa_mapping_add_ucm_device(pa_alsa_mapping *m, pa_alsa_ucm_device * m->description = m->description ? m->description : pa_xstrdup(""); /* save mapping to ucm device */ - if (m->direction == PA_ALSA_DIRECTION_OUTPUT) + if (is_sink) device->playback_mapping = m; else device->capture_mapping = m; + + mdev = get_mixer_device(device, is_sink); + if (mdev) + pa_proplist_sets(m->proplist, "alsa.mixer_device", mdev); } static void alsa_mapping_add_ucm_modifier(pa_alsa_mapping *m, pa_alsa_ucm_modifier *modifier) { @@ -1294,6 +1593,7 @@ static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *d pa_alsa_jack *j; const char *device_name; const char *jack_control; + const char *mixer_device_name; char *name; pa_assert(ucm); @@ -1303,6 +1603,22 @@ static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *d jack_control = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_JACK_CONTROL); if (jack_control) { +#if SND_LIB_VERSION >= 0x10201 + snd_ctl_elem_id_t *ctl; + int err, index; + snd_ctl_elem_id_alloca(&ctl); + err = snd_use_case_parse_ctl_elem_id(ctl, "JackControl", jack_control); + if (err < 0) + return NULL; + jack_control = snd_ctl_elem_id_get_name(ctl); + index = snd_ctl_elem_id_get_index(ctl); + if (index > 0) { + pa_log("[%s] Invalid JackControl index value: \"%s\",%d", device_name, jack_control, index); + return NULL; + } +#else +#warning "Upgrade to alsa-lib 1.2.1!" +#endif if (!pa_endswith(jack_control, " Jack")) { pa_log("[%s] Invalid JackControl value: \"%s\"", device_name, jack_control); return NULL; @@ -1312,16 +1628,22 @@ static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *d * end, so drop the trailing " Jack". */ name = pa_xstrndup(jack_control, strlen(jack_control) - 5); } else { - /* The jack control hasn't been explicitly configured - try a jack name - * that is the same as the device name. */ - name = pa_xstrdup(device_name); + /* The jack control hasn't been explicitly configured, fail. */ + return NULL; } PA_LLIST_FOREACH(j, ucm->jacks) if (pa_streq(j->name, name)) goto finish; - j = pa_alsa_jack_new(NULL, name); + mixer_device_name = get_jack_mixer_device(device, true); + if (!mixer_device_name) + mixer_device_name = get_jack_mixer_device(device, false); + if (!mixer_device_name) { + pa_log("[%s] No mixer device name for JackControl \"%s\"", device_name, jack_control); + return NULL; + } + j = pa_alsa_jack_new(NULL, mixer_device_name, name); PA_LLIST_PREPEND(pa_alsa_jack, ucm->jacks, j); finish: @@ -1342,7 +1664,7 @@ static int ucm_create_profile( pa_alsa_ucm_modifier *mod; int i = 0; const char *name, *sink, *source; - char *verb_cmp, *c; + unsigned int priority; pa_assert(ps); @@ -1362,24 +1684,26 @@ static int ucm_create_profile( p->supported = true; pa_hashmap_put(ps->profiles, p->name, p); - /* TODO: get profile priority from ucm info or policy management */ - c = verb_cmp = pa_xstrdup(verb_name); - while (*c) { - if (*c == '_') *c = ' '; - c++; - } + /* TODO: get profile priority from policy management */ + priority = verb->priority; - for (i = 0; verb_info[i].id; i++) { - if (strcasecmp(verb_info[i].id, verb_cmp) == 0) { - p->priority = verb_info[i].priority; - break; + if (priority == 0) { + char *verb_cmp, *c; + c = verb_cmp = pa_xstrdup(verb_name); + while (*c) { + if (*c == '_') *c = ' '; + c++; + } + for (i = 0; verb_info[i].id; i++) { + if (strcasecmp(verb_info[i].id, verb_cmp) == 0) { + priority = verb_info[i].priority; + break; + } } + pa_xfree(verb_cmp); } - pa_xfree(verb_cmp); - - if (verb_info[i].id == NULL) - p->priority = 1000; + p->priority = priority; PA_LLIST_FOREACH(dev, verb->devices) { pa_alsa_jack *jack; @@ -1393,7 +1717,8 @@ static int ucm_create_profile( ucm_create_mapping(ucm, ps, p, dev, verb_name, name, sink, source); jack = ucm_get_jack(ucm, dev); - device_set_jack(dev, jack); + if (jack) + device_set_jack(dev, jack); /* JackHWMute contains a list of device names. Each listed device must * be associated with the jack object that we just created. */ @@ -1402,7 +1727,7 @@ static int ucm_create_profile( char *hw_mute_device_name; const char *state = NULL; - while ((hw_mute_device_name = pa_split_spaces(jack_hw_mute, &state))) { + while ((hw_mute_device_name = ucm_split_devnames(jack_hw_mute, &state))) { pa_alsa_ucm_verb *verb2; bool device_found = false; @@ -1446,6 +1771,33 @@ static int ucm_create_profile( return 0; } +static void mapping_init_eld(pa_alsa_mapping *m, snd_pcm_t *pcm) +{ + pa_alsa_ucm_mapping_context *context = &m->ucm_context; + pa_alsa_ucm_device *dev; + uint32_t idx; + char *mdev; + snd_pcm_info_t *info; + int pcm_card, pcm_device; + + snd_pcm_info_alloca(&info); + if (snd_pcm_info(pcm, info) < 0) + return; + + if ((pcm_card = snd_pcm_info_get_card(info)) < 0) + return; + if ((pcm_device = snd_pcm_info_get_device(info)) < 0) + return; + + PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) { + mdev = pa_sprintf_malloc("hw:%i", pcm_card); + if (mdev == NULL) + continue; + dev->eld_mixer_device_name = mdev; + dev->eld_device = pcm_device; + } +} + static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, int mode) { snd_pcm_t* pcm; pa_sample_spec try_ss = ucm->core->default_sample_spec; @@ -1467,8 +1819,11 @@ static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, pcm = pa_alsa_open_by_device_string(m->device_strings[0], NULL, &try_ss, &try_map, mode, &try_period_size, &try_buffer_size, 0, NULL, NULL, exact_channels); - if (pcm && !exact_channels) - m->channel_map = try_map; + if (pcm) { + if (!exact_channels) + m->channel_map = try_map; + mapping_init_eld(m, pcm); + } return pcm; } @@ -1500,27 +1855,28 @@ static void profile_finalize_probing(pa_alsa_profile *p) { } } -static void ucm_mapping_jack_probe(pa_alsa_mapping *m) { - snd_pcm_t *pcm_handle; +static void ucm_mapping_jack_probe(pa_alsa_mapping *m, pa_hashmap *mixers) { snd_mixer_t *mixer_handle; pa_alsa_ucm_mapping_context *context = &m->ucm_context; pa_alsa_ucm_device *dev; uint32_t idx; - pcm_handle = m->direction == PA_ALSA_DIRECTION_OUTPUT ? m->output_pcm : m->input_pcm; - mixer_handle = pa_alsa_open_mixer_for_pcm(pcm_handle, NULL); - if (!mixer_handle) - return; - PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) { bool has_control; - has_control = pa_alsa_mixer_find(mixer_handle, dev->jack->alsa_name, 0) != NULL; + if (!dev->jack || !dev->jack->mixer_device_name) + continue; + + mixer_handle = pa_alsa_open_mixer_by_name(mixers, dev->jack->mixer_device_name, true); + if (!mixer_handle) { + pa_log_error("Unable to determine open mixer device '%s' for jack %s", dev->jack->mixer_device_name, dev->jack->name); + continue; + } + + has_control = pa_alsa_mixer_find_card(mixer_handle, dev->jack->alsa_name, 0) != NULL; pa_alsa_jack_set_has_control(dev->jack, has_control); pa_log_info("UCM jack %s has_control=%d", dev->jack->name, dev->jack->has_control); } - - snd_mixer_close(mixer_handle); } static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps) { @@ -1578,11 +1934,11 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set * PA_IDXSET_FOREACH(m, p->output_mappings, idx) if (!PA_UCM_IS_MODIFIER_MAPPING(m)) - ucm_mapping_jack_probe(m); + ucm_mapping_jack_probe(m, ucm->mixers); PA_IDXSET_FOREACH(m, p->input_mappings, idx) if (!PA_UCM_IS_MODIFIER_MAPPING(m)) - ucm_mapping_jack_probe(m); + ucm_mapping_jack_probe(m, ucm->mixers); profile_finalize_probing(p); } @@ -1636,11 +1992,20 @@ static void free_verb(pa_alsa_ucm_verb *verb) { if (di->ucm_ports) pa_dynarray_free(di->ucm_ports); + if (di->playback_volumes) + pa_hashmap_free(di->playback_volumes); + if (di->capture_volumes) + pa_hashmap_free(di->capture_volumes); + pa_proplist_free(di->proplist); + if (di->conflicting_devices) pa_idxset_free(di->conflicting_devices, NULL); if (di->supported_devices) pa_idxset_free(di->supported_devices, NULL); + + pa_xfree(di->eld_mixer_device_name); + pa_xfree(di); } @@ -1771,7 +2136,7 @@ void pa_alsa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, pa_ } } -static void device_add_ucm_port(pa_alsa_ucm_device *device, struct ucm_port *port) { +static void device_add_ucm_port(pa_alsa_ucm_device *device, pa_alsa_ucm_port_data *port) { pa_assert(device); pa_assert(port); @@ -1799,7 +2164,7 @@ static void device_add_hw_mute_jack(pa_alsa_ucm_device *device, pa_alsa_jack *ja } static void device_set_available(pa_alsa_ucm_device *device, pa_available_t available) { - struct ucm_port *port; + pa_alsa_ucm_port_data *port; unsigned idx; pa_assert(device); @@ -1833,8 +2198,8 @@ void pa_alsa_ucm_device_update_available(pa_alsa_ucm_device *device) { device_set_available(device, available); } -static void ucm_port_init(struct ucm_port *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port, - pa_alsa_ucm_device **devices, unsigned n_devices) { +static void ucm_port_data_init(pa_alsa_ucm_port_data *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port, + pa_alsa_ucm_device **devices, unsigned n_devices) { unsigned i; pa_assert(ucm); @@ -1844,17 +2209,21 @@ static void ucm_port_init(struct ucm_port *port, pa_alsa_ucm_config *ucm, pa_dev port->ucm = ucm; port->core_port = core_port; port->devices = pa_dynarray_new(NULL); + port->eld_device = -1; for (i = 0; i < n_devices; i++) { pa_dynarray_append(port->devices, devices[i]); device_add_ucm_port(devices[i], port); } + port->paths = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree, + (pa_free_cb_t) pa_alsa_path_free); + ucm_port_update_available(port); } -static void ucm_port_free(pa_device_port *port) { - struct ucm_port *ucm_port; +static void ucm_port_data_free(pa_device_port *port) { + pa_alsa_ucm_port_data *ucm_port; pa_assert(port); @@ -1862,9 +2231,14 @@ static void ucm_port_free(pa_device_port *port) { if (ucm_port->devices) pa_dynarray_free(ucm_port->devices); + + if (ucm_port->paths) + pa_hashmap_free(ucm_port->paths); + + pa_xfree(ucm_port->eld_mixer_device_name); } -static void ucm_port_update_available(struct ucm_port *port) { +static void ucm_port_update_available(pa_alsa_ucm_port_data *port) { pa_alsa_ucm_device *device; unsigned idx; pa_available_t available = PA_AVAILABLE_YES; @@ -1896,7 +2270,7 @@ pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_cha return NULL; } -int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, const char *new_profile, const char *old_profile) { +int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, const char *new_profile, const char *old_profile) { return -1; } @@ -1909,7 +2283,9 @@ void pa_alsa_ucm_add_ports( pa_proplist *proplist, pa_alsa_ucm_mapping_context *context, bool is_sink, - pa_card *card) { + pa_card *card, + snd_pcm_t *pcm_handle, + bool ignore_dB) { } void pa_alsa_ucm_add_ports_combination( diff --git a/src/modules/alsa/alsa-ucm.h b/src/modules/alsa/alsa-ucm.h index c926f3c..e7a795c 100644 --- a/src/modules/alsa/alsa-ucm.h +++ b/src/modules/alsa/alsa-ucm.h @@ -45,12 +45,33 @@ typedef void snd_use_case_mgr_t; /** For devices: Playback roles */ #define PA_ALSA_PROP_UCM_PLAYBACK_ROLES "alsa.ucm.playback.roles" +/** For devices: Playback control device name */ +#define PA_ALSA_PROP_UCM_PLAYBACK_CTL_DEVICE "alsa.ucm.playback.ctldev" + /** For devices: Playback control volume ID string. e.g PlaybackVolume */ #define PA_ALSA_PROP_UCM_PLAYBACK_VOLUME "alsa.ucm.playback.volume" /** For devices: Playback switch e.g PlaybackSwitch */ #define PA_ALSA_PROP_UCM_PLAYBACK_SWITCH "alsa.ucm.playback.switch" +/** For devices: Playback mixer device name */ +#define PA_ALSA_PROP_UCM_PLAYBACK_MIXER_DEVICE "alsa.ucm.playback.mixer.device" + +/** For devices: Playback mixer identifier */ +#define PA_ALSA_PROP_UCM_PLAYBACK_MIXER_ELEM "alsa.ucm.playback.mixer.element" + +/** For devices: Playback mixer master identifier */ +#define PA_ALSA_PROP_UCM_PLAYBACK_MASTER_ELEM "alsa.ucm.playback.master.element" + +/** For devices: Playback mixer master type */ +#define PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE "alsa.ucm.playback.master.type" + +/** For devices: Playback mixer master identifier */ +#define PA_ALSA_PROP_UCM_PLAYBACK_MASTER_ID "alsa.ucm.playback.master.id" + +/** For devices: Playback mixer master type */ +#define PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE "alsa.ucm.playback.master.type" + /** For devices: Playback priority */ #define PA_ALSA_PROP_UCM_PLAYBACK_PRIORITY "alsa.ucm.playback.priority" @@ -63,12 +84,33 @@ typedef void snd_use_case_mgr_t; /** For devices: Capture roles */ #define PA_ALSA_PROP_UCM_CAPTURE_ROLES "alsa.ucm.capture.roles" +/** For devices: Capture control device name */ +#define PA_ALSA_PROP_UCM_CAPTURE_CTL_DEVICE "alsa.ucm.capture.ctldev" + /** For devices: Capture controls volume ID string. e.g CaptureVolume */ #define PA_ALSA_PROP_UCM_CAPTURE_VOLUME "alsa.ucm.capture.volume" /** For devices: Capture switch e.g CaptureSwitch */ #define PA_ALSA_PROP_UCM_CAPTURE_SWITCH "alsa.ucm.capture.switch" +/** For devices: Capture mixer device name */ +#define PA_ALSA_PROP_UCM_CAPTURE_MIXER_DEVICE "alsa.ucm.capture.mixer.device" + +/** For devices: Capture mixer identifier */ +#define PA_ALSA_PROP_UCM_CAPTURE_MIXER_ELEM "alsa.ucm.capture.mixer.element" + +/** For devices: Capture mixer identifier */ +#define PA_ALSA_PROP_UCM_CAPTURE_MASTER_ELEM "alsa.ucm.capture.master.element" + +/** For devices: Capture mixer identifier */ +#define PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE "alsa.ucm.capture.master.type" + +/** For devices: Capture mixer identifier */ +#define PA_ALSA_PROP_UCM_CAPTURE_MASTER_ID "alsa.ucm.capture.master.id" + +/** For devices: Capture mixer identifier */ +#define PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE "alsa.ucm.capture.master.type" + /** For devices: Capture priority */ #define PA_ALSA_PROP_UCM_CAPTURE_PRIORITY "alsa.ucm.capture.priority" @@ -84,6 +126,9 @@ typedef void snd_use_case_mgr_t; /** For devices: The modifier (if any) that this device corresponds to */ #define PA_ALSA_PROP_UCM_MODIFIER "alsa.ucm.modifier" +/* Corresponds to the "JackCTL" UCM value. */ +#define PA_ALSA_PROP_UCM_JACK_DEVICE "alsa.ucm.jack_device" + /* Corresponds to the "JackControl" UCM value. */ #define PA_ALSA_PROP_UCM_JACK_CONTROL "alsa.ucm.jack_control" @@ -95,10 +140,12 @@ typedef struct pa_alsa_ucm_modifier pa_alsa_ucm_modifier; typedef struct pa_alsa_ucm_device pa_alsa_ucm_device; typedef struct pa_alsa_ucm_config pa_alsa_ucm_config; typedef struct pa_alsa_ucm_mapping_context pa_alsa_ucm_mapping_context; +typedef struct pa_alsa_ucm_port_data pa_alsa_ucm_port_data; +typedef struct pa_alsa_ucm_volume pa_alsa_ucm_volume; int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index); pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map); -int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, const char *new_profile, const char *old_profile); +int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, const char *new_profile, const char *old_profile); int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, const char *verb_desc, pa_alsa_ucm_verb **p_verb); @@ -107,7 +154,9 @@ void pa_alsa_ucm_add_ports( pa_proplist *proplist, pa_alsa_ucm_mapping_context *context, bool is_sink, - pa_card *card); + pa_card *card, + snd_pcm_t *pcm_handle, + bool ignore_dB); void pa_alsa_ucm_add_ports_combination( pa_hashmap *hash, pa_alsa_ucm_mapping_context *context, @@ -139,6 +188,11 @@ struct pa_alsa_ucm_device { unsigned playback_channels; unsigned capture_channels; + /* These may be different per verb, so we store this as a hashmap of verb -> volume_control. We might eventually want to + * make this a hashmap of verb -> per-verb-device-properties-struct. */ + pa_hashmap *playback_volumes; + pa_hashmap *capture_volumes; + pa_alsa_mapping *playback_mapping; pa_alsa_mapping *capture_mapping; @@ -153,6 +207,9 @@ struct pa_alsa_ucm_device { pa_alsa_jack *jack; pa_dynarray *hw_mute_jacks; /* pa_alsa_jack */ pa_available_t available; + + char *eld_mixer_device_name; + int eld_device; }; void pa_alsa_ucm_device_update_available(pa_alsa_ucm_device *device); @@ -184,6 +241,7 @@ struct pa_alsa_ucm_verb { PA_LLIST_FIELDS(pa_alsa_ucm_verb); pa_proplist *proplist; + unsigned priority; PA_LLIST_HEAD(pa_alsa_ucm_device, devices); PA_LLIST_HEAD(pa_alsa_ucm_modifier, modifiers); @@ -194,6 +252,7 @@ struct pa_alsa_ucm_config { snd_use_case_mgr_t *ucm_mgr; pa_alsa_ucm_verb *active_verb; + pa_hashmap *mixers; PA_LLIST_HEAD(pa_alsa_ucm_verb, verbs); PA_LLIST_HEAD(pa_alsa_jack, jacks); }; @@ -206,4 +265,28 @@ struct pa_alsa_ucm_mapping_context { pa_idxset *ucm_modifiers; }; +struct pa_alsa_ucm_port_data { + pa_alsa_ucm_config *ucm; + pa_device_port *core_port; + + /* A single port will be associated with multiple devices if it represents + * a combination of devices. */ + pa_dynarray *devices; /* pa_alsa_ucm_device */ + + /* profile name -> pa_alsa_path for volume control */ + pa_hashmap *paths; + /* Current path, set when activating profile */ + pa_alsa_path *path; + + /* ELD info */ + char *eld_mixer_device_name; + int eld_device; /* PCM device number */ +}; + +struct pa_alsa_ucm_volume { + char *mixer_elem; /* mixer element identifier */ + char *master_elem; /* master mixer element identifier */ + char *master_type; +}; + #endif diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c index bd0a47e..d86f43c 100644 --- a/src/modules/alsa/alsa-util.c +++ b/src/modules/alsa/alsa-util.c @@ -1066,6 +1066,7 @@ void pa_alsa_init_proplist_ctl(pa_proplist *p, const char *name) { int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents) { snd_pcm_state_t state; + snd_pcm_hw_params_t *hwparams; int err; pa_assert(pcm); @@ -1103,16 +1104,25 @@ int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents) { break; case SND_PCM_STATE_SUSPENDED: - /* Retry resume 3 times before giving up, then fallback to restarting the stream. */ - for (int i = 0; i < 3; i++) { - if ((err = snd_pcm_resume(pcm)) == 0) - return 0; - if (err != -EAGAIN) - break; - pa_msleep(25); + snd_pcm_hw_params_alloca(&hwparams); + + if ((err = snd_pcm_hw_params_any(pcm, hwparams)) < 0) { + pa_log_debug("snd_pcm_hw_params_any() failed: %s", pa_alsa_strerror(err)); + return -1; } - pa_log_warn("Could not recover alsa device from SUSPENDED state, trying to restart PCM"); - /* Fall through */ + + if (snd_pcm_hw_params_can_resume(hwparams)) { + /* Retry resume 3 times before giving up, then fallback to restarting the stream. */ + for (int i = 0; i < 3; i++) { + if ((err = snd_pcm_resume(pcm)) == 0) + return 0; + if (err != -EAGAIN) + break; + pa_msleep(25); + } + pa_log_warn("Could not recover alsa device from SUSPENDED state, trying to restart PCM"); + } + /* Fall through */ default: @@ -1600,7 +1610,11 @@ bool pa_alsa_may_tsched(bool want) { #define SND_MIXER_ELEM_PULSEAUDIO (SND_MIXER_ELEM_LAST + 10) -snd_mixer_elem_t *pa_alsa_mixer_find(snd_mixer_t *mixer, const char *name, unsigned int device) { +static snd_mixer_elem_t *pa_alsa_mixer_find(snd_mixer_t *mixer, + snd_ctl_elem_iface_t iface, + const char *name, + unsigned int index, + unsigned int device) { snd_mixer_elem_t *elem; for (elem = snd_mixer_first_elem(mixer); elem; elem = snd_mixer_elem_next(elem)) { @@ -1608,8 +1622,12 @@ snd_mixer_elem_t *pa_alsa_mixer_find(snd_mixer_t *mixer, const char *name, unsig if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_PULSEAUDIO) continue; helem = snd_mixer_elem_get_private(elem); + if (snd_hctl_elem_get_interface(helem) != iface) + continue; if (!pa_streq(snd_hctl_elem_get_name(helem), name)) continue; + if (snd_hctl_elem_get_index(helem) != index) + continue; if (snd_hctl_elem_get_device(helem) != device) continue; return elem; @@ -1617,6 +1635,14 @@ snd_mixer_elem_t *pa_alsa_mixer_find(snd_mixer_t *mixer, const char *name, unsig return NULL; } +snd_mixer_elem_t *pa_alsa_mixer_find_card(snd_mixer_t *mixer, const char *name, unsigned int device) { + return pa_alsa_mixer_find(mixer, SND_CTL_ELEM_IFACE_CARD, name, 0, device); +} + +snd_mixer_elem_t *pa_alsa_mixer_find_pcm(snd_mixer_t *mixer, const char *name, unsigned int device) { + return pa_alsa_mixer_find(mixer, SND_CTL_ELEM_IFACE_PCM, name, 0, device); +} + static int mixer_class_compare(const snd_mixer_elem_t *c1, const snd_mixer_elem_t *c2) { /* Dummy compare function */ @@ -1700,85 +1726,119 @@ static int prepare_mixer(snd_mixer_t *mixer, const char *dev) { return 0; } -snd_mixer_t *pa_alsa_open_mixer(int alsa_card_index, char **ctl_device) { +snd_mixer_t *pa_alsa_open_mixer(pa_hashmap *mixers, int alsa_card_index, bool probe) { + char *md = pa_sprintf_malloc("hw:%i", alsa_card_index); + snd_mixer_t *m = pa_alsa_open_mixer_by_name(mixers, md, probe); + pa_xfree(md); + return m; +} + +snd_mixer_t *pa_alsa_open_mixer_by_name(pa_hashmap *mixers, const char *dev, bool probe) { int err; snd_mixer_t *m; - char *md; - snd_pcm_info_t* info; - snd_pcm_info_alloca(&info); + pa_alsa_mixer *pm; + char *dev2; + void *state; + + pa_assert(mixers); + pa_assert(dev); + + pm = pa_hashmap_get(mixers, dev); + + /* The quick card number/index lookup (hw:#) + * We already know the card number/index, thus use the mixer + * from the cache at first. + */ + if (!pm && pa_strneq(dev, "hw:", 3)) { + const char *s = dev + 3; + int card_index; + while (*s && *s >= 0 && *s <= '9') s++; + if (*s == '\0' && pa_atoi(dev + 3, &card_index) >= 0) { + PA_HASHMAP_FOREACH_KV(dev2, pm, mixers, state) { + if (pm->card_index == card_index) { + dev = dev2; + pm = pa_hashmap_get(mixers, dev); + break; + } + } + } + } + + if (pm) { + if (!probe) + pm->used_for_probe_only = false; + return pm->mixer_handle; + } if ((err = snd_mixer_open(&m, 0)) < 0) { pa_log("Error opening mixer: %s", pa_alsa_strerror(err)); return NULL; } - /* Then, try by card index */ - md = pa_sprintf_malloc("hw:%i", alsa_card_index); - if (prepare_mixer(m, md) >= 0) { - - if (ctl_device) - *ctl_device = md; - else - pa_xfree(md); - - return m; + if (prepare_mixer(m, dev) >= 0) { + pm = pa_xnew0(pa_alsa_mixer, 1); + if (pm) { + snd_hctl_t *hctl; + pm->card_index = -1; + /* determine the ALSA card number (index) and store it to card_index */ + err = snd_mixer_get_hctl(m, dev, &hctl); + if (err >= 0) { + snd_ctl_card_info_t *info; + snd_ctl_card_info_alloca(&info); + err = snd_ctl_card_info(snd_hctl_ctl(hctl), info); + if (err >= 0) + pm->card_index = snd_ctl_card_info_get_card(info); + } + pm->used_for_probe_only = probe; + pm->mixer_handle = m; + pa_hashmap_put(mixers, pa_xstrdup(dev), pm); + return m; + } } - pa_xfree(md); - snd_mixer_close(m); return NULL; } -snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device) { - int err; - snd_mixer_t *m; - const char *dev; +snd_mixer_t *pa_alsa_open_mixer_for_pcm(pa_hashmap *mixers, snd_pcm_t *pcm, bool probe) { snd_pcm_info_t* info; snd_pcm_info_alloca(&info); pa_assert(pcm); - if ((err = snd_mixer_open(&m, 0)) < 0) { - pa_log("Error opening mixer: %s", pa_alsa_strerror(err)); - return NULL; - } - - /* First, try by name */ - if ((dev = snd_pcm_name(pcm))) - if (prepare_mixer(m, dev) >= 0) { - if (ctl_device) - *ctl_device = pa_xstrdup(dev); - - return m; - } - - /* Then, try by card index */ if (snd_pcm_info(pcm, info) >= 0) { - char *md; int card_idx; - if ((card_idx = snd_pcm_info_get_card(info)) >= 0) { - - md = pa_sprintf_malloc("hw:%i", card_idx); - - if (!dev || !pa_streq(dev, md)) - if (prepare_mixer(m, md) >= 0) { + if ((card_idx = snd_pcm_info_get_card(info)) >= 0) + return pa_alsa_open_mixer(mixers, card_idx, probe); + } - if (ctl_device) - *ctl_device = md; - else - pa_xfree(md); + return NULL; +} - return m; - } +void pa_alsa_mixer_set_fdlist(pa_hashmap *mixers, snd_mixer_t *mixer_handle, pa_mainloop_api *ml) +{ + pa_alsa_mixer *pm; + void *state; - pa_xfree(md); + PA_HASHMAP_FOREACH(pm, mixers, state) + if (pm->mixer_handle == mixer_handle) { + pm->used_for_probe_only = false; + if (!pm->fdl) { + pm->fdl = pa_alsa_fdlist_new(); + if (pm->fdl) + pa_alsa_fdlist_set_handle(pm->fdl, pm->mixer_handle, NULL, ml); + } } - } +} - snd_mixer_close(m); - return NULL; +void pa_alsa_mixer_free(pa_alsa_mixer *mixer) +{ + if (mixer->fdl) + pa_alsa_fdlist_free(mixer->fdl); + if (mixer->mixer_handle) + snd_mixer_close(mixer->mixer_handle); + pa_xfree(mixer); } int pa_alsa_get_hdmi_eld(snd_hctl_elem_t *elem, pa_hdmi_eld *eld) { diff --git a/src/modules/alsa/alsa-util.h b/src/modules/alsa/alsa-util.h index 4ceaa06..0d3d5af 100644 --- a/src/modules/alsa/alsa-util.h +++ b/src/modules/alsa/alsa-util.h @@ -141,9 +141,14 @@ const char* pa_alsa_strerror(int errnum); bool pa_alsa_may_tsched(bool want); -snd_mixer_elem_t *pa_alsa_mixer_find(snd_mixer_t *mixer, const char *name, unsigned int device); - -snd_mixer_t *pa_alsa_open_mixer(int alsa_card_index, char **ctl_device); +snd_mixer_elem_t *pa_alsa_mixer_find_card(snd_mixer_t *mixer, const char *name, unsigned int device); +snd_mixer_elem_t *pa_alsa_mixer_find_pcm(snd_mixer_t *mixer, const char *name, unsigned int device); + +snd_mixer_t *pa_alsa_open_mixer(pa_hashmap *mixers, int alsa_card_index, bool probe); +snd_mixer_t *pa_alsa_open_mixer_by_name(pa_hashmap *mixers, const char *dev, bool probe); +snd_mixer_t *pa_alsa_open_mixer_for_pcm(pa_hashmap *mixers, snd_pcm_t *pcm, bool probe); +void pa_alsa_mixer_set_fdlist(pa_hashmap *mixers, snd_mixer_t *mixer, pa_mainloop_api *ml); +void pa_alsa_mixer_free(pa_alsa_mixer *mixer); typedef struct pa_hdmi_eld pa_hdmi_eld; struct pa_hdmi_eld { diff --git a/src/modules/alsa/meson.build b/src/modules/alsa/meson.build index 5309dc1..f31eeb5 100644 --- a/src/modules/alsa/meson.build +++ b/src/modules/alsa/meson.build @@ -32,7 +32,7 @@ libalsa_util = shared_library('alsa-util', c_args : [pa_c_args, server_c_args], link_args : [nodelete_link_args], include_directories : [configinc, topinc], - dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, alsa_dep, dbus_dep, libatomic_ops_dep, libm_dep, udev_dep], + dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, alsa_dep, dbus_dep, libatomic_ops_dep, libm_dep, udev_dep, libintl_dep], install : true, install_rpath : privlibdir, install_dir : modlibexecdir, diff --git a/src/modules/alsa/mixer/paths/analog-output-speaker.conf b/src/modules/alsa/mixer/paths/analog-output-speaker.conf index 9f4dac4..6f9968e 100644 --- a/src/modules/alsa/mixer/paths/analog-output-speaker.conf +++ b/src/modules/alsa/mixer/paths/analog-output-speaker.conf @@ -56,6 +56,9 @@ state.unplugged = unknown state.plugged = no state.unplugged = unknown +[Jack Speaker] +required-any = any + [Jack Speaker Phantom] required-any = any state.plugged = unknown diff --git a/src/modules/alsa/mixer/paths/analog-output.conf.common b/src/modules/alsa/mixer/paths/analog-output.conf.common index e52830d..7bf3463 100644 --- a/src/modules/alsa/mixer/paths/analog-output.conf.common +++ b/src/modules/alsa/mixer/paths/analog-output.conf.common @@ -85,7 +85,8 @@ ; required-any = ignore | enumeration | any # In this element, either this or another option must exist (or an element) ; required-absent = ignore | enumeration | any # In this element, this option must not exist or the path will be invalid ; -; [Element ...] # For each element that we shall control +; [Element ...] # For each element that we shall control. The "..." here is the element name, +; # or name and index separated by a comma. ; required = ignore | switch | volume | enumeration | any # If set, require this element to be of this kind and available, ; # otherwise don't consider this path valid for the card ; required-any = ignore | switch | volume | enumeration | any # If set, at least one of the elements or jacks with required-any in this diff --git a/src/modules/alsa/mixer/paths/steelseries-arctis-5-output-chat.conf b/src/modules/alsa/mixer/paths/steelseries-arctis-output-chat-common.conf index 5842bfe..5842bfe 100644 --- a/src/modules/alsa/mixer/paths/steelseries-arctis-5-output-chat.conf +++ b/src/modules/alsa/mixer/paths/steelseries-arctis-output-chat-common.conf diff --git a/src/modules/alsa/mixer/paths/steelseries-arctis-5-output-game.conf b/src/modules/alsa/mixer/paths/steelseries-arctis-output-game-common.conf index b758a6f..b758a6f 100644 --- a/src/modules/alsa/mixer/paths/steelseries-arctis-5-output-game.conf +++ b/src/modules/alsa/mixer/paths/steelseries-arctis-output-game-common.conf diff --git a/src/modules/alsa/mixer/paths/steelseries-arctis-7-input.conf b/src/modules/alsa/mixer/paths/usb-gaming-headset-input.conf index 3fa36e9..9fa7fe9 100644 --- a/src/modules/alsa/mixer/paths/steelseries-arctis-7-input.conf +++ b/src/modules/alsa/mixer/paths/usb-gaming-headset-input.conf @@ -13,8 +13,16 @@ # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. -; Steelseries Arctis 7 USB headset microphone path. Works also with Arctis Pro -; Wireless. +; USB gaming headset microphone input path. These headsets usually have two +; output devices. The first one is mono, meant for voice audio, and the second +; one is stereo, meant for everything else. The purpose of this unusual design +; is to provide separate volume controls for voice and other audio, which can +; be useful in gaming. +; +; Works with: +; Steelseries Arctis 7 +; Steelseries Arctis Pro Wireless. +; Lucidsound LS31 [General] description-key = analog-input-microphone-headset diff --git a/src/modules/alsa/mixer/paths/steelseries-arctis-7-output-mono.conf b/src/modules/alsa/mixer/paths/usb-gaming-headset-output-mono.conf index d8b24a2..6df662f 100644 --- a/src/modules/alsa/mixer/paths/steelseries-arctis-7-output-mono.conf +++ b/src/modules/alsa/mixer/paths/usb-gaming-headset-output-mono.conf @@ -13,11 +13,16 @@ # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. -; Steelseries Arctis 7 USB headset mono output path. Works also with Arctis Pro -; Wireless. The headset has two output devices. The first one is mono, meant -; for voice audio, and the second one is stereo, meant for everything else. The -; purpose of this unusual design is to provide separate volume controls for -; voice and other audio, which can be useful in gaming. +; USB gaming headset mono output path. These headsets usually have two +; output devices. The first one is mono, meant for voice audio, and the second +; one is stereo, meant for everything else. The purpose of this unusual design +; is to provide separate volume controls for voice and other audio, which can +; be useful in gaming. +; +; Works with: +; Steelseries Arctis 7 +; Steelseries Arctis Pro Wireless. +; Lucidsound LS31 [General] description-key = analog-output-headphones-mono diff --git a/src/modules/alsa/mixer/paths/steelseries-arctis-7-output-stereo.conf b/src/modules/alsa/mixer/paths/usb-gaming-headset-output-stereo.conf index fcc58a0..e3f91cd 100644 --- a/src/modules/alsa/mixer/paths/steelseries-arctis-7-output-stereo.conf +++ b/src/modules/alsa/mixer/paths/usb-gaming-headset-output-stereo.conf @@ -13,11 +13,16 @@ # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. -; Steelseries Arctis 7 USB headset stereo output path. Works also with Arctis -; Pro Wireless. The headset has two output devices. The first one is mono, -; meant for voice audio, and the second one is stereo, meant for everything -; else. The purpose of this unusual design is to provide separate volume -; controls for voice and other audio, which can be useful in gaming. +; USB gaming headset mono output path. These headsets usually have two +; output devices. The first one is mono, meant for voice audio, and the second +; one is stereo, meant for everything else. The purpose of this unusual design +; is to provide separate volume controls for voice and other audio, which can +; be useful in gaming. +; +; Works with: +; Steelseries Arctis 7 +; Steelseries Arctis Pro Wireless. +; Lucidsound LS31 ; ; This path doesn't provide hardware volume control, because the stereo ; output is controlled by the PCM element with index 1, and currently diff --git a/src/modules/alsa/mixer/profile-sets/default.conf b/src/modules/alsa/mixer/profile-sets/default.conf index 34c51bc..c73bf5c 100644 --- a/src/modules/alsa/mixer/profile-sets/default.conf +++ b/src/modules/alsa/mixer/profile-sets/default.conf @@ -49,7 +49,8 @@ ; # If multiple are found to be working they will be available as device ports ; paths-output = ... ; element-input = ... # Instead of configuring a full mixer path simply configure a single -; # mixer element for volume/mute handling +; # mixer element for volume/mute handling. The value can be an element +; # name, or name and index separated by a comma. ; element-output = ... ; priority = ... ; direction = any | input | output # Only useful for? @@ -72,7 +73,8 @@ ; [DecibelFix element] # Decibel fixes can be used to work around missing or incorrect dB ; # information from alsa. A decibel fix is a table that maps volume steps ; # to decibel values for one volume element. The "element" part in the -; # section title is the name of the volume element. +; # section title is the name of the volume element (or name and index +; # separated by a comma). ; # ; # NOTE: This feature is meant just as a help for figuring out the correct ; # decibel values. PulseAudio is not the correct place to maintain the diff --git a/src/modules/alsa/mixer/profile-sets/steelseries-arctis-5-usb-audio.conf b/src/modules/alsa/mixer/profile-sets/steelseries-arctis-common-usb-audio.conf index fe353c3..5f11ed1 100644 --- a/src/modules/alsa/mixer/profile-sets/steelseries-arctis-5-usb-audio.conf +++ b/src/modules/alsa/mixer/profile-sets/steelseries-arctis-common-usb-audio.conf @@ -6,13 +6,14 @@ description = Chat device-strings = hw:%f,0,0 channel-map = left,right paths-input = analog-input-mic -paths-output = steelseries-arctis-5-output-chat +paths-output = steelseries-arctis-output-chat-common +intended-roles = phone [Mapping analog-game] description = Game device-strings = hw:%f,1,0 channel-map = left,right -paths-output = steelseries-arctis-5-output-game +paths-output = steelseries-arctis-output-game-common direction = output [Profile output:analog-chat+output:analog-game+input:analog-chat] diff --git a/src/modules/alsa/mixer/profile-sets/steelseries-arctis-7-usb-audio.conf b/src/modules/alsa/mixer/profile-sets/usb-gaming-headset.conf index e1394dc..f48b44f 100644 --- a/src/modules/alsa/mixer/profile-sets/steelseries-arctis-7-usb-audio.conf +++ b/src/modules/alsa/mixer/profile-sets/usb-gaming-headset.conf @@ -13,12 +13,17 @@ # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. -; Steelseries Arctis 7 USB and Arctis Pro Wireless USB headset. These headsets -; have a microphone and two output devices. The first output device is mono, -; meant for voice audio, and the second one is stereo, meant for everything -; else. The purpose of this unusual design is to provide separate volume +; USB gaming headset. +; These headsets usually have two output devices. The first one is mono, +; meant for voice audio, and the second one is stereo, meant for everything +; else. The purpose of this unusual design is to provide separate volume ; controls for voice and other audio, which can be useful in gaming. ; +; Works with: +; Steelseries Arctis 7 +; Steelseries Arctis Pro Wireless. +; Lucidsound LS31 +; ; See default.conf for an explanation on the directives used here. [General] @@ -27,13 +32,14 @@ auto-profiles = yes [Mapping analog-mono] device-strings = hw:%f,0,0 channel-map = mono -paths-output = steelseries-arctis-7-output-mono -paths-input = steelseries-arctis-7-input +paths-output = usb-gaming-headset-output-mono +paths-input = usb-gaming-headset-input +intended-roles = phone [Mapping analog-stereo] device-strings = hw:%f,1,0 channel-map = left,right -paths-output = steelseries-arctis-7-output-stereo +paths-output = usb-gaming-headset-output-stereo direction = output [Profile output:analog-mono+output:analog-stereo+input:analog-mono] diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c index 1e1090f..c5852b4 100644 --- a/src/modules/alsa/module-alsa-card.c +++ b/src/modules/alsa/module-alsa-card.c @@ -111,9 +111,8 @@ struct userdata { char *device_id; int alsa_card_index; - snd_mixer_t *mixer_handle; + pa_hashmap *mixers; pa_hashmap *jacks; - pa_alsa_fdlist *mixer_fdl; pa_card *card; @@ -241,7 +240,7 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) { /* if UCM is available for this card then update the verb */ if (u->use_ucm) { - if (pa_alsa_ucm_set_profile(&u->ucm, nd->profile ? nd->profile->name : NULL, + if (pa_alsa_ucm_set_profile(&u->ucm, c, nd->profile ? nd->profile->name : NULL, od->profile ? od->profile->name : NULL) < 0) { ret = -1; goto finish; @@ -294,7 +293,7 @@ static void init_profile(struct userdata *u) { if (d->profile && u->use_ucm) { /* Set initial verb */ - if (pa_alsa_ucm_set_profile(ucm, d->profile->name, NULL) < 0) { + if (pa_alsa_ucm_set_profile(ucm, u->card, d->profile->name, NULL) < 0) { pa_log("Failed to set ucm profile %s", d->profile->name); return; } @@ -513,15 +512,24 @@ static int report_jack_state(snd_mixer_elem_t *melem, unsigned int mask) { return 0; } -static pa_device_port* find_port_with_eld_device(pa_hashmap *ports, int device) { +static pa_device_port* find_port_with_eld_device(struct userdata *u, int device) { void *state; pa_device_port *p; - PA_HASHMAP_FOREACH(p, ports, state) { - pa_alsa_port_data *data = PA_DEVICE_PORT_DATA(p); - pa_assert(data->path); - if (device == data->path->eld_device) - return p; + if (u->use_ucm) { + PA_HASHMAP_FOREACH(p, u->card->ports, state) { + pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(p); + pa_assert(data->eld_mixer_device_name); + if (device == data->eld_device) + return p; + } + } else { + PA_HASHMAP_FOREACH(p, u->card->ports, state) { + pa_alsa_port_data *data = PA_DEVICE_PORT_DATA(p); + pa_assert(data->path); + if (device == data->path->eld_device) + return p; + } } return NULL; } @@ -538,7 +546,7 @@ static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask) { if (mask == SND_CTL_EVENT_MASK_REMOVE) return 0; - p = find_port_with_eld_device(u->card->ports, device); + p = find_port_with_eld_device(u, device); if (p == NULL) { pa_log_error("Invalid device changed in ALSA: %d", device); return 0; @@ -566,33 +574,46 @@ static void init_eld_ctls(struct userdata *u) { void *state; pa_device_port *port; - if (!u->mixer_handle) - return; - /* The code in this function expects ports to have a pa_alsa_port_data * struct as their data, but in UCM mode ports don't have any data. Hence, * the ELD controls can't currently be used in UCM mode. */ - if (u->use_ucm) - return; - PA_HASHMAP_FOREACH(port, u->card->ports, state) { - pa_alsa_port_data *data = PA_DEVICE_PORT_DATA(port); + snd_mixer_t *mixer_handle; snd_mixer_elem_t* melem; int device; - pa_assert(data->path); - device = data->path->eld_device; - if (device < 0) + if (u->use_ucm) { + pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(port); + device = data->eld_device; + if (device < 0 || !data->eld_mixer_device_name) + continue; + + mixer_handle = pa_alsa_open_mixer_by_name(u->mixers, data->eld_mixer_device_name, true); + } else { + pa_alsa_port_data *data = PA_DEVICE_PORT_DATA(port); + + pa_assert(data->path); + + device = data->path->eld_device; + if (device < 0) + continue; + + mixer_handle = pa_alsa_open_mixer(u->mixers, u->alsa_card_index, true); + } + + if (!mixer_handle) continue; - melem = pa_alsa_mixer_find(u->mixer_handle, "ELD", device); + melem = pa_alsa_mixer_find_pcm(mixer_handle, "ELD", device); if (melem) { + pa_alsa_mixer_set_fdlist(u->mixers, mixer_handle, u->core->mainloop); snd_mixer_elem_set_callback(melem, hdmi_eld_changed); snd_mixer_elem_set_callback_private(melem, u); hdmi_eld_changed(melem, 0); + pa_log_info("ELD device found for port %s (%d).", port->name, device); } else - pa_log_debug("No ELD device found for port %s.", port->name); + pa_log_debug("No ELD device found for port %s (%d).", port->name, device); } } @@ -627,25 +648,31 @@ static void init_jacks(struct userdata *u) { if (pa_hashmap_size(u->jacks) == 0) return; - u->mixer_fdl = pa_alsa_fdlist_new(); - - u->mixer_handle = pa_alsa_open_mixer(u->alsa_card_index, NULL); - if (u->mixer_handle && pa_alsa_fdlist_set_handle(u->mixer_fdl, u->mixer_handle, NULL, u->core->mainloop) >= 0) { - PA_HASHMAP_FOREACH(jack, u->jacks, state) { - jack->melem = pa_alsa_mixer_find(u->mixer_handle, jack->alsa_name, 0); - if (!jack->melem) { - pa_log_warn("Jack '%s' seems to have disappeared.", jack->alsa_name); - pa_alsa_jack_set_has_control(jack, false); - continue; + PA_HASHMAP_FOREACH(jack, u->jacks, state) { + if (!jack->mixer_device_name) { + jack->mixer_handle = pa_alsa_open_mixer(u->mixers, u->alsa_card_index, false); + if (!jack->mixer_handle) { + pa_log("Failed to open mixer for card %d for jack detection", u->alsa_card_index); + continue; + } + } else { + jack->mixer_handle = pa_alsa_open_mixer_by_name(u->mixers, jack->mixer_device_name, false); + if (!jack->mixer_handle) { + pa_log("Failed to open mixer '%s' for jack detection", jack->mixer_device_name); + continue; } - snd_mixer_elem_set_callback(jack->melem, report_jack_state); - snd_mixer_elem_set_callback_private(jack->melem, u); - report_jack_state(jack->melem, 0); } - - } else - pa_log("Failed to open mixer for jack detection"); - + pa_alsa_mixer_set_fdlist(u->mixers, jack->mixer_handle, u->core->mainloop); + jack->melem = pa_alsa_mixer_find_card(jack->mixer_handle, jack->alsa_name, 0); + if (!jack->melem) { + pa_log_warn("Jack '%s' seems to have disappeared.", jack->alsa_name); + pa_alsa_jack_set_has_control(jack, false); + continue; + } + snd_mixer_elem_set_callback(jack->melem, report_jack_state); + snd_mixer_elem_set_callback_private(jack->melem, u); + report_jack_state(jack->melem, 0); + } } static void set_card_name(pa_card_new_data *data, pa_modargs *ma, const char *device_id) { @@ -769,6 +796,10 @@ int pa__init(pa_module *m) { u->use_ucm = true; u->ucm.core = m->core; + u->mixers = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, + pa_xfree, (pa_free_cb_t) pa_alsa_mixer_free); + u->ucm.mixers = u->mixers; /* alias */ + if (!(u->modargs = pa_modargs_new(m->argument, valid_modargs))) { pa_log("Failed to parse module arguments."); goto fail; @@ -849,7 +880,7 @@ int pa__init(pa_module *m) { u->profile_set->ignore_dB = ignore_dB; - pa_alsa_profile_set_probe(u->profile_set, u->device_id, &m->core->default_sample_spec, m->core->default_n_fragments, m->core->default_fragment_size_msec); + pa_alsa_profile_set_probe(u->profile_set, u->mixers, u->device_id, &m->core->default_sample_spec, m->core->default_n_fragments, m->core->default_fragment_size_msec); pa_alsa_profile_set_dump(u->profile_set); pa_card_new_data_init(&data); @@ -900,7 +931,8 @@ int pa__init(pa_module *m) { * results in an infinite loop of "fill buffer, handle underrun". To work * around this issue, the suspend_when_unavailable flag is used to stop * playback when the HDMI cable is unplugged. */ - if (pa_safe_streq(pa_proplist_gets(data.proplist, "alsa.driver_name"), "snd_hdmi_lpe_audio")) { + if (!u->use_ucm && + pa_safe_streq(pa_proplist_gets(data.proplist, "alsa.driver_name"), "snd_hdmi_lpe_audio")) { pa_device_port *port; void *state; @@ -948,6 +980,16 @@ int pa__init(pa_module *m) { init_profile(u); init_eld_ctls(u); + /* Remove all probe only mixers */ + if (u->mixers) { + const char *devname; + pa_alsa_mixer *pm; + void *state; + PA_HASHMAP_FOREACH_KV(devname, pm, u->mixers, state) + if (pm->used_for_probe_only) + pa_hashmap_remove_and_free(u->mixers, devname); + } + if (reserve) pa_reserve_wrapper_unref(reserve); @@ -998,10 +1040,8 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) goto finish; - if (u->mixer_fdl) - pa_alsa_fdlist_free(u->mixer_fdl); - if (u->mixer_handle) - snd_mixer_close(u->mixer_handle); + if (u->mixers) + pa_hashmap_free(u->mixers); if (u->jacks) pa_hashmap_free(u->jacks); diff --git a/src/modules/alsa/module-alsa-sink.c b/src/modules/alsa/module-alsa-sink.c index 4d4beb3..a90c5e4 100644 --- a/src/modules/alsa/module-alsa-sink.c +++ b/src/modules/alsa/module-alsa-sink.c @@ -52,7 +52,7 @@ PA_MODULE_USAGE( "tsched_buffer_size=<buffer size when using timer based scheduling> " "tsched_buffer_watermark=<lower fill watermark> " "ignore_dB=<ignore dB information from the device?> " - "control=<name of mixer control> " + "control=<name of mixer control, or name and index separated by a comma> " "rewind_safeguard=<number of bytes that cannot be rewound> " "deferred_volume=<Synchronize software and hardware volume changes to avoid momentary jumps?> " "deferred_volume_safety_margin=<usec adjustment depending on volume direction> " diff --git a/src/modules/alsa/module-alsa-source.c b/src/modules/alsa/module-alsa-source.c index 747ba93..d152283 100644 --- a/src/modules/alsa/module-alsa-source.c +++ b/src/modules/alsa/module-alsa-source.c @@ -61,7 +61,7 @@ PA_MODULE_USAGE( "tsched_buffer_size=<buffer size when using timer based scheduling> " "tsched_buffer_watermark=<upper fill watermark> " "ignore_dB=<ignore dB information from the device?> " - "control=<name of mixer control>" + "control=<name of mixer control, or name and index separated by a comma>" "deferred_volume=<Synchronize software and hardware volume changes to avoid momentary jumps?> " "deferred_volume_safety_margin=<usec adjustment depending on volume direction> " "deferred_volume_extra_delay=<usec adjustment to HW volume changes> " |