diff mbox

[RFC,4/4] Add UCM jack detection into alsa card module

Message ID 1337335608-6901-5-git-send-email-feng.wei@linaro.org
State New
Headers show

Commit Message

Feng Wei May 18, 2012, 10:06 a.m. UTC
From: Feng Wei <feng.wei@linaro.org>

Jack in UCM is decided by UCM device name, although in fact
not all UCM devices have "jacks". Because port is also mapped
to UCM device, we can always find target port when some jack
event happens.

Signed-off-by: Feng Wei <feng.wei@linaro.org>
---
 src/modules/alsa/alsa-mixer.c       |   32 ++++---
 src/modules/alsa/alsa-mixer.h       |    1 +
 src/modules/alsa/alsa-ucm.c         |  165 ++++++++++++++++++++++++++++++++++-
 src/modules/alsa/alsa-ucm.h         |    8 ++
 src/modules/alsa/module-alsa-card.c |   63 +++++++++----
 5 files changed, 237 insertions(+), 32 deletions(-)
diff mbox

Patch

diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
index 1151b8d..c0d0e2f 100644
--- a/src/modules/alsa/alsa-mixer.c
+++ b/src/modules/alsa/alsa-mixer.c
@@ -3294,7 +3294,7 @@  static void mapping_free(pa_alsa_mapping *m) {
     pa_assert(!m->input_pcm);
     pa_assert(!m->output_pcm);
 
-    pa_xfree(m->ucm_context.ucm_devices);
+    pa_ucm_mapping_context_free(&m->ucm_context);
 
     pa_xfree(m);
 }
