From patchwork Mon Feb 20 07:34:01 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Feng Wei X-Patchwork-Id: 6832 Return-Path: X-Original-To: patchwork@peony.canonical.com Delivered-To: patchwork@peony.canonical.com Received: from fiordland.canonical.com (fiordland.canonical.com [91.189.94.145]) by peony.canonical.com (Postfix) with ESMTP id D330423EC9 for ; Mon, 20 Feb 2012 07:34:05 +0000 (UTC) Received: from mail-iy0-f180.google.com (mail-iy0-f180.google.com [209.85.210.180]) by fiordland.canonical.com (Postfix) with ESMTP id 4510DA183C4 for ; Mon, 20 Feb 2012 07:34:05 +0000 (UTC) Received: by iabz7 with SMTP id z7so10103926iab.11 for ; Sun, 19 Feb 2012 23:34:04 -0800 (PST) Received: from mr.google.com ([10.42.131.129]) by 10.42.131.129 with SMTP id z1mr19918029ics.53.1329723244669 (num_hops = 1); Sun, 19 Feb 2012 23:34:04 -0800 (PST) Received: by 10.42.131.129 with SMTP id z1mr15877290ics.53.1329723244105; Sun, 19 Feb 2012 23:34:04 -0800 (PST) X-Forwarded-To: linaro-patchwork@canonical.com X-Forwarded-For: patch@linaro.org linaro-patchwork@canonical.com Delivered-To: patches@linaro.org Received: by 10.231.11.10 with SMTP id r10csp38735ibr; Sun, 19 Feb 2012 23:34:03 -0800 (PST) Received: by 10.229.107.21 with SMTP id z21mr12564025qco.89.1329723241725; Sun, 19 Feb 2012 23:34:01 -0800 (PST) Received: from mail-qw0-f50.google.com (mail-qw0-f50.google.com [209.85.216.50]) by mx.google.com with ESMTPS id c9si17603797qao.40.2012.02.19.23.34.01 (version=TLSv1/SSLv3 cipher=OTHER); Sun, 19 Feb 2012 23:34:01 -0800 (PST) Received-SPF: neutral (google.com: 209.85.216.50 is neither permitted nor denied by best guess record for domain of feng.wei@linaro.org) client-ip=209.85.216.50; Authentication-Results: mx.google.com; spf=neutral (google.com: 209.85.216.50 is neither permitted nor denied by best guess record for domain of feng.wei@linaro.org) smtp.mail=feng.wei@linaro.org Received: by qabg27 with SMTP id g27so7154384qab.16 for ; Sun, 19 Feb 2012 23:34:01 -0800 (PST) Received-SPF: pass (google.com: domain of feng.wei@linaro.org designates 10.229.78.151 as permitted sender) client-ip=10.229.78.151; Received: from mr.google.com ([10.229.78.151]) by 10.229.78.151 with SMTP id l23mr14701492qck.135.1329723241494 (num_hops = 1); Sun, 19 Feb 2012 23:34:01 -0800 (PST) MIME-Version: 1.0 Received: by 10.229.78.151 with SMTP id l23mr12478201qck.135.1329723241239; Sun, 19 Feb 2012 23:34:01 -0800 (PST) Received: by 10.229.121.201 with HTTP; Sun, 19 Feb 2012 23:34:01 -0800 (PST) Date: Mon, 20 Feb 2012 15:34:01 +0800 Message-ID: Subject: [PATCH] UCM patches on ubuntu 1:1.1-0ubuntu4 From: Feng Wei To: David Henningsson , General PulseAudio Discussion Cc: patches@linaro.org X-Gm-Message-State: ALoCoQn1dvvvjNYWgqgNhDP00K7YAdkvm+GEMGs6wJF7ERE1dp7RvSHsD/rgu0/Pd94i4qymXD2Y Somewhat verified on Panda board and i.mx53 board. Integrate UCM in alsa module Index: pulseaudio-1.1/src/Makefile.am =================================================================== --- pulseaudio-1.1.orig/src/Makefile.am 2012-02-17 13:19:52.600542570 +0800 +++ pulseaudio-1.1/src/Makefile.am 2012-02-17 13:19:52.648542570 +0800 @@ -1561,6 +1561,7 @@ libalsa_util_la_SOURCES = \ modules/alsa/alsa-util.c modules/alsa/alsa-util.h \ + modules/alsa/alsa-ucm.c modules/alsa/alsa-ucm.h \ modules/alsa/alsa-mixer.c modules/alsa/alsa-mixer.h \ modules/alsa/alsa-jack-inputdev.c modules/alsa/alsa-jack-inputdev.h \ modules/alsa/alsa-sink.c modules/alsa/alsa-sink.h \ Index: pulseaudio-1.1/src/modules/alsa/alsa-mixer.c =================================================================== --- pulseaudio-1.1.orig/src/modules/alsa/alsa-mixer.c 2012-02-17 13:19:52.548542570 +0800 +++ pulseaudio-1.1/src/modules/alsa/alsa-mixer.c 2012-02-17 13:19:52.652542570 +0800 @@ -3180,6 +3180,8 @@ pa_xstrfreev(m->input_element); pa_xstrfreev(m->output_element); + pa_xfree(m->ucm_context.ucm_devices); + pa_assert(!m->input_pcm); pa_assert(!m->output_pcm); @@ -3255,7 +3257,7 @@ pa_xfree(ps); } -static pa_alsa_mapping *mapping_get(pa_alsa_profile_set *ps, const char *name) { +pa_alsa_mapping *mapping_get(pa_alsa_profile_set *ps, const char *name) { pa_alsa_mapping *m; if (!pa_startswith(name, "Mapping ")) Index: pulseaudio-1.1/src/modules/alsa/alsa-mixer.h =================================================================== --- pulseaudio-1.1.orig/src/modules/alsa/alsa-mixer.h 2012-02-17 13:19:52.528542570 +0800 +++ pulseaudio-1.1/src/modules/alsa/alsa-mixer.h 2012-02-17 13:19:52.652542570 +0800 @@ -49,6 +49,7 @@ typedef struct pa_alsa_jack_inputdev pa_alsa_jack_inputdev; #include "alsa-util.h" +#include "alsa-ucm.h" typedef enum pa_alsa_switch_use { PA_ALSA_SWITCH_IGNORE, @@ -225,6 +226,8 @@ void pa_alsa_path_set_set_callback(pa_alsa_path_set *ps, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata); void pa_alsa_path_set_free(pa_alsa_path_set *s); +pa_alsa_mapping *mapping_get(pa_alsa_profile_set *ps, const char *name); + struct pa_alsa_mapping { pa_alsa_profile_set *profile_set; @@ -252,6 +255,9 @@ pa_sink *sink; pa_source *source; + + /* ucm device context*/ + pa_alsa_ucm_mapping_context ucm_context; }; struct pa_alsa_profile { Index: pulseaudio-1.1/src/modules/alsa/alsa-sink.c =================================================================== --- pulseaudio-1.1.orig/src/modules/alsa/alsa-sink.c 2012-02-17 13:19:52.532542570 +0800 +++ pulseaudio-1.1/src/modules/alsa/alsa-sink.c 2012-02-17 13:19:52.652542570 +0800 @@ -151,6 +151,9 @@ pa_hook_slot *reserve_slot; pa_reserve_monitor_wrapper *monitor; pa_hook_slot *monitor_slot; + + /* ucm context */ + pa_alsa_ucm_mapping_context *ucm_context; }; static void userdata_free(struct userdata *u); @@ -1450,6 +1453,14 @@ } } +static int sink_set_port_ucm_cb(pa_sink *s, pa_device_port *p) { + struct userdata *u = s->userdata; + + pa_assert(p); + + return ucm_set_port(u->ucm_context, p, 1); +} + static int sink_set_port_cb(pa_sink *s, pa_device_port *p) { struct userdata *u = s->userdata; pa_alsa_port_data *data; @@ -1840,6 +1851,16 @@ } } +/* FIXME: hardware volume/mute ??? */ +static int ucm_setup_mixer(struct userdata *u, pa_bool_t ignore_dB) { + pa_assert(u && u->sink); + + if (u->sink->active_port) { + return ucm_set_port(u->ucm_context, u->sink->active_port, 1); + } + + return 0; +} static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB) { pa_bool_t need_mixer_callback = FALSE; @@ -2020,6 +2041,8 @@ TRUE); u->smoother_interval = SMOOTHER_MIN_INTERVAL; + u->ucm_context = &mapping->ucm_context; + dev_id = pa_modargs_get_value( ma, "device_id", pa_modargs_get_value(ma, "device", DEFAULT_DEVICE)); @@ -2108,7 +2131,8 @@ /* ALSA might tweak the sample spec, so recalculate the frame size */ frame_size = pa_frame_size(&ss); - find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB); + if (!mapping->ucm_context.ucm) + find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB); pa_sink_new_data_init(&data); data.driver = driver; @@ -2153,7 +2177,9 @@ goto fail; } - if (u->mixer_path_set) + if (mapping->ucm_context.ucm) + ucm_add_ports(&data.ports, data.proplist, mapping, 1, card); + else if (u->mixer_path_set) pa_alsa_add_ports(&data.ports, u->mixer_path_set, card); u->sink = pa_sink_new(m->core, &data, PA_SINK_HARDWARE | PA_SINK_LATENCY | (u->use_tsched ? PA_SINK_DYNAMIC_LATENCY : 0) | @@ -2181,7 +2207,10 @@ if (u->use_tsched) u->sink->update_requested_latency = sink_update_requested_latency_cb; u->sink->set_state = sink_set_state_cb; - u->sink->set_port = sink_set_port_cb; + if (mapping->ucm_context.ucm) + u->sink->set_port = sink_set_port_ucm_cb; + else + u->sink->set_port = sink_set_port_cb; u->sink->userdata = u; pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); @@ -2233,7 +2262,11 @@ if (update_sw_params(u) < 0) goto fail; - if (setup_mixer(u, ignore_dB) < 0) + if (mapping->ucm_context.ucm) { + if (ucm_setup_mixer(u, ignore_dB) < 0) + goto fail; + } + else if (setup_mixer(u, ignore_dB) < 0) goto fail; pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle); Index: pulseaudio-1.1/src/modules/alsa/alsa-source.c =================================================================== --- pulseaudio-1.1.orig/src/modules/alsa/alsa-source.c 2012-02-17 13:19:52.532542570 +0800 +++ pulseaudio-1.1/src/modules/alsa/alsa-source.c 2012-02-17 13:19:52.656542570 +0800 @@ -133,6 +133,9 @@ pa_hook_slot *reserve_slot; pa_reserve_monitor_wrapper *monitor; pa_hook_slot *monitor_slot; + + /* ucm context */ + pa_alsa_ucm_mapping_context *ucm_context; }; static void userdata_free(struct userdata *u); @@ -1301,6 +1304,14 @@ } } +static int source_set_port_ucm_cb(pa_source *s, pa_device_port *p) { + struct userdata *u = s->userdata; + + pa_assert(p); + + return ucm_set_port(u->ucm_context, p, 0); +} + static int source_set_port_cb(pa_source *s, pa_device_port *p) { struct userdata *u = s->userdata; pa_alsa_port_data *data; @@ -1540,6 +1551,17 @@ } } +/* FIXME: hardware volume/mute ??? */ +static int ucm_setup_mixer(struct userdata *u, pa_bool_t ignore_dB) { + pa_assert(u && u->source); + + if (u->source->active_port) { + return ucm_set_port(u->ucm_context, u->source->active_port, 0); + } + + return 0; +} + static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB) { pa_bool_t need_mixer_callback = FALSE; @@ -1712,6 +1734,8 @@ TRUE); u->smoother_interval = SMOOTHER_MIN_INTERVAL; + u->ucm_context = &mapping->ucm_context; + dev_id = pa_modargs_get_value( ma, "device_id", pa_modargs_get_value(ma, "device", DEFAULT_DEVICE)); @@ -1797,7 +1821,8 @@ /* ALSA might tweak the sample spec, so recalculate the frame size */ frame_size = pa_frame_size(&ss); - find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB); + if (!mapping->ucm_context.ucm) + find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB); pa_source_new_data_init(&data); data.driver = driver; @@ -1842,7 +1867,9 @@ goto fail; } - if (u->mixer_path_set) + if (mapping->ucm_context.ucm) + ucm_add_ports(&data.ports, data.proplist, mapping, 0, card); + else if (u->mixer_path_set) pa_alsa_add_ports(&data.ports, u->mixer_path_set, card); u->source = pa_source_new(m->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY|(u->use_tsched ? PA_SOURCE_DYNAMIC_LATENCY : 0)); @@ -1869,7 +1896,10 @@ if (u->use_tsched) u->source->update_requested_latency = source_update_requested_latency_cb; u->source->set_state = source_set_state_cb; - u->source->set_port = source_set_port_cb; + if (mapping->ucm_context.ucm) + u->source->set_port = source_set_port_ucm_cb; + else + u->source->set_port = source_set_port_cb; u->source->userdata = u; pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); @@ -1913,7 +1943,11 @@ if (update_sw_params(u) < 0) goto fail; - if (setup_mixer(u, ignore_dB) < 0) + if (mapping->ucm_context.ucm) { + if (ucm_setup_mixer(u, ignore_dB) < 0) + goto fail; + } + else if (setup_mixer(u, ignore_dB) < 0) goto fail; pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle); Index: pulseaudio-1.1/src/modules/alsa/alsa-ucm.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ pulseaudio-1.1/src/modules/alsa/alsa-ucm.c 2012-02-17 13:20:30.016542590 +0800 @@ -0,0 +1,1129 @@ +/*** + This file is part of PulseAudio. + + Copyright 2011 Wolfson Microelectronics PLC + Author Margarita Olaya + + 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 +#endif + +#include +#include +#include + +#ifdef HAVE_VALGRIND_MEMCHECK_H +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alsa-mixer.h" +#include "alsa-util.h" +#include "alsa-ucm.h" + +#define PA_UCM_PLAYBACK_PRIORITY_UNSET(device) ((device)->playback_channels && !(device)->playback_priority) +#define PA_UCM_CAPTURE_PRIORITY_UNSET(device) ((device)->capture_channels && !(device)->capture_priority) +#define PA_UCM_DEVICE_PRIORITY_SET(device, priority) \ + do { \ + if (PA_UCM_PLAYBACK_PRIORITY_UNSET(device)) (device)->playback_priority = (priority); \ + if (PA_UCM_CAPTURE_PRIORITY_UNSET(device)) (device)->capture_priority = (priority); \ + } while(0) + +struct ucm_items { + const char *id; + const char *property; +}; + +struct ucm_info { + const char *id; + unsigned priority; +}; + +static struct ucm_items item[] = { + {"PlaybackPCM", PA_PROP_UCM_SINK}, + {"CapturePCM", PA_PROP_UCM_SOURCE}, + {"PlaybackVolume", PA_PROP_UCM_PLAYBACK_VOLUME}, + {"PlaybackSwitch", PA_PROP_UCM_PLAYBACK_SWITCH}, + {"PlaybackPriority", PA_PROP_UCM_PLAYBACK_PRIORITY}, + {"PlaybackChannels", PA_PROP_UCM_PLAYBACK_CHANNELS}, + {"CaptureVolume", PA_PROP_UCM_CAPTURE_VOLUME}, + {"CaptureSwitch", PA_PROP_UCM_CAPTURE_SWITCH}, + {"CapturePriority", PA_PROP_UCM_CAPTURE_PRIORITY}, + {"CaptureChannels", PA_PROP_UCM_CAPTURE_CHANNELS}, + {"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 - should be overwritten by ucm property */ +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: %s", item[i].id, verb_name, value); + pa_proplist_sets(verb->proplist, item[i].property, value); + free((void*)value); + } while (item[++i].id); + + return 0; +}; + +static char **dup_strv(const char **src, int n) { + char **dest = pa_xnew0(char *, n+1); + int i; + for (i=0; iproplist, PA_PROP_UCM_NAME); + + for (i=0; iproplist, item[i].property, value); + free((void*)value); + } while (item[++i].id); + + /* get direction and channels */ + value = pa_proplist_gets(device->proplist, PA_PROP_UCM_PLAYBACK_CHANNELS); + if (value) { /* output */ + /* get channels */ + if (pa_atou(value, &ui) == 0 && ui < PA_CHANNELS_MAX) + device->playback_channels = ui; + else + pa_log("UCM playback channels %s for device %s out of range", value, device_name); + + /* get pcm */ + value = pa_proplist_gets(device->proplist, PA_PROP_UCM_SINK); + if (!value) { /* take pcm from verb playback default */ + value = pa_proplist_gets(verb->proplist, PA_PROP_UCM_SINK); + if (value) { + pa_log_info("UCM playback device %s fetch pcm from verb default %s", device_name, value); + pa_proplist_sets(device->proplist, PA_PROP_UCM_SINK, value); + } + else { + pa_log("UCM playback device %s fetch pcm failed", device_name); + } + } + } + value = pa_proplist_gets(device->proplist, PA_PROP_UCM_CAPTURE_CHANNELS); + if (value) { /* input */ + /* get channels */ + if (pa_atou(value, &ui) == 0 && ui < PA_CHANNELS_MAX) + device->capture_channels = ui; + else + pa_log("UCM capture channels %s for device %s out of range", value, device_name); + + /* get pcm */ + value = pa_proplist_gets(device->proplist, PA_PROP_UCM_SOURCE); + if (!value) { /* take pcm from verb capture default */ + value = pa_proplist_gets(verb->proplist, PA_PROP_UCM_SOURCE); + if (value) { + pa_log_info("UCM capture device %s fetch pcm from verb default %s", device_name, value); + pa_proplist_sets(device->proplist, PA_PROP_UCM_SOURCE, value); + } + else { + pa_log("UCM capture device %s fetch pcm failed", device_name); + } + } + } + pa_assert(device->playback_channels || device->capture_channels); + + /* get priority of device */ + if (device->playback_channels) { /* sink device */ + value = pa_proplist_gets(device->proplist, PA_PROP_UCM_PLAYBACK_PRIORITY); + if (value) { + /* get priority from ucm config */ + if (pa_atou(value, &ui) == 0) + device->playback_priority = ui; + else + pa_log("UCM playback priority %s for device %s error", value, device_name); + } + } + if (device->capture_channels) { /* source device */ + value = pa_proplist_gets(device->proplist, PA_PROP_UCM_CAPTURE_PRIORITY); + if (value) { + /* get priority from ucm config */ + if (pa_atou(value, &ui) == 0) + device->capture_priority = ui; + else + pa_log("UCM capture priority %s for device %s error", value, device_name); + } + } + if (PA_UCM_PLAYBACK_PRIORITY_UNSET(device) || PA_UCM_CAPTURE_PRIORITY_UNSET(device)) { + /* get priority from static table */ + i = 0; + do { + if (strcasecmp(dev_info[i].id, device_name) == 0) { + PA_UCM_DEVICE_PRIORITY_SET(device, dev_info[i].priority); + break; + } + } while (dev_info[++i].id); + } + if (PA_UCM_PLAYBACK_PRIORITY_UNSET(device)) { + /* fall through to default priority */ + device->playback_priority = 100; + } + if (PA_UCM_CAPTURE_PRIORITY_UNSET(device)) { + /* fall through to default priority */ + device->capture_priority = 100; + } + + id = pa_sprintf_malloc("%s/%s", "_conflictingdevs", device_name); + device->n_confdev = snd_use_case_get_list(uc_mgr, id, &devices); + pa_xfree(id); + if (device->n_confdev <= 0) + pa_log_info("No %s for device %s", "_conflictingdevs", device_name); + else { + device->conflicting_devices = dup_strv(devices, device->n_confdev); + snd_use_case_free_list(devices, device->n_confdev); + } + + id = pa_sprintf_malloc("%s/%s", "_supporteddevs", device_name); + device->n_suppdev = snd_use_case_get_list(uc_mgr, id, &devices); + pa_xfree(id); + if (device->n_suppdev <= 0) + pa_log_info("No %s for device %s", "_supporteddevs", device_name); + else { + device->supported_devices = dup_strv(devices, device->n_suppdev); + snd_use_case_free_list(devices, device->n_suppdev); + } + + 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: %s", item[i].id, modifier_name, value); + 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, pa_strnull(dev_list[i])); + pa_proplist_sets(d->proplist, PA_PROP_UCM_DESCRIPTION, pa_strna(dev_list[i+1])); + 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, pa_strnull(mod_list[i])); + pa_proplist_sets(m->proplist, PA_PROP_UCM_DESCRIPTION, pa_strna(mod_list[i+1])); + 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(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 *dev_name, + 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); + } + pa_log_info("Add role %s to device %s(%s), result %s", role, + dev_name, role_name, pa_proplist_gets(dev->proplist, role_name)); +} + +static void add_media_role (const char *name, pa_alsa_ucm_device *list, + const char *role_name, const char *role, int 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, dev_name, 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; + int is_sink=0; + const char *sub = NULL; + + if ((sub = strcasestr(mod_name, "Play")) != NULL) { + is_sink = 1; + sub += 4; + } + else if ((sub = strcasestr(mod_name, "Capture")) != NULL) { + sub += 7; + } + + if (!sub || !*sub || !*(sub+1)) { + pa_log_warn("Can't match media roles for modifer %s", mod_name); + return; + } + + modifier->action_direct = is_sink ? + PA_ALSA_UCM_DIRECT_SINK : PA_ALSA_UCM_DIRECT_SOURCE; + modifier->media_role = pa_xstrdup(sub); + + for (i=0; in_suppdev; i++) + { + add_media_role(modifier->supported_devices[i], + list, PA_PROP_DEVICE_INTENDED_ROLES, sub, is_sink); + } +/* + for (i=0; in_confdev; i++) + { + add_media_role(modifier->conflicting_devices[i], + list, PA_PROP_DEVICE_DENYED_ROLES, sub, is_sink); + } +*/ +} + +static void append_me_to_device(pa_alsa_ucm_device *devices, + const char *dev_name, pa_alsa_ucm_device *me, const char *my_name, int is_conflicting) { + pa_alsa_ucm_device *d; + char ***pdevices; + int *pnum; + PA_LLIST_FOREACH(d, devices) { + const char *name = pa_proplist_gets(d->proplist, PA_PROP_UCM_NAME); + if (!strcmp(name, dev_name)) { + pdevices = is_conflicting ? &d->conflicting_devices : &d->supported_devices; + pnum = is_conflicting ? &d->n_confdev : &d->n_suppdev; + if (!ucm_device_in(*pdevices, *pnum, me)) { + /* append my name */ + *pdevices = pa_xrealloc(*pdevices, sizeof(char *) * (*pnum+2)); + (*pdevices)[*pnum] = pa_xstrdup(my_name); + (*pdevices)[*pnum+1] = NULL; + (*pnum)++; + pa_log_info("== Device %s complemented to %s's %s list", + my_name, name, is_conflicting ? "conflicting" : "supported"); + } + break; + } + } +} + +static void append_lost_relationship(pa_alsa_ucm_device *devices, + pa_alsa_ucm_device *dev, const char *dev_name) { + int i; + for (i=0; in_confdev; i++) { + append_me_to_device(devices, dev->conflicting_devices[i], dev, dev_name, 1); + } + for (i=0; in_suppdev; i++) { + append_me_to_device(devices, dev->supported_devices[i], dev, dev_name, 0); + } +} + +int ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, + const char *verb_desc, struct pa_alsa_ucm_verb **p_verb) { + struct pa_alsa_ucm_device *d; + struct pa_alsa_ucm_modifier *mod; + struct pa_alsa_ucm_verb *verb; + int err=0; + + *p_verb = NULL; + pa_log_info("ucm_get_verb: Set ucm verb to %s", verb_name); + 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, pa_strnull(verb_name)); + pa_proplist_sets(verb->proplist, PA_PROP_UCM_DESCRIPTION, pa_strna(verb_desc)); + 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, verb, dev_name); + } + /* make conflicting or supported device mutual */ + PA_LLIST_FOREACH(d, verb->devices) { + const char *dev_name = pa_proplist_gets(d->proplist, PA_PROP_UCM_NAME); + append_lost_relationship(verb->devices, d, 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 */ + pa_log_info("Set media roles for verb %s, modifier %s", verb_name, mod_name); + 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 is_sink, int *dev_indices, int num, pa_hashmap *ports, pa_card_profile *cp) { + pa_device_port *port; + int i; + unsigned priority; + char *name, *desc; + const char *dev_name; + const char *direction; + pa_alsa_ucm_device *dev; + + dev = mapping->ucm_context.ucm_devices[dev_indices[0]]; + dev_name = pa_proplist_gets(dev->proplist, PA_PROP_UCM_NAME); + name = pa_xstrdup(dev_name); + desc = num == 1 ? pa_xstrdup(pa_proplist_gets(dev->proplist, PA_PROP_UCM_DESCRIPTION)) + : pa_sprintf_malloc("Combination port for %s", dev_name); + priority = is_sink ? dev->playback_priority : dev->capture_priority; + for (i=1; iucm_context.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 += (is_sink ? dev->playback_priority : dev->capture_priority); + } + + port = pa_hashmap_get(ports, name); + if (!port) { + port = pa_device_port_new(pa_strna(name), desc, sizeof(pa_alsa_port_data_ucm)); + pa_assert(port); + pa_hashmap_put(ports, port->name, port); + pa_log_debug("Add port %s: %s", port->name, port->description); + port->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + /* TODO: jack detection */ + } + port->priority = priority; + if (is_sink) + port->is_output = TRUE; + else + port->is_input = TRUE; + + pa_xfree(name); + pa_xfree(desc); + + direction = is_sink ? "output" : "input"; + pa_log_debug("Port %s direction %s, priority %d", port->name, direction, priority); + + if (cp) { + pa_log_debug("Adding port %s to profile %s", port->name, cp->name); + pa_hashmap_put(port->profiles, cp->name, cp); + } + if (hash) { + pa_hashmap_put(hash, port->name, port); + pa_device_port_ref(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; iucm_context.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_port_contain(const char *port_name, const char *dev_name) { + int ret=0; + char *r; + const char *state=NULL; + while ((r = pa_split(port_name, "+", &state))) { + if (!strcmp(r, dev_name)) { + pa_xfree(r); + ret = 1; + break; + } + pa_xfree(r); + } + return ret; +} + +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_context.ucm_devices[map_index]; + + pa_log_debug("Check device %s conformance with %d other devices", + pa_proplist_gets(dev->proplist, PA_PROP_UCM_NAME), dev_num); + if (dev_num == 0) { + pa_log_debug("First device in combination, number 1"); + return 1; + } + + if (dev->n_confdev > 0) + { /* the device defines conflicting devices */ + for (i=0; in_confdev; i++) + { + if (ucm_device_contain(mapping, dev_indices, + dev_num, dev->conflicting_devices[i])) { + pa_log_debug("Conflicting device found"); + return 0; + } + } + } + else if (dev->n_suppdev >= dev_num) + { /* the device defines supported devices */ + for (i=0; isupported_devices, + dev->n_suppdev, mapping->ucm_context.ucm_devices[dev_indices[i]])) { + pa_log_debug("Supported device not found"); + return 0; + } + } + } + else { /* not support any other devices */ + pa_log_debug("Not support any other devices"); + return 0; + } + + pa_log_debug("Device added to combination, number %d", dev_num+1); + return 1; +} + +void ucm_add_ports_combination(pa_hashmap *hash, + pa_alsa_mapping *mapping, int is_sink, int *dev_indices, int dev_num, + int map_index, pa_hashmap *ports, pa_card_profile *cp) { + + if (map_index >= mapping->ucm_context.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, is_sink, dev_indices, dev_num+1, ports, cp); + /* try more elements combination */ + ucm_add_ports_combination(hash, mapping, is_sink, dev_indices, dev_num+1, map_index+1, ports, cp); + } + /* try other device with current elements number */ + ucm_add_ports_combination(hash, mapping, is_sink, dev_indices, dev_num, map_index+1, ports, cp); +} + +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(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 is_sink, pa_card *card) { + int *dev_indices = pa_xnew(int, mapping->ucm_context.ucm_devices_num); + int i; + char *merged_roles; + + pa_assert(p); + pa_assert(!*p); + pa_assert(mapping->ucm_context.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, is_sink, dev_indices, 0, 0, card->ports, NULL); + 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; iucm_context.ucm_devices_num; i++) + { + const char *roles = pa_proplist_gets( + mapping->ucm_context.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_log_info("Alsa device %s roles: %s", mapping->device_strings[0], pa_strnull(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 */ + pa_log_info("Set ucm verb to %s", profile); + 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; +} + +int ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port, int is_sink) { + int i, ret=0; + pa_alsa_ucm_config *ucm; + char *enable_devs=NULL; + char *r; + const char *state=NULL; + + pa_assert(context && context->ucm); + + ucm = context->ucm; + pa_assert(ucm->ucm_mgr); + + /* first disable then enable */ + for (i=0; iucm_devices_num; i++) + { + const char *dev_name = pa_proplist_gets(context->ucm_devices[i]->proplist, PA_PROP_UCM_NAME); + if (ucm_port_contain(port->name, dev_name)) + { + char *tmp = enable_devs ? pa_sprintf_malloc("%s,%s", + enable_devs, dev_name) : pa_xstrdup(dev_name); + pa_xfree(enable_devs); + enable_devs = tmp; + } + else + { + pa_log_info("Disable ucm device %s", dev_name); + 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; + } + } + } + if (enable_devs) { + while ((r = pa_split(enable_devs, ",", &state))) { + pa_log_info("Enable ucm device %s", r); + if (snd_use_case_set(ucm->ucm_mgr, "_enadev", r) < 0) { + pa_log("failed to enable ucm device %s", r); + pa_xfree(r); + ret = -1; + break; + } + pa_xfree(r); + } + pa_xfree(enable_devs); + } + 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_context.ucm_devices_num & 7) == 0) + m->ucm_context.ucm_devices = pa_xrealloc(m->ucm_context.ucm_devices, + sizeof(pa_alsa_ucm_device *) * (m->ucm_context.ucm_devices_num + 8)); + m->ucm_context.ucm_devices[m->ucm_context.ucm_devices_num++] = device; + + new_desc = pa_proplist_gets(device->proplist, PA_PROP_UCM_DESCRIPTION); + 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; + unsigned priority, channels; + + 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); + + priority = is_sink ? device->playback_priority : device->capture_priority; + channels = is_sink ? device->playback_channels : device->capture_channels; + if (m->ucm_context.ucm_devices_num == 0) + { /* new mapping */ + m->supported = TRUE; + m->ucm_context.ucm = ucm; + + m->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; + + ucm_add_mapping(p, m); + pa_channel_map_init_extend(&m->channel_map, channels, PA_CHANNEL_MAP_ALSA); + } + + /* mapping priority is the highest one of ucm devices */ + if (priority > m->priority) { + m->priority = priority; + } + /* mapping channels is the lowest one of ucm devices */ + if (channels < m->channel_map.channels) { + pa_channel_map_init_extend(&m->channel_map, channels, PA_CHANNEL_MAP_ALSA); + } + 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=0; + + 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, const char *verb_desc) { + 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->description = pa_xstrdup(verb_desc); + + 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 { + char *verb_cmp = pa_xstrdup(verb_name); + char *c = verb_cmp; + while (*c) { + if (*c == '_') *c = ' '; + c++; + } + if (strcasecmp(verb_info[i].id, verb_cmp) == 0) + { + p->priority = verb_info[i].priority; + pa_xfree(verb_cmp); + break; + } + pa_xfree(verb_cmp); + } 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; + const char *verb_desc; + + verb_name = pa_proplist_gets(verb->proplist, PA_PROP_UCM_NAME); + verb_desc = pa_proplist_gets(verb->proplist, PA_PROP_UCM_DESCRIPTION); + if (verb_name == NULL) { + pa_log("verb with no name"); + continue; + } + + ucm_create_profile(ucm, ps, verb, verb_name, verb_desc); + } + + 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) + pa_xstrfreev(di->supported_devices); + if (di->n_confdev > 0) + pa_xstrfreev(di->conflicting_devices); + 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, int 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); + pa_log_info("Enable ucm modifiers %s", mod_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, int 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); + pa_log_info("Disable ucm modifiers %s", mod_name); + if (snd_use_case_set(ucm->ucm_mgr, "_dismod", mod_name) < 0) { + pa_log("failed to disable ucm modifier %s", mod_name); + } + break; + } + } +} Index: pulseaudio-1.1/src/modules/alsa/alsa-ucm.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ pulseaudio-1.1/src/modules/alsa/alsa-ucm.h 2012-02-17 13:19:57.948542572 +0800 @@ -0,0 +1,106 @@ +#ifndef foopulseucmhfoo +#define foopulseucmhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2011 Wolfson Microelectronics PLC + Author Margarita Olaya + + 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 +#include + +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_ucm_mapping_context pa_alsa_ucm_mapping_context; +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, const char *verb_desc, struct pa_alsa_ucm_verb ** p_verb); +void ucm_add_ports(pa_hashmap **p, pa_proplist *proplist, pa_alsa_mapping *mapping, int is_sink, pa_card *card); +void ucm_add_ports_combination(pa_hashmap *hash, pa_alsa_mapping *mapping, int is_sink, int *dev_indices, + int dev_num, int map_index, pa_hashmap *ports, pa_card_profile *cp); +int ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port, int is_sink); +void ucm_new_stream_role(pa_alsa_ucm_config *ucm, const char *role, int is_sink); +void ucm_del_stream_role(pa_alsa_ucm_config *ucm, const char *role, int 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 playback_priority; + unsigned capture_priority; + unsigned playback_channels; + unsigned capture_channels; + int n_confdev; + int n_suppdev; + char **conflicting_devices; + 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_ucm_mapping_context { + pa_alsa_ucm_config *ucm; + int ucm_devices_num; + pa_alsa_ucm_device **ucm_devices; +}; + +struct pa_alsa_port_data_ucm { + int dummy; +}; + +#endif Index: pulseaudio-1.1/src/modules/alsa/module-alsa-card.c =================================================================== --- pulseaudio-1.1.orig/src/modules/alsa/module-alsa-card.c 2012-02-17 13:19:52.616542570 +0800 +++ pulseaudio-1.1/src/modules/alsa/module-alsa-card.c 2012-02-17 13:19:57.960542572 +0800 @@ -39,6 +39,7 @@ #include "alsa-util.h" #include "alsa-sink.h" #include "alsa-source.h" +#include "alsa-ucm.h" #include "module-alsa-card-symdef.h" #include "alsa-jack-inputdev.h" @@ -67,7 +68,8 @@ "profile= " "ignore_dB= " "deferred_volume= " - "profile_set= "); + "profile_set= " + "use_ucm= "); static const char* const valid_modargs[] = { "name", @@ -91,11 +93,20 @@ "ignore_dB", "deferred_volume", "profile_set", + "use_ucm", NULL }; #define DEFAULT_DEVICE_ID "0" +typedef struct pa_media_role_count pa_media_role_count; + +struct pa_media_role_count { + PA_LLIST_FIELDS(pa_media_role_count); + char *role; + int num; +}; + struct userdata { pa_core *core; pa_module *module; @@ -107,6 +118,21 @@ pa_modargs *modargs; pa_alsa_profile_set *profile_set; + + /* ucm stuffs */ + pa_bool_t use_ucm; + pa_alsa_ucm_config ucm; + + /* hooks for modifier */ + pa_hook_slot + *sink_input_put_hook_slot, + *sink_input_unlink_hook_slot, + *source_output_put_hook_slot, + *source_output_unlink_hook_slot; + + /* hashmap records numbers of certain media role */ + PA_LLIST_HEAD(pa_media_role_count, sink_role_counts); + PA_LLIST_HEAD(pa_media_role_count, source_role_counts); }; struct profile_data { @@ -116,6 +142,7 @@ static void add_profiles(struct userdata *u, pa_hashmap *h, pa_hashmap *ports) { pa_alsa_profile *ap; void *state; + int *dev_indices; pa_assert(u); pa_assert(h); @@ -133,7 +160,13 @@ cp->n_sinks = pa_idxset_size(ap->output_mappings); PA_IDXSET_FOREACH(m, ap->output_mappings, idx) { - pa_alsa_path_set_add_ports(m->output_path_set, cp, ports, NULL); + if (u->use_ucm) { + dev_indices = pa_xnew(int, m->ucm_context.ucm_devices_num); + ucm_add_ports_combination(NULL, m, 1, dev_indices, 0, 0, ports, cp); + pa_xfree(dev_indices); + } + else + pa_alsa_path_set_add_ports(m->output_path_set, cp, ports, NULL); if (m->channel_map.channels > cp->max_sink_channels) cp->max_sink_channels = m->channel_map.channels; } @@ -143,7 +176,13 @@ cp->n_sources = pa_idxset_size(ap->input_mappings); PA_IDXSET_FOREACH(m, ap->input_mappings, idx) { - pa_alsa_path_set_add_ports(m->input_path_set, cp, ports, NULL); + if (u->use_ucm) { + dev_indices = pa_xnew(int, m->ucm_context.ucm_devices_num); + ucm_add_ports_combination(NULL, m, 0, dev_indices, 0, 0, ports, cp); + pa_xfree(dev_indices); + } + else + pa_alsa_path_set_add_ports(m->input_path_set, cp, ports, NULL); if (m->channel_map.channels > cp->max_source_channels) cp->max_source_channels = m->channel_map.channels; } @@ -212,6 +251,24 @@ am->source = NULL; } + /* if UCM is available for this card then update the verb */ + if (u->use_ucm) { + pa_media_role_count *item; + if (ucm_set_profile(&u->ucm, nd->profile ? nd->profile->name : NULL, + od->profile ? od->profile->name : NULL) < 0) + return -1; + /* + * enable modifier matching the role in new profile, + * modifier in old profile was automaticly disabled + */ + PA_LLIST_FOREACH(item, u->sink_role_counts) { + ucm_new_stream_role(&u->ucm, item->role, 1); + } + PA_LLIST_FOREACH(item, u->source_role_counts) { + ucm_new_stream_role(&u->ucm, item->role, 0); + } + } + if (nd->profile && nd->profile->output_mappings) PA_IDXSET_FOREACH(am, nd->profile->output_mappings, idx) { @@ -236,6 +293,11 @@ } } + /* TODO: route policy + * re-route the sink-inputs/source-outputs to new sinks/sources, + * take modifier's device into consideration + */ + if (sink_inputs) pa_sink_move_all_fail(sink_inputs); @@ -249,11 +311,23 @@ uint32_t idx; pa_alsa_mapping *am; struct profile_data *d; + struct pa_alsa_ucm_config *ucm = &u->ucm; pa_assert(u); d = PA_CARD_PROFILE_DATA(u->card->active_profile); + if (!d || !d->profile) + return; + + if (u->use_ucm) { + /* Set initial verb */ + if (ucm_set_profile(ucm, d->profile->name, NULL) < 0) { + pa_log("failed to set ucm profile %s", d->profile->name); + return; + } + } + if (d->profile && d->profile->output_mappings) PA_IDXSET_FOREACH(am, d->profile->output_mappings, idx) am->sink = pa_alsa_sink_new(u->module, u->modargs, __FILE__, u->card, am); @@ -289,6 +363,197 @@ pa_xfree(t); } +static int card_query_ucm_profiles(struct userdata *u, int card_index) +{ + char *card_name; + const char **verb_list; + int num_verbs, i, err=0; + + /* is UCM available for this card ? */ + if(snd_card_get_name(card_index, &card_name) < 0) + { + pa_log("Card can't get card_name from card_index %d", card_index); + err = -1; + goto name_fail; + } + err = snd_use_case_mgr_open(&u->ucm.ucm_mgr, card_name); + if (err < 0) { + pa_log("UCM not available for card %s", card_name); + err = -1; + goto ucm_mgr_fail; + } + + pa_log("UCM available for card %s", card_name); + + /* get a list of all UCM verbs (profiles) for this card */ + num_verbs = snd_use_case_verb_list(u->ucm.ucm_mgr, &verb_list); + if (num_verbs <= 0) { + pa_log("UCM verb list not found for %s", card_name); + err = -1; + goto ucm_verb_fail; + } + + /* get the properties of each UCM verb */ + for (i = 0; i < num_verbs; i += 2) { + struct pa_alsa_ucm_verb *verb; + + /* Get devices and modifiers for each verb */ + err = ucm_get_verb(u->ucm.ucm_mgr, verb_list[i], verb_list[i+1], &verb); + if (err < 0) { + pa_log("Failed to set the verb %s", verb_list[i]); + continue; + } + PA_LLIST_PREPEND(pa_alsa_ucm_verb, u->ucm.verbs, verb); + } + + if(u->ucm.verbs) + { + /* create the profile set for the UCM card */ + u->profile_set = add_ucm_profile_set(&u->ucm, &u->core->default_channel_map); + pa_alsa_profile_set_dump(u->profile_set); + err = 0; + } + else + { + pa_log("No UCM verb is valid for %s", card_name); + err = -1; + } + snd_use_case_free_list(verb_list, num_verbs); +ucm_verb_fail: + if(err < 0) + { + snd_use_case_mgr_close(u->ucm.ucm_mgr); + u->ucm.ucm_mgr = NULL; + } +ucm_mgr_fail: + free(card_name); +name_fail: + return err; +} + +static int add_role_number (pa_media_role_count **head, const char *role) { + + pa_media_role_count *item; + + PA_LLIST_FOREACH(item, *head) { + if (!strcasecmp(role, item->role)) { + item->num++; + return item->num; + } + } + + /* not found */ + item = pa_xnew0(pa_media_role_count, 1); + item->role = pa_xstrdup(role); + item->num = 1; + + if ((item->next = *head)) + item->next->prev = item; + *head = item; + + return item->num; +} + +static int minus_role_number (pa_media_role_count **head, const char *role) { + + pa_media_role_count *item; + int num; + + PA_LLIST_FOREACH(item, *head) { + if (!strcasecmp(role, item->role)) { + item->num--; + break; + } + } + + pa_assert (item); + + num = item->num; + if (num == 0) { /* last one */ + if (item->next) + item->next->prev = item->prev; + if (item->prev) + item->prev->next = item->next; + else { + *head = item->next; + } + pa_xfree(item->role); + pa_xfree(item); + } + + return num; +} + +static pa_hook_result_t sink_input_put_hook_callback( + pa_core *c, pa_sink_input *sink_input, struct userdata *u) { + + const char *role = pa_proplist_gets(sink_input->proplist, PA_PROP_MEDIA_ROLE); + + /* FIXME: check if sink_input link to our card? */ + if (role) + { + int num = add_role_number(&u->sink_role_counts, role); + if (num == 1) { /* first stream of certain role */ + /* enable modifier matching the role */ + ucm_new_stream_role(&u->ucm, role, 1); + } + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_unlink_hook_callback(pa_core *c, pa_sink_input *sink_input, struct userdata *u) { + + const char *role = pa_proplist_gets(sink_input->proplist, PA_PROP_MEDIA_ROLE); + + /* FIXME: check if sink_input link to our card? */ + if (role) + { + int num = minus_role_number(&u->sink_role_counts, role); + if (num == 0) { /* last stream of certain role */ + /* enable modifier matching the role */ + ucm_del_stream_role(&u->ucm, role, TRUE); + } + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_put_hook_callback( + pa_core *c, pa_source_output *source_output, struct userdata *u) { + + const char *role = pa_proplist_gets(source_output->proplist, PA_PROP_MEDIA_ROLE); + + /* FIXME: check if source_output link to our card? */ + if (role) + { + int num = add_role_number(&u->source_role_counts, role); + if (num == 1) { /* first stream of certain role */ + /* enable modifier matching the role */ + ucm_new_stream_role(&u->ucm, role, 0); + } + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_unlink_hook_callback(pa_core *c, pa_source_output *source_output, struct userdata *u) { + + const char *role = pa_proplist_gets(source_output->proplist, PA_PROP_MEDIA_ROLE); + + /* FIXME: check if source_output link to our card? */ + if (role) + { + int num = minus_role_number(&u->source_role_counts, role); + if (num == 0) { /* last stream of certain role */ + /* enable modifier matching the role */ + ucm_del_stream_role(&u->ucm, role, FALSE); + } + } + + return PA_HOOK_OK; +} + int pa__init(pa_module *m) { pa_card_new_data data; pa_modargs *ma; @@ -337,18 +602,39 @@ } } + pa_modargs_get_value_boolean(ma, "use_ucm", &u->use_ucm); + if (u->use_ucm && !card_query_ucm_profiles(u, alsa_card_index)) { + pa_log_info("Found UCM profiles"); + /* hook sink input/source output to enable/disable modifiers */ + u->sink_input_put_hook_slot = pa_hook_connect( + &m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_EARLY, + (pa_hook_cb_t) sink_input_put_hook_callback, u); + u->sink_input_unlink_hook_slot = pa_hook_connect( + &m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], + PA_HOOK_LATE, (pa_hook_cb_t) sink_input_unlink_hook_callback, u); + u->source_output_put_hook_slot = pa_hook_connect( + &m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], PA_HOOK_EARLY, + (pa_hook_cb_t) source_output_put_hook_callback, u); + u->source_output_unlink_hook_slot = pa_hook_connect( + &m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], + PA_HOOK_LATE, (pa_hook_cb_t) source_output_unlink_hook_callback, u); + } + else { + u->use_ucm = FALSE; + #ifdef HAVE_UDEV - fn = pa_udev_get_property(alsa_card_index, "PULSE_PROFILE_SET"); + fn = pa_udev_get_property(alsa_card_index, "PULSE_PROFILE_SET"); #endif - if (pa_modargs_get_value(ma, "profile_set", NULL)) { + if (pa_modargs_get_value(ma, "profile_set", NULL)) { + pa_xfree(fn); + fn = pa_xstrdup(pa_modargs_get_value(ma, "profile_set", NULL)); + } + + u->profile_set = pa_alsa_profile_set_new(fn, &u->core->default_channel_map); pa_xfree(fn); - fn = pa_xstrdup(pa_modargs_get_value(ma, "profile_set", NULL)); } - u->profile_set = pa_alsa_profile_set_new(fn, &u->core->default_channel_map); - pa_xfree(fn); - if (!u->profile_set) goto fail; @@ -456,12 +742,37 @@ void pa__done(pa_module*m) { struct userdata *u; + pa_media_role_count *item; pa_assert(m); if (!(u = m->userdata)) goto finish; + if (u->sink_input_put_hook_slot) + pa_hook_slot_free(u->sink_input_put_hook_slot); + + if (u->sink_input_unlink_hook_slot) + pa_hook_slot_free(u->sink_input_unlink_hook_slot); + + if (u->source_output_put_hook_slot) + pa_hook_slot_free(u->source_output_put_hook_slot); + + if (u->source_output_unlink_hook_slot) + pa_hook_slot_free(u->source_output_unlink_hook_slot); + + while ((item = u->sink_role_counts)) { + PA_LLIST_REMOVE(pa_media_role_count, u->sink_role_counts, item); + pa_xfree(item->role); + pa_xfree(item); + } + + while ((item = u->source_role_counts)) { + PA_LLIST_REMOVE(pa_media_role_count, u->source_role_counts, item); + pa_xfree(item->role); + pa_xfree(item); + } + if (u->card && u->card->sinks) { pa_sink *s; @@ -481,6 +792,8 @@ u->profile_set->jack_inputdevs = NULL; } + free_ucm(&u->ucm); + if (u->card) pa_card_free(u->card); Index: pulseaudio-1.1/src/modules/module-udev-detect.c =================================================================== --- pulseaudio-1.1.orig/src/modules/module-udev-detect.c 2012-02-17 13:11:50.688542312 +0800 +++ pulseaudio-1.1/src/modules/module-udev-detect.c 2012-02-17 13:19:52.660542570 +0800 @@ -390,6 +390,7 @@ "tsched=%s " "ignore_dB=%s " "deferred_volume=%s " + "use_ucm=1 " "card_properties=\"module-udev-detect.discovered=1\"", path_get_card_id(path), n, Index: pulseaudio-1.1/src/pulse/proplist.h =================================================================== --- pulseaudio-1.1.orig/src/pulse/proplist.h 2012-02-17 13:11:50.780542312 +0800 +++ pulseaudio-1.1/src/pulse/proplist.h 2012-02-17 13:19:52.660542570 +0800 @@ -254,6 +254,45 @@ /** 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: Playback priority */ +#define PA_PROP_UCM_PLAYBACK_PRIORITY "ucm.playback.priority" + +/** For devices: Playback channels */ +#define PA_PROP_UCM_PLAYBACK_CHANNELS "ucm.playback.channels" + +/** 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: Capture priority */ +#define PA_PROP_UCM_CAPTURE_PRIORITY "ucm.capture.priority" + +/** For devices: Capture channels */ +#define PA_PROP_UCM_CAPTURE_CHANNELS "ucm.capture.channels" + +/** 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"