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