@@ -4450,17 +4450,7 @@  void pa_alsa_profile_set_probe(
     /* Clean up */
     profile_finalize_probing(last, NULL);
 
-    PA_HASHMAP_FOREACH(p, ps->profiles, state)
-        if (!p->supported) {
-            pa_hashmap_remove(ps->profiles, p->name);
-            profile_free(p);
-        }
-
-    PA_HASHMAP_FOREACH(m, ps->mappings, state)
-        if (m->supported <= 0) {
-            pa_hashmap_remove(ps->mappings, m->name);
-            mapping_free(m);
-        }
+    pa_alsa_profile_set_drop_unsupported(ps);
 
     paths_drop_unsupported(ps->input_paths);
     paths_drop_unsupported(ps->output_paths);
@@ -4495,6 +4485,24 @@  void pa_alsa_profile_set_dump(pa_alsa_profile_set *ps) {
         pa_alsa_decibel_fix_dump(db_fix);
 }
 
+void pa_alsa_profile_set_drop_unsupported(pa_alsa_profile_set *ps) {
+    pa_alsa_profile *p;
+    pa_alsa_mapping *m;
+    void *state;
+
+    PA_HASHMAP_FOREACH(p, ps->profiles, state)
+        if (!p->supported) {
+            pa_hashmap_remove(ps->profiles, p->name);
+            profile_free(p);
+        }
+
+    PA_HASHMAP_FOREACH(m, ps->mappings, state)
+        if (m->supported <= 0) {
+            pa_hashmap_remove(ps->mappings, m->name);
+            mapping_free(m);
+        }
+}
+
 static pa_device_port* device_port_alsa_init(pa_hashmap *ports,
     const char* name,
     const char* description,
diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h
index 1e424bd..40d2f2a 100644
--- a/src/modules/alsa/alsa-mixer.h
+++ b/src/modules/alsa/alsa-mixer.h
@@ -323,6 +323,7 @@  pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel
 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_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, snd_hctl_t **hctl);
 
diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c
index b74a3e7..7341bc9 100644
--- a/src/modules/alsa/alsa-ucm.c
+++ b/src/modules/alsa/alsa-ucm.c
@@ -584,6 +584,7 @@  static void ucm_add_port_combination(pa_hashmap *hash, pa_alsa_ucm_mapping_conte
         pa_log_debug("Add port %s: %s", port->name, port->description);
         port->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
     }
+
     port->priority = priority;
     if (is_sink)
         port->is_output = TRUE;
@@ -910,8 +911,8 @@  static int ucm_create_mapping_direction(pa_alsa_ucm_config *ucm,
     priority = is_sink ? device->playback_priority : device->capture_priority;
     channels = is_sink ? device->playback_channels : device->capture_channels;
     if (m->ucm_context.ucm_devices_num == 0) {   /* new mapping */
-        m->supported = TRUE;
         m->ucm_context.ucm = ucm;
+        m->ucm_context.direction = is_sink ? PA_ALSA_UCM_DIRECT_SINK : PA_ALSA_UCM_DIRECT_SOURCE;
 
         m->device_strings = pa_xnew0(char*, 2);
         m->device_strings[0] = pa_xstrdup(device_str);
@@ -952,6 +953,24 @@  static int ucm_create_mapping(pa_alsa_ucm_config *ucm,
     return ret;
 }
 
+static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, const char *dev_name) {
+    pa_alsa_jack *j;
+
+    PA_LLIST_FOREACH(j, ucm->jacks)
+        if (pa_streq(j->name, dev_name))
+            return j;
+
+    j = pa_xnew0(pa_alsa_jack, 1);
+    j->state_unplugged = PA_PORT_AVAILABLE_NO;
+    j->state_plugged = PA_PORT_AVAILABLE_YES;
+    j->name = pa_xstrdup(dev_name);
+    j->alsa_name = pa_sprintf_malloc("%s Jack", dev_name);
+
+    PA_LLIST_PREPEND(pa_alsa_jack, ucm->jacks, j);
+
+    return j;
+}
+
 static int ucm_create_profile(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps,
         pa_alsa_ucm_verb *verb, const char *verb_name, const char *verb_desc) {
     pa_alsa_profile *p;
@@ -1007,12 +1026,56 @@  static int ucm_create_profile(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps,
         source = pa_proplist_gets(dev->proplist, PA_PROP_UCM_SOURCE);
 
         ucm_create_mapping(ucm, ps, p, dev, verb_name, dev_name, sink, source);
+        dev->jack = ucm_get_jack(ucm, dev_name);
     }
     pa_alsa_profile_dump(p);
 
     return 0;
 }
 
+static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, int mode) {
+    pa_sample_spec try_ss = ucm->core->default_sample_spec;
+    pa_channel_map try_map = m->channel_map;
+    snd_pcm_uframes_t try_period_size, try_buffer_size;
+
+    try_ss.channels = try_map.channels;
+
+    try_period_size =
+        pa_usec_to_bytes(ucm->core->default_fragment_size_msec * PA_USEC_PER_MSEC, &try_ss) /
+        pa_frame_size(&try_ss);
+    try_buffer_size = ucm->core->default_n_fragments * try_period_size;
+
+    return 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, TRUE);
+}
+
+static void profile_finalize_probing(pa_alsa_profile *p) {
+    pa_alsa_mapping *m;
+    uint32_t idx;
+
+    PA_IDXSET_FOREACH(m, p->output_mappings, idx) {
+        if (!m->output_pcm)
+            continue;
+
+        if (p->supported)
+            m->supported++;
+
+        snd_pcm_close(m->output_pcm);
+        m->output_pcm = NULL;
+    }
+
+    PA_IDXSET_FOREACH(m, p->input_mappings, idx) {
+        if (!m->input_pcm)
+            continue;
+
+        if (p->supported)
+            m->supported++;
+
+        snd_pcm_close(m->input_pcm);
+        m->input_pcm = NULL;
+    }
+}
+
 static pa_alsa_ucm_device *find_ucm_dev(pa_alsa_ucm_verb *verb, const char *dev_name) {
     pa_alsa_ucm_device *dev;
 
@@ -1025,6 +1088,81 @@  static pa_alsa_ucm_device *find_ucm_dev(pa_alsa_ucm_verb *verb, const char *dev_
     return NULL;
 }
 
+static void ucm_mapping_jack_probe(pa_alsa_mapping *m) {
+    snd_pcm_t *pcm_handle;
+    snd_mixer_t *mixer_handle;
+    snd_hctl_t *hctl_handle;
+    pa_alsa_ucm_mapping_context *context = &m->ucm_context;
+    pa_alsa_ucm_device *dev;
+    int i;
+
+    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, &hctl_handle);
+    if (!mixer_handle || !hctl_handle)
+        return;
+
+    for (i=0; i<context->ucm_devices_num; i++) {
+        dev = context->ucm_devices[i];
+        pa_assert (dev->jack);
+        dev->jack->has_control = pa_alsa_find_jack(hctl_handle, dev->jack->alsa_name) != NULL;
+        pa_log_info("ucm_mapping_jack_probe: %s has_control=%d", dev->jack->name, dev->jack->has_control);
+    }
+
+    snd_mixer_close(mixer_handle);
+}
+
+static void ucm_probe_jacks(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps) {
+    void *state;
+    pa_alsa_profile *p;
+    pa_alsa_mapping *m;
+    uint32_t idx;
+
+    PA_HASHMAP_FOREACH(p, ps->profiles, state) {
+        /* change verb */
+        pa_log_info("ucm_probe_jacks: set ucm verb to %s", p->name);
+        if ((snd_use_case_set(ucm->ucm_mgr, "_verb", p->name)) < 0) {
+            pa_log("ucm_probe_jacks: failed to set verb %s", p->name);
+            p->supported = FALSE;
+            continue;
+        }
+        PA_IDXSET_FOREACH(m, p->output_mappings, idx) {
+            m->output_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_PLAYBACK);
+            if (!m->output_pcm) {
+                p->supported = FALSE;
+                break;
+            }
+        }
+        if (p->supported) {
+            PA_IDXSET_FOREACH(m, p->input_mappings, idx) {
+                m->input_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_CAPTURE);
+                if (!m->input_pcm) {
+                    p->supported = FALSE;
+                    break;
+                }
+            }
+        }
+        if (!p->supported) {
+            profile_finalize_probing(p);
+            continue;
+        }
+
+        pa_log_debug("Profile %s supported.", p->name);
+
+        PA_IDXSET_FOREACH(m, p->output_mappings, idx)
+            ucm_mapping_jack_probe(m);
+
+        PA_IDXSET_FOREACH(m, p->input_mappings, idx)
+            ucm_mapping_jack_probe(m);
+
+        profile_finalize_probing(p);
+    }
+
+    /* restore ucm state */
+    snd_use_case_set(ucm->ucm_mgr, "_verb", SND_USE_CASE_VERB_INACTIVE);
+
+    pa_alsa_profile_set_drop_unsupported(ps);
+}
+
 pa_alsa_profile_set* pa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map) {
     pa_alsa_ucm_verb *verb;
     pa_alsa_profile_set *ps;
@@ -1048,6 +1186,8 @@  pa_alsa_profile_set* pa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_
 
 	    ucm_create_profile(ucm, ps, verb, verb_name, verb_desc);
     }
