diff options
author | Wim Taymans <wtaymans@redhat.com> | 2020-05-20 20:06:47 +0200 |
---|---|---|
committer | Wim Taymans <wtaymans@redhat.com> | 2020-07-03 17:33:07 +0200 |
commit | 0cf77cf7755e7bfc33e0626837e3228b8f643097 (patch) | |
tree | ffae3d0a75b200910279b2683b6dae284c27aa97 | |
parent | a115127eaf23df2d1984dd5a966bb00579570160 (diff) |
wip: more updates
-rw-r--r-- | spa/plugins/pulse/alsa-card.c | 331 | ||||
-rw-r--r-- | spa/plugins/pulse/alsa-port.h | 8 | ||||
-rw-r--r-- | spa/plugins/pulse/test-probe.c | 20 |
3 files changed, 335 insertions, 24 deletions
diff --git a/spa/plugins/pulse/alsa-card.c b/spa/plugins/pulse/alsa-card.c index 675e89a3..61cdb77d 100644 --- a/spa/plugins/pulse/alsa-card.c +++ b/spa/plugins/pulse/alsa-card.c @@ -35,8 +35,14 @@ struct impl_card { pa_alsa_ucm_config ucm; pa_alsa_profile_set *profile_set; - struct spa_array profiles; - struct spa_array ports; + pa_hashmap *ports; + pa_hashmap *profiles; + pa_hashmap *jacks; + + struct { + struct spa_array ports; + struct spa_array profiles; + } out; }; struct profile_data { @@ -67,17 +73,16 @@ static void port_free(void *data) static void add_profiles(struct impl_card *impl) { pa_alsa_profile *ap; - void *state = NULL; + void *state; struct profile_data *d; struct spa_alsa_card_profile *cp; - pa_hashmap *ports; pa_alsa_device_port *dp; int count = 0; - spa_array_init(&impl->profiles, 16); - spa_array_init(&impl->ports, 16); - - ports = pa_hashmap_new_full(pa_idxset_string_hash_func, + impl->profiles = pa_hashmap_new_full(pa_idxset_string_hash_func, + pa_idxset_string_compare_func, NULL, + (pa_free_cb_t) profile_free); + impl->ports = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) port_free); @@ -98,9 +103,9 @@ static void add_profiles(struct impl_card *impl) PA_IDXSET_FOREACH(m, ap->output_mappings, idx) { if (impl->use_ucm) pa_alsa_ucm_add_ports_combination(NULL, &m->ucm_context, - true, ports, cp); + true, impl->ports, cp); else - pa_alsa_path_set_add_ports(m->output_path_set, cp, ports, NULL); + pa_alsa_path_set_add_ports(m->output_path_set, cp, impl->ports, NULL); if (m->channel_map.channels > cp->max_sink_channels) cp->max_sink_channels = m->channel_map.channels; @@ -113,22 +118,24 @@ static void add_profiles(struct impl_card *impl) PA_IDXSET_FOREACH(m, ap->input_mappings, idx) { if (impl->use_ucm) pa_alsa_ucm_add_ports_combination(NULL, &m->ucm_context, - false, ports, cp); + false, impl->ports, cp); else - pa_alsa_path_set_add_ports(m->input_path_set, cp, ports, NULL); + pa_alsa_path_set_add_ports(m->input_path_set, cp, impl->ports, NULL); if (m->channel_map.channels > cp->max_source_channels) cp->max_source_channels = m->channel_map.channels; } } - spa_array_add_ptr(&impl->profiles, cp); + pa_hashmap_put(impl->profiles, cp->name, cp); } d = profile_new("off", _("Off")); - spa_array_add_ptr(&impl->profiles, d); + d->cp.available = SPA_ALSA_AVAILABLE_YES; + pa_hashmap_put(impl->profiles, d->cp.name, &d->cp); - PA_HASHMAP_FOREACH(dp, ports, state) { + spa_array_init(&impl->out.ports, 16); + PA_HASHMAP_FOREACH(dp, impl->ports, state) { int count = 0; - void *state2 = NULL; + void *state2; spa_array_init(&dp->prof, 16); PA_HASHMAP_FOREACH(cp, dp->profiles, state2) { spa_array_add_ptr(&dp->prof, cp); @@ -138,9 +145,287 @@ static void add_profiles(struct impl_card *impl) dp->port.profiles = dp->prof.data; pa_proplist_as_dict(dp->proplist, &dp->port.props); - spa_array_add_ptr(&impl->ports, dp); + spa_array_add_ptr(&impl->out.ports, dp); } + spa_array_init(&impl->out.profiles, 16); + PA_HASHMAP_FOREACH(ap, impl->profiles, state) + spa_array_add_ptr(&impl->out.profiles, ap); + +} + +static pa_available_t calc_port_state(pa_alsa_device_port *p, struct impl_card *impl) { + void *state; + pa_alsa_jack *jack; + pa_available_t pa = PA_AVAILABLE_UNKNOWN; + pa_alsa_device_port *port; + + PA_HASHMAP_FOREACH(jack, impl->jacks, state) { + pa_available_t cpa; + + if (impl->use_ucm) + port = pa_hashmap_get(impl->ports, jack->name); + else { + if (jack->path) + port = jack->path->port; + else + continue; + } + + if (p != port) + continue; + + cpa = jack->plugged_in ? jack->state_plugged : jack->state_unplugged; + + if (cpa == PA_AVAILABLE_NO) { + /* If a plugged-in jack causes the availability to go to NO, it + * should override all other availability information (like a + * blacklist) so set and bail */ + if (jack->plugged_in) { + pa = cpa; + break; + } + + /* If the current availablility is unknown go the more precise no, + * but otherwise don't change state */ + if (pa == PA_AVAILABLE_UNKNOWN) + pa = cpa; + } else if (cpa == PA_AVAILABLE_YES) { + /* Output is available through at least one jack, so go to that + * level of availability. We still need to continue iterating through + * the jacks in case a jack is plugged in that forces the state to no + */ + pa = cpa; + } + } + return pa; +} +struct temp_port_avail { + pa_alsa_device_port *port; + pa_available_t avail; +}; + +static int report_jack_state(snd_mixer_elem_t *melem, unsigned int mask) +{ + struct impl_card *impl = snd_mixer_elem_get_callback_private(melem); + snd_hctl_elem_t *elem = snd_mixer_elem_get_private(melem); + snd_ctl_elem_value_t *elem_value; + bool plugged_in; + void *state; + pa_alsa_jack *jack; + struct temp_port_avail *tp, *tports; + struct spa_alsa_card_profile *profile; + enum spa_alsa_available active_available = SPA_ALSA_AVAILABLE_UNKNOWN; + +#if 0 + /* Changing the jack state may cause a port change, and a port change will + * make the sink or source change the mixer settings. If there are multiple + * users having pulseaudio running, the mixer changes done by inactive + * users may mess up the volume settings for the active users, because when + * the inactive users change the mixer settings, those changes are picked + * up by the active user's pulseaudio instance and the changes are + * interpreted as if the active user changed the settings manually e.g. + * with alsamixer. Even single-user systems suffer from this, because gdm + * runs its own pulseaudio instance. + * + * We rerun this function when being unsuspended to catch up on jack state + * changes */ + if (u->card->suspend_cause & PA_SUSPEND_SESSION) + return 0; +#endif + + if (mask == SND_CTL_EVENT_MASK_REMOVE) + return 0; + + snd_ctl_elem_value_alloca(&elem_value); + if (snd_hctl_elem_read(elem, elem_value) < 0) { + pa_log_warn("Failed to read jack detection from '%s'", pa_strnull(snd_hctl_elem_get_name(elem))); + return 0; + } + + plugged_in = !!snd_ctl_elem_value_get_boolean(elem_value, 0); + + pa_log_debug("Jack '%s' is now %s", pa_strnull(snd_hctl_elem_get_name(elem)), + plugged_in ? "plugged in" : "unplugged"); + + tports = tp = pa_xnew0(struct temp_port_avail, pa_hashmap_size(impl->jacks)+1); + + PA_HASHMAP_FOREACH(jack, impl->jacks, state) + if (jack->melem == melem) { + pa_alsa_jack_set_plugged_in(jack, plugged_in); + + if (impl->use_ucm) { + /* When using UCM, pa_alsa_jack_set_plugged_in() maps the jack + * state to port availability. */ + continue; + } + + /* When not using UCM, we have to do the jack state -> port + * availability mapping ourselves. */ + pa_assert_se(tp->port = jack->path->port); + tp->avail = calc_port_state(tp->port, impl); + tp++; + } + + /* Report available ports before unavailable ones: in case port 1 + * becomes available when port 2 becomes unavailable, + * this prevents an unnecessary switch port 1 -> port 3 -> port 2 */ + + for (tp = tports; tp->port; tp++) + if (tp->avail != PA_AVAILABLE_NO) + pa_alsa_device_port_set_available(tp->port, tp->avail); + for (tp = tports; tp->port; tp++) + if (tp->avail == PA_AVAILABLE_NO) + pa_alsa_device_port_set_available(tp->port, tp->avail); + + for (tp = tports; tp->port; tp++) { + pa_alsa_port_data *data; + + data = PA_ALSA_DEVICE_PORT_DATA(tp->port); + + if (!data->suspend_when_unavailable) + continue; + +#if 0 + pa_sink *sink; + uint32_t idx; + PA_IDXSET_FOREACH(sink, u->core->sinks, idx) { + if (sink->active_port == tp->port) + pa_sink_suspend(sink, tp->avail == PA_AVAILABLE_NO, PA_SUSPEND_UNAVAILABLE); + } +#endif + } + + /* Update profile availabilities. Ideally we would mark all profiles + * unavailable that contain unavailable devices. We can't currently do that + * in all cases, because if there are multiple sinks in a profile, and the + * profile contains a mix of available and unavailable ports, we don't know + * how the ports are distributed between the different sinks. It's possible + * that some sinks contain only unavailable ports, in which case we should + * mark the profile as unavailable, but it's also possible that all sinks + * contain at least one available port, in which case we should mark the + * profile as available. Until the data structures are improved so that we + * can distinguish between these two cases, we mark the problematic cases + * as available (well, "unknown" to be precise, but there's little + * practical difference). + * + * When all output ports are unavailable, we know that all sinks are + * unavailable, and therefore the profile is marked unavailable as well. + * The same applies to input ports as well, of course. + * + * If there are no output ports at all, but the profile contains at least + * one sink, then the output is considered to be available. */ + if (impl->this.active_profile) + active_available = impl->this.active_profile->available; + + PA_HASHMAP_FOREACH(profile, impl->profiles, state) { + pa_alsa_device_port *port; + void *state2; + bool has_input_port = false; + bool has_output_port = false; + bool found_available_input_port = false; + bool found_available_output_port = false; + enum spa_alsa_available available = SPA_ALSA_AVAILABLE_UNKNOWN; + + PA_HASHMAP_FOREACH(port, impl->ports, state2) { + if (!pa_hashmap_get(port->profiles, profile->name)) + continue; + + if (port->port.direction == SPA_ALSA_INPUT) { + has_input_port = true; + + if (port->port.available != SPA_ALSA_AVAILABLE_NO) + found_available_input_port = true; + } else { + has_output_port = true; + + if (port->port.available != SPA_ALSA_AVAILABLE_NO) + found_available_output_port = true; + } + } + + if ((has_input_port && !found_available_input_port) || (has_output_port && !found_available_output_port)) + available = SPA_ALSA_AVAILABLE_NO; + + if (has_input_port && !has_output_port && found_available_input_port) + available = SPA_ALSA_AVAILABLE_YES; + if (has_output_port && !has_input_port && found_available_output_port) + available = SPA_ALSA_AVAILABLE_YES; + if (has_output_port && has_input_port && found_available_output_port && found_available_input_port) + available = SPA_ALSA_AVAILABLE_YES; + + /* We want to update the active profile's status last, so logic that + * may change the active profile based on profile availability status + * has an updated view of all profiles' availabilities. */ + if (profile == impl->this.active_profile) + active_available = available; + else + spa_alsa_card_profile_set_available(profile, available); + } + + if (impl->this.active_profile) + spa_alsa_card_profile_set_available(impl->this.active_profile, active_available); + + pa_xfree(tports); + return 0; +} +static void init_jacks(struct impl_card *impl) +{ + void *state; + pa_alsa_path* path; + pa_alsa_jack* jack; + + impl->jacks = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + if (impl->use_ucm) { + PA_LLIST_FOREACH(jack, impl->ucm.jacks) + if (jack->has_control) + pa_hashmap_put(impl->jacks, jack, jack); + } else { + /* See if we have any jacks */ + if (impl->profile_set->output_paths) + PA_HASHMAP_FOREACH(path, impl->profile_set->output_paths, state) + PA_LLIST_FOREACH(jack, path->jacks) + if (jack->has_control) + pa_hashmap_put(impl->jacks, jack, jack); + + if (impl->profile_set->input_paths) + PA_HASHMAP_FOREACH(path, impl->profile_set->input_paths, state) + PA_LLIST_FOREACH(jack, path->jacks) + if (jack->has_control) + pa_hashmap_put(impl->jacks, jack, jack); + } + + pa_log_debug("Found %d jacks.", pa_hashmap_size(impl->jacks)); + + if (pa_hashmap_size(impl->jacks) == 0) + return; + + PA_HASHMAP_FOREACH(jack, impl->jacks, state) { + if (!jack->mixer_device_name) { + jack->mixer_handle = pa_alsa_open_mixer(impl->ucm.mixers, impl->this.index, false); + if (!jack->mixer_handle) { + pa_log("Failed to open mixer for card %d for jack detection", impl->this.index); + continue; + } + } else { + jack->mixer_handle = pa_alsa_open_mixer_by_name(impl->ucm.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; + } + } +// pa_alsa_mixer_set_fdlist(impl->mixers, jack->mixer_handle, impl->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, impl); + report_jack_state(jack->melem, 0); + } } struct spa_alsa_card *spa_alsa_card_new(uint32_t index, const struct spa_dict *props) @@ -160,6 +445,7 @@ struct spa_alsa_card *spa_alsa_card_new(uint32_t index, const struct spa_dict *p impl->proplist = pa_proplist_new_dict(props); this = &impl->this; + this->index = index; impl->use_ucm = true; @@ -179,6 +465,7 @@ struct spa_alsa_card *spa_alsa_card_new(uint32_t index, const struct spa_dict *p impl->ucm.default_sample_spec.channels, PA_CHANNEL_MAP_ALSA); impl->ucm.default_n_fragments = 4; impl->ucm.default_fragment_size_msec = 25; + impl->ucm.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); @@ -206,14 +493,16 @@ struct spa_alsa_card *spa_alsa_card_new(uint32_t index, const struct spa_dict *p add_profiles(impl); - this->n_profiles = spa_array_get_len(&impl->profiles, void *); - this->profiles = impl->profiles.data; + this->n_profiles = spa_array_get_len(&impl->out.profiles, void *); + this->profiles = impl->out.profiles.data; - this->n_ports = spa_array_get_len(&impl->ports, void *); - this->ports = impl->ports.data; + this->n_ports = spa_array_get_len(&impl->out.ports, void *); + this->ports = impl->out.ports.data; pa_proplist_as_dict(impl->proplist, &this->props); + init_jacks(impl); + return &impl->this; } diff --git a/spa/plugins/pulse/alsa-port.h b/spa/plugins/pulse/alsa-port.h index f46ed5a3..dec2d2cb 100644 --- a/spa/plugins/pulse/alsa-port.h +++ b/spa/plugins/pulse/alsa-port.h @@ -41,6 +41,14 @@ typedef struct pa_alsa_device_port { void *user_data; } pa_alsa_device_port; +static inline void spa_alsa_card_profile_set_available(struct spa_alsa_card_profile *p, + enum spa_alsa_available status) +{ + if (p->available == status) + return; + p->available = status; +} + #define PA_ALSA_DEVICE_PORT_DATA(p) (p->user_data); static inline pa_alsa_device_port *pa_alsa_device_port_new(const char *name, diff --git a/spa/plugins/pulse/test-probe.c b/spa/plugins/pulse/test-probe.c index 708c8908..80ae7e66 100644 --- a/spa/plugins/pulse/test-probe.c +++ b/spa/plugins/pulse/test-probe.c @@ -42,6 +42,19 @@ struct data { int card; }; +static const char *str_available(enum spa_alsa_available status) +{ + switch (status) { + case SPA_ALSA_AVAILABLE_UNKNOWN: + return "unknown"; + case SPA_ALSA_AVAILABLE_NO: + return "no"; + case SPA_ALSA_AVAILABLE_YES: + return "yes"; + } + return "error"; +} + static int do_probe(struct data *data) { struct spa_alsa_card *card; @@ -60,13 +73,14 @@ static int do_probe(struct data *data) for (i = 0; i < card->n_profiles; i++) { struct spa_alsa_card_profile *p = card->profiles[i]; - fprintf(stderr, "profile %u: sink/sources:%d/%d channels %d/%d name:%s\n", i, + fprintf(stderr, "profile %u: sink/sources:%d/%d channels %d/%d name:%s (%s)\n", i, p->n_sinks, p->n_sources, - p->max_sink_channels, p->max_source_channels, p->name); + p->max_sink_channels, p->max_source_channels, p->name, + str_available(p->available)); } for (i = 0; i < card->n_ports; i++) { struct spa_alsa_port *p = card->ports[i]; - fprintf(stderr, "port %u: name:%s\n", i, p->name); + fprintf(stderr, "port %u: name:%s (%s)\n", i, p->name, str_available(p->available)); spa_debug_dict(2, &p->props); fprintf(stderr, " profiles: "); |