diff options
author | David Henningsson <david.henningsson@canonical.com> | 2012-02-23 07:17:02 +0100 |
---|---|---|
committer | Arun Raghavan <arun.raghavan@collabora.co.uk> | 2012-03-11 12:23:46 +0530 |
commit | dedf1340c611c3d9b17a08e0ee18ae22afac623c (patch) | |
tree | 3a3d53638115b32a5e7155fb55cbe6e34e8046e8 /src/modules/alsa/module-alsa-card.c | |
parent | 74e01ad6164060ac1dc9aa98239b2d435f2d4bda (diff) |
alsa: Jack detection kcontrol implementation
Support the new jack detection interface implemented in Linux 3.3
(and Ubuntu's 3.2 kernel).
Jacks are probed and detected using the snd_hctl_* commands, which
means we need to listen to them using fdlists. As this detection
needs to be active even if there is currently no sink for the jack,
so this polling is done on the card level.
Also add configuration support in paths, like this:
[Jack Headphone]
required-any = any
...where 'Jack Headphone' should match 'Headphone Jack' as given by
ALSA (as seen in e g 'amixer controls').
"Required", "required-any" and "required-absent" is supported. Using
required-any, one can have several ports even though there is no
other indication in the mixer that this path exists.
Signed-off-by: David Henningsson <david.henningsson@canonical.com>
Diffstat (limited to 'src/modules/alsa/module-alsa-card.c')
-rw-r--r-- | src/modules/alsa/module-alsa-card.c | 132 |
1 files changed, 127 insertions, 5 deletions
diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c index 92483e18..c5bffcd2 100644 --- a/src/modules/alsa/module-alsa-card.c +++ b/src/modules/alsa/module-alsa-card.c @@ -105,6 +105,12 @@ struct userdata { pa_module *module; char *device_id; + int alsa_card_index; + + snd_mixer_t *mixer_handle; + snd_hctl_t *hctl_handle; + pa_hashmap *jacks; + pa_alsa_fdlist *mixer_fdl; pa_card *card; @@ -267,6 +273,115 @@ static void init_profile(struct userdata *u) { am->source = pa_alsa_source_new(u->module, u->modargs, __FILE__, u->card, am); } +static void report_port_state(pa_device_port *p, struct userdata *u) +{ + void *state; + pa_alsa_jack *jack; + pa_port_available_t pa = PA_PORT_AVAILABLE_UNKNOWN; + + PA_HASHMAP_FOREACH(jack, u->jacks, state) { + pa_port_available_t cpa; + + if (!jack->path) + continue; + + if (p != jack->path->port) + continue; + + cpa = jack->plugged_in ? PA_PORT_AVAILABLE_YES : PA_PORT_AVAILABLE_NO; + + /* "Yes" and "no" trumphs "unknown" if we have more than one jack */ + if (cpa == PA_PORT_AVAILABLE_UNKNOWN) + continue; + + if ((cpa == PA_PORT_AVAILABLE_NO && pa == PA_PORT_AVAILABLE_YES) || + (pa == PA_PORT_AVAILABLE_NO && cpa == PA_PORT_AVAILABLE_YES)) + pa_log_warn("Availability of port '%s' is inconsistent!", p->name); + else + pa = cpa; + } + + pa_device_port_set_available(p, pa); +} + +static int report_jack_state(snd_hctl_elem_t *elem, unsigned int mask) +{ + struct userdata *u = snd_hctl_elem_get_callback_private(elem); + snd_ctl_elem_value_t *elem_value; + pa_bool_t plugged_in; + void *state; + pa_alsa_jack *jack; + + pa_assert(u); + + 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"); + + PA_HASHMAP_FOREACH(jack, u->jacks, state) + if (jack->hctl_elem == elem) { + jack->plugged_in = plugged_in; + pa_assert(jack->path && jack->path->port); + report_port_state(jack->path->port, u); + } + return 0; +} + +static void init_jacks(struct userdata *u) { + void *state; + pa_alsa_path* path; + pa_alsa_jack* jack; + + u->jacks = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + /* See if we have any jacks */ + if (u->profile_set->output_paths) + PA_HASHMAP_FOREACH(path, u->profile_set->output_paths, state) + PA_LLIST_FOREACH(jack, path->jacks) + if (jack->has_control) + pa_hashmap_put(u->jacks, jack, jack); + + if (u->profile_set->input_paths) + PA_HASHMAP_FOREACH(path, u->profile_set->input_paths, state) + PA_LLIST_FOREACH(jack, path->jacks) + if (jack->has_control) + pa_hashmap_put(u->jacks, jack, jack); + + pa_log_debug("Found %d jacks.", pa_hashmap_size(u->jacks)); + + 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, &u->hctl_handle); + if (u->mixer_handle && pa_alsa_fdlist_set_handle(u->mixer_fdl, NULL, u->hctl_handle, u->core->mainloop) >= 0) { + PA_HASHMAP_FOREACH(jack, u->jacks, state) { + jack->hctl_elem = pa_alsa_find_jack(u->hctl_handle, jack->alsa_name); + if (!jack->hctl_elem) { + pa_log_warn("Jack '%s' seems to have disappeared.", jack->alsa_name); + jack->has_control = FALSE; + continue; + } + snd_hctl_elem_set_callback_private(jack->hctl_elem, u); + snd_hctl_elem_set_callback(jack->hctl_elem, report_jack_state); + report_jack_state(jack->hctl_elem, 0); + } + + } else + pa_log("Failed to open hctl/mixer for jack detection"); + +} + static void set_card_name(pa_card_new_data *data, pa_modargs *ma, const char *device_id) { char *t; const char *n; @@ -296,7 +411,6 @@ static void set_card_name(pa_card_new_data *data, pa_modargs *ma, const char *de int pa__init(pa_module *m) { pa_card_new_data data; pa_modargs *ma; - int alsa_card_index; pa_bool_t ignore_dB = FALSE; struct userdata *u; pa_reserve_wrapper *reserve = NULL; @@ -325,8 +439,8 @@ int pa__init(pa_module *m) { u->device_id = pa_xstrdup(pa_modargs_get_value(ma, "device_id", DEFAULT_DEVICE_ID)); u->modargs = ma; - if ((alsa_card_index = snd_card_get_index(u->device_id)) < 0) { - pa_log("Card '%s' doesn't exist: %s", u->device_id, pa_alsa_strerror(alsa_card_index)); + if ((u->alsa_card_index = snd_card_get_index(u->device_id)) < 0) { + pa_log("Card '%s' doesn't exist: %s", u->device_id, pa_alsa_strerror(u->alsa_card_index)); goto fail; } @@ -343,7 +457,7 @@ int pa__init(pa_module *m) { } #ifdef HAVE_UDEV - fn = pa_udev_get_property(alsa_card_index, "PULSE_PROFILE_SET"); + fn = pa_udev_get_property(u->alsa_card_index, "PULSE_PROFILE_SET"); #endif if (pa_modargs_get_value(ma, "profile_set", NULL)) { @@ -366,7 +480,7 @@ int pa__init(pa_module *m) { data.driver = __FILE__; data.module = m; - pa_alsa_init_proplist_card(m->core, data.proplist, alsa_card_index); + pa_alsa_init_proplist_card(m->core, data.proplist, u->alsa_card_index); pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device_id); pa_alsa_init_description(data.proplist); @@ -418,6 +532,7 @@ int pa__init(pa_module *m) { u->card->set_profile = card_set_profile; init_profile(u); + init_jacks(u); if (reserve) pa_reserve_wrapper_unref(reserve); @@ -469,6 +584,13 @@ 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->jacks) + pa_hashmap_free(u->jacks, NULL, NULL); + if (u->card && u->card->sinks) { pa_sink *s; |