+
+    ucm_probe_jacks(ucm, ps);
     ps->probed = TRUE;
 
     return ps;
@@ -1083,17 +1223,40 @@  static void free_verb(pa_alsa_ucm_verb *verb) {
 
 void pa_ucm_free(pa_alsa_ucm_config *ucm) {
     pa_alsa_ucm_verb *vi, *vn;
+    pa_alsa_jack *ji, *jn;
 
     PA_LLIST_FOREACH_SAFE(vi, vn, ucm->verbs) {
         PA_LLIST_REMOVE(pa_alsa_ucm_verb, ucm->verbs, vi);
         free_verb(vi);
     }
+    PA_LLIST_FOREACH_SAFE(ji, jn, ucm->jacks) {
+        PA_LLIST_REMOVE(pa_alsa_jack, ucm->jacks, ji);
+        pa_xfree(ji->alsa_name);
+        pa_xfree(ji->name);
+        pa_xfree(ji);
+    }
     if (ucm->ucm_mgr) {
         snd_use_case_mgr_close(ucm->ucm_mgr);
         ucm->ucm_mgr = NULL;
     }
 }
 
+void pa_ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context) {
+    pa_alsa_ucm_device *dev;
+    int i;
+
+    /* clear ucm device pointer to mapping */
+    for (i=0; i<context->ucm_devices_num; i++) {
+        dev = context->ucm_devices[i];
+        if (context->direction == PA_ALSA_UCM_DIRECT_SINK)
+            dev->playback_mapping = NULL;
+        else
+            dev->capture_mapping = NULL;
+    }
+
+    pa_xfree(context->ucm_devices);
+}
+
 static pa_bool_t stream_routed_to_mod_intent (pa_alsa_ucm_verb *verb,
         pa_alsa_ucm_modifier *mod, const char *mapping_name) {
     int i;
diff --git a/src/modules/alsa/alsa-ucm.h b/src/modules/alsa/alsa-ucm.h
index 8e2dd71..24f9eec 100644
--- a/src/modules/alsa/alsa-ucm.h
+++ b/src/modules/alsa/alsa-ucm.h
@@ -27,6 +27,8 @@ 
 #include <asoundlib.h>
 #include <use-case.h>
 
+typedef struct pa_core pa_core;
+typedef struct pa_device_port pa_device_port;
 typedef struct pa_alsa_mapping pa_alsa_mapping;
 typedef struct pa_alsa_ucm_verb pa_alsa_ucm_verb;
 typedef struct pa_alsa_ucm_modifier pa_alsa_ucm_modifier;
@@ -34,6 +36,7 @@  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_port_data_ucm pa_alsa_port_data_ucm;
+typedef struct pa_alsa_jack pa_alsa_jack;
 
 pa_alsa_profile_set* pa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map);
 int pa_ucm_set_profile(pa_alsa_ucm_config *ucm, const char *new_profile, const char *old_profile);
