[pulseaudio-discuss] [RFC Patch 1/3] Support UCM in Pulseaudio

Feng Wei feng.wei at linaro.org
Tue Nov 15 19:48:10 PST 2011


ucm utils for pulseaudio

diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c
new file mode 100644
index 0000000..692ae49
--- /dev/null
+++ b/src/modules/alsa/alsa-ucm.c
@@ -0,0 +1,908 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2011 Wolfson Microelectronics PLC
+ Author Margarita Olaya <magi at slimlogic.co.uk>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <limits.h>
+#include <asoundlib.h>
+
+#ifdef HAVE_VALGRIND_MEMCHECK_H
+#include <valgrind/memcheck.h>
+#endif
+
+#include <pulse/sample.h>
+#include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
+#include <pulse/util.h>
+
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/atomic.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/once.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/conf-parser.h>
+#include <pulsecore/strbuf.h>
+
+#include "alsa-mixer.h"
+#include "alsa-util.h"
+#include "alsa-ucm.h"
+
+struct ucm_items {
+    const char *id;
+    const char *property;
+};
+
+struct ucm_info {
+    const char *id;
+    unsigned priority;
+};
+
+static struct ucm_items item[] = {
+    {"Comment", PA_PROP_UCM_DESCRIPTION},
+    {"PlaybackPCM", PA_PROP_UCM_SINK},
+    {"CapturePCM", PA_PROP_UCM_SOURCE},
+    {"PlaybackVolume", PA_PROP_UCM_PLAYBACK_VOLUME},
+    {"PlaybackSwitch", PA_PROP_UCM_PLAYBACK_SWITCH},
+    {"CaptureVolume", PA_PROP_UCM_CAPTURE_VOLUME},
+    {"CaptureSwitch", PA_PROP_UCM_CAPTURE_SWITCH},
+    {"TQ", PA_PROP_UCM_QOS},
+    {NULL, NULL},
+};
+
+/* UCM verb info - this should eventually be part of policy manangement */
+static struct ucm_info verb_info[] = {
+    {SND_USE_CASE_VERB_INACTIVE, 0},
+    {SND_USE_CASE_VERB_HIFI, 8000},
+    {SND_USE_CASE_VERB_HIFI_LOW_POWER, 7000},
+    {SND_USE_CASE_VERB_VOICE, 6000},
+    {SND_USE_CASE_VERB_VOICE_LOW_POWER, 5000},
+    {SND_USE_CASE_VERB_VOICECALL, 4000},
+    {SND_USE_CASE_VERB_IP_VOICECALL, 4000},
+    {SND_USE_CASE_VERB_ANALOG_RADIO, 3000},
+    {SND_USE_CASE_VERB_DIGITAL_RADIO, 3000},
+    {NULL, 0}
+};
+
+/* UCM device info - this should eventually be part of policy manangement */
+static struct ucm_info dev_info[] = {
+    {SND_USE_CASE_DEV_SPEAKER, 100},
+    {SND_USE_CASE_DEV_LINE, 100},
+    {SND_USE_CASE_DEV_HEADPHONES, 100},
+    {SND_USE_CASE_DEV_HEADSET, 300},
+    {SND_USE_CASE_DEV_HANDSET, 200},
+    {SND_USE_CASE_DEV_BLUETOOTH, 400},
+    {SND_USE_CASE_DEV_EARPIECE, 100},
+    {SND_USE_CASE_DEV_SPDIF, 100},
+    {SND_USE_CASE_DEV_HDMI, 100},
+    {SND_USE_CASE_DEV_NONE, 100},
+    {NULL, 0}
+};
+
+/* UCM profile properties - The verb data is store so it can be used to fill
+ * the new profiles properties */
+
+int ucm_get_property(struct pa_alsa_ucm_verb *verb,
snd_use_case_mgr_t *uc_mgr, const char *verb_name) {
+    const char *value;
+    char *id;
+    int i = 0;
+
+    do {
+        int err;
+
+        id = pa_sprintf_malloc("%s//%s", item[i].id, verb_name);
+        err = snd_use_case_get(uc_mgr, id, &value);
+        pa_xfree(id);
+        if (err < 0 ) {
+            pa_log_info("No %s for verb %s", item[i].id, verb_name);
+            continue;
+        }
+
+        pa_log_info("Got %s for verb %s", item[i].id, verb_name);
+        pa_proplist_sets(verb->proplist, item[i].property, value);
+        free((void*)value);
+    } while (item[++i].id);
+
+    return 0;
+};
+
+/* Create a property list for this ucm device */
+static int ucm_get_device_property(struct pa_alsa_ucm_device *device,
snd_use_case_mgr_t *uc_mgr, const char *device_name) {
+    const char *value;
+    char *id;
+    int i = 0;
+    int err;
+
+    do {
+        id = pa_sprintf_malloc("%s/%s", item[i].id, device_name);
+        err = snd_use_case_get(uc_mgr, id, &value);
+        pa_xfree(id);
+        if (err < 0) {
+            pa_log_info("No %s for device %s", item[i].id, device_name);
+            continue;
+        }
+
+        pa_log_info("Got %s for device %s", item[i].id, device_name);
+        pa_proplist_sets(device->proplist, item[i].property, value);
+        free((void*)value);
+    }  while (item[++i].id);
+
+    id = pa_sprintf_malloc("%s/%s", "_conflictingdevs", device_name);
+    device->n_confdev = snd_use_case_get_list(uc_mgr, id,
&device->conflicting_devices);
+    pa_xfree(id);
+    if (device->n_confdev < 0)
+        pa_log_info("No %s for device %s", "_conflictingdevs", device_name);
+
+    id = pa_sprintf_malloc("%s/%s", "_supporteddevs", device_name);
+    device->n_suppdev = snd_use_case_get_list(uc_mgr, id,
&device->supported_devices);
+    pa_xfree(id);
+    if (device->n_suppdev < 0)
+        pa_log_info("No %s for device %s", "_supporteddevs", device_name);
+
+    return 0;
+};
+
+/* Create a property list for this ucm modifier */
+static int ucm_get_modifier_property(struct pa_alsa_ucm_modifier
*modifier, snd_use_case_mgr_t *uc_mgr, const char *modifier_name) {
+    const char *value;
+    char *id;
+    int i = 0;
+
+    do {
+        int err;
+
+        id = pa_sprintf_malloc("%s/%s", item[i].id, modifier_name);
+        err = snd_use_case_get(uc_mgr, id, &value);
+        pa_xfree(id);
+        if (err < 0 ) {
+            pa_log_info("No %s for modifier %s", item[i].id, modifier_name);
+            continue;
+        }
+
+        pa_log_info("Got %s for modifier %s", item[i].id, modifier_name);
+        pa_proplist_sets(modifier->proplist, item[i].property, value);
+        free((void*)value);
+    } while (item[++i].id);
+
+    id = pa_sprintf_malloc("%s/%s", "_conflictingdevs", modifier_name);
+    modifier->n_confdev = snd_use_case_get_list(uc_mgr, id,
&modifier->conflicting_devices);
+    pa_xfree(id);
+    if (modifier->n_confdev < 0)
+        pa_log_info("No %s for modifier %s", "_conflictingdevs",
modifier_name);
+
+    id = pa_sprintf_malloc("%s/%s", "_supporteddevs", modifier_name);
+    modifier->n_suppdev = snd_use_case_get_list(uc_mgr, id,
&modifier->supported_devices);
+    pa_xfree(id);
+    if (modifier->n_suppdev < 0)
+        pa_log_info("No %s for modifier %s", "_supporteddevs", modifier_name);
+
+    return 0;
+};
+
+/* Create a list of devices for this verb */
+static int ucm_get_devices(struct pa_alsa_ucm_verb *verb,
snd_use_case_mgr_t *uc_mgr) {
+    const char **dev_list;
+    int num_dev, i;
+
+    num_dev = snd_use_case_get_list(uc_mgr, "_devices", &dev_list);
+    if (num_dev <= 0)
+        return num_dev;
+
+    for (i = 0; i < num_dev; i += 2) {
+        pa_alsa_ucm_device *d;
+        d = pa_xnew0(pa_alsa_ucm_device, 1);
+        d->proplist = pa_proplist_new();
+        pa_proplist_sets(d->proplist, PA_PROP_UCM_NAME, dev_list[i]);
+        PA_LLIST_PREPEND(pa_alsa_ucm_device, verb->devices, d);
+    }
+    snd_use_case_free_list(dev_list, num_dev);
+
+    return 0;
+};
+
+static int ucm_get_modifiers(struct pa_alsa_ucm_verb *verb,
snd_use_case_mgr_t *uc_mgr) {
+    const char **mod_list;
+    int num_mod, i;
+
+    num_mod = snd_use_case_get_list(uc_mgr, "_modifiers", &mod_list);
+    if (num_mod <= 0)
+        return num_mod;
+
+    for (i = 0; i < num_mod; i += 2) {
+        pa_alsa_ucm_modifier *m;
+        m = pa_xnew0(pa_alsa_ucm_modifier, 1);
+        m->proplist = pa_proplist_new();
+        pa_proplist_sets(m->proplist, PA_PROP_UCM_NAME, mod_list[i]);
+        PA_LLIST_PREPEND(pa_alsa_ucm_modifier, verb->modifiers, m);
+    }
+    snd_use_case_free_list(mod_list, num_mod);
+
+    return 0;
+};
+
+static pa_bool_t role_match(const char *cur, const char *role) {
+    char *r;
+    const char *state=NULL;
+
+    while ((r = pa_split_spaces(cur, &state))) {
+        if (!strcasecmp(role, r)) {
+            pa_xfree(r);
+            return TRUE;
+        }
+        pa_xfree(r);
+    }
+
+    return FALSE;
+}
+
+static void add_role_to_device (pa_alsa_ucm_device *dev,
+        const char *role_name, const char *role) {
+    const char *cur = pa_proplist_gets(dev->proplist, role_name);
+
+    if (!cur) {
+        pa_proplist_sets(dev->proplist, role_name, role);
+    }
+    else if (!role_match(cur, role)) /* not exists */
+    {
+        char *value = pa_sprintf_malloc("%s %s", cur, role);
+        pa_proplist_sets(dev->proplist, role_name, value);
+        pa_xfree(value);
+    }
+}
+
+static void add_media_role (const char *name, pa_alsa_ucm_device *list,
+        const char *role_name, const char *role, pa_bool_t is_sink) {
+    pa_alsa_ucm_device *d;
+
+    PA_LLIST_FOREACH(d, list) {
+        const char *dev_name = pa_proplist_gets(d->proplist, PA_PROP_UCM_NAME);
+        if (!strcmp(dev_name, name)) {
+            const char *sink = pa_proplist_gets(d->proplist, PA_PROP_UCM_SINK);
+            if (((is_sink && sink) || (!is_sink && !sink))) {
+                add_role_to_device (d, role_name, role);
+            }
+            break;
+        }
+    }
+}
+
+static void ucm_set_media_roles(struct pa_alsa_ucm_modifier *modifier,
+        pa_alsa_ucm_device *list, const char *mod_name) {
+    int i;
+    pa_bool_t is_sink=FALSE, is_source=FALSE;
+    const char *sub = NULL;
+
+    if ((sub = strcasestr(mod_name, "Play")) != NULL) {
+        is_sink = TRUE;
+        sub += 4;
+    }
+    else if ((sub = strcasestr(mod_name, "Capture")) != NULL) {
+        is_source = TRUE;
+        sub += 7;
+    }
+
+    if (!sub || !*sub || !*(sub+1))
+        return;
+
+    /* we assume role is a character after the action */
+    sub++;
+    modifier->action_direct = is_sink ?
+        PA_ALSA_UCM_DIRECT_SINK : PA_ALSA_UCM_DIRECT_SOURCE;
+    modifier->media_role = pa_xstrdup(sub);
+
+    for (i=0; i<modifier->n_suppdev; i++)
+    {
+        add_media_role(modifier->supported_devices[i],
+                list, PA_PROP_DEVICE_INTENDED_ROLES, sub, is_sink);
+    }
+/*
+    for (i=0; i<modifier->n_confdev; i++)
+    {
+        add_media_role(modifier->conflicting_devices[i],
+                list, PA_PROP_DEVICE_DENYED_ROLES, sub, is_sink);
+    }
+*/
+}
+
+int ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name,
struct pa_alsa_ucm_verb **p_verb) {
+    struct pa_alsa_ucm_device *d;
+    struct pa_alsa_ucm_modifier *mod;
+    struct pa_alsa_ucm_verb *verb;
+    int err=0;
+
+    *p_verb = NULL;
+    err = snd_use_case_set(uc_mgr, "_verb", verb_name);
+    if (err < 0)
+        return err;
+
+    verb = pa_xnew0(pa_alsa_ucm_verb, 1);
+    verb->proplist = pa_proplist_new();
+    pa_proplist_sets(verb->proplist, PA_PROP_UCM_NAME, verb_name);
+    err = ucm_get_devices(verb, uc_mgr);
+    if (err < 0)
+        pa_log("No UCM devices for verb %s", verb_name);
+
+    err = ucm_get_modifiers(verb, uc_mgr);
+    if (err < 0)
+        pa_log("No UCM modifiers for verb %s", verb_name);
+
+    /* Verb properties */
+    ucm_get_property(verb, uc_mgr, verb_name);
+
+    PA_LLIST_FOREACH(d, verb->devices) {
+        const char *dev_name = pa_proplist_gets(d->proplist, PA_PROP_UCM_NAME);
+
+        /* Devices properties */
+        ucm_get_device_property(d, uc_mgr, dev_name);
+    }
+
+    PA_LLIST_FOREACH(mod, verb->modifiers) {
+        const char *mod_name = pa_proplist_gets(mod->proplist,
PA_PROP_UCM_NAME);
+
+        /* Modifier properties */
+        ucm_get_modifier_property(mod, uc_mgr, mod_name);
+
+        /* Set PA_PROP_DEVICE_INTENDED_ROLES property to devices */
+        ucm_set_media_roles(mod, verb->devices, mod_name);
+    }
+
+    *p_verb = verb;
+    return 0;
+}
+
+static void ucm_add_port_combination(pa_hashmap *hash,
pa_alsa_mapping *mapping,
+        int *dev_indices, int num) {
+    pa_device_port *port;
+    pa_alsa_port_data_ucm *data;
+    int i;
+    unsigned priority;
+    char *name, *desc;
+    const char *dev_name;
+    pa_alsa_ucm_device *dev;
+
+    dev = mapping->ucm_devices[dev_indices[0]];
+    dev_name = pa_proplist_gets(dev->proplist, PA_PROP_UCM_NAME);
+    name = pa_xstrdup(dev_name);
+    desc = pa_sprintf_malloc("combination port for %s", dev_name);
+    priority = dev->priority;
+    for (i=1; i<num; i++)
+    {
+        char *tmp;
+        dev = mapping->ucm_devices[dev_indices[i]];
+        dev_name = pa_proplist_gets(dev->proplist, PA_PROP_UCM_NAME);
+        tmp = pa_sprintf_malloc("%s+%s", name, dev_name);
+        pa_xfree(name);
+        name = tmp;
+        tmp = pa_sprintf_malloc("%s,%s", desc, dev_name);
+        pa_xfree(desc);
+        desc = tmp;
+        /* FIXME: Is it true? */
+        priority += dev->priority;
+    }
+
+    port = pa_device_port_new(name, desc,
sizeof(pa_alsa_port_data_ucm) + sizeof(int) * (num-1));
+    port->priority = priority;
+
+    pa_xfree(name);
+    pa_xfree(desc);
+
+    data = PA_DEVICE_PORT_DATA(port);
+    data->mapping = mapping;
+    data->enable_indices_num = num;
+    memcpy(data->enable_indices, dev_indices, sizeof(dev_indices[0]) * num);
+
+    pa_hashmap_put(hash, port->name, port);
+}
+
+static int ucm_device_contain(pa_alsa_mapping *mapping, int *dev_indices,
+        int dev_num, const char *device_name) {
+    int i;
+    const char *dev_name;
+    pa_alsa_ucm_device *dev;
+
+    for (i=0; i<dev_num; i++)
+    {
+        dev = mapping->ucm_devices[dev_indices[i]];
+        dev_name = pa_proplist_gets(dev->proplist, PA_PROP_UCM_NAME);
+        if (!strcmp(dev_name, device_name))
+            return 1;
+    }
+
+    return 0;
+}
+
+static int ucm_device_in(const char **device_names, int num,
pa_alsa_ucm_device *dev) {
+    int i;
+    const char *dev_name = pa_proplist_gets(dev->proplist, PA_PROP_UCM_NAME);
+
+    for (i=0; i<num; i++)
+    {
+        if (!strcmp(dev_name, device_names[num]))
+            return 1;
+    }
+
+    return 0;
+}
+
+static int ucm_check_conformance(pa_alsa_mapping *mapping, int *dev_indices,
+        int dev_num, int map_index) {
+    int i;
+    pa_alsa_ucm_device *dev = mapping->ucm_devices[map_index];
+
+    if (dev_num == 0)
+        return 1;
+
+    if (dev->n_confdev > 0)
+    { /* the device defines conflicting devices */
+        for (i=0; i<dev->n_confdev; i++)
+        {
+            if (ucm_device_contain(mapping, dev_indices,
+                        dev_num, dev->conflicting_devices[i]))
+                return 0;
+        }
+    }
+    else if (dev->n_suppdev >= dev_num)
+    { /* the device defines supported devices */
+        for (i=0; i<dev_num; i++)
+        {
+            if (!ucm_device_in(dev->supported_devices,
+                dev->n_suppdev, mapping->ucm_devices[dev_indices[i]]))
+                return 0;
+        }
+    }
+    else /* not support any other devices */
+        return 0;
+
+    return 1;
+}
+
+static void ucm_add_ports_combination(pa_hashmap *hash,
+        pa_alsa_mapping *mapping, int *dev_indices, int dev_num,
+        int map_index) {
+
+    if (map_index >= mapping->ucm_devices_num)
+        return;
+
+    /* check if device at map_index can combine with existing devices
combination */
+    if (ucm_check_conformance(mapping, dev_indices, dev_num, map_index))
+    {
+        /* add device at map_index to devices combination */
+        dev_indices[dev_num] = map_index;
+        /* add current devices combination as a new port */
+        ucm_add_port_combination(hash, mapping, dev_indices, dev_num+1);
+        /* try more elements combination */
+        ucm_add_ports_combination(hash, mapping, dev_indices,
dev_num+1, map_index+1);
+    }
+    /* try other device with current elements number */
+    ucm_add_ports_combination(hash, mapping, dev_indices, dev_num,
map_index+1);
+}
+
+static char* merge_roles(const char *cur, const char *add) {
+    char *r, *ret;
+    const char *state=NULL;
+
+    ret = pa_xstrdup(cur);
+
+    if (add == NULL)
+        return ret;
+
+    while ((r = pa_split_spaces(add, &state))) {
+        char *value;
+        if (!ret)
+            value = pa_xstrdup(r);
+        else if (!role_match (cur, r))
+            value = pa_sprintf_malloc("%s %s", ret, r);
+        else {
+            pa_xfree(r);
+            continue;
+        }
+        pa_xfree(ret);
+        ret = value;
+        pa_xfree(r);
+    }
+
+    return ret;
+}
+
+void ucm_add_ports(pa_hashmap **p, pa_proplist *proplist,
pa_alsa_mapping *mapping) {
+    int *dev_indices = pa_xnew(int, mapping->ucm_devices_num);
+    int i;
+    char *merged_roles;
+
+    pa_assert(p);
+    pa_assert(!*p);
+    pa_assert(mapping->ucm_devices_num > 0);
+
+    /* add ports first */
+    *p = pa_hashmap_new(pa_idxset_string_hash_func,
pa_idxset_string_compare_func);
+    ucm_add_ports_combination(*p, mapping, dev_indices, 0, 0);
+    pa_xfree(dev_indices);
+
+    /* then set property PA_PROP_DEVICE_INTENDED_ROLES */
+    merged_roles = pa_xstrdup(pa_proplist_gets(proplist,
PA_PROP_DEVICE_INTENDED_ROLES));
+    for (i=0; i<mapping->ucm_devices_num; i++)
+    {
+        const char *roles = pa_proplist_gets(
+                mapping->ucm_devices[i]->proplist,
PA_PROP_DEVICE_INTENDED_ROLES);
+        char *tmp;
+        tmp = merge_roles(merged_roles, roles);
+        pa_xfree(merged_roles);
+        merged_roles = tmp;
+    }
+    if (merged_roles) {
+        pa_proplist_sets(proplist, PA_PROP_DEVICE_INTENDED_ROLES,
merged_roles);
+        pa_xfree(merged_roles);
+    }
+}
+
+/* Change UCM verb and device to match selected card profile */
+int ucm_set_profile(struct pa_alsa_ucm_config *ucm, const char *new_profile,
+        const char *old_profile) {
+    int ret = 0;
+    const char *profile;
+    pa_alsa_ucm_verb *verb;
+
+    if (new_profile == old_profile)
+        return ret;
+    else if (new_profile == NULL || old_profile == NULL)
+        profile = new_profile ? new_profile : SND_USE_CASE_VERB_INACTIVE;
+    else if (strcmp(new_profile, old_profile) != 0)
+        profile = new_profile;
+    else
+        return ret;
+
+    /* change verb */
+    if ((snd_use_case_set(ucm->ucm_mgr, "_verb", profile)) < 0) {
+        pa_log("failed to set verb %s", profile);
+        ret = -1;
+    }
+
+    /* find active verb */
+    ucm->active_verb = NULL;
+    PA_LLIST_FOREACH(verb, ucm->verbs) {
+        const char *verb_name;
+        verb_name = pa_proplist_gets(verb->proplist, PA_PROP_UCM_NAME);
+        if (!strcmp(verb_name, profile)) {
+            ucm->active_verb = verb;
+            break;
+        }
+    }
+
+    return ret;
+}
+
+static int ucm_device_index_contain(int *indices, int num, int ind) {
+    int i;
+
+    for (i=0; i<num; i++)
+        if (indices[i] == ind)
+            return 1;
+    return 0;
+}
+
+int ucm_set_port(pa_alsa_port_data_ucm *data) {
+    int i, ret=0;
+    pa_alsa_mapping *mapping;
+    pa_alsa_ucm_config *ucm;
+
+    pa_assert(data->mapping && data->mapping->ucm);
+
+    mapping = data->mapping;
+    ucm = mapping->ucm;
+
+    pa_assert(ucm->ucm_mgr);
+    for (i=0; i<mapping->ucm_devices_num; i++)
+    {
+        const char *dev_name =
pa_proplist_gets(mapping->ucm_devices[i]->proplist, PA_PROP_UCM_NAME);
+        if (ucm_device_index_contain(data->enable_indices,
data->enable_indices_num, i))
+        {
+            if (snd_use_case_set(ucm->ucm_mgr, "_enadev", dev_name) < 0) {
+                pa_log("failed to enable ucm device %s", dev_name);
+                ret = -1;
+                break;
+            }
+        }
+        else
+        {
+            if (snd_use_case_set(ucm->ucm_mgr, "_disdev", dev_name) > 0) {
+                pa_log("failed to disable ucm device %s", dev_name);
+                ret = -1;
+                break;
+            }
+        }
+    }
+    return ret;
+}
+
+static void ucm_add_mapping(pa_alsa_profile *p, pa_alsa_mapping *m) {
+    switch (m->direction) {
+    case PA_ALSA_DIRECTION_ANY:
+        pa_idxset_put(p->output_mappings, m, NULL);
+        pa_idxset_put(p->input_mappings, m, NULL);
+        break;
+     case PA_ALSA_DIRECTION_OUTPUT:
+        pa_idxset_put(p->output_mappings, m, NULL);
+        break;
+     case PA_ALSA_DIRECTION_INPUT:
+        pa_idxset_put(p->input_mappings, m, NULL);
+        break;
+    }
+}
+
+static void alsa_mapping_add_ucm_device(pa_alsa_mapping *m,
pa_alsa_ucm_device *device) {
+    char *cur_desc;
+    const char *new_desc;
+
+    /* we expand 8 entries each time */
+    if ((m->ucm_devices_num & 7) == 0)
+        m->ucm_devices = pa_xrealloc(m->ucm_devices,
sizeof(pa_alsa_ucm_device *) * (m->ucm_devices_num + 8));
+    m->ucm_devices[m->ucm_devices_num++] = device;
+
+    new_desc = pa_proplist_gets(device->proplist, PA_PROP_UCM_NAME);
+    cur_desc = m->description;
+    if (cur_desc)
+        m->description = pa_sprintf_malloc("%s + %s", cur_desc, new_desc);
+    else
+        m->description = pa_xstrdup(new_desc);
+    pa_xfree(cur_desc);
+
+    /* walk around null case */
+    m->description = m->description ? m->description : pa_xstrdup("");
+}
+
+static int ucm_create_mapping_direction(struct pa_alsa_ucm_config *ucm,
+        pa_alsa_profile_set *ps, struct pa_alsa_profile *p,
+        struct pa_alsa_ucm_device *device, const char *verb_name,
+        const char *device_name, const char *device_str, int is_sink) {
+    pa_alsa_mapping *m;
+    char *mapping_name;
+    int i=0;
+
+    mapping_name = pa_sprintf_malloc("Mapping %s: %s: %s", verb_name,
device_str, is_sink ? "sink" : "source");
+
+    m = mapping_get(ps, mapping_name);
+    if (!m) {
+        pa_log("no mapping for %s", mapping_name);
+        pa_xfree(mapping_name);
+        return -1;
+    }
+    pa_log_info("ucm mapping: %s dev %s", mapping_name, device_name);
+    pa_xfree(mapping_name);
+
+    if (m->ucm_devices_num == 0)
+    {   /* new mapping */
+        m->supported = TRUE;
+        m->ucm = ucm;
+
+        m->device_strings = pa_xnew0(char*, 2);
+        m->device_strings[0] = pa_xstrdup(device_str);
+        m->direction = is_sink ? PA_ALSA_DIRECTION_OUTPUT :
PA_ALSA_DIRECTION_INPUT;
+
+        /* FIXME: get alsa device (sink/source) info from ucm or
policy management */
+        m->channel_map.map[0] = PA_CHANNEL_POSITION_LEFT;
+        m->channel_map.map[1] = PA_CHANNEL_POSITION_RIGHT;
+        m->channel_map.channels = 2;
+        ucm_add_mapping(p, m);
+    }
+
+    /* FIXME: get ucm device (port) info from ucm */
+    if (!device_name)
+        goto not_found;
+    do {
+        if (strcasecmp(dev_info[i].id, device_name) == 0)
+            goto found;
+    } while (dev_info[++i].id);
+not_found:
+    device->priority = 100;
+    goto end;
+found:
+    device->priority = dev_info[i].priority;
+end:
+    if (device->priority > m->priority)
+        m->priority = device->priority;
+    alsa_mapping_add_ucm_device(m, device);
+
+    return 0;
+}
+
+static int ucm_create_mapping(struct pa_alsa_ucm_config *ucm,
+        pa_alsa_profile_set *ps, struct pa_alsa_profile *p,
+        struct pa_alsa_ucm_device *device, const char *verb_name,
+        const char *device_name, const char *sink, const char *source) {
+    int ret;
+
+    if (!sink && !source)
+    {
+        pa_log("no sink and source at %s: %s", verb_name, device_name);
+        return -1;
+    }
+
+    if (sink)
+        ret = ucm_create_mapping_direction(ucm, ps, p, device,
verb_name, device_name, sink, 1);
+    if (ret == 0 && source)
+        ret = ucm_create_mapping_direction(ucm, ps, p, device,
verb_name, device_name, source, 0);
+
+    return ret;
+}
+
+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) {
+    struct pa_alsa_profile *p;
+    struct pa_alsa_ucm_device *dev;
+    int i=0;
+
+    pa_assert(ps);
+
+    if (pa_hashmap_get(ps->profiles, verb_name)) {
+        pa_log("verb %s already exists", verb_name);
+        return -1;
+    }
+
+    p = pa_xnew0(pa_alsa_profile, 1);
+    p->profile_set = ps;
+    p->name = pa_xstrdup(verb_name);
+
+    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);
+
+    ps->probed = TRUE;
+    p->supported = 1;
+    pa_hashmap_put(ps->profiles, p->name, p);
+
+    /* TODO: get profile priority from ucm info or policy management */
+    do {
+        if (strcasecmp(verb_info[i].id, verb_name) == 0)
+        {
+            p->priority = verb_info[i].priority;
+            break;
+        }
+    } while (verb_info[++i].id);
+
+    if (verb_info[++i].id == NULL)
+        p->priority = 1000;
+
+    PA_LLIST_FOREACH(dev, verb->devices) {
+        const char *dev_name, *sink, *source;
+
+        dev_name = pa_proplist_gets(dev->proplist, PA_PROP_UCM_NAME);
+
+        sink = pa_proplist_gets(dev->proplist, PA_PROP_UCM_SINK);
+        source = pa_proplist_gets(dev->proplist, PA_PROP_UCM_SOURCE);
+
+        ucm_create_mapping(ucm, ps, p, dev, verb_name, dev_name, sink, source);
+    }
+    pa_alsa_profile_dump(p);
+
+    return 0;
+}
+
+pa_alsa_profile_set* add_ucm_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;
+
+    ps = pa_xnew0(pa_alsa_profile_set, 1);
+    ps->mappings = pa_hashmap_new(pa_idxset_string_hash_func,
pa_idxset_string_compare_func);
+    ps->profiles = pa_hashmap_new(pa_idxset_string_hash_func,
pa_idxset_string_compare_func);
+    ps->decibel_fixes = pa_hashmap_new(pa_idxset_string_hash_func,
pa_idxset_string_compare_func);
+
+    /* create a profile for each verb */
+    PA_LLIST_FOREACH(verb, ucm->verbs) {
+        const char *verb_name;
+
+        verb_name = pa_proplist_gets(verb->proplist, PA_PROP_UCM_NAME);
+        if (verb_name == NULL) {
+            pa_log("verb with no name");
+            continue;
+        }
+
+	    ucm_create_profile(ucm, ps, verb, verb_name);
+    }
+
+    return ps;
+}
+
+void free_verb(struct pa_alsa_ucm_verb *verb) {
+    struct pa_alsa_ucm_device *di, *dn;
+    struct pa_alsa_ucm_modifier *mi, *mn;
+
+    PA_LLIST_FOREACH_SAFE(di, dn, verb->devices) {
+        PA_LLIST_REMOVE(pa_alsa_ucm_device, verb->devices, di);
+        pa_proplist_free(di->proplist);
+        if (di->n_suppdev > 0)
+            snd_use_case_free_list(di->supported_devices, di->n_suppdev);
+        if (di->n_confdev > 0)
+            snd_use_case_free_list(di->conflicting_devices, di->n_confdev);
+        pa_xfree(di);
+    }
+
+    PA_LLIST_FOREACH_SAFE(mi, mn, verb->modifiers) {
+        PA_LLIST_REMOVE(pa_alsa_ucm_modifier, verb->modifiers, mi);
+        pa_proplist_free(mi->proplist);
+        if (mi->n_suppdev > 0)
+            snd_use_case_free_list(mi->supported_devices, mi->n_suppdev);
+        if (mi->n_confdev > 0)
+            snd_use_case_free_list(mi->conflicting_devices, mi->n_confdev);
+        pa_xfree(mi->media_role);
+        pa_xfree(mi);
+    }
+    pa_proplist_free(verb->proplist);
+    pa_xfree(verb);
+}
+
+void free_ucm(struct pa_alsa_ucm_config *ucm) {
+    struct pa_alsa_ucm_verb *vi, *vn;
+
+    PA_LLIST_FOREACH_SAFE(vi, vn, ucm->verbs) {
+        PA_LLIST_REMOVE(pa_alsa_ucm_verb, ucm->verbs, vi);
+        free_verb(vi);
+    }
+    if (ucm->ucm_mgr)
+    {
+        snd_use_case_mgr_close(ucm->ucm_mgr);
+        ucm->ucm_mgr = NULL;
+    }
+}
+
+void ucm_new_stream_role(pa_alsa_ucm_config *ucm,
+        const char *role, pa_bool_t is_sink) {
+    struct pa_alsa_ucm_modifier *mod;
+
+    if (!ucm->active_verb)
+        return;
+
+    PA_LLIST_FOREACH(mod, ucm->active_verb->modifiers) {
+        if (((mod->action_direct == PA_ALSA_UCM_DIRECT_SINK && is_sink) ||
+            (mod->action_direct == PA_ALSA_UCM_DIRECT_SOURCE && !is_sink)) &&
+            (!strcasecmp(mod->media_role, role))) {
+            const char *mod_name = pa_proplist_gets(mod->proplist,
PA_PROP_UCM_NAME);
+            if (snd_use_case_set(ucm->ucm_mgr, "_enamod", mod_name) < 0) {
+                pa_log("failed to enable ucm modifier %s", mod_name);
+            }
+            break;
+        }
+    }
+}
+
+void ucm_del_stream_role(pa_alsa_ucm_config *ucm,
+        const char *role, pa_bool_t is_sink) {
+    struct pa_alsa_ucm_modifier *mod;
+
+    if (!ucm->active_verb)
+        return;
+
+    PA_LLIST_FOREACH(mod, ucm->active_verb->modifiers) {
+        if (((mod->action_direct == PA_ALSA_UCM_DIRECT_SINK && is_sink) ||
+            (mod->action_direct == PA_ALSA_UCM_DIRECT_SOURCE && !is_sink)) &&
+            (!strcasecmp(mod->media_role, role))) {
+            const char *mod_name = pa_proplist_gets(mod->proplist,
PA_PROP_UCM_NAME);
+            if (snd_use_case_set(ucm->ucm_mgr, "_dismod", mod_name) < 0) {
+                pa_log("failed to enable ucm modifier %s", mod_name);
+            }
+            break;
+        }
+    }
+}
diff --git a/src/modules/alsa/alsa-ucm.h b/src/modules/alsa/alsa-ucm.h
new file mode 100644
index 0000000..dd98d90
--- /dev/null
+++ b/src/modules/alsa/alsa-ucm.h
@@ -0,0 +1,96 @@
+#ifndef foopulseucmhfoo
+#define foopulseucmhfoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2011 Wolfson Microelectronics PLC
+  Author Margarita Olaya <magi at slimlogic.co.uk>
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#include <asoundlib.h>
+#include <use-case.h>
+
+typedef struct pa_alsa_ucm_verb pa_alsa_ucm_verb;
+typedef struct pa_alsa_ucm_modifier pa_alsa_ucm_modifier;
+typedef struct pa_alsa_ucm_device pa_alsa_ucm_device;
+typedef struct pa_alsa_ucm_config pa_alsa_ucm_config;
+typedef struct pa_alsa_port_data_ucm pa_alsa_port_data_ucm;
+
+int ucm_set_profile(struct pa_alsa_ucm_config *ucm, const char
*new_profile, const char *old_profile);
+void free_ucm(struct pa_alsa_ucm_config *ucm);
+void free_verb(struct pa_alsa_ucm_verb *verb);
+pa_alsa_profile_set* add_ucm_profile_set(struct pa_alsa_ucm_config
*ucm, pa_channel_map *default_channel_map);
+int ucm_get_property(struct pa_alsa_ucm_verb *verb,
snd_use_case_mgr_t *uc_mgr, const char *verb_name);
+int ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name,
struct pa_alsa_ucm_verb ** p_verb);
+void ucm_add_ports(pa_hashmap **p, pa_proplist *proplist,
pa_alsa_mapping *mapping);
+int ucm_set_port(pa_alsa_port_data_ucm *data);
+void ucm_new_stream_role(pa_alsa_ucm_config *ucm, const char *role,
pa_bool_t is_sink);
+void ucm_del_stream_role(pa_alsa_ucm_config *ucm, const char *role,
pa_bool_t is_sink);
+
+/* UCM modifier action direction */
+enum {
+    PA_ALSA_UCM_DIRECT_NONE = 0,
+    PA_ALSA_UCM_DIRECT_SINK,
+    PA_ALSA_UCM_DIRECT_SOURCE
+};
+
+/* UCM - Use Case Manager is available on some audio cards */
+
+struct pa_alsa_ucm_device {
+    PA_LLIST_FIELDS(pa_alsa_ucm_device);
+    pa_proplist *proplist;
+    unsigned priority;
+    int n_confdev;
+    int n_suppdev;
+    const char **conflicting_devices;
+    const char **supported_devices;
+};
+
+struct pa_alsa_ucm_modifier {
+    PA_LLIST_FIELDS(pa_alsa_ucm_modifier);
+    pa_proplist *proplist;
+    int n_confdev;
+    int n_suppdev;
+    const char **conflicting_devices;
+    const char **supported_devices;
+    int action_direct;
+    char *media_role;
+};
+
+struct pa_alsa_ucm_verb {
+    PA_LLIST_FIELDS(pa_alsa_ucm_verb);
+    pa_proplist *proplist;
+    PA_LLIST_HEAD(pa_alsa_ucm_device, devices);
+    PA_LLIST_HEAD(pa_alsa_ucm_modifier, modifiers);
+};
+
+struct pa_alsa_ucm_config {
+    snd_use_case_mgr_t *ucm_mgr;
+    pa_alsa_ucm_verb *active_verb;
+
+    PA_LLIST_HEAD(pa_alsa_ucm_verb, verbs);
+};
+
+struct pa_alsa_port_data_ucm {
+    pa_alsa_mapping *mapping;
+    int enable_indices_num;
+    int enable_indices[1];
+};
+
+#endif
diff --git a/src/pulse/proplist.h b/src/pulse/proplist.h
index 50b1806..36bb306 100644
--- a/src/pulse/proplist.h
+++ b/src/pulse/proplist.h
@@ -254,6 +254,33 @@ PA_C_DECL_BEGIN
 /** For modules: a version string for the module. e.g. "0.9.15" */
 #define PA_PROP_MODULE_VERSION                 "module.version"

