2 This file is part of PulseAudio.
4 Copyright 2011 Wolfson Microelectronics PLC
5 Author Margarita Olaya <magi@slimlogic.co.uk>
6 Copyright 2012 Feng Wei <wei.feng@freescale.com>, Freescale Ltd.
8 PulseAudio is free software; you can redistribute it and/or modify
9 it under the terms of the GNU Lesser General Public License as published
10 by the Free Software Foundation; either version 2.1 of the License,
11 or (at your option) any later version.
13 PulseAudio is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with PulseAudio; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
30 #include <sys/types.h>
32 #include <asoundlib.h>
34 #ifdef HAVE_VALGRIND_MEMCHECK_H
35 #include <valgrind/memcheck.h>
38 #include <pulse/sample.h>
39 #include <pulse/xmalloc.h>
40 #include <pulse/timeval.h>
41 #include <pulse/util.h>
43 #include <pulsecore/log.h>
44 #include <pulsecore/macro.h>
45 #include <pulsecore/core-util.h>
46 #include <pulsecore/atomic.h>
47 #include <pulsecore/core-error.h>
48 #include <pulsecore/once.h>
49 #include <pulsecore/thread.h>
50 #include <pulsecore/conf-parser.h>
51 #include <pulsecore/strbuf.h>
53 #include "alsa-mixer.h"
54 #include "alsa-util.h"
57 #define PA_UCM_PRE_TAG_OUTPUT "[Out] "
58 #define PA_UCM_PRE_TAG_INPUT "[In] "
60 #define PA_UCM_PLAYBACK_PRIORITY_UNSET(device) ((device)->playback_channels && !(device)->playback_priority)
61 #define PA_UCM_CAPTURE_PRIORITY_UNSET(device) ((device)->capture_channels && !(device)->capture_priority)
62 #define PA_UCM_DEVICE_PRIORITY_SET(device, priority) \
64 if (PA_UCM_PLAYBACK_PRIORITY_UNSET(device)) (device)->playback_priority = (priority); \
65 if (PA_UCM_CAPTURE_PRIORITY_UNSET(device)) (device)->capture_priority = (priority); \
67 #define PA_UCM_IS_MODIFIER_MAPPING(m) ((pa_proplist_gets((m)->proplist, PA_ALSA_PROP_UCM_MODIFIER)) != NULL)
81 static struct ucm_items item[] = {
82 {"PlaybackPCM", PA_ALSA_PROP_UCM_SINK},
83 {"CapturePCM", PA_ALSA_PROP_UCM_SOURCE},
84 {"PlaybackVolume", PA_ALSA_PROP_UCM_PLAYBACK_VOLUME},
85 {"PlaybackSwitch", PA_ALSA_PROP_UCM_PLAYBACK_SWITCH},
86 {"PlaybackPriority", PA_ALSA_PROP_UCM_PLAYBACK_PRIORITY},
87 {"PlaybackRate", PA_ALSA_PROP_UCM_PLAYBACK_RATE},
88 {"PlaybackChannels", PA_ALSA_PROP_UCM_PLAYBACK_CHANNELS},
89 {"CaptureVolume", PA_ALSA_PROP_UCM_CAPTURE_VOLUME},
90 {"CaptureSwitch", PA_ALSA_PROP_UCM_CAPTURE_SWITCH},
91 {"CapturePriority", PA_ALSA_PROP_UCM_CAPTURE_PRIORITY},
92 {"CaptureRate", PA_ALSA_PROP_UCM_CAPTURE_RATE},
93 {"CaptureChannels", PA_ALSA_PROP_UCM_CAPTURE_CHANNELS},
94 {"TQ", PA_ALSA_PROP_UCM_QOS},
98 /* UCM verb info - this should eventually be part of policy manangement */
99 static struct ucm_info verb_info[] = {
100 {SND_USE_CASE_VERB_INACTIVE, 0},
101 {SND_USE_CASE_VERB_HIFI, 8000},
102 {SND_USE_CASE_VERB_HIFI_LOW_POWER, 7000},
103 {SND_USE_CASE_VERB_VOICE, 6000},
104 {SND_USE_CASE_VERB_VOICE_LOW_POWER, 5000},
105 {SND_USE_CASE_VERB_VOICECALL, 4000},
106 {SND_USE_CASE_VERB_IP_VOICECALL, 4000},
107 {SND_USE_CASE_VERB_ANALOG_RADIO, 3000},
108 {SND_USE_CASE_VERB_DIGITAL_RADIO, 3000},
112 /* UCM device info - should be overwritten by ucm property */
113 static struct ucm_info dev_info[] = {
114 {SND_USE_CASE_DEV_SPEAKER, 100},
115 {SND_USE_CASE_DEV_LINE, 100},
116 {SND_USE_CASE_DEV_HEADPHONES, 100},
117 {SND_USE_CASE_DEV_HEADSET, 300},
118 {SND_USE_CASE_DEV_HANDSET, 200},
119 {SND_USE_CASE_DEV_BLUETOOTH, 400},
120 {SND_USE_CASE_DEV_EARPIECE, 100},
121 {SND_USE_CASE_DEV_SPDIF, 100},
122 {SND_USE_CASE_DEV_HDMI, 100},
123 {SND_USE_CASE_DEV_NONE, 100},
127 /* UCM profile properties - The verb data is store so it can be used to fill
128 * the new profiles properties */
129 static int ucm_get_property(pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr, const char *verb_name) {
134 for (i = 0; item[i].id; i++) {
137 id = pa_sprintf_malloc("=%s//%s", item[i].id, verb_name);
138 err = snd_use_case_get(uc_mgr, id, &value);
143 pa_log_debug("Got %s for verb %s: %s", item[i].id, verb_name, value);
144 pa_proplist_sets(verb->proplist, item[i].property, value);
151 static int ucm_device_exists(pa_idxset *idxset, pa_alsa_ucm_device *dev) {
152 pa_alsa_ucm_device *d;
155 PA_IDXSET_FOREACH(d, idxset, idx)
162 static void ucm_add_devices_to_idxset(
164 pa_alsa_ucm_device *me,
165 pa_alsa_ucm_device *devices,
166 const char **dev_names,
169 pa_alsa_ucm_device *d;
171 PA_LLIST_FOREACH(d, devices) {
178 name = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME);
180 for (i = 0; i < n; i++)
181 if (pa_streq(dev_names[i], name))
182 pa_idxset_put(idxset, d, NULL);
186 /* Create a property list for this ucm device */
187 static int ucm_get_device_property(
188 pa_alsa_ucm_device *device,
189 snd_use_case_mgr_t *uc_mgr,
190 pa_alsa_ucm_verb *verb,
191 const char *device_name) {
194 const char **devices;
199 int n_confdev, n_suppdev;
201 for (i = 0; item[i].id; i++) {
202 id = pa_sprintf_malloc("=%s/%s", item[i].id, device_name);
203 err = snd_use_case_get(uc_mgr, id, &value);
208 pa_log_debug("Got %s for device %s: %s", item[i].id, device_name, value);
209 pa_proplist_sets(device->proplist, item[i].property, value);
213 /* get direction and channels */
214 value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_PLAYBACK_CHANNELS);
215 if (value) { /* output */
217 if (pa_atou(value, &ui) == 0 && pa_channels_valid(ui))
218 device->playback_channels = ui;
220 pa_log("UCM playback channels %s for device %s out of range", value, device_name);
223 value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SINK);
224 if (!value) { /* take pcm from verb playback default */
225 value = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_SINK);
227 pa_log_debug("UCM playback device %s fetch pcm from verb default %s", device_name, value);
228 pa_proplist_sets(device->proplist, PA_ALSA_PROP_UCM_SINK, value);
230 pa_log("UCM playback device %s fetch pcm failed", device_name);
234 value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_CAPTURE_CHANNELS);
235 if (value) { /* input */
237 if (pa_atou(value, &ui) == 0 && pa_channels_valid(ui))
238 device->capture_channels = ui;
240 pa_log("UCM capture channels %s for device %s out of range", value, device_name);
243 value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SOURCE);
244 if (!value) { /* take pcm from verb capture default */
245 value = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_SOURCE);
247 pa_log_debug("UCM capture device %s fetch pcm from verb default %s", device_name, value);
248 pa_proplist_sets(device->proplist, PA_ALSA_PROP_UCM_SOURCE, value);
250 pa_log("UCM capture device %s fetch pcm failed", device_name);
254 if (device->playback_channels == 0 && device->capture_channels == 0) {
255 pa_log_warn("UCM file does not specify 'PlaybackChannels' or 'CaptureChannels'"
256 "for device %s, assuming stereo duplex.", device_name);
257 device->playback_channels = 2;
258 device->capture_channels = 2;
261 /* get rate and priority of device */
262 if (device->playback_channels) { /* sink device */
264 if ((value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_PLAYBACK_RATE)) ||
265 (value = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_PLAYBACK_RATE))) {
266 if (pa_atou(value, &ui) == 0 && pa_sample_rate_valid(ui)) {
267 pa_log_debug("UCM playback device %s rate %d", device_name, ui);
268 device->playback_rate = ui;
270 pa_log_debug("UCM playback device %s has bad rate %s", device_name, value);
273 value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_PLAYBACK_PRIORITY);
275 /* get priority from ucm config */
276 if (pa_atou(value, &ui) == 0)
277 device->playback_priority = ui;
279 pa_log_debug("UCM playback priority %s for device %s error", value, device_name);
283 if (device->capture_channels) { /* source device */
285 if ((value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_CAPTURE_RATE)) ||
286 (value = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_CAPTURE_RATE))) {
287 if (pa_atou(value, &ui) == 0 && pa_sample_rate_valid(ui)) {
288 pa_log_debug("UCM capture device %s rate %d", device_name, ui);
289 device->capture_rate = ui;
291 pa_log_debug("UCM capture device %s has bad rate %s", device_name, value);
294 value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_CAPTURE_PRIORITY);
296 /* get priority from ucm config */
297 if (pa_atou(value, &ui) == 0)
298 device->capture_priority = ui;
300 pa_log_debug("UCM capture priority %s for device %s error", value, device_name);
304 if (PA_UCM_PLAYBACK_PRIORITY_UNSET(device) || PA_UCM_CAPTURE_PRIORITY_UNSET(device)) {
305 /* get priority from static table */
306 for (i = 0; dev_info[i].id; i++) {
307 if (strcasecmp(dev_info[i].id, device_name) == 0) {
308 PA_UCM_DEVICE_PRIORITY_SET(device, dev_info[i].priority);
314 if (PA_UCM_PLAYBACK_PRIORITY_UNSET(device)) {
315 /* fall through to default priority */
316 device->playback_priority = 100;
319 if (PA_UCM_CAPTURE_PRIORITY_UNSET(device)) {
320 /* fall through to default priority */
321 device->capture_priority = 100;
324 id = pa_sprintf_malloc("%s/%s", "_conflictingdevs", device_name);
325 n_confdev = snd_use_case_get_list(uc_mgr, id, &devices);
329 pa_log_debug("No %s for device %s", "_conflictingdevs", device_name);
331 device->conflicting_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
332 ucm_add_devices_to_idxset(device->conflicting_devices, device, verb->devices, devices, n_confdev);
333 snd_use_case_free_list(devices, n_confdev);
336 id = pa_sprintf_malloc("%s/%s", "_supporteddevs", device_name);
337 n_suppdev = snd_use_case_get_list(uc_mgr, id, &devices);
341 pa_log_debug("No %s for device %s", "_supporteddevs", device_name);
343 device->supported_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
344 ucm_add_devices_to_idxset(device->supported_devices, device, verb->devices, devices, n_suppdev);
345 snd_use_case_free_list(devices, n_suppdev);
351 /* Create a property list for this ucm modifier */
352 static int ucm_get_modifier_property(pa_alsa_ucm_modifier *modifier, snd_use_case_mgr_t *uc_mgr, const char *modifier_name) {
357 for (i = 0; item[i].id; i++) {
360 id = pa_sprintf_malloc("=%s/%s", item[i].id, modifier_name);
361 err = snd_use_case_get(uc_mgr, id, &value);
366 pa_log_debug("Got %s for modifier %s: %s", item[i].id, modifier_name, value);
367 pa_proplist_sets(modifier->proplist, item[i].property, value);
371 id = pa_sprintf_malloc("%s/%s", "_conflictingdevs", modifier_name);
372 modifier->n_confdev = snd_use_case_get_list(uc_mgr, id, &modifier->conflicting_devices);
374 if (modifier->n_confdev < 0)
375 pa_log_debug("No %s for modifier %s", "_conflictingdevs", modifier_name);
377 id = pa_sprintf_malloc("%s/%s", "_supporteddevs", modifier_name);
378 modifier->n_suppdev = snd_use_case_get_list(uc_mgr, id, &modifier->supported_devices);
380 if (modifier->n_suppdev < 0)
381 pa_log_debug("No %s for modifier %s", "_supporteddevs", modifier_name);
386 /* Create a list of devices for this verb */
387 static int ucm_get_devices(pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr) {
388 const char **dev_list;
391 num_dev = snd_use_case_get_list(uc_mgr, "_devices", &dev_list);
395 for (i = 0; i < num_dev; i += 2) {
396 pa_alsa_ucm_device *d = pa_xnew0(pa_alsa_ucm_device, 1);
398 d->proplist = pa_proplist_new();
399 pa_proplist_sets(d->proplist, PA_ALSA_PROP_UCM_NAME, pa_strnull(dev_list[i]));
400 pa_proplist_sets(d->proplist, PA_ALSA_PROP_UCM_DESCRIPTION, pa_strna(dev_list[i + 1]));
402 PA_LLIST_PREPEND(pa_alsa_ucm_device, verb->devices, d);
405 snd_use_case_free_list(dev_list, num_dev);
410 static int ucm_get_modifiers(pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr) {
411 const char **mod_list;
414 num_mod = snd_use_case_get_list(uc_mgr, "_modifiers", &mod_list);
418 for (i = 0; i < num_mod; i += 2) {
419 pa_alsa_ucm_modifier *m;
422 pa_log_warn("Got a modifier with a null name. Skipping.");
426 m = pa_xnew0(pa_alsa_ucm_modifier, 1);
427 m->proplist = pa_proplist_new();
429 pa_proplist_sets(m->proplist, PA_ALSA_PROP_UCM_NAME, mod_list[i]);
430 pa_proplist_sets(m->proplist, PA_ALSA_PROP_UCM_DESCRIPTION, pa_strna(mod_list[i + 1]));
432 PA_LLIST_PREPEND(pa_alsa_ucm_modifier, verb->modifiers, m);
435 snd_use_case_free_list(mod_list, num_mod);
440 static void add_role_to_device(pa_alsa_ucm_device *dev, const char *dev_name, const char *role_name, const char *role) {
441 const char *cur = pa_proplist_gets(dev->proplist, role_name);
444 pa_proplist_sets(dev->proplist, role_name, role);
445 else if (!pa_str_in_list_spaces(cur, role)) { /* does not exist */
446 char *value = pa_sprintf_malloc("%s %s", cur, role);
448 pa_proplist_sets(dev->proplist, role_name, value);
452 pa_log_info("Add role %s to device %s(%s), result %s", role, dev_name, role_name, pa_proplist_gets(dev->proplist,
456 static void add_media_role(const char *name, pa_alsa_ucm_device *list, const char *role_name, const char *role, bool is_sink) {
457 pa_alsa_ucm_device *d;
459 PA_LLIST_FOREACH(d, list) {
460 const char *dev_name = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME);
462 if (pa_streq(dev_name, name)) {
463 const char *sink = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_SINK);
464 const char *source = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_SOURCE);
467 add_role_to_device(d, dev_name, role_name, role);
468 else if (!is_sink && source)
469 add_role_to_device(d, dev_name, role_name, role);
475 static char *modifier_name_to_role(const char *mod_name, bool *is_sink) {
476 char *sub = NULL, *tmp;
480 if (pa_startswith(mod_name, "Play")) {
482 sub = pa_xstrdup(mod_name + 4);
483 } else if (pa_startswith(mod_name, "Capture"))
484 sub = pa_xstrdup(mod_name + 7);
488 pa_log_warn("Can't match media roles for modifer %s", mod_name);
495 *tmp = tolower(*tmp);
501 static void ucm_set_media_roles(pa_alsa_ucm_modifier *modifier, pa_alsa_ucm_device *list, const char *mod_name) {
503 bool is_sink = false;
505 const char *role_name;
507 sub = modifier_name_to_role(mod_name, &is_sink);
511 modifier->action_direction = is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT;
512 modifier->media_role = sub;
514 role_name = is_sink ? PA_ALSA_PROP_UCM_PLAYBACK_ROLES : PA_ALSA_PROP_UCM_CAPTURE_ROLES;
515 for (i = 0; i < modifier->n_suppdev; i++) {
516 /* if modifier has no specific pcm, we add role intent to its supported devices */
517 if (!pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_SINK) &&
518 !pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_SOURCE))
519 add_media_role(modifier->supported_devices[i], list, role_name, sub, is_sink);
523 static void append_lost_relationship(pa_alsa_ucm_device *dev) {
525 pa_alsa_ucm_device *d;
527 if (dev->conflicting_devices) {
528 PA_IDXSET_FOREACH(d, dev->conflicting_devices, idx) {
529 if (!d->conflicting_devices)
530 d->conflicting_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
532 if (pa_idxset_put(d->conflicting_devices, dev, NULL) == 0)
533 pa_log_warn("Add lost conflicting device %s to %s",
534 pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME),
535 pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME));
539 if (dev->supported_devices) {
540 PA_IDXSET_FOREACH(d, dev->supported_devices, idx) {
541 if (!d->supported_devices)
542 d->supported_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
544 if (pa_idxset_put(d->supported_devices, dev, NULL) == 0)
545 pa_log_warn("Add lost supported device %s to %s",
546 pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME),
547 pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME));
552 int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index) {
554 const char **verb_list;
555 int num_verbs, i, err = 0;
557 /* is UCM available for this card ? */
558 err = snd_card_get_name(card_index, &card_name);
560 pa_log("Card can't get card_name from card_index %d", card_index);
564 err = snd_use_case_mgr_open(&ucm->ucm_mgr, card_name);
566 pa_log_info("UCM not available for card %s", card_name);
570 pa_log_info("UCM available for card %s", card_name);
572 /* get a list of all UCM verbs (profiles) for this card */
573 num_verbs = snd_use_case_verb_list(ucm->ucm_mgr, &verb_list);
575 pa_log("UCM verb list not found for %s", card_name);
579 /* get the properties of each UCM verb */
580 for (i = 0; i < num_verbs; i += 2) {
581 pa_alsa_ucm_verb *verb;
583 /* Get devices and modifiers for each verb */
584 err = pa_alsa_ucm_get_verb(ucm->ucm_mgr, verb_list[i], verb_list[i+1], &verb);
586 pa_log("Failed to get the verb %s", verb_list[i]);
590 PA_LLIST_PREPEND(pa_alsa_ucm_verb, ucm->verbs, verb);
594 pa_log("No UCM verb is valid for %s", card_name);
598 snd_use_case_free_list(verb_list, num_verbs);
602 snd_use_case_mgr_close(ucm->ucm_mgr);
613 int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, const char *verb_desc, pa_alsa_ucm_verb **p_verb) {
614 pa_alsa_ucm_device *d;
615 pa_alsa_ucm_modifier *mod;
616 pa_alsa_ucm_verb *verb;
620 pa_log_info("Set UCM verb to %s", verb_name);
621 err = snd_use_case_set(uc_mgr, "_verb", verb_name);
625 verb = pa_xnew0(pa_alsa_ucm_verb, 1);
626 verb->proplist = pa_proplist_new();
628 pa_proplist_sets(verb->proplist, PA_ALSA_PROP_UCM_NAME, pa_strnull(verb_name));
629 pa_proplist_sets(verb->proplist, PA_ALSA_PROP_UCM_DESCRIPTION, pa_strna(verb_desc));
631 err = ucm_get_devices(verb, uc_mgr);
633 pa_log("No UCM devices for verb %s", verb_name);
635 err = ucm_get_modifiers(verb, uc_mgr);
637 pa_log("No UCM modifiers for verb %s", verb_name);
639 /* Verb properties */
640 ucm_get_property(verb, uc_mgr, verb_name);
642 PA_LLIST_FOREACH(d, verb->devices) {
643 const char *dev_name = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME);
645 /* Devices properties */
646 ucm_get_device_property(d, uc_mgr, verb, dev_name);
648 /* make conflicting or supported device mutual */
649 PA_LLIST_FOREACH(d, verb->devices)
650 append_lost_relationship(d);
652 PA_LLIST_FOREACH(mod, verb->modifiers) {
653 const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME);
655 /* Modifier properties */
656 ucm_get_modifier_property(mod, uc_mgr, mod_name);
658 /* Set PA_PROP_DEVICE_INTENDED_ROLES property to devices */
659 pa_log_debug("Set media roles for verb %s, modifier %s", verb_name, mod_name);
660 ucm_set_media_roles(mod, verb->devices, mod_name);
667 static int pa_alsa_ucm_device_cmp(const void *a, const void *b) {
668 const pa_alsa_ucm_device *d1 = *(pa_alsa_ucm_device **)a;
669 const pa_alsa_ucm_device *d2 = *(pa_alsa_ucm_device **)b;
671 return strcmp(pa_proplist_gets(d1->proplist, PA_ALSA_PROP_UCM_NAME), pa_proplist_gets(d2->proplist, PA_ALSA_PROP_UCM_NAME));
674 static void ucm_add_port_combination(
676 pa_alsa_ucm_mapping_context *context,
678 pa_alsa_ucm_device **pdevices,
684 pa_device_port *port;
689 const char *dev_name;
690 const char *direction;
691 pa_alsa_ucm_device *sorted[num], *dev;
693 for (i = 0; i < num; i++)
694 sorted[i] = pdevices[i];
696 /* Sort by alphabetical order so as to have a deterministic naming scheme
697 * for combination ports */
698 qsort(&sorted[0], num, sizeof(pa_alsa_ucm_device *), pa_alsa_ucm_device_cmp);
701 dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
703 name = pa_sprintf_malloc("%s%s", is_sink ? PA_UCM_PRE_TAG_OUTPUT : PA_UCM_PRE_TAG_INPUT, dev_name);
704 desc = num == 1 ? pa_xstrdup(pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_DESCRIPTION))
705 : pa_sprintf_malloc("Combination port for %s", dev_name);
707 priority = is_sink ? dev->playback_priority : dev->capture_priority;
708 prio2 = (priority == 0 ? 0 : 1.0/priority);
710 for (i = 1; i < num; i++) {
714 dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
716 tmp = pa_sprintf_malloc("%s+%s", name, dev_name);
720 tmp = pa_sprintf_malloc("%s,%s", desc, dev_name);
724 priority = is_sink ? dev->playback_priority : dev->capture_priority;
725 if (priority != 0 && prio2 > 0)
726 prio2 += 1.0/priority;
729 /* Make combination ports always have lower priority, and use the formula
730 1/p = 1/p1 + 1/p2 + ... 1/pn.
731 This way, the result will always be less than the individual components,
732 yet higher components will lead to higher result. */
735 priority = prio2 > 0 ? 1.0/prio2 : 0;
737 port = pa_hashmap_get(ports, name);
739 pa_device_port_new_data port_data;
741 pa_device_port_new_data_init(&port_data);
742 pa_device_port_new_data_set_name(&port_data, name);
743 pa_device_port_new_data_set_description(&port_data, desc);
744 pa_device_port_new_data_set_direction(&port_data, is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT);
746 port = pa_device_port_new(core, &port_data, 0);
747 pa_device_port_new_data_done(&port_data);
750 pa_hashmap_put(ports, port->name, port);
751 pa_log_debug("Add port %s: %s", port->name, port->description);
752 port->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
755 port->priority = priority;
760 direction = is_sink ? "output" : "input";
761 pa_log_debug("Port %s direction %s, priority %d", port->name, direction, priority);
764 pa_log_debug("Adding profile %s to port %s.", cp->name, port->name);
765 pa_hashmap_put(port->profiles, cp->name, cp);
769 pa_hashmap_put(hash, port->name, port);
770 pa_device_port_ref(port);
774 static int ucm_port_contains(const char *port_name, const char *dev_name, bool is_sink) {
777 const char *state = NULL;
780 if (!port_name || !dev_name)
783 port_name += is_sink ? strlen(PA_UCM_PRE_TAG_OUTPUT) : strlen(PA_UCM_PRE_TAG_INPUT);
785 while ((r = pa_split_in_place(port_name, "+", &len, &state))) {
786 if (!strncmp(r, dev_name, len)) {
795 static int ucm_check_conformance(
796 pa_alsa_ucm_mapping_context *context,
797 pa_alsa_ucm_device **pdevices,
799 pa_alsa_ucm_device *dev) {
802 pa_alsa_ucm_device *d;
807 pa_log_debug("Check device %s conformance with %d other devices",
808 pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME), dev_num);
810 pa_log_debug("First device in combination, number 1");
814 if (dev->conflicting_devices) { /* the device defines conflicting devices */
815 PA_IDXSET_FOREACH(d, dev->conflicting_devices, idx) {
816 for (i = 0; i < dev_num; i++) {
817 if (pdevices[i] == d) {
818 pa_log_debug("Conflicting device found");
823 } else if (dev->supported_devices) { /* the device defines supported devices */
824 for (i = 0; i < dev_num; i++) {
825 if (!ucm_device_exists(dev->supported_devices, pdevices[i])) {
826 pa_log_debug("Supported device not found");
830 } else { /* not support any other devices */
831 pa_log_debug("Not support any other devices");
835 pa_log_debug("Device added to combination, number %d", dev_num + 1);
839 static inline pa_alsa_ucm_device *get_next_device(pa_idxset *idxset, uint32_t *idx) {
840 pa_alsa_ucm_device *dev;
842 if (*idx == PA_IDXSET_INVALID)
843 dev = pa_idxset_first(idxset, idx);
845 dev = pa_idxset_next(idxset, idx);
850 static void ucm_add_ports_combination(
852 pa_alsa_ucm_mapping_context *context,
854 pa_alsa_ucm_device **pdevices,
861 pa_alsa_ucm_device *dev;
862 uint32_t idx = map_index;
864 if ((dev = get_next_device(context->ucm_devices, &idx)) == NULL)
867 /* check if device at map_index can combine with existing devices combination */
868 if (ucm_check_conformance(context, pdevices, dev_num, dev)) {
869 /* add device at map_index to devices combination */
870 pdevices[dev_num] = dev;
871 /* add current devices combination as a new port */
872 ucm_add_port_combination(hash, context, is_sink, pdevices, dev_num + 1, ports, cp, core);
873 /* try more elements combination */
874 ucm_add_ports_combination(hash, context, is_sink, pdevices, dev_num + 1, idx, ports, cp, core);
877 /* try other device with current elements number */
878 ucm_add_ports_combination(hash, context, is_sink, pdevices, dev_num, idx, ports, cp, core);
881 static char* merge_roles(const char *cur, const char *add) {
883 const char *state = NULL;
886 return pa_xstrdup(cur);
887 else if (cur == NULL)
888 return pa_xstrdup(add);
890 ret = pa_xstrdup(cur);
892 while ((r = pa_split_spaces(add, &state))) {
895 if (!pa_str_in_list_spaces(ret, r))
896 value = pa_sprintf_malloc("%s %s", ret, r);
910 void pa_alsa_ucm_add_ports_combination(
912 pa_alsa_ucm_mapping_context *context,
918 pa_alsa_ucm_device **pdevices;
920 pa_assert(context->ucm_devices);
922 if (pa_idxset_size(context->ucm_devices) > 0) {
923 pdevices = pa_xnew(pa_alsa_ucm_device *, pa_idxset_size(context->ucm_devices));
924 ucm_add_ports_combination(p, context, is_sink, pdevices, 0, PA_IDXSET_INVALID, ports, cp, core);
929 void pa_alsa_ucm_add_ports(
931 pa_proplist *proplist,
932 pa_alsa_ucm_mapping_context *context,
938 const char *role_name = is_sink ? PA_ALSA_PROP_UCM_PLAYBACK_ROLES : PA_ALSA_PROP_UCM_CAPTURE_ROLES;
939 pa_alsa_ucm_device *dev;
940 pa_alsa_ucm_modifier *mod;
946 /* add ports first */
947 pa_alsa_ucm_add_ports_combination(*p, context, is_sink, card->ports, NULL, card->core);
949 /* then set property PA_PROP_DEVICE_INTENDED_ROLES */
950 merged_roles = pa_xstrdup(pa_proplist_gets(proplist, PA_PROP_DEVICE_INTENDED_ROLES));
951 PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) {
952 const char *roles = pa_proplist_gets(dev->proplist, role_name);
953 tmp = merge_roles(merged_roles, roles);
954 pa_xfree(merged_roles);
958 if (context->ucm_modifiers)
959 PA_IDXSET_FOREACH(mod, context->ucm_modifiers, idx) {
960 tmp = merge_roles(merged_roles, mod->media_role);
961 pa_xfree(merged_roles);
966 pa_proplist_sets(proplist, PA_PROP_DEVICE_INTENDED_ROLES, merged_roles);
968 pa_log_info("ALSA device %s roles: %s", pa_proplist_gets(proplist, PA_PROP_DEVICE_STRING), pa_strnull(merged_roles));
969 pa_xfree(merged_roles);
972 /* Change UCM verb and device to match selected card profile */
973 int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, const char *new_profile, const char *old_profile) {
976 pa_alsa_ucm_verb *verb;
978 if (new_profile == old_profile)
980 else if (new_profile == NULL || old_profile == NULL)
981 profile = new_profile ? new_profile : SND_USE_CASE_VERB_INACTIVE;
982 else if (!pa_streq(new_profile, old_profile))
983 profile = new_profile;
988 pa_log_info("Set UCM verb to %s", profile);
989 if ((snd_use_case_set(ucm->ucm_mgr, "_verb", profile)) < 0) {
990 pa_log("Failed to set verb %s", profile);
994 /* find active verb */
995 ucm->active_verb = NULL;
996 PA_LLIST_FOREACH(verb, ucm->verbs) {
997 const char *verb_name;
998 verb_name = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME);
999 if (pa_streq(verb_name, profile)) {
1000 ucm->active_verb = verb;
1008 int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port, bool is_sink) {
1011 pa_alsa_ucm_config *ucm;
1012 const char **enable_devs;
1015 pa_alsa_ucm_device *dev;
1017 pa_assert(context && context->ucm);
1020 pa_assert(ucm->ucm_mgr);
1022 enable_devs = pa_xnew(const char *, pa_idxset_size(context->ucm_devices));
1024 /* first disable then enable */
1025 PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) {
1026 const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
1028 if (ucm_port_contains(port->name, dev_name, is_sink))
1029 enable_devs[enable_num++] = dev_name;
1031 pa_log_debug("Disable ucm device %s", dev_name);
1032 if (snd_use_case_set(ucm->ucm_mgr, "_disdev", dev_name) > 0) {
1033 pa_log("Failed to disable ucm device %s", dev_name);
1040 for (i = 0; i < enable_num; i++) {
1041 pa_log_debug("Enable ucm device %s", enable_devs[i]);
1042 if (snd_use_case_set(ucm->ucm_mgr, "_enadev", enable_devs[i]) < 0) {
1043 pa_log("Failed to enable ucm device %s", enable_devs[i]);
1049 pa_xfree(enable_devs);
1054 static void ucm_add_mapping(pa_alsa_profile *p, pa_alsa_mapping *m) {
1056 switch (m->direction) {
1057 case PA_ALSA_DIRECTION_ANY:
1058 pa_idxset_put(p->output_mappings, m, NULL);
1059 pa_idxset_put(p->input_mappings, m, NULL);
1061 case PA_ALSA_DIRECTION_OUTPUT:
1062 pa_idxset_put(p->output_mappings, m, NULL);
1064 case PA_ALSA_DIRECTION_INPUT:
1065 pa_idxset_put(p->input_mappings, m, NULL);
1070 static void alsa_mapping_add_ucm_device(pa_alsa_mapping *m, pa_alsa_ucm_device *device) {
1072 const char *new_desc;
1074 pa_idxset_put(m->ucm_context.ucm_devices, device, NULL);
1076 new_desc = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_DESCRIPTION);
1077 cur_desc = m->description;
1079 m->description = pa_sprintf_malloc("%s + %s", cur_desc, new_desc);
1081 m->description = pa_xstrdup(new_desc);
1084 /* walk around null case */
1085 m->description = m->description ? m->description : pa_xstrdup("");
1087 /* save mapping to ucm device */
1088 if (m->direction == PA_ALSA_DIRECTION_OUTPUT)
1089 device->playback_mapping = m;
1091 device->capture_mapping = m;
1094 static void alsa_mapping_add_ucm_modifier(pa_alsa_mapping *m, pa_alsa_ucm_modifier *modifier) {
1096 const char *new_desc, *mod_name, *channel_str;
1097 uint32_t channels = 0;
1099 pa_idxset_put(m->ucm_context.ucm_modifiers, modifier, NULL);
1101 new_desc = pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_DESCRIPTION);
1102 cur_desc = m->description;
1104 m->description = pa_sprintf_malloc("%s + %s", cur_desc, new_desc);
1106 m->description = pa_xstrdup(new_desc);
1109 m->description = m->description ? m->description : pa_xstrdup("");
1111 /* Modifier sinks should not be routed to by default */
1114 mod_name = pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_NAME);
1115 pa_proplist_sets(m->proplist, PA_ALSA_PROP_UCM_MODIFIER, mod_name);
1117 /* save mapping to ucm modifier */
1118 if (m->direction == PA_ALSA_DIRECTION_OUTPUT) {
1119 modifier->playback_mapping = m;
1120 channel_str = pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_PLAYBACK_CHANNELS);
1122 modifier->capture_mapping = m;
1123 channel_str = pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_CAPTURE_CHANNELS);
1127 /* FIXME: channel_str is unsanitized input from the UCM configuration,
1128 * we should do proper error handling instead of asserting.
1129 * https://bugs.freedesktop.org/show_bug.cgi?id=71823 */
1130 pa_assert_se(pa_atou(channel_str, &channels) == 0 && pa_channels_valid(channels));
1131 pa_log_debug("Got channel count %" PRIu32 " for modifier", channels);
1135 pa_channel_map_init_extend(&m->channel_map, channels, PA_CHANNEL_MAP_ALSA);
1137 pa_channel_map_init(&m->channel_map);
1140 static int ucm_create_mapping_direction(
1141 pa_alsa_ucm_config *ucm,
1142 pa_alsa_profile_set *ps,
1144 pa_alsa_ucm_device *device,
1145 const char *verb_name,
1146 const char *device_name,
1147 const char *device_str,
1152 unsigned priority, rate, channels;
1154 mapping_name = pa_sprintf_malloc("Mapping %s: %s: %s", verb_name, device_str, is_sink ? "sink" : "source");
1156 m = pa_alsa_mapping_get(ps, mapping_name);
1158 pa_log("No mapping for %s", mapping_name);
1159 pa_xfree(mapping_name);
1162 pa_log_debug("UCM mapping: %s dev %s", mapping_name, device_name);
1163 pa_xfree(mapping_name);
1165 priority = is_sink ? device->playback_priority : device->capture_priority;
1166 rate = is_sink ? device->playback_rate : device->capture_rate;
1167 channels = is_sink ? device->playback_channels : device->capture_channels;
1169 if (!m->ucm_context.ucm_devices) { /* new mapping */
1170 m->ucm_context.ucm_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
1171 m->ucm_context.ucm = ucm;
1172 m->ucm_context.direction = is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT;
1174 m->device_strings = pa_xnew0(char*, 2);
1175 m->device_strings[0] = pa_xstrdup(device_str);
1176 m->direction = is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT;
1178 ucm_add_mapping(p, m);
1180 m->sample_spec.rate = rate;
1181 pa_channel_map_init_extend(&m->channel_map, channels, PA_CHANNEL_MAP_ALSA);
1184 /* mapping priority is the highest one of ucm devices */
1185 if (priority > m->priority)
1186 m->priority = priority;
1188 /* mapping channels is the lowest one of ucm devices */
1189 if (channels < m->channel_map.channels)
1190 pa_channel_map_init_extend(&m->channel_map, channels, PA_CHANNEL_MAP_ALSA);
1192 alsa_mapping_add_ucm_device(m, device);
1197 static int ucm_create_mapping_for_modifier(
1198 pa_alsa_ucm_config *ucm,
1199 pa_alsa_profile_set *ps,
1201 pa_alsa_ucm_modifier *modifier,
1202 const char *verb_name,
1203 const char *mod_name,
1204 const char *device_str,
1210 mapping_name = pa_sprintf_malloc("Mapping %s: %s: %s", verb_name, device_str, is_sink ? "sink" : "source");
1212 m = pa_alsa_mapping_get(ps, mapping_name);
1214 pa_log("no mapping for %s", mapping_name);
1215 pa_xfree(mapping_name);
1218 pa_log_info("ucm mapping: %s modifier %s", mapping_name, mod_name);
1219 pa_xfree(mapping_name);
1221 if (!m->ucm_context.ucm_devices && !m->ucm_context.ucm_modifiers) { /* new mapping */
1222 m->ucm_context.ucm_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
1223 m->ucm_context.ucm_modifiers = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
1224 m->ucm_context.ucm = ucm;
1225 m->ucm_context.direction = is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT;
1227 m->device_strings = pa_xnew0(char*, 2);
1228 m->device_strings[0] = pa_xstrdup(device_str);
1229 m->direction = is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT;
1230 /* Modifier sinks should not be routed to by default */
1233 ucm_add_mapping(p, m);
1234 } else if (!m->ucm_context.ucm_modifiers) /* share pcm with device */
1235 m->ucm_context.ucm_modifiers = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
1237 alsa_mapping_add_ucm_modifier(m, modifier);
1242 static int ucm_create_mapping(
1243 pa_alsa_ucm_config *ucm,
1244 pa_alsa_profile_set *ps,
1246 pa_alsa_ucm_device *device,
1247 const char *verb_name,
1248 const char *device_name,
1250 const char *source) {
1254 if (!sink && !source) {
1255 pa_log("No sink and source at %s: %s", verb_name, device_name);
1260 ret = ucm_create_mapping_direction(ucm, ps, p, device, verb_name, device_name, sink, true);
1261 if (ret == 0 && source)
1262 ret = ucm_create_mapping_direction(ucm, ps, p, device, verb_name, device_name, source, false);
1267 static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, const char *dev_name, const char *pre_tag) {
1269 char *name = pa_sprintf_malloc("%s%s", pre_tag, dev_name);
1271 PA_LLIST_FOREACH(j, ucm->jacks)
1272 if (pa_streq(j->name, name))
1275 j = pa_xnew0(pa_alsa_jack, 1);
1276 j->state_unplugged = PA_AVAILABLE_NO;
1277 j->state_plugged = PA_AVAILABLE_YES;
1278 j->name = pa_xstrdup(name);
1279 j->alsa_name = pa_sprintf_malloc("%s Jack", dev_name);
1281 PA_LLIST_PREPEND(pa_alsa_jack, ucm->jacks, j);
1288 static int ucm_create_profile(
1289 pa_alsa_ucm_config *ucm,
1290 pa_alsa_profile_set *ps,
1291 pa_alsa_ucm_verb *verb,
1292 const char *verb_name,
1293 const char *verb_desc) {
1296 pa_alsa_ucm_device *dev;
1297 pa_alsa_ucm_modifier *mod;
1299 const char *name, *sink, *source;
1304 if (pa_hashmap_get(ps->profiles, verb_name)) {
1305 pa_log("Verb %s already exists", verb_name);
1309 p = pa_xnew0(pa_alsa_profile, 1);
1310 p->profile_set = ps;
1311 p->name = pa_xstrdup(verb_name);
1312 p->description = pa_xstrdup(verb_desc);
1314 p->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
1315 p->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
1317 p->supported = true;
1318 pa_hashmap_put(ps->profiles, p->name, p);
1320 /* TODO: get profile priority from ucm info or policy management */
1321 c = verb_cmp = pa_xstrdup(verb_name);
1323 if (*c == '_') *c = ' ';
1327 for (i = 0; verb_info[i].id; i++) {
1328 if (strcasecmp(verb_info[i].id, verb_cmp) == 0) {
1329 p->priority = verb_info[i].priority;
1336 if (verb_info[i].id == NULL)
1339 PA_LLIST_FOREACH(dev, verb->devices) {
1340 name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
1342 sink = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SINK);
1343 source = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SOURCE);
1345 ucm_create_mapping(ucm, ps, p, dev, verb_name, name, sink, source);
1348 dev->output_jack = ucm_get_jack(ucm, name, PA_UCM_PRE_TAG_OUTPUT);
1350 dev->input_jack = ucm_get_jack(ucm, name, PA_UCM_PRE_TAG_INPUT);
1353 /* Now find modifiers that have their own PlaybackPCM and create
1354 * separate sinks for them. */
1355 PA_LLIST_FOREACH(mod, verb->modifiers) {
1356 name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME);
1358 sink = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_SINK);
1359 source = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_SOURCE);
1362 ucm_create_mapping_for_modifier(ucm, ps, p, mod, verb_name, name, sink, true);
1364 ucm_create_mapping_for_modifier(ucm, ps, p, mod, verb_name, name, source, false);
1367 pa_alsa_profile_dump(p);
1372 static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, int mode) {
1374 pa_sample_spec try_ss = ucm->core->default_sample_spec;
1375 pa_channel_map try_map;
1376 snd_pcm_uframes_t try_period_size, try_buffer_size;
1377 bool exact_channels = m->channel_map.channels > 0;
1379 if (exact_channels) {
1380 try_map = m->channel_map;
1381 try_ss.channels = try_map.channels;
1383 pa_channel_map_init_extend(&try_map, try_ss.channels, PA_CHANNEL_MAP_ALSA);
1386 pa_usec_to_bytes(ucm->core->default_fragment_size_msec * PA_USEC_PER_MSEC, &try_ss) /
1387 pa_frame_size(&try_ss);
1388 try_buffer_size = ucm->core->default_n_fragments * try_period_size;
1390 pcm = pa_alsa_open_by_device_string(
1394 m->device_strings[0], NULL, &try_ss,
1395 &try_map, mode, &try_period_size, &try_buffer_size, 0, NULL, NULL, exact_channels);
1397 if (pcm && !exact_channels)
1398 m->channel_map = try_map;
1403 static void profile_finalize_probing(pa_alsa_profile *p) {
1407 PA_IDXSET_FOREACH(m, p->output_mappings, idx) {
1414 snd_pcm_close(m->output_pcm);
1415 m->output_pcm = NULL;
1418 PA_IDXSET_FOREACH(m, p->input_mappings, idx) {
1425 snd_pcm_close(m->input_pcm);
1426 m->input_pcm = NULL;
1430 static void ucm_mapping_jack_probe(pa_alsa_mapping *m) {
1431 snd_pcm_t *pcm_handle;
1432 snd_mixer_t *mixer_handle;
1433 snd_hctl_t *hctl_handle;
1434 pa_alsa_ucm_mapping_context *context = &m->ucm_context;
1435 pa_alsa_ucm_device *dev;
1438 pcm_handle = m->direction == PA_ALSA_DIRECTION_OUTPUT ? m->output_pcm : m->input_pcm;
1439 mixer_handle = pa_alsa_open_mixer_for_pcm(pcm_handle, NULL, &hctl_handle);
1440 if (!mixer_handle || !hctl_handle)
1443 PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) {
1445 jack = m->direction == PA_ALSA_DIRECTION_OUTPUT ? dev->output_jack : dev->input_jack;
1447 jack->has_control = pa_alsa_find_jack(hctl_handle, jack->alsa_name) != NULL;
1448 pa_log_info("UCM jack %s has_control=%d", jack->name, jack->has_control);
1451 snd_mixer_close(mixer_handle);
1454 static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps) {
1460 PA_HASHMAP_FOREACH(p, ps->profiles, state) {
1462 pa_log_info("Set ucm verb to %s", p->name);
1464 if ((snd_use_case_set(ucm->ucm_mgr, "_verb", p->name)) < 0) {
1465 pa_log("Failed to set verb %s", p->name);
1466 p->supported = false;
1470 PA_IDXSET_FOREACH(m, p->output_mappings, idx) {
1471 if (PA_UCM_IS_MODIFIER_MAPPING(m)) {
1472 /* Skip jack probing on modifier PCMs since we expect this to
1473 * only be controlled on the main device/verb PCM. */
1477 m->output_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_PLAYBACK);
1478 if (!m->output_pcm) {
1479 p->supported = false;
1485 PA_IDXSET_FOREACH(m, p->input_mappings, idx) {
1486 if (PA_UCM_IS_MODIFIER_MAPPING(m)) {
1487 /* Skip jack probing on modifier PCMs since we expect this to
1488 * only be controlled on the main device/verb PCM. */
1492 m->input_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_CAPTURE);
1493 if (!m->input_pcm) {
1494 p->supported = false;
1500 if (!p->supported) {
1501 profile_finalize_probing(p);
1505 pa_log_debug("Profile %s supported.", p->name);
1507 PA_IDXSET_FOREACH(m, p->output_mappings, idx)
1508 if (!PA_UCM_IS_MODIFIER_MAPPING(m))
1509 ucm_mapping_jack_probe(m);
1511 PA_IDXSET_FOREACH(m, p->input_mappings, idx)
1512 if (!PA_UCM_IS_MODIFIER_MAPPING(m))
1513 ucm_mapping_jack_probe(m);
1515 profile_finalize_probing(p);
1518 /* restore ucm state */
1519 snd_use_case_set(ucm->ucm_mgr, "_verb", SND_USE_CASE_VERB_INACTIVE);
1521 pa_alsa_profile_set_drop_unsupported(ps);
1524 pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map) {
1525 pa_alsa_ucm_verb *verb;
1526 pa_alsa_profile_set *ps;
1528 ps = pa_xnew0(pa_alsa_profile_set, 1);
1529 ps->mappings = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
1530 ps->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
1531 ps->decibel_fixes = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
1533 /* create a profile for each verb */
1534 PA_LLIST_FOREACH(verb, ucm->verbs) {
1535 const char *verb_name;
1536 const char *verb_desc;
1538 verb_name = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME);
1539 verb_desc = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_DESCRIPTION);
1540 if (verb_name == NULL) {
1541 pa_log("Verb with no name");
1545 ucm_create_profile(ucm, ps, verb, verb_name, verb_desc);
1548 ucm_probe_profile_set(ucm, ps);
1554 static void free_verb(pa_alsa_ucm_verb *verb) {
1555 pa_alsa_ucm_device *di, *dn;
1556 pa_alsa_ucm_modifier *mi, *mn;
1558 PA_LLIST_FOREACH_SAFE(di, dn, verb->devices) {
1559 PA_LLIST_REMOVE(pa_alsa_ucm_device, verb->devices, di);
1560 pa_proplist_free(di->proplist);
1561 if (di->conflicting_devices)
1562 pa_idxset_free(di->conflicting_devices, NULL);
1563 if (di->supported_devices)
1564 pa_idxset_free(di->supported_devices, NULL);
1568 PA_LLIST_FOREACH_SAFE(mi, mn, verb->modifiers) {
1569 PA_LLIST_REMOVE(pa_alsa_ucm_modifier, verb->modifiers, mi);
1570 pa_proplist_free(mi->proplist);
1571 if (mi->n_suppdev > 0)
1572 snd_use_case_free_list(mi->supported_devices, mi->n_suppdev);
1573 if (mi->n_confdev > 0)
1574 snd_use_case_free_list(mi->conflicting_devices, mi->n_confdev);
1575 pa_xfree(mi->media_role);
1578 pa_proplist_free(verb->proplist);
1582 void pa_alsa_ucm_free(pa_alsa_ucm_config *ucm) {
1583 pa_alsa_ucm_verb *vi, *vn;
1584 pa_alsa_jack *ji, *jn;
1586 PA_LLIST_FOREACH_SAFE(vi, vn, ucm->verbs) {
1587 PA_LLIST_REMOVE(pa_alsa_ucm_verb, ucm->verbs, vi);
1590 PA_LLIST_FOREACH_SAFE(ji, jn, ucm->jacks) {
1591 PA_LLIST_REMOVE(pa_alsa_jack, ucm->jacks, ji);
1592 pa_xfree(ji->alsa_name);
1597 snd_use_case_mgr_close(ucm->ucm_mgr);
1598 ucm->ucm_mgr = NULL;
1602 void pa_alsa_ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context) {
1603 pa_alsa_ucm_device *dev;
1604 pa_alsa_ucm_modifier *mod;
1607 if (context->ucm_devices) {
1608 /* clear ucm device pointer to mapping */
1609 PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) {
1610 if (context->direction == PA_DIRECTION_OUTPUT)
1611 dev->playback_mapping = NULL;
1613 dev->capture_mapping = NULL;
1616 pa_idxset_free(context->ucm_devices, NULL);
1619 if (context->ucm_modifiers) {
1620 PA_IDXSET_FOREACH(mod, context->ucm_modifiers, idx) {
1621 if (context->direction == PA_DIRECTION_OUTPUT)
1622 mod->playback_mapping = NULL;
1624 mod->capture_mapping = NULL;
1627 pa_idxset_free(context->ucm_modifiers, NULL);
1631 /* Enable the modifier when the first stream with matched role starts */
1632 void pa_alsa_ucm_roled_stream_begin(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) {
1633 pa_alsa_ucm_modifier *mod;
1635 if (!ucm->active_verb)
1638 PA_LLIST_FOREACH(mod, ucm->active_verb->modifiers) {
1639 if ((mod->action_direction == dir) && (pa_streq(mod->media_role, role))) {
1640 if (mod->enabled_counter == 0) {
1641 const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME);
1643 pa_log_info("Enable ucm modifier %s", mod_name);
1644 if (snd_use_case_set(ucm->ucm_mgr, "_enamod", mod_name) < 0) {
1645 pa_log("Failed to enable ucm modifier %s", mod_name);
1649 mod->enabled_counter++;
1655 /* Disable the modifier when the last stream with matched role ends */
1656 void pa_alsa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) {
1657 pa_alsa_ucm_modifier *mod;
1659 if (!ucm->active_verb)
1662 PA_LLIST_FOREACH(mod, ucm->active_verb->modifiers) {
1663 if ((mod->action_direction == dir) && (pa_streq(mod->media_role, role))) {
1665 mod->enabled_counter--;
1666 if (mod->enabled_counter == 0) {
1667 const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME);
1669 pa_log_info("Disable ucm modifier %s", mod_name);
1670 if (snd_use_case_set(ucm->ucm_mgr, "_dismod", mod_name) < 0) {
1671 pa_log("Failed to disable ucm modifier %s", mod_name);
1680 #else /* HAVE_ALSA_UCM */
1682 /* Dummy functions for systems without UCM support */
1684 int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index) {
1685 pa_log_info("UCM not available.");
1689 pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map) {
1693 int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, const char *new_profile, const char *old_profile) {
1697 int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, const char *verb_desc, pa_alsa_ucm_verb **p_verb) {
1701 void pa_alsa_ucm_add_ports(
1703 pa_proplist *proplist,
1704 pa_alsa_ucm_mapping_context *context,
1709 void pa_alsa_ucm_add_ports_combination(
1711 pa_alsa_ucm_mapping_context *context,
1714 pa_card_profile *cp,
1718 int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port, bool is_sink) {
1722 void pa_alsa_ucm_free(pa_alsa_ucm_config *ucm) {
1725 void pa_alsa_ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context) {
1728 void pa_alsa_ucm_roled_stream_begin(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) {
1731 void pa_alsa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) {