summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWim Taymans <wtaymans@redhat.com>2020-05-20 20:06:47 +0200
committerWim Taymans <wtaymans@redhat.com>2020-07-03 17:33:07 +0200
commit0cf77cf7755e7bfc33e0626837e3228b8f643097 (patch)
treeffae3d0a75b200910279b2683b6dae284c27aa97
parenta115127eaf23df2d1984dd5a966bb00579570160 (diff)
wip: more updates
-rw-r--r--spa/plugins/pulse/alsa-card.c331
-rw-r--r--spa/plugins/pulse/alsa-port.h8
-rw-r--r--spa/plugins/pulse/test-probe.c20
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: ");