From: Janos Kovacs <jankovac503@xxxxxxxxx> --- src/modules/alsa/alsa-ucm.c | 1481 +++++++++++++++++++++++++++++++++++++++++++ src/modules/alsa/alsa-ucm.h | 52 ++ 2 files changed, 1533 insertions(+), 0 deletions(-) create mode 100644 src/modules/alsa/alsa-ucm.c create mode 100644 src/modules/alsa/alsa-ucm.h diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c new file mode 100644 index 0000000..4036227 --- /dev/null +++ b/src/modules/alsa/alsa-ucm.c @@ -0,0 +1,1481 @@ +/*** + This file is part of PulseAudio. + + Copyright 2011 Intel Corporation. + Author Janos Kovacs <jankovac503 at gmail.com> + + 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 <string.h> +#include <stdio.h> +#include <ctype.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 <pulse/utf8.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 <pulsecore/i18n.h> + +#include "alsa-mixer.h" +#include "alsa-util.h" +#include "alsa-ucm.h" + + + +#define UNDEFINED 0 +#define NONE UNDEFINED +#define OUTPUT 1 +#define INPUT 2 +#define DUPLEX (INPUT | OUTPUT) + +#define MUSIC 1 +#define VOICE 2 + +#define SINGLEDEV 0 +#define DUALDEV 1 + + +#define DIRECTION_ALSA(d) direction_map[(d) & 3].alsa +#define DIRECTION_PULSE(d) direction_map[(d) & 3].id +#define DIRECTION_DESCRIPTION(d) direction_map[(d) & 3].descr + +#define VERB_TO_MEDIA(v) strcasestr(v, "voice") ? VOICE : MUSIC; +#define VERB_TO_MODE(v) verb_to_mode(v) +#define VERB_TO_DESCRIPTION(v) verb_to_desc(v) + +#define MEDIA_ID(m) media_map[(m) & 3].id +#define MEDIA_DESCRIPTION(m) media_map[(m) & 3].descr + + + +enum { + CAPTURE_CTL = 0, + CAPTURE_PCM, + PLAYBACK_CTL, + PLAYBACK_PCM, + MAX_DEVICE +}; + +typedef enum { + PROFILE_MATCHING_MODIFIER, + PROFILE_ANY_MODIFIER, + PORT_MODIFIER +} modifier_type; + + +struct ucm_list { + const char *name; + const char *descr; +}; + +struct direction { + pa_alsa_direction_t alsa; + const char *id; + const char *descr; +}; + +struct media { + const char *id; + const char *descr; +}; + +struct mode { + const char *verb; + const char *mode; + const char *descr; +}; + + +struct support { + int dir; /* INPUT | OUTPUT */ + const char **devs; /* UCM device list */ + int len; /* length of devs list */ +}; + + +static struct media media_map[4] = { + [ UNDEFINED ] = { "music" , "music" }, + [ MUSIC ] = { "music" , "music" }, + [ VOICE ] = { "voice" , "voice" }, + [MUSIC | VOICE] = { "music-and-voice", "music and voice" } +}; + + +static struct direction direction_map[4] = { + [UNDEFINED] = { PA_ALSA_DIRECTION_ANY , "duplex", "playback and capture" }, + [ OUTPUT ] = { PA_ALSA_DIRECTION_OUTPUT , "output", "playback" }, + [ INPUT ] = { PA_ALSA_DIRECTION_INPUT , "input" , "capture" }, + [ DUPLEX ] = { PA_ALSA_DIRECTION_ANY , "duplex", "playback and capture" } +}; + +static struct mode mode_map[] = { + { SND_USE_CASE_VERB_HIFI , "hifi" , "HiFi" }, + { SND_USE_CASE_VERB_HIFI_LOW_POWER , "hifi-low-power" , "HiFi powersaving" }, + { SND_USE_CASE_VERB_VOICECALL , "gsm" , "gsm or 3g" }, + { SND_USE_CASE_VERB_IP_VOICECALL , "voip" , "VoIP" }, + { SND_USE_CASE_VERB_ANALOG_RADIO , "analog-radio" , "analog FM radio" }, + { SND_USE_CASE_VERB_DIGITAL_RADIO , "digital-radio" , "digital FM radio" }, + { NULL , NULL , NULL } +}; + + +static void profile_set_populate(snd_use_case_mgr_t *, pa_alsa_profile_set *, const char *); +static void profile_set_add_profile(snd_use_case_mgr_t *, pa_alsa_profile_set *, const char *, + struct ucm_list *, struct ucm_list *); +static char **merge_device_list(const char **, int); +static char **get_dualdev_list(snd_use_case_mgr_t *, const char *, struct ucm_list *); +static void get_path_lists(snd_use_case_mgr_t *, pa_alsa_profile_set *, const char *, + char **, char **, char ***, char ***, pa_alsa_direction_t *); +static int get_volctl(snd_use_case_mgr_t *, const char *, char *, const char *, + int, const char **); +static void add_path(snd_use_case_mgr_t *, pa_alsa_profile_set *, unsigned int, + const char *, char *, char **,int *, char **,int *, + char **,char **, +#ifndef INPUT_DEVICE_BASED_JACK_DETECTION + const char *, +#else + const char *,const char *, +#endif + int); +static pa_alsa_path *path_new(const char *, const char *, pa_alsa_direction_t, + const char *, const char *, char **); +static void path_add_volume_control(pa_alsa_path *, const char *); +static pa_alsa_profile *profile_new(pa_alsa_profile_set *, const char *, struct ucm_list *); +static void profile_add_mapping(pa_alsa_profile *, const char *, const char *, pa_alsa_direction_t, + char **, char **, char **); +static char *compose_profile_name(const char *, struct ucm_list *, char *, int); +static char *compose_profile_description(const char *, struct ucm_list *, char *, int); +static void get_device_changes(char *, char *, pa_bool_t *, pa_bool_t *); +static void get_modifier_changes(pa_bool_t, char **, char **, char **,int, char **,int); +static int get_list(snd_use_case_mgr_t *, const char *, struct ucm_list **); +static struct ucm_list *get_modifiers(snd_use_case_mgr_t *, const char *, modifier_type); +static void init_channel_map(pa_channel_map *, const char *); +static const char *verb_to_mode(const char *); +static const char *verb_to_desc(const char *); +static char **listdup(struct ucm_list *); +static char **listmerge(char **, const char **); + +#ifdef INPUT_DEVICE_BASED_JACK_DETECTION +static void get_inputdev(snd_use_case_mgr_t *, const char *, char *, + const char **, const char **); +static int path_add_jack_input_device(pa_alsa_path *, const char *, const char *); +#endif + +pa_alsa_profile_set *pa_alsa_ucm_profile_set_new(int alsa_card_index, struct pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map) +{ + char *alsa_card_name; + pa_alsa_profile_set *ps; + pa_alsa_profile *p; + pa_alsa_mapping *m; + snd_use_case_mgr_t *uc_mgr; + struct ucm_list *verbs; + const char *verb; + int i,n; + void *state; + + if (snd_card_get_name(alsa_card_index, &alsa_card_name) < 0 || + snd_use_case_mgr_open(&uc_mgr, alsa_card_name) < 0) + return NULL; + + 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); + ps->input_paths = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + ps->output_paths = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + ps->use_ucm = TRUE; + + for (n = get_list(uc_mgr, "_verbs", &verbs), i = 0; i < n; i++) { + verb = verbs[i].name; + + if (snd_use_case_set(uc_mgr, "_verb", verb) < 0) { + pa_log("can't set verb '%s'", verb); + continue; + } + + profile_set_populate(uc_mgr, ps, verb); + } + + ucm->enabled = TRUE; + ucm->uc_mgr = uc_mgr; + + + PA_HASHMAP_FOREACH(m, ps->mappings, state) { + if (pa_alsa_mapping_verify(m, default_channel_map) < 0) + goto fail; + } + + PA_HASHMAP_FOREACH(p, ps->profiles, state) { + if (pa_alsa_profile_verify(p) < 0) + goto fail; + } + + return ps; + +fail: + snd_use_case_mgr_close(uc_mgr); + pa_alsa_profile_set_free(ps); + return NULL; +} + +int pa_alsa_ucm_profile_select(pa_alsa_profile *newp, pa_alsa_profile *oldp, pa_alsa_ucm_config *ucm) +{ + snd_use_case_mgr_t *uc_mgr; + const char *old_verb; + const char *new_verb; + const char **modifs; + int nmodif; + int i; + int ret = 0; + + pa_assert(ucm); + pa_assert_se(uc_mgr = ucm->uc_mgr); + pa_assert(!newp || newp->ucm_verb); + + new_verb = newp ? newp->ucm_verb : "Inactive"; + + if (snd_use_case_get(uc_mgr, "_verb", &old_verb) < 0) { + if (!oldp) + old_verb = pa_xstrdup(""); + else { + pa_log("can't get _verb"); + return -1; + } + } + + if (!pa_streq(old_verb, new_verb)) { + pa_log_debug("changing UCM verb '%s' => '%s'", old_verb, new_verb); + pa_log_debug(" disabling modiofiers:"); + + if ((nmodif = snd_use_case_get_list(uc_mgr, "_enamods", &modifs)) < 1) + pa_log_debug(" no modiers were enabled"); + else { + for (i = 0; i < nmodif; i++) { + pa_log_debug(" disabling '%s'", modifs[i]); + snd_use_case_set(uc_mgr, "_dismod", modifs[i]); + } + + snd_use_case_free_list(modifs, nmodif); + } + + if (snd_use_case_set(uc_mgr, "_verb", new_verb) == 0) + pa_log_debug(" verb '%s' was sucessfully set", new_verb); + else { + pa_log("can't set new verb '%s', when profile changed", new_verb); + ret = -1; + } + } + + pa_xfree((void *)old_verb); + + return ret; +} + +int pa_alsa_ucm_path_select(pa_alsa_path *newp, pa_alsa_path *oldp, pa_alsa_ucm_config *ucm) +{ +#define MAX_MODIFIER_LIST 16 + + snd_use_case_mgr_t *uc_mgr; + char *modnam; + char *od, **om; + char *nd, **nm; + pa_bool_t device_changed; + pa_bool_t device_swapped; + char *disable_modifs[MAX_MODIFIER_LIST]; + char *enable_modifs[MAX_MODIFIER_LIST]; + char id[256]; + int i; + + pa_assert(ucm); + pa_assert_se(uc_mgr = ucm->uc_mgr); + + + if (newp) { + pa_log_debug("Activating ucm path %s", newp->name); + pa_alsa_path_dump(newp); + + nd = newp->ucm_device; + nm = newp->ucm_modifiers; + } + else { + nd = NULL; + nm = NULL; + } + + if (oldp) { + if (!newp) { + pa_log_debug("Reseting ucm path %s", oldp->name); + pa_alsa_path_dump(oldp); + } + + od = oldp->ucm_device; + om = oldp->ucm_modifiers; + } + else { + od = NULL; + om = NULL; + } + + + get_device_changes(od, nd, &device_changed, &device_swapped); + get_modifier_changes(device_changed, om, nm, + disable_modifs, MAX_MODIFIER_LIST, + enable_modifs, MAX_MODIFIER_LIST); + + + for (i = 0; (modnam = disable_modifs[i]); i++) { + pa_log_debug(" disabling modifier '%s'", modnam); + if (snd_use_case_set(uc_mgr, "_dismod", modnam) < 0) { + pa_log("failed to disable modifier '%s'", modnam); + return -1; + } + } + + if (device_changed) { + if (device_swapped) { + pa_log_debug(" swapping devices '%s' => '%s'", od, nd); + snprintf(id, sizeof(id), "_swdev/%s", od); + if (snd_use_case_set(uc_mgr, id, nd) < 0) { + pa_log("failed to swap devices '%s' => '%s'", od, nd); + return -1; + } + } + else { + if (od) { + pa_log_debug(" disabling device '%s'", od); + if (snd_use_case_set(uc_mgr, "_disdev", od) < 0) { + pa_log("failed to disable device '%s'", od); + return -1; + } + } + if (nd) { + pa_log_debug(" enabling device '%s'", nd); + if (snd_use_case_set(uc_mgr, "_enadev", nd) < 0) { + pa_log("failed to enable device '%s'", nd); + return -1; + } + } + } + } + + for (i = 0; (modnam = enable_modifs[i]); i++) { + pa_log_debug(" enabling modifier '%s'", modnam); + if (snd_use_case_set(uc_mgr, "_enamod", modnam) < 0) { + pa_log("failed to enable modifier '%s'", modnam); + return -1; + } + } + + return 0; + +#undef MAX_MODIFIER_LIST +} + + +static void profile_set_populate(snd_use_case_mgr_t *uc_mgr, pa_alsa_profile_set *ps, const char *verb) +{ + struct ucm_list *profmods; + struct ucm_list *portmods; + struct ucm_list subset[3]; + int i,j; + + pa_assert(ps); + pa_assert(verb); + + profmods = get_modifiers(uc_mgr, verb, PROFILE_ANY_MODIFIER); + portmods = get_modifiers(uc_mgr, verb, PORT_MODIFIER); + + memset(subset+2, 0, sizeof(struct ucm_list)); + + if (!profmods[0].name) + profile_set_add_profile(uc_mgr, ps, verb, profmods, portmods); + else { + for (i = 0; profmods[i].name; i++) { + subset[0] = profmods[i]; + memset(subset + 1, 0, sizeof(struct ucm_list)); + + profile_set_add_profile(uc_mgr, ps, verb, subset, portmods); + + for (j = i + 1; profmods[j].name; j++) { + subset[1] = profmods[j]; + profile_set_add_profile(uc_mgr, ps, verb, subset, portmods); + } + } + } + + pa_xstrfreev((char **)portmods); + pa_xstrfreev((char **)profmods); +} + + +static void profile_set_add_profile(snd_use_case_mgr_t *uc_mgr, + pa_alsa_profile_set *ps, + const char *verb, + struct ucm_list *profmods, + struct ucm_list *portmods) +{ + pa_alsa_profile *p; + const char *devs[MAX_DEVICE]; + char ***inpp; + char ***outpp; + char **merged_devs; + char **in_paths = NULL; + char **out_paths = NULL; + char **dualdevs; + pa_alsa_direction_t direction; + char map_name[64]; + char map_desc[512]; + char captpcm_id[80]; + char captctl_id[80]; + char playpcm_id[80]; + char playctl_id[80]; + + + snprintf(captpcm_id, sizeof(captpcm_id), "CapturePCM/%s", verb); + snprintf(captctl_id, sizeof(captctl_id), "CaptureCTL/%s", verb); + + snprintf(playpcm_id, sizeof(playpcm_id), "PlaybackPCM/%s", verb); + snprintf(playctl_id, sizeof(playctl_id), "PlaybackCTL/%s", verb); + + + memset(devs, 0, sizeof(devs)); + + snd_use_case_get(uc_mgr, captpcm_id, &devs[CAPTURE_PCM ]); + snd_use_case_get(uc_mgr, captctl_id, &devs[CAPTURE_CTL ]); + snd_use_case_get(uc_mgr, playpcm_id, &devs[PLAYBACK_PCM]); + snd_use_case_get(uc_mgr, playctl_id, &devs[PLAYBACK_CTL]); + + inpp = (devs[CAPTURE_PCM] || devs[CAPTURE_CTL] ) ? &in_paths : NULL; + outpp = (devs[PLAYBACK_PCM] || devs[PLAYBACK_CTL]) ? &out_paths : NULL; + + /* entries in devs either moved to merged devices of freed */ + merged_devs = merge_device_list(devs, MAX_DEVICE); + dualdevs = get_dualdev_list(uc_mgr, verb, portmods); + + if (!(p = profile_new(ps, verb, profmods))) + return; + + snprintf(map_name, sizeof(map_name), "%s", p->name); + snprintf(map_desc, sizeof(map_desc), "Mapping for profile %s", p->name); + + pa_log_debug("Find devices for profile '%s'", p->name); + get_path_lists(uc_mgr, ps, verb, p->ucm_modifiers, dualdevs, inpp, outpp, &direction); + + profile_add_mapping(p, map_name, map_desc, direction, merged_devs, in_paths, out_paths); + + pa_xstrfreev(dualdevs); +} + +static char **merge_device_list(const char **list, int len) +{ + char **merged; + int cnt; + int i,j; + int duplicate; + + pa_assert(list); + pa_assert(len > 0); + + merged = pa_xnew0(char *, len+1); + + for (cnt = i = 0; i < len; i++) { + if (list[i]) { + for (duplicate = FALSE, j = 0; j < cnt; j++) { + if (pa_streq(list[i], merged[j])) { + duplicate = TRUE; + free((void *)list[i]); + list[i] = NULL; + break; + } + } + if (!duplicate) + merged[cnt++] = (char *)list[i]; + } + } + + return merged; +} + +static char **get_dualdev_list(snd_use_case_mgr_t *uc_mgr, const char *verb, struct ucm_list *portmods) +{ + struct ucm_list *modifier; + char id[256]; + char **dev_list; + char **ret_list; + int n; + + pa_assert(uc_mgr); + + if (portmods) { + for (modifier = portmods; modifier->name; modifier++) { + if (pa_streq(modifier->name, SND_USE_CASE_MOD_PLAY_TONE)) { + snprintf(id, sizeof(id), "_supporteddevs/" SND_USE_CASE_MOD_PLAY_TONE "/%s", verb); + + if ((n = snd_use_case_get_list(uc_mgr, id, (const char ***)&dev_list)) > 0) { + ret_list = pa_xrealloc(dev_list, sizeof(char *)*(n+1)); + + pa_assert(ret_list); + + ret_list[n] = NULL; + + return ret_list; + } + + break; + } + } + } + + return pa_xnew0(char *, 1); +} + + +static void get_path_lists(snd_use_case_mgr_t *uc_mgr, + pa_alsa_profile_set *ps, + const char *verb, + char **modifiers, + char **dualdevs, + char ***input_path_names_ret, + char ***output_path_names_ret, + pa_alsa_direction_t *direction_ret) +{ +#define MAX_MODIFIERS 16 + + char id[256]; + struct ucm_list *devices; + int ndev; + const char **sd; + int nmodif; + char devnam[256]; + char *modnam; + struct support sup_mods[MAX_MODIFIERS]; + struct support *sm; + char **in_paths; + char **out_paths; + char **in_modifs; + char **out_modifs; + int in_pidx; + int out_pidx; + int in_midx; + int out_midx; + unsigned int supported; + unsigned int mask = 0; + char *c; + int i,j,k; + int found; +#ifndef INPUT_DEVICE_BASED_JACK_DETECTION + const char *inpctl = NULL; +#else + const char *inpdev; + const char *inpcode; +#endif + + pa_assert(uc_mgr); + pa_assert(ps); + pa_assert(verb); + pa_assert(modifiers); + pa_assert(input_path_names_ret || output_path_names_ret); + pa_assert(direction_ret); + + for (nmodif = 0; (modnam = modifiers[nmodif]) && nmodif < MAX_MODIFIERS; nmodif++) { + sm = sup_mods + nmodif; + + snprintf(id, sizeof(id), "_supporteddevs/%s/%s", modnam, verb); + + if (strcasestr(modnam, "capture")) + sm->dir = INPUT; + else if (strcasestr(modnam, "play")) + sm->dir = OUTPUT; + else + sm->dir = UNDEFINED; + + if ((sm->len = snd_use_case_get_list(uc_mgr, id, &sd)) > 0 && + (sm->devs = realloc(sd, sizeof(char *)*(sm->len + 1))) ) + sm->devs[sm->len] = NULL; + else { + sm->len = 0; + sm->devs = pa_xnew0(const char *, 1); + } + } + + snprintf(id, sizeof(id), "_devices/%s", verb); + ndev = get_list(uc_mgr, id, &devices); + + /* it is faster to over allocate for the worst case + than calculate the exact number of entries. + The worst case is twice the devices + a terminating NULL */ + + if (input_path_names_ret) { + in_paths = *input_path_names_ret = pa_xnew0(char *, ndev * 2 + 1); + in_pidx = 0; + } + + if (output_path_names_ret) { + out_paths = *output_path_names_ret = pa_xnew0(char *, ndev * 2 + 1); + out_pidx = 0; + } + + for (i = 0; i < ndev; i++) { + + strncpy(devnam, devices[i].name, sizeof(devnam)); + devnam[sizeof(devnam)-1] = '\0'; + if ((c = strchr(devnam, '.'))) + *c = '\0'; + + supported = 0; + in_midx = out_midx = 0; + + in_modifs = pa_xnew0(char *, nmodif + 1); + out_modifs = pa_xnew0(char *, nmodif + 1); + + pa_log_debug(" looking at UCM configuration of device '%s':", devnam); + + if (!modifiers[0]) { + pa_log_debug(" no modifier to check"); + + if (input_path_names_ret) + supported |= INPUT; + if (output_path_names_ret) + supported |= OUTPUT; + } + else { + pa_log_debug(" checking supported modifiers:"); + + for (j = 0; (modnam = modifiers[j]); j++) { + sm = sup_mods + j; + + if (sm->dir != UNDEFINED) { + for (found = FALSE, k = 0; k < sm->len; k++) { + if (pa_streq(devnam, sm->devs[k])) { + found = TRUE; + break; + } + } + + if (!found) + pa_log_debug(" modifier '%s' is not supported", modnam); + else { + pa_log_debug(" modifier '%s' supported", modnam); + if ((sm->dir & INPUT) && input_path_names_ret) { + supported |= INPUT; + in_modifs[in_midx++] = pa_xstrdup(modnam); + } + else if ((sm->dir && OUTPUT) && output_path_names_ret) { + supported = OUTPUT; + out_modifs[out_midx++] = pa_xstrdup(modnam); + } + } + } + } /* for */ + } + + if (!supported) { + pa_log_debug(" not supported"); + pa_xstrfreev(in_modifs); + pa_xstrfreev(out_modifs); + } + else { + pa_log_debug(" supported"); + + mask |= supported; + +#ifndef INPUT_DEVICE_BASED_JACK_DETECTION +#if 0 /* TODO: something to get the ALSA controls */ + get_inputdev(uc_mgr, verb, devnam, &inpctl); +#endif + + add_path(uc_mgr, ps, supported, verb, devnam, + in_paths,&in_pidx, out_paths,&out_pidx, + in_modifs,out_modifs, inpctl, SINGLEDEV); + + for (k = 0; dualdevs[i]; i++) { + if (pa_streq(devnam, dualdevs[i])) { + add_path(uc_mgr, ps, supported, verb, devnam, + in_paths,&in_pidx, out_paths,&out_pidx, + in_modifs,out_modifs, inpctl, DUALDEV); + break; + } + } +#else + get_inputdev(uc_mgr, verb, devnam, &inpdev, &inpcode); + + add_path(uc_mgr, ps, supported, verb, devnam, + in_paths,&in_pidx, out_paths,&out_pidx, + in_modifs,out_modifs, inpdev,inpcode, SINGLEDEV); + + for (k = 0; dualdevs[i]; i++) { + if (pa_streq(devnam, dualdevs[i])) { + add_path(uc_mgr, ps, supported, verb, devnam, + in_paths,&in_pidx, out_paths,&out_pidx, + in_modifs,out_modifs, inpdev,inpcode, DUALDEV); + break; + } + } +#endif + } + } + + *direction_ret = DIRECTION_ALSA(mask); + +#undef MAX_MODIFIERS +} + +static int get_volctl(snd_use_case_mgr_t *uc_mgr, + const char *id, + char *devnam, + const char *verb, + int dualdev, + const char **volctl_ret) +{ + char query[256]; + + if (dualdev) { + snprintf(query, sizeof(query), "%s/%s/%s", id, SND_USE_CASE_MOD_PLAY_TONE, verb); + + if (snd_use_case_get(uc_mgr, query, volctl_ret) == 0) + goto found; + } + + snprintf(query, sizeof(query), "%s/%s/%s", id, devnam, verb); + + if (snd_use_case_get(uc_mgr, query, volctl_ret) == 0) + goto found; + + *volctl_ret = NULL; + return - 1; + + found: + pa_log_debug(" found capture volume control @ '%s'", *volctl_ret); + return 0; +} + +#ifdef INPUT_DEVICE_BASED_JACK_DETECTION +static void get_inputdev(snd_use_case_mgr_t *uc_mgr, + const char *verb, + char *devnam, + const char **name_ret, + const char **code_ret) +{ + char id[256]; + const char *name; + const char *code; + + pa_assert(uc_mgr); + pa_assert(verb); + pa_assert(devnam); + pa_assert(name_ret); + pa_assert(code_ret); + + snprintf(id, sizeof(id), "InputDeviceName/%s/%s", devnam, verb); + + if (snd_use_case_get(uc_mgr, id, &name) == 0) + pa_log_debug(" input device '%s'", name); + else + name = NULL; + + snprintf(id, sizeof(id), "InputDeviceCode/%s/%s", devnam, verb); + + if (snd_use_case_get(uc_mgr, id, &code) == 0) + pa_log_debug(" input device code '%s'", code); + else + code = NULL; + + if ((!name && code) || (name && !code)) { + name = code = NULL; + pa_log_debug("incomplete input device specification (verb %s, device %s)", verb, devnam); + } + + *name_ret = name; + *code_ret = code; +} +#endif + +static void add_path(snd_use_case_mgr_t *uc_mgr, + pa_alsa_profile_set *ps, + unsigned int supported, + const char *verb, + char *devnam, + char **in_paths, + int *in_pidx_ptr, + char **out_paths, + int *out_pidx_ptr, + char **in_modifs, + char **out_modifs, +#ifndef INPUT_DEVICE_BASED_JACK_DETECTION + const char *inpctl, +#else + const char *inpdev, + const char *inpcode, +#endif + int dualdev) +{ + static const char *dual_modifs[2] = {SND_USE_CASE_MOD_PLAY_TONE, NULL}; + + char id[256]; + char extnam[512]; + char **modifs; + pa_alsa_path *p; + int in_pidx; + int out_pidx; + const char *volctl; + + snprintf(extnam, sizeof(extnam), "%s%s", devnam, dualdev ? "+" SND_USE_CASE_DEV_SPEAKER : ""); + + if (!(supported & INPUT) || dualdev) { + if (!dualdev) + pa_xstrfreev(in_modifs); + } + else { + in_pidx = *in_pidx_ptr; + modifs = dualdev ? listmerge(in_modifs, dual_modifs) : in_modifs; + + snprintf(id, sizeof(id), "%s-%s-input", extnam, verb); + + if ((p = path_new(id, extnam, PA_ALSA_DIRECTION_INPUT, verb, devnam, modifs))) { + pa_hashmap_put(ps->input_paths, p->name, p); + in_paths[in_pidx++] = pa_xstrdup(id); + + if (get_volctl(uc_mgr, "CaptureVolume", devnam, verb, dualdev, &volctl) == 0) + path_add_volume_control(p, volctl); + } + + *in_pidx_ptr = in_pidx; + } + + if (!(supported & OUTPUT)) { + if (!dualdev) + pa_xstrfreev(out_modifs); + } + else { + out_pidx = *out_pidx_ptr; + modifs = dualdev ? listmerge(out_modifs, dual_modifs) : out_modifs; + + snprintf(id, sizeof(id), "%s-%s-output", extnam, verb); + + if ((p = path_new(id, extnam, PA_ALSA_DIRECTION_OUTPUT, verb, devnam, modifs))) { + pa_hashmap_put(ps->output_paths, p->name, p); + out_paths[out_pidx++] = pa_xstrdup(id); + + if (get_volctl(uc_mgr, "PlaybackVolume", devnam, verb, dualdev, &volctl) == 0) + path_add_volume_control(p, volctl); + } + + *out_pidx_ptr = out_pidx; + } + +#ifndef INPUT_DEVICE_BASED_JACK_DETECTION +#if 0 /* TODO: implement this when it will emerge */ + if (supported && inpctl) + path_add_jack_alsa_control(p, inpctl); +#endif +#else + if (supported && inpdev && inpcode) + path_add_jack_input_device(p, inpdev, inpcode); +#endif +} + + +static pa_alsa_path *path_new(const char *name, + const char *desc, + pa_alsa_direction_t direction, + const char *ucm_verb, + const char *ucm_device, + char **ucm_modifiers) +{ + pa_alsa_path *p; + + pa_assert(name); + pa_assert(desc); + pa_assert(ucm_device); + pa_assert(ucm_modifiers); + + pa_log_debug("new alsa path '%s' (%s)", name, desc); + + p = pa_xnew0(pa_alsa_path, 1); + + p->direction = direction; + p->name = pa_xstrdup(name); + p->description = pa_xstrdup(desc); + p->use_ucm = TRUE; + p->ucm_verb = pa_xstrdup(ucm_verb); + p->ucm_device = pa_xstrdup(ucm_device); + p->ucm_modifiers = ucm_modifiers; + + return p; +} + +static void path_add_volume_control(pa_alsa_path *p, const char *alsa_name) +{ + pa_alsa_element *e; + + pa_assert(p); + pa_assert(alsa_name); + + e = pa_xnew0(pa_alsa_element, 1); + + e->path = p; + e->alsa_name = (char *)alsa_name; + e->direction = p->direction; + e->volume_limit = -1; + e->switch_use = PA_ALSA_SWITCH_MUTE; + e->volume_use = PA_ALSA_VOLUME_MERGE; + + PA_LLIST_PREPEND(pa_alsa_element, p->elements, e); + p->last_element = e; +} + +#ifdef INPUT_DEVICE_BASED_JACK_DETECTION +static int path_add_jack_input_device(pa_alsa_path *p, const char *dev_name, const char *dev_code) +{ + int ret; + + pa_assert(p); + pa_assert(dev_name); + pa_assert(dev_code); + + if (p->jack_inputdev_name || p->jack_inputdev_code) + ret = -1; + else { + ret = 0; + p->jack_inputdev_name = pa_xstrdup(dev_name); /* this is probably a leak */ + p->jack_inputdev_code = pa_xstrdup(dev_code); + } + + return ret; +} +#endif + +static pa_alsa_profile *profile_new(pa_alsa_profile_set *ps, const char *verb, struct ucm_list *modifiers) +{ + pa_alsa_profile *p; + char name[512]; + char descr[1024]; + + pa_assert(ps); + pa_assert(verb); + + if (!compose_profile_name(verb, modifiers, name, sizeof(name)) || + !compose_profile_description(verb, modifiers, descr, sizeof(descr))) + return NULL; + + if (pa_hashmap_get(ps->profiles, name)) + return NULL; + + p = pa_xnew0(pa_alsa_profile, 1); + p->profile_set = ps; + p->name = pa_xstrdup(name); + p->description = pa_xstrdup(descr); + p->ucm_verb = pa_xstrdup(verb); + p->ucm_modifiers = listdup(modifiers); + + pa_hashmap_put(ps->profiles, p->name, p); + + return p; +} + + +static void profile_add_mapping(pa_alsa_profile *p, + const char *name, + const char *desc, + pa_alsa_direction_t direction, + char **devs, + char **in_paths, + char **out_paths) +{ + pa_alsa_profile_set *ps; + pa_alsa_mapping *m; + char **inmap_names = NULL; + char **outmap_names = NULL; + char query[256]; + + pa_assert(p); + pa_assert(name); + pa_assert(desc); + pa_assert(devs); + pa_assert_se(ps = p->profile_set); + + + snprintf(query, sizeof(query), "Mapping %s", name); + + if (!(m = pa_alsa_mapping_get(ps, query))) { + pa_xstrfreev(devs); + pa_xstrfreev(in_paths); + pa_xstrfreev(out_paths); + return; + } + + if (direction == PA_ALSA_DIRECTION_ANY || direction == PA_ALSA_DIRECTION_INPUT) + inmap_names = pa_split_spaces_strv(name); + + if (direction == PA_ALSA_DIRECTION_ANY || direction == PA_ALSA_DIRECTION_OUTPUT) + outmap_names = pa_split_spaces_strv(name); + + + if (m->description) { + /* mapping already exists: modify it */ + + if (direction != m->direction && m->direction != PA_ALSA_DIRECTION_ANY) { + m->direction = PA_ALSA_DIRECTION_ANY; + + /* TODO: merge the devs */ + + pa_xstrfreev(devs); + pa_xstrfreev(in_paths); + pa_xstrfreev(out_paths); + } + } + else { + /* new mapping: initialize it */ + m->description = pa_xstrdup(desc); + m->priority = 1; + m->direction = direction; + m->device_strings = devs; + m->input_path_names = in_paths; + m->output_path_names = out_paths; + + init_channel_map(&m->channel_map, p->ucm_verb); + } + + p->input_mapping_names = inmap_names; + p->output_mapping_names = outmap_names; +} + + +static char *compose_profile_name(const char *verb, struct ucm_list *modifiers, char *buf, int len) +{ + const char *mode_str; + const char *media_str; + const char *dir_str; + const char *modifier; + int media; + int dir; + int i; + + + pa_assert(verb); + pa_assert(buf); + pa_assert(len > 0); + + if (!(mode_str = VERB_TO_MODE(verb))) + return NULL; + + if (!modifiers || !modifiers[0].name) { + dir = DUPLEX; + media = VERB_TO_MEDIA(verb); + } + else { + for (i = 0, media = dir = UNDEFINED; (modifier = modifiers[i].name); i++) { + + if (strcasestr(modifier, "capture")) + dir |= INPUT; + else if(strcasestr(modifier, "play")) + dir |= OUTPUT; + + if (strcasestr(modifier, "voice")) + media |= VOICE; + else + media |= MUSIC; + } + } + + + dir_str = DIRECTION_PULSE(dir); + media_str = MEDIA_ID(media); + + snprintf(buf, len, "%s-%s-for-%s", mode_str, dir_str, media_str); + + return buf; +} + +static char *compose_profile_description(const char *verb, struct ucm_list *modifiers, char *buf, int len) +{ + const char *mode_str; + const char *media_str; + const char *dir_str; + const char *modifier; + int media; + int dir; + int i; + + pa_assert(verb); + pa_assert(buf); + pa_assert(len > 0); + + if (!(mode_str = VERB_TO_DESCRIPTION(verb))) + return NULL; + + if (!modifiers || !modifiers[0].name) { + dir = DUPLEX; + media = VERB_TO_MEDIA(verb); + } + else { + for (i = 0, media = dir = UNDEFINED; (modifier = modifiers[i].name); i++) { + + if (strcasestr(modifier, "capture")) + dir |= INPUT; + else if(strcasestr(modifier, "play")) + dir |= OUTPUT; + + if (strcasestr(modifier, "voice")) + media |= VOICE; + else + media |= MUSIC; + } + } + + + dir_str = DIRECTION_DESCRIPTION(dir); + media_str = MEDIA_DESCRIPTION(media); + + snprintf(buf, len, "%s %s in %s mode", media_str, dir_str, mode_str); + + return buf; +} + + +static void get_device_changes(char *old_dev, char *new_dev, + pa_bool_t *changed_ret, pa_bool_t *swapped_ret) +{ + pa_bool_t changed; + pa_bool_t swapped; + + if (old_dev) { + changed = !(new_dev && pa_streq(old_dev, new_dev)); + swapped = changed ? !!new_dev : FALSE; + } + else { + changed = !!new_dev; + swapped = FALSE; + } + + *changed_ret = changed; + *swapped_ret = swapped; +} + + +/* + * We assume that the enable and disable sequences are inverse operations, i.e + * a disable/enable or an enable/disable operation can be safely ignored without + * any side effects. + * + * Furthermore we assume that neither old_modifs nor new_modifs have duplicate + * entries. + * + * For finding duplicates we use linear search since the number of available + * modifiers is 5 and the linear search beleived to be faster. However, it can + * be changed easily to using PA_HASHMAPs instead. + * + */ + +static void get_modifier_changes(pa_bool_t dev_changed, + char **old_modifs, + char **new_modifs, + char **disable_mods, + int disable_len, + char **enable_mods, + int enable_len) +{ +#define MODIFIER_LIST_DIM 16 + + struct modsts { + char *name; + int status; + }; + + struct modsts m[MODIFIER_LIST_DIM]; + int handled; + int i,j,k,l; + + (void)dev_changed; + + pa_assert(disable_mods); + pa_assert(enable_mods); + pa_assert(disable_len > 0); + pa_assert(enable_len > 0); + + *disable_mods = *enable_mods = NULL; + + i = 0; + + if (old_modifs) { + for (; old_modifs[i]; i++) { + if (i >= MODIFIER_LIST_DIM) + goto overflow; + + m[i].name = old_modifs[i]; + m[i].status = -1; + } + } + + if (new_modifs) { + for (j = 0; new_modifs[j]; j++) { + for (handled = FALSE, k = 0; k < i; k++) { + if (pa_streq(new_modifs[j], m[k].name)) { + m[k].status = 0; + handled = TRUE; + break; + } + } + if (!handled) { + if (i >= MODIFIER_LIST_DIM) + goto overflow; + m[i].name = new_modifs[j]; + m[i].status = +1; + i++; + } + } + } + + for (j = k = l = 0; j < i; j++) { + if (m[j].status < 0) { + if (k < disable_len - 1) + disable_mods[k++] = m[j].name; + else { + pa_log("%s: disable_mods overflowed (dimension is %d)", + __FUNCTION__, disable_len); + } + } + else if (m[j].status > 0) { + if (l < enable_len - 1) + enable_mods[l++] = m[j].name; + else { + pa_log("%s: enable_mods overflowed (dimension is %d)", + __FUNCTION__, enable_len); + } + } + } + + disable_mods[k] = NULL; + enable_mods[l] = NULL; + + return; + + overflow: + pa_log("%s: internal array overflow (dimension is %d)", + __FUNCTION__, MODIFIER_LIST_DIM); + + +#undef MODIFIER_LIST_DIM +} + + +static int get_list(snd_use_case_mgr_t *uc_mgr, const char *id, struct ucm_list **list_ret) +{ + int n; + + if ((n = snd_use_case_get_list(uc_mgr, id, (const char ***)list_ret)) < 0) + *list_ret = pa_xnew0(struct ucm_list, 1); + else + n >>= 1; + + return n; +} + + +static struct ucm_list *get_modifiers(snd_use_case_mgr_t *uc_mgr, const char *verb, modifier_type type) +{ + static struct ucm_list empty_list_entry; + static int medias[] = { MUSIC, VOICE, UNDEFINED }; + + int media; + struct ucm_list *modifiers; + struct ucm_list *modifier; + struct ucm_list *list; + char id[256]; + int i,j,k,n; + + pa_assert(uc_mgr); + pa_assert(verb); + + snprintf(id, sizeof(id), "_modifiers/%s", verb); + + n = get_list(uc_mgr, id, &modifiers); + list = pa_xnew0(struct ucm_list, (n > 0 ? n + 1 : 1)); + + for (i = j = 0; i < n; i++) { + modifier = modifiers + i; + + switch (type) { + + case PROFILE_MATCHING_MODIFIER: + media = VERB_TO_MEDIA(verb); + if (strcasestr(modifier->name, MEDIA_ID(media))) { + list[j++] = *modifier; + *modifier = empty_list_entry; + } + break; + + case PROFILE_ANY_MODIFIER: + for (k = 0; (media = medias[k]) != UNDEFINED; k++) { + if (strcasestr(modifier->name, MEDIA_ID(media))) { + list[j++] = *modifier; + *modifier = empty_list_entry; + break; + } + } + break; + + case PORT_MODIFIER: + if (strstr(modifier->name, SND_USE_CASE_MOD_PLAY_TONE)) { + list[j++] = *modifier; + *modifier = empty_list_entry; + } + break; + + default: + break; + } /* switch type */ + } /* for */ + + snd_use_case_free_list((const char **)modifiers, n); + + memset(list + j, 0, sizeof(struct ucm_list)); + + return list; +} + + +static void init_channel_map(pa_channel_map *cm, const char *verb) +{ + pa_assert(cm); + pa_assert(verb); + + if (!pa_channel_map_valid(cm)) { + if (strcasestr(verb, "voice")) + pa_channel_map_init_mono(cm); + else + pa_channel_map_init_stereo(cm); + } +} + +static const char *verb_to_mode(const char *verb) +{ + struct mode *m; + + pa_assert(verb); + + for (m = mode_map; m->verb; m++) { + if (pa_streq(verb, m->verb)) + return m->mode; + } + + return NULL; +} + +static const char *verb_to_desc(const char *verb) +{ + struct mode *m; + + pa_assert(verb); + + for (m = mode_map; m->verb; m++) { + if (pa_streq(verb, m->verb)) + return m->descr; + } + + return NULL; +} + +static char **listdup(struct ucm_list *list) +{ + char **copy; + char *str, *c; + int len; + int i; + + if (!list) + copy = NULL; + else { + for (i = 0; list[i].name; i++) + ; + + copy = pa_xnew0(char *, i+1); + + for (i = 0; list[i].name; i++) { + len = (c = strchr(list[i].name, '.')) ? c - list[i].name : (int)strlen(list[i].name); + copy[i] = str = pa_xnew0(char, len+1); + memcpy(str, list[i].name, len); + } + } + + return copy; +} + +static char **listmerge(char **list1, const char **list2) +{ + char **merged; + int i,j,k,l; + int duplicate; + + for (i = 0; list1[i]; i++) + ; + + for (j = 0; list2[j]; j++) + ; + + merged = pa_xnew0(char *, i + j + 1); + k = 0; + + for (i = 0; list1[i]; i++) { + for (l = 0; l < k; l++) { + if (pa_streq(list1[i], merged[l])) { + duplicate = TRUE; + break; + } + } + + if (!duplicate) + merged[k++] = pa_xstrdup(list1[i]); + } + + for (duplicate = FALSE, j = 0; list2[j]; j++) { + for (l = 0; l < k; l++) { + if (pa_streq(list2[j], merged[l])) { + duplicate = TRUE; + break; + } + } + + if (!duplicate) + merged[k++] = pa_xstrdup(list2[j]); + } + + return merged; +} diff --git a/src/modules/alsa/alsa-ucm.h b/src/modules/alsa/alsa-ucm.h new file mode 100644 index 0000000..a4a928e --- /dev/null +++ b/src/modules/alsa/alsa-ucm.h @@ -0,1 +1,52 @@ +#ifndef foopulseucmhfoo +#define foopulseucmhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2011 Intel Corporation. + Author Janos Kovacs <jankovac503 at gmail.com> + + 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_config pa_alsa_ucm_config; + +struct profile_data { + pa_alsa_profile *profile; +}; + +struct pa_alsa_ucm_config { + pa_bool_t enabled; + snd_use_case_mgr_t *uc_mgr; +}; + +/* Argh... this stuff just temporarily parking here */ +struct pa_card; +pa_alsa_ucm_config *pa_alsa_card_get_ucm_config(struct pa_card *); + + +pa_alsa_profile_set *pa_alsa_ucm_profile_set_new(int alsa_card_index, + struct pa_alsa_ucm_config *ucm, + pa_channel_map *default_channel_map); + +int pa_alsa_ucm_profile_select(pa_alsa_profile *, pa_alsa_profile *, pa_alsa_ucm_config *); +int pa_alsa_ucm_path_select(pa_alsa_path *, pa_alsa_path *, pa_alsa_ucm_config *); + +#endif -- 1.7.5.2