diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index 24f7de558..42212a7ce 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -630,6 +630,7 @@ static void init_jacks(pa_card *impl) void *state; pa_alsa_path* path; pa_alsa_jack* jack; + char buf[64]; impl->jacks = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); @@ -673,9 +674,10 @@ static void init_jacks(pa_card *impl) } pa_alsa_mixer_use_for_poll(impl->ucm.mixers, jack->mixer_handle); - jack->melem = pa_alsa_mixer_find_card(jack->mixer_handle, jack->alsa_name, 0); + jack->melem = pa_alsa_mixer_find_card(jack->mixer_handle, &jack->alsa_id, 0); if (!jack->melem) { - pa_log_warn("Jack '%s' seems to have disappeared.", jack->alsa_name); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &jack->alsa_id); + pa_log_warn("Jack '%s' seems to have disappeared.", buf); pa_alsa_jack_set_has_control(jack, false); continue; } diff --git a/spa/plugins/alsa/acp/alsa-mixer.c b/spa/plugins/alsa/acp/alsa-mixer.c index 014a5889f..cd41d97f4 100644 --- a/spa/plugins/alsa/acp/alsa-mixer.c +++ b/spa/plugins/alsa/acp/alsa-mixer.c @@ -99,7 +99,7 @@ struct description2_map { pa_device_port_type_t type; }; -static char *alsa_id_str(char *dst, size_t dst_len, pa_alsa_mixer_id *id) { +char *pa_alsa_mixer_id_to_string(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 { @@ -139,7 +139,7 @@ static int alsa_id_decode(const char *src, char *name, int *index) { return 0; } -pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name) { +pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name, int index) { pa_alsa_jack *jack; pa_assert(name); @@ -148,7 +148,8 @@ pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name 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->alsa_id.name = pa_sprintf_malloc("%s Jack", name); + jack->alsa_id.index = index; jack->state_unplugged = PA_AVAILABLE_NO; jack->state_plugged = PA_AVAILABLE_YES; jack->ucm_devices = pa_dynarray_new(NULL); @@ -163,7 +164,7 @@ void pa_alsa_jack_free(pa_alsa_jack *jack) { pa_dynarray_free(jack->ucm_hw_mute_devices); pa_dynarray_free(jack->ucm_devices); - pa_xfree(jack->alsa_name); + pa_xfree(jack->alsa_id.name); pa_xfree(jack->name); pa_xfree(jack->mixer_device_name); pa_xfree(jack); @@ -690,6 +691,20 @@ static const snd_mixer_selem_channel_id_t alsa_channel_ids[PA_CHANNEL_POSITION_M [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SND_MIXER_SCHN_UNKNOWN }; +static snd_mixer_selem_channel_id_t alsa_channel_positions[POSITION_MASK_CHANNELS] = { + SND_MIXER_SCHN_FRONT_LEFT, + SND_MIXER_SCHN_FRONT_RIGHT, + SND_MIXER_SCHN_REAR_LEFT, + SND_MIXER_SCHN_REAR_RIGHT, + SND_MIXER_SCHN_FRONT_CENTER, + SND_MIXER_SCHN_WOOFER, + SND_MIXER_SCHN_SIDE_LEFT, + SND_MIXER_SCHN_SIDE_RIGHT, +#if POSITION_MASK_CHANNELS > 8 +#error "Extend alsa_channel_positions[] array (9+)" +#endif +}; + static void setting_free(pa_alsa_setting *s) { pa_assert(s); @@ -822,7 +837,7 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Element %s seems to have disappeared.", buf); return -1; } @@ -848,14 +863,14 @@ 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_alsa_mixer_id_to_string(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.", 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_alsa_mixer_id_to_string(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.", buf, c, e->db_fix->db_values[value - e->db_fix->min_step] / 100.0); @@ -878,14 +893,14 @@ 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_alsa_mixer_id_to_string(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.", 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_alsa_mixer_id_to_string(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.", buf, c, e->db_fix->db_values[value - e->db_fix->min_step] / 100.0); @@ -993,7 +1008,7 @@ static int element_get_switch(pa_alsa_element *e, snd_mixer_t *m, bool *b) { SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Element %s seems to have disappeared.", buf); return -1; } @@ -1159,7 +1174,7 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Element %s seems to have disappeared.", buf); return -1; } @@ -1351,7 +1366,7 @@ static int element_set_switch(pa_alsa_element *e, snd_mixer_t *m, bool b) { SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Element %s seems to have disappeared.", buf); return -1; } @@ -1362,7 +1377,7 @@ static int element_set_switch(pa_alsa_element *e, snd_mixer_t *m, bool b) { r = snd_mixer_selem_set_capture_switch_all(me, b); if (r < 0) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Failed to set switch of %s: %s", buf, pa_alsa_strerror(errno)); } @@ -1406,7 +1421,7 @@ static int element_set_constant_volume(pa_alsa_element *e, snd_mixer_t *m) { SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Element %s seems to have disappeared.", buf); return -1; } @@ -1451,7 +1466,7 @@ static int element_set_constant_volume(pa_alsa_element *e, snd_mixer_t *m) { } if (r < 0) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Failed to set volume of %s: %s", buf, pa_alsa_strerror(errno)); } @@ -1657,19 +1672,19 @@ 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) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(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) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(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_alsa_mixer_id_to_string(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, buf, e->min_volume, e->max_volume); return false; @@ -1677,7 +1692,7 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { if (e->db_fix && ((e->min_volume > e->db_fix->min_step) || (e->max_volume < e->db_fix->max_step))) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(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); @@ -1704,19 +1719,19 @@ 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) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(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) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(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_alsa_mixer_id_to_string(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.", buf, min_dB / 100.0, max_dB / 100.0, @@ -1739,7 +1754,7 @@ 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) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(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.", buf, e->path->name, e->volume_limit, e->min_volume + 1, e->max_volume); @@ -1751,7 +1766,7 @@ 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) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(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 @@ -1768,7 +1783,11 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { if (is_mono) { e->n_channels = 1; - if (!e->override_map) { + if ((e->override_map & (1 << (e->n_channels-1))) && e->masks[SND_MIXER_SCHN_MONO][e->n_channels-1] == 0) { + pa_log_warn("Override map for mono element %s is invalid, ignoring override map", e->path->name); + e->override_map &= ~(1 << (e->n_channels-1)); + } + if (!(e->override_map & (1 << (e->n_channels-1)))) { for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) { if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN) continue; @@ -1792,27 +1811,28 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { } if (e->n_channels <= 0) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Volume element %s with no channels?", buf); return false; - } else if (e->n_channels > 2) { + } else if (e->n_channels > POSITION_MASK_CHANNELS) { /* FIXME: In some places code like this is used: * * e->masks[alsa_channel_ids[p]][e->n_channels-1] * * The definition of e->masks is * - * pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST + 1][2]; + * pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST + 1][POSITION_MASK_CHANNELS]; * - * Since the array size is fixed at 2, we obviously - * don't support elements with more than two + * Since the array size is fixed at POSITION_MASK_CHANNELS, we obviously + * don't support elements with more than POSITION_MASK_CHANNELS * channels... */ - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(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; } - if (!e->override_map) { +retry: + if (!(e->override_map & (1 << (e->n_channels-1)))) { for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) { bool has_channel; @@ -1835,6 +1855,17 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { e->merged_mask |= e->masks[alsa_channel_ids[p]][e->n_channels-1]; } + + if (e->merged_mask == 0) { + if (!(e->override_map & (1 << (e->n_channels-1)))) { + pa_log_warn("Channel map for element %s is invalid", e->path->name); + return false; + } + pa_log_warn("Override map for element %s has empty result, ignoring override map", e->path->name); + e->override_map &= ~(1 << (e->n_channels-1)); + goto retry; + } + return true; } @@ -1944,12 +1975,12 @@ static int jack_probe(pa_alsa_jack *j, pa_alsa_mapping *mapping, snd_mixer_t *m) } new_name = pa_sprintf_malloc("%s,pcm=%i Jack", j->name, mapping->hw_device_index); - pa_xfree(j->alsa_name); - j->alsa_name = new_name; + pa_xfree(j->alsa_id.name); + j->alsa_id.name = new_name; j->append_pcm_to_name = false; } - has_control = pa_alsa_mixer_find_card(m, j->alsa_name, 0) != NULL; + has_control = pa_alsa_mixer_find_card(m, &j->alsa_id, 0) != NULL; pa_alsa_jack_set_has_control(j, has_control); if (j->has_control) { @@ -2012,19 +2043,26 @@ finish: static pa_alsa_jack* jack_get(pa_alsa_path *p, const char *section) { pa_alsa_jack *j; + char *name; + int index; if (!pa_startswith(section, "Jack ")) return NULL; section += 5; - if (p->last_jack && pa_streq(p->last_jack->name, section)) + name = alloca(strlen(section) + 1); + if (alsa_id_decode(section, name, &index)) + return NULL; + + if (p->last_jack && pa_streq(p->last_jack->name, name) && + p->last_jack->alsa_id.index == index) return p->last_jack; PA_LLIST_FOREACH(j, p->jacks) - if (pa_streq(j->name, section)) + if (pa_streq(j->name, name) && j->alsa_id.index == index) goto finish; - j = pa_alsa_jack_new(p, NULL, section); + j = pa_alsa_jack_new(p, NULL, name, index); PA_LLIST_INSERT_AFTER(pa_alsa_jack, p->jacks, p->last_jack, j); finish: @@ -2428,6 +2466,16 @@ static int element_parse_volume_limit(pa_config_parser_state *state) { return 0; } +static unsigned int parse_channel_position(const char *m) +{ + pa_channel_position_t p; + + if ((p = pa_channel_position_from_string(m)) == PA_CHANNEL_POSITION_INVALID) + return SND_MIXER_SCHN_UNKNOWN; + + return alsa_channel_ids[p]; +} + static pa_channel_position_mask_t parse_mask(const char *m) { pa_channel_position_mask_t v; @@ -2465,7 +2513,9 @@ static int element_parse_override_map(pa_config_parser_state *state) { pa_alsa_path *p; pa_alsa_element *e; const char *split_state = NULL; + char *s; unsigned i = 0; + int channel_count = 0; char *n; pa_assert(state); @@ -2477,31 +2527,60 @@ static int element_parse_override_map(pa_config_parser_state *state) { return -1; } + s = strstr(state->lvalue, "."); + if (s) { + pa_atoi(s + 1, &channel_count); + if (channel_count < 1 || channel_count > POSITION_MASK_CHANNELS) { + pa_log("[%s:%u] Override map index '%s' invalid in '%s'", state->filename, state->lineno, state->lvalue, state->section); + return 0; + } + } else { + pa_log("[%s:%u] Invalid override map syntax '%s' in '%s'", state->filename, state->lineno, state->lvalue, state->section); + return -1; + } + while ((n = pa_split(state->rvalue, ",", &split_state))) { pa_channel_position_mask_t m; + snd_mixer_selem_channel_id_t channel_position; + + if (i >= (unsigned)channel_count) { + pa_log("[%s:%u] Invalid override map size (>%d) in '%s'", state->filename, state->lineno, channel_count, state->section); + return -1; + } + channel_position = alsa_channel_positions[i]; if (!*n) m = 0; else { - if ((m = parse_mask(n)) == 0) { - pa_log("[%s:%u] Override map '%s' invalid in '%s'", state->filename, state->lineno, n, state->section); + s = strstr(n, ":"); + if (s) { + *s = '\0'; + s++; + channel_position = parse_channel_position(n); + if (channel_position == SND_MIXER_SCHN_UNKNOWN) { + pa_log("[%s:%u] Override map position '%s' invalid in '%s'", state->filename, state->lineno, n, state->section); + pa_xfree(n); + return -1; + } + } + if ((m = parse_mask(s ? s : n)) == 0) { + pa_log("[%s:%u] Override map '%s' invalid in '%s'", state->filename, state->lineno, s ? s : n, state->section); pa_xfree(n); return -1; } } - if (pa_streq(state->lvalue, "override-map.1")) - e->masks[i++][0] = m; - else - e->masks[i++][1] = m; - - /* Later on we might add override-map.3 and so on here ... */ - + if (e->masks[channel_position][channel_count-1]) { + pa_log("[%s:%u] Override map '%s' duplicate position '%s' in '%s'", state->filename, state->lineno, s ? s : n, snd_mixer_selem_channel_name(channel_position), state->section); + pa_xfree(n); + return -1; + } + e->override_map |= (1 << (channel_count - 1)); + e->masks[channel_position][channel_count-1] = m; pa_xfree(n); + i++; } - e->override_map = true; - return 0; } @@ -2575,7 +2654,7 @@ static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx) SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Element %s seems to have disappeared.", buf); return -1; } @@ -2588,7 +2667,7 @@ static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx) r = snd_mixer_selem_set_capture_switch_all(me, alsa_idx); if (r < 0) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Failed to set switch of %s: %s", buf, pa_alsa_strerror(errno)); } @@ -2596,7 +2675,7 @@ static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx) pa_assert(e->enumeration_use == PA_ALSA_ENUMERATION_SELECT); if ((r = snd_mixer_selem_set_enum_item(me, 0, alsa_idx)) < 0) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Failed to set enumeration of %s: %s", buf, pa_alsa_strerror(errno)); } } @@ -2653,7 +2732,7 @@ 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) { - alsa_id_str(buf, sizeof(buf), &o->element->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &o->element->alsa_id); pa_log("Element %s of option %s not set for select.", buf, o->name); return -1; } @@ -2661,7 +2740,7 @@ static int option_verify(pa_alsa_option *o) { if (o->element->switch_use == PA_ALSA_SWITCH_SELECT && !pa_streq(o->alsa_name, "on") && !pa_streq(o->alsa_name, "off")) { - alsa_id_str(buf, sizeof(buf), &o->element->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &o->element->alsa_id); pa_log("Switch %s options need be named off or on ", buf); return -1; } @@ -2687,13 +2766,13 @@ 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)) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(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) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log("Element %s cannot set select for both switch and enumeration.", buf); return -1; } @@ -2719,6 +2798,7 @@ static int path_verify(pa_alsa_path *p) { { "analog-input-video", N_("Video"), PA_DEVICE_PORT_TYPE_VIDEO }, { "analog-output", N_("Analog Output"), PA_DEVICE_PORT_TYPE_ANALOG }, { "analog-output-headphones", N_("Headphones"), PA_DEVICE_PORT_TYPE_HEADPHONES }, + { "analog-output-headphones-2", N_("Headphones 2"), PA_DEVICE_PORT_TYPE_HEADPHONES }, { "analog-output-headphones-mono", N_("Headphones Mono Output"), PA_DEVICE_PORT_TYPE_HEADPHONES }, { "analog-output-lineout", N_("Line Out"), PA_DEVICE_PORT_TYPE_LINE }, { "analog-output-mono", N_("Analog Mono Output"), PA_DEVICE_PORT_TYPE_ANALOG }, @@ -2809,6 +2889,15 @@ pa_alsa_path* pa_alsa_path_new(const char *paths_dir, const char *fname, pa_alsa { "enumeration", element_parse_enumeration, NULL, NULL }, { "override-map.1", element_parse_override_map, NULL, NULL }, { "override-map.2", element_parse_override_map, NULL, NULL }, + { "override-map.3", element_parse_override_map, NULL, NULL }, + { "override-map.4", element_parse_override_map, NULL, NULL }, + { "override-map.5", element_parse_override_map, NULL, NULL }, + { "override-map.6", element_parse_override_map, NULL, NULL }, + { "override-map.7", element_parse_override_map, NULL, NULL }, + { "override-map.8", element_parse_override_map, NULL, NULL }, +#if POSITION_MASK_CHANNELS > 8 +#error "Add override-map.9+ definitions" +#endif /* ... later on we might add override-map.3 and so on here ... */ { "required", element_parse_required, NULL, NULL }, { "required-any", element_parse_required, NULL, NULL }, @@ -3026,6 +3115,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; + bool min_dB_set, max_dB_set; char buf[64]; pa_assert(p); @@ -3041,22 +3131,23 @@ int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m pa_log_debug("Probing path '%s'", p->name); PA_LLIST_FOREACH(j, p->jacks) { + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &j->alsa_id); if (jack_probe(j, mapping, m) < 0) { p->supported = false; - pa_log_debug("Probe of jack '%s' failed.", j->alsa_name); + pa_log_debug("Probe of jack %s failed.", buf); return -1; } - pa_log_debug("Probe of jack '%s' succeeded (%s)", j->alsa_name, j->has_control ? "found!" : "not found"); + pa_log_debug("Probe of jack %s succeeded (%s)", buf, j->has_control ? "found!" : "not found"); } PA_LLIST_FOREACH(e, p->elements) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); if (element_probe(e, m) < 0) { p->supported = false; 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).", buf, e->volume_use, e->switch_use, e->enumeration_use); + pa_log_debug("Probe of element %s succeeded (volume=%d, switch=%d, enumeration=%d, has_dB=%d).", buf, e->volume_use, e->switch_use, e->enumeration_use, e->has_dB); if (ignore_dB) e->has_dB = false; @@ -3120,18 +3211,30 @@ int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m p->supported = true; p->min_dB = INFINITY; + min_dB_set = false; p->max_dB = -INFINITY; + max_dB_set = false; for (t = 0; t < PA_CHANNEL_POSITION_MAX; t++) { if (path_volume_channels & PA_CHANNEL_POSITION_MASK(t)) { - if (p->min_dB > min_dB[t]) + if (p->min_dB > min_dB[t]) { p->min_dB = min_dB[t]; + min_dB_set = true; + } - if (p->max_dB < max_dB[t]) + if (p->max_dB < max_dB[t]) { p->max_dB = max_dB[t]; + max_dB_set = true; + } } } + /* this is probably a wrong prediction, but it should be safe */ + if (!min_dB_set) + p->min_dB = -INFINITY; + if (!max_dB_set) + p->max_dB = 0; + return 0; } @@ -3147,7 +3250,7 @@ void pa_alsa_setting_dump(pa_alsa_setting *s) { void pa_alsa_jack_dump(pa_alsa_jack *j) { pa_assert(j); - pa_log_debug("Jack %s, alsa_name='%s', detection %s", j->name, j->alsa_name, j->has_control ? "possible" : "unavailable"); + pa_log_debug("Jack %s, alsa_name='%s', index='%d', detection %s", j->name, j->alsa_id.name, j->alsa_id.index, j->has_control ? "possible" : "unavailable"); } void pa_alsa_option_dump(pa_alsa_option *o) { @@ -3167,8 +3270,8 @@ void pa_alsa_element_dump(pa_alsa_element *e) { 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", + pa_alsa_mixer_id_to_string(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=%02x", buf, e->direction, e->switch_use, @@ -3180,7 +3283,7 @@ void pa_alsa_element_dump(pa_alsa_element *e) { e->required_absent, (long long unsigned) e->merged_mask, e->n_channels, - pa_yes_no(e->override_map)); + e->override_map); PA_LLIST_FOREACH(o, e->options) pa_alsa_option_dump(o); @@ -3227,7 +3330,7 @@ static void element_set_callback(pa_alsa_element *e, snd_mixer_t *m, snd_mixer_e SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Element %s seems to have disappeared.", buf); return; } @@ -3522,7 +3625,7 @@ static bool element_is_subset(pa_alsa_element *a, pa_alsa_element *b, snd_mixer_ SELEM_INIT(sid, &a->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { - alsa_id_str(buf, sizeof(buf), &a->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &a->alsa_id); pa_log_warn("Element %s seems to have disappeared.", buf); return false; } @@ -3553,7 +3656,7 @@ 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_alsa_mixer_id_to_string(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", buf, a->masks[s][a->n_channels-1], b->masks[s][b->n_channels-1], s); return false; @@ -3630,7 +3733,8 @@ static void path_set_condense(pa_alsa_path_set *ps, snd_mixer_t *m) { continue; PA_LLIST_FOREACH(jb, p2->jacks) { - if (jb->has_control && pa_streq(jb->alsa_name, ja->alsa_name) && + if (jb->has_control && pa_streq(ja->alsa_id.name, jb->alsa_id.name) && + (ja->alsa_id.index == jb->alsa_id.index) && (ja->state_plugged == jb->state_plugged) && (ja->state_unplugged == jb->state_unplugged)) { exists = true; @@ -4325,7 +4429,8 @@ static void profile_set_set_availability_groups(pa_alsa_profile_set *ps) { PA_LLIST_FOREACH(j2, p2->jacks) { if (!j2->has_control || j2->state_plugged == PA_AVAILABLE_NO) continue; - if (pa_streq(j->alsa_name, j2->alsa_name)) { + if (pa_streq(j->alsa_id.name, j2->alsa_id.name) && + j->alsa_id.index == j2->alsa_id.index) { j->state_plugged = PA_AVAILABLE_UNKNOWN; j2->state_plugged = PA_AVAILABLE_UNKNOWN; found = p2->availability_group; diff --git a/spa/plugins/alsa/acp/alsa-mixer.h b/spa/plugins/alsa/acp/alsa-mixer.h index 8dece154e..6255bbfaa 100644 --- a/spa/plugins/alsa/acp/alsa-mixer.h +++ b/spa/plugins/alsa/acp/alsa-mixer.h @@ -40,6 +40,8 @@ typedef struct pa_alsa_profile pa_alsa_profile; typedef struct pa_alsa_profile pa_card_profile; typedef struct pa_alsa_device pa_alsa_device; +#define POSITION_MASK_CHANNELS 8 + typedef enum pa_alsa_switch_use { PA_ALSA_SWITCH_IGNORE, PA_ALSA_SWITCH_MUTE, /* make this switch follow mute status */ @@ -110,6 +112,8 @@ struct pa_alsa_mixer_id { int index; }; +char *pa_alsa_mixer_id_to_string(char *dst, size_t dst_len, pa_alsa_mixer_id *id); + /* 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. */ @@ -149,7 +153,7 @@ struct pa_alsa_element { long constant_volume; - bool override_map:1; + unsigned int override_map; bool direction_try_other:1; bool has_dB:1; @@ -157,7 +161,7 @@ struct pa_alsa_element { long volume_limit; /* -1 for no configured limit */ double min_dB, max_dB; - pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST + 1][2]; + pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST + 1][POSITION_MASK_CHANNELS]; unsigned n_channels; pa_channel_position_mask_t merged_mask; @@ -174,6 +178,7 @@ struct pa_alsa_jack { snd_mixer_t *mixer_handle; char *mixer_device_name; + struct pa_alsa_mixer_id alsa_id; char *name; /* E g "Headphone" */ char *alsa_name; /* E g "Headphone Jack" */ bool has_control; /* is the jack itself present? */ @@ -191,7 +196,7 @@ struct pa_alsa_jack { bool append_pcm_to_name; }; -pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name); +pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name, int index); 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); diff --git a/spa/plugins/alsa/acp/alsa-ucm.c b/spa/plugins/alsa/acp/alsa-ucm.c index 246390b6b..8bfa298c6 100644 --- a/spa/plugins/alsa/acp/alsa-ucm.c +++ b/spa/plugins/alsa/acp/alsa-ucm.c @@ -1705,7 +1705,7 @@ static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *d j = NULL; goto finish; } - j = pa_alsa_jack_new(NULL, mixer_device_name, name); + j = pa_alsa_jack_new(NULL, mixer_device_name, name, 0); PA_LLIST_PREPEND(pa_alsa_jack, ucm->jacks, j); finish: @@ -1939,7 +1939,7 @@ static void ucm_mapping_jack_probe(pa_alsa_mapping *m, pa_hashmap *mixers) { continue; } - has_control = pa_alsa_mixer_find_card(mixer_handle, dev->jack->alsa_name, 0) != NULL; + has_control = pa_alsa_mixer_find_card(mixer_handle, &dev->jack->alsa_id, 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); } diff --git a/spa/plugins/alsa/acp/alsa-util.c b/spa/plugins/alsa/acp/alsa-util.c index d57251ddd..30173093d 100644 --- a/spa/plugins/alsa/acp/alsa-util.c +++ b/spa/plugins/alsa/acp/alsa-util.c @@ -1609,8 +1609,8 @@ static snd_mixer_elem_t *pa_alsa_mixer_find(snd_mixer_t *mixer, 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_card(snd_mixer_t *mixer, struct pa_alsa_mixer_id *alsa_id, unsigned int device) { + return pa_alsa_mixer_find(mixer, SND_CTL_ELEM_IFACE_CARD, alsa_id->name, alsa_id->index, device); } snd_mixer_elem_t *pa_alsa_mixer_find_pcm(snd_mixer_t *mixer, const char *name, unsigned int device) { diff --git a/spa/plugins/alsa/acp/alsa-util.h b/spa/plugins/alsa/acp/alsa-util.h index 609e53c66..76b8402ac 100644 --- a/spa/plugins/alsa/acp/alsa-util.h +++ b/spa/plugins/alsa/acp/alsa-util.h @@ -154,7 +154,7 @@ const char* pa_alsa_strerror(int errnum); bool pa_alsa_may_tsched(bool want); #endif -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_card(snd_mixer_t *mixer, struct pa_alsa_mixer_id *alsa_id, 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); diff --git a/spa/plugins/alsa/mixer/paths/analog-output-headphones-2.conf b/spa/plugins/alsa/mixer/paths/analog-output-headphones-2.conf index 30815d0a8..178999088 100644 --- a/spa/plugins/alsa/mixer/paths/analog-output-headphones-2.conf +++ b/spa/plugins/alsa/mixer/paths/analog-output-headphones-2.conf @@ -13,17 +13,24 @@ # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . -; Path for mixers that have a 'Headphone2' control +; Path for the second headphone output on dual-headphone machines. ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 98 -description-key = analog-output-headphones [Properties] device.icon_name = audio-headphones +; HP EliteDesk 800 SFF Headphone +[Jack Front Headphone,1] +required-any = any + +; HP EliteDesk 800 DM Headphone +[Jack Front Headphone Surround] +required-any = any + [Element Hardware Master] switch = mute volume = merge @@ -47,6 +54,13 @@ volume = off switch = mute volume = zero +[Element Headphone,1] +required-any = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + [Element Headphone+LO] switch = mute volume = zero @@ -56,7 +70,7 @@ switch = off volume = off [Element Headphone2] -required = any +required-any = any switch = mute volume = merge override-map.1 = all diff --git a/spa/plugins/alsa/mixer/paths/analog-output-headphones.conf b/spa/plugins/alsa/mixer/paths/analog-output-headphones.conf index 14fb81481..c808e19e1 100644 --- a/spa/plugins/alsa/mixer/paths/analog-output-headphones.conf +++ b/spa/plugins/alsa/mixer/paths/analog-output-headphones.conf @@ -35,6 +35,10 @@ state.unplugged = unknown [Jack Front Headphone] required-any = any +; HP EliteDesk 800 DM Headset +[Jack Front Headphone Front] +required-any = any + [Jack Front Headphone Phantom] required-any = any state.plugged = unknown @@ -89,6 +93,13 @@ volume = merge override-map.1 = all override-map.2 = all-left,all-right +; This path is intended to control the first headphones, not +; the second headphones. But it should not hurt if we leave the second +; headphone jack enabled nonetheless. +[Element Headphone,1] +switch = mute +volume = zero + [Element Headset] required-any = any switch = mute diff --git a/spa/plugins/alsa/mixer/paths/analog-output-lineout.conf b/spa/plugins/alsa/mixer/paths/analog-output-lineout.conf index 61df49965..1ffce2225 100644 --- a/spa/plugins/alsa/mixer/paths/analog-output-lineout.conf +++ b/spa/plugins/alsa/mixer/paths/analog-output-lineout.conf @@ -127,6 +127,10 @@ required-any = any switch = off volume = off +[Element Headphone,1] +switch = off +volume = off + [Element Headphone2] switch = off volume = off diff --git a/spa/plugins/alsa/mixer/paths/analog-output-mono.conf b/spa/plugins/alsa/mixer/paths/analog-output-mono.conf index 989654334..5e4940598 100644 --- a/spa/plugins/alsa/mixer/paths/analog-output-mono.conf +++ b/spa/plugins/alsa/mixer/paths/analog-output-mono.conf @@ -44,6 +44,10 @@ override-map.2 = all-left,all-right switch = mute volume = zero +[Element Headphone,1] +switch = mute +volume = zero + [Element Headphone+LO] switch = mute volume = zero diff --git a/spa/plugins/alsa/mixer/paths/analog-output-speaker-always.conf b/spa/plugins/alsa/mixer/paths/analog-output-speaker-always.conf index bf781c671..756afa954 100644 --- a/spa/plugins/alsa/mixer/paths/analog-output-speaker-always.conf +++ b/spa/plugins/alsa/mixer/paths/analog-output-speaker-always.conf @@ -76,6 +76,10 @@ volume = off switch = mute volume = zero +[Element Headphone,1] +switch = mute +volume = zero + [Element Headphone2] switch = mute volume = zero diff --git a/spa/plugins/alsa/mixer/paths/analog-output-speaker.conf b/spa/plugins/alsa/mixer/paths/analog-output-speaker.conf index 91a4bc163..72f928fd9 100644 --- a/spa/plugins/alsa/mixer/paths/analog-output-speaker.conf +++ b/spa/plugins/alsa/mixer/paths/analog-output-speaker.conf @@ -101,6 +101,10 @@ name = analog-output-speaker switch = off volume = off +[Element Headphone,1] +switch = off +volume = off + [Element Headphone2] switch = off volume = off diff --git a/spa/plugins/alsa/mixer/profile-sets/native-instruments-traktorkontrol-s4.conf b/spa/plugins/alsa/mixer/profile-sets/native-instruments-traktorkontrol-s4.conf index fd73c2a31..934965fdc 100644 --- a/spa/plugins/alsa/mixer/profile-sets/native-instruments-traktorkontrol-s4.conf +++ b/spa/plugins/alsa/mixer/profile-sets/native-instruments-traktorkontrol-s4.conf @@ -77,3 +77,4 @@ description = Analog Stereo Input Channel D input-mappings = analog-stereo-d-input priority = 1 skip-probe = yes +