@@ -48,6 +51,7 @@  void pa_ucm_add_ports_combination(pa_hashmap *hash, pa_alsa_ucm_mapping_context
 int pa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port, int is_sink);
 
 void pa_ucm_free(pa_alsa_ucm_config *ucm);
+void pa_ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context);
 
 void pa_ucm_roled_stream_begin(pa_alsa_ucm_config *ucm, const char *role, const char *mapping_name, int is_sink);
 void pa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, const char *mapping_name, int is_sink);
@@ -74,6 +78,7 @@  struct pa_alsa_ucm_device {
     int n_suppdev;
     char **conflicting_devices;
     char **supported_devices;
+    pa_alsa_jack *jack;
 };
 
 struct pa_alsa_ucm_modifier {
@@ -98,14 +103,17 @@  struct pa_alsa_ucm_verb {
 };
 
 struct pa_alsa_ucm_config {
+    pa_core *core;
     snd_use_case_mgr_t *ucm_mgr;
     pa_alsa_ucm_verb *active_verb;
 
     PA_LLIST_HEAD(pa_alsa_ucm_verb, verbs);
+    PA_LLIST_HEAD(pa_alsa_jack, jacks);
 };
 
 struct pa_alsa_ucm_mapping_context {
     pa_alsa_ucm_config *ucm;
+    int direction;
     int ucm_devices_num;
     pa_alsa_ucm_device **ucm_devices;
 };
diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c
index 4802a39..7d8716f 100644
--- a/src/modules/alsa/module-alsa-card.c
+++ b/src/modules/alsa/module-alsa-card.c
@@ -326,14 +326,21 @@  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_device_port *port;
 
     PA_HASHMAP_FOREACH(jack, u->jacks, state) {
         pa_port_available_t cpa;
 
-        if (!jack->path)
-            continue;
+        if (u->use_ucm)
+            port = pa_hashmap_get(u->card->ports, jack->name);
+        else {
+            if (jack->path)
+                port = jack->path->port;
+            else
+                continue;
+        }
 
-        if (p != jack->path->port)
+        if (p != port)
             continue;
 
         cpa = jack->plugged_in ? jack->state_plugged : jack->state_unplugged;
@@ -359,6 +366,7 @@  static int report_jack_state(snd_hctl_elem_t *elem, unsigned int mask)
     pa_bool_t plugged_in;
     void *state;
     pa_alsa_jack *jack;
+    pa_device_port *port;
 
     pa_assert(u);
 
@@ -378,8 +386,16 @@  static int report_jack_state(snd_hctl_elem_t *elem, unsigned int mask)
     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);
+            if (u->use_ucm) {
+                pa_assert(u->card->ports);
+                port = pa_hashmap_get(u->card->ports, jack->name);
+                pa_assert(port);
+            }
+            else {
+                pa_assert(jack->path && jack->path->port);
+                port = jack->path->port;
+            }
+            report_port_state(port, u);
         }
     return 0;
 }
@@ -391,18 +407,25 @@  static void init_jacks(struct userdata *u) {
 
     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);
+    if (u->use_ucm) {
+        PA_LLIST_FOREACH(jack, u->ucm.jacks)
+            if (jack->has_control)
+                pa_hashmap_put(u->jacks, jack, jack);
+    }
+    else {
+        /* 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));
 
@@ -651,6 +674,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;
 
+    u->ucm.core = m->core;
+
     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;
@@ -880,8 +905,6 @@  void pa__done(pa_module*m) {
             pa_alsa_source_free(s);
     }
 
-    pa_ucm_free(&u->ucm);
-
     if (u->card)
         pa_card_free(u->card);
 
@@ -891,6 +914,8 @@  void pa__done(pa_module*m) {
     if (u->profile_set)
         pa_alsa_profile_set_free(u->profile_set);
 
+    pa_ucm_free(&u->ucm);
+
     pa_xfree(u->device_id);
     pa_xfree(u);