diff mbox

[pulseaudio-discuss,RFC,v2] Add UCM jack detection into alsa card module

Message ID 1331890390-24053-2-git-send-email-feng.wei@linaro.org
State New
Headers show

Commit Message

Feng Wei March 16, 2012, 9:33 a.m. UTC
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.
---
 src/modules/alsa/alsa-mixer.c       |   32 ++++--
 src/modules/alsa/alsa-mixer.h       |    1 +
 src/modules/alsa/alsa-ucm.c         |  197 +++++++++++++++++++++++++++++++++--
 src/modules/alsa/alsa-ucm.h         |    8 ++
 src/modules/alsa/module-alsa-card.c |   54 +++++++---
 5 files changed, 255 insertions(+), 37 deletions(-)
diff mbox

Patch

diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
index 317fe12..39a4633 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);
+    ucm_mapping_context_free(&m->ucm_context);
 
     pa_xfree(m);
 }
@@ -4449,17 +4449,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);
@@ -4494,6 +4484,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 4234a2b..c640915 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 2d299e3..705518b 100644
--- a/src/modules/alsa/alsa-ucm.c
+++ b/src/modules/alsa/alsa-ucm.c
@@ -590,6 +590,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;
@@ -926,8 +927,8 @@  static int ucm_create_mapping_direction(struct pa_alsa_ucm_config *ucm,
     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);
@@ -970,6 +971,24 @@  static int ucm_create_mapping(struct pa_alsa_ucm_config *ucm,
     return ret;
 }
 
+static pa_alsa_jack* ucm_get_jack(struct 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(struct pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps,
         struct pa_alsa_ucm_verb *verb, const char *verb_name, const char *verb_desc) {
     struct pa_alsa_profile *p;
@@ -991,7 +1010,7 @@  static int ucm_create_profile(struct pa_alsa_ucm_config *ucm, pa_alsa_profile_se
     p->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
     p->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
 
-    p->supported = 1;
+    p->supported = TRUE;
     pa_hashmap_put(ps->profiles, p->name, p);
 
     /* TODO: get profile priority from ucm info or policy management */
@@ -1026,12 +1045,146 @@  static int ucm_create_profile(struct pa_alsa_ucm_config *ucm, pa_alsa_profile_se
         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(struct 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 struct pa_alsa_ucm_device *find_ucm_dev(
+        struct pa_alsa_ucm_verb *verb, const char *dev_name) {
+    struct pa_alsa_ucm_device *dev;
+
+    PA_LLIST_FOREACH(dev, verb->devices) {
+        const char *name = pa_proplist_gets(dev->proplist, PA_PROP_UCM_NAME);
+        if (pa_streq(name, dev_name))
+            return 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;
+    struct 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(struct 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* ucm_add_profile_set(struct pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map) {
     struct pa_alsa_ucm_verb *verb;
     pa_alsa_profile_set *ps;
@@ -1055,6 +1208,8 @@  pa_alsa_profile_set* ucm_add_profile_set(struct pa_alsa_ucm_config *ucm, pa_chan
 
 	    ucm_create_profile(ucm, ps, verb, verb_name, verb_desc);
     }
+
+    ucm_probe_jacks(ucm, ps);
     ps->probed = TRUE;
 
     return ps;
@@ -1090,11 +1245,18 @@  static void free_verb(struct pa_alsa_ucm_verb *verb) {
 
 void ucm_free(struct pa_alsa_ucm_config *ucm) {
     struct 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);
@@ -1102,6 +1264,22 @@  void ucm_free(struct pa_alsa_ucm_config *ucm) {
     }
 }
 
+void ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context) {
+    struct 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 (struct pa_alsa_ucm_verb *verb,
         struct pa_alsa_ucm_modifier *mod, const char *mapping_name) {
     int i;
@@ -1113,15 +1291,12 @@  static pa_bool_t stream_routed_to_mod_intent (struct pa_alsa_ucm_verb *verb,
     for (i=0; i<mod->n_suppdev; i++) {
         dev_name = mod->supported_devices[i];
         /* first find the supported device */
-        PA_LLIST_FOREACH(dev, verb->devices) {
-            const char *name = pa_proplist_gets(dev->proplist, PA_PROP_UCM_NAME);
-            if (pa_streq(name, dev_name)) {
-                /* then match the mapping name */
-                mapping = mod->action_direct == PA_ALSA_UCM_DIRECT_SINK ? dev->playback_mapping : dev->capture_mapping;
-                if (mapping && pa_streq(mapping->name, mapping_name))
-                    return TRUE;
-                break;
-            }
+        dev = find_ucm_dev(verb, dev_name);
+        if (dev) {
+            /* then match the mapping name */
+            mapping = mod->action_direct == PA_ALSA_UCM_DIRECT_SINK ? dev->playback_mapping : dev->capture_mapping;
+            if (mapping && pa_streq(mapping->name, mapping_name))
+                return TRUE;
         }
     }
 
diff --git a/src/modules/alsa/alsa-ucm.h b/src/modules/alsa/alsa-ucm.h
index 680be5a..625de18 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;
@@ -35,9 +37,11 @@  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;
 
 int  ucm_set_profile(struct pa_alsa_ucm_config *ucm, const char *new_profile, const char *old_profile);
 void ucm_free(struct pa_alsa_ucm_config *ucm);
+void ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context);
 pa_alsa_profile_set* ucm_add_profile_set(struct pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map);
 int  ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, const char *verb_desc, struct pa_alsa_ucm_verb ** p_verb);
 void ucm_add_ports(pa_hashmap **p, pa_proplist *proplist, pa_alsa_ucm_mapping_context *context, int is_sink, pa_card *card);
@@ -70,6 +74,7 @@  struct pa_alsa_ucm_device {
     int n_suppdev;
     char **conflicting_devices;
     char **supported_devices;
+    pa_alsa_jack *jack;
 };
 
 struct pa_alsa_ucm_modifier {
@@ -94,14 +99,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 d7f1043..659bddb 100644
--- a/src/modules/alsa/module-alsa-card.c
+++ b/src/modules/alsa/module-alsa-card.c
@@ -326,14 +326,22 @@  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 +367,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 +387,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 +408,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 (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 (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));
 
@@ -652,6 +676,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;
@@ -881,8 +907,6 @@  void pa__done(pa_module*m) {
             pa_alsa_source_free(s);
     }
 
-    ucm_free(&u->ucm);
-
     if (u->card)
         pa_card_free(u->card);
 
@@ -892,6 +916,8 @@  void pa__done(pa_module*m) {
     if (u->profile_set)
         pa_alsa_profile_set_free(u->profile_set);
 
+    ucm_free(&u->ucm);
+
     pa_xfree(u->device_id);
     pa_xfree(u);