+/** For devices: List of verbs, devices or modifiers availables */
+#define PA_PROP_UCM_NAME                       "ucm.name"
+
+/** For devices: List of supported devices per verb*/
+#define PA_PROP_UCM_DESCRIPTION                "ucm.description"
+
+/** For devices: Playback device name e.g PlaybackPCM */
+#define PA_PROP_UCM_SINK                       "ucm.sink"
+
+/** For devices: Capture device name e.g CapturePCM*/
+#define PA_PROP_UCM_SOURCE                     "ucm.source"
+
+/** For devices: Playback control volume ID string. e.g PlaybackVolume */
+#define PA_PROP_UCM_PLAYBACK_VOLUME            "ucm.playback.volume"
+
+/** For devices: Playback switch e.g PlaybackSwitch */
+#define PA_PROP_UCM_PLAYBACK_SWITCH            "ucm.playback.switch"
+
+/** For devices: Capture controls volume ID string. e.g CaptureVolume */
+#define PA_PROP_UCM_CAPTURE_VOLUME             "ucm.capture.volume"
+
+/** For devices: Capture switch e.g CaptureSwitch */
+#define PA_PROP_UCM_CAPTURE_SWITCH             "ucm.capture.switch"
+
+/** For devices: Quality of Service */
+#define PA_PROP_UCM_QOS                        "ucm.qos"
+
 /** For PCM formats: the sample format used as returned by
pa_sample_format_to_string() \since 1.0 */
 #define PA_PROP_FORMAT_SAMPLE_FORMAT           "format.sample_format"



-- 
Wei.Feng (irc wei_feng)
Linaro Multimedia Team
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog


More information about the pulseaudio-discuss mailing list