mirror of
https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
synced 2026-04-14 21:40:36 +02:00
UCM basic functions will provide another way to handle the alsa mixer and controls. That means alsa card module will make use of alsa ucm configurations provided by various audio systems instead of mixer and paths configurations provided by PA. PA profiles come from UCM verb, PA sinks/sources and ports come from UCM devices. In case the proper UCM configurations are found, ucm branches are activated, or we will still fall through to the original way. Signed-off-by: Feng Wei <wei.feng@freescale.com>
1370 lines
42 KiB
C
1370 lines
42 KiB
C
/***
|
|
This file is part of PulseAudio.
|
|
|
|
Copyright 2011 Wolfson Microelectronics PLC
|
|
Author Margarita Olaya <magi@slimlogic.co.uk>
|
|
Copyright 2012 Feng Wei <wei.feng@freescale.com>, Freescale Ltd.
|
|
|
|
PulseAudio is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU Lesser General Public License as published
|
|
by the Free Software Foundation; either version 2.1 of the License,
|
|
or (at your option) any later version.
|
|
|
|
PulseAudio is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
along with PulseAudio; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
|
USA.
|
|
|
|
***/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <ctype.h>
|
|
#include <sys/types.h>
|
|
#include <limits.h>
|
|
#include <asoundlib.h>
|
|
|
|
#ifdef HAVE_VALGRIND_MEMCHECK_H
|
|
#include <valgrind/memcheck.h>
|
|
#endif
|
|
|
|
#include <pulse/sample.h>
|
|
#include <pulse/xmalloc.h>
|
|
#include <pulse/timeval.h>
|
|
#include <pulse/util.h>
|
|
|
|
#include <pulsecore/log.h>
|
|
#include <pulsecore/macro.h>
|
|
#include <pulsecore/core-util.h>
|
|
#include <pulsecore/atomic.h>
|
|
#include <pulsecore/core-error.h>
|
|
#include <pulsecore/once.h>
|
|
#include <pulsecore/thread.h>
|
|
#include <pulsecore/conf-parser.h>
|
|
#include <pulsecore/strbuf.h>
|
|
|
|
#include "alsa-mixer.h"
|
|
#include "alsa-util.h"
|
|
#include "alsa-ucm.h"
|
|
|
|
#define PA_UCM_PRE_TAG_OUTPUT "[Out] "
|
|
#define PA_UCM_PRE_TAG_INPUT "[In] "
|
|
|
|
#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_ALSA_PROP_UCM_SINK},
|
|
{"CapturePCM", PA_ALSA_PROP_UCM_SOURCE},
|
|
{"PlaybackVolume", PA_ALSA_PROP_UCM_PLAYBACK_VOLUME},
|
|
{"PlaybackSwitch", PA_ALSA_PROP_UCM_PLAYBACK_SWITCH},
|
|
{"PlaybackPriority", PA_ALSA_PROP_UCM_PLAYBACK_PRIORITY},
|
|
{"PlaybackChannels", PA_ALSA_PROP_UCM_PLAYBACK_CHANNELS},
|
|
{"CaptureVolume", PA_ALSA_PROP_UCM_CAPTURE_VOLUME},
|
|
{"CaptureSwitch", PA_ALSA_PROP_UCM_CAPTURE_SWITCH},
|
|
{"CapturePriority", PA_ALSA_PROP_UCM_CAPTURE_PRIORITY},
|
|
{"CaptureChannels", PA_ALSA_PROP_UCM_CAPTURE_CHANNELS},
|
|
{"TQ", PA_ALSA_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 */
|
|
static int ucm_get_property(pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr, const char *verb_name) {
|
|
const char *value;
|
|
char *id;
|
|
int i;
|
|
|
|
for (i = 0; item[i].id; i++) {
|
|
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 )
|
|
continue;
|
|
|
|
pa_log_debug("Got %s for verb %s: %s", item[i].id, verb_name, value);
|
|
pa_proplist_sets(verb->proplist, item[i].property, value);
|
|
free((void*)value);
|
|
}
|
|
|
|
return 0;
|
|
};
|
|
|
|
static int ucm_device_exists(pa_idxset *idxset, pa_alsa_ucm_device *dev) {
|
|
pa_alsa_ucm_device *d;
|
|
uint32_t idx;
|
|
|
|
PA_IDXSET_FOREACH(d, idxset, idx)
|
|
if (d == dev)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ucm_add_devices_to_idxset(
|
|
pa_idxset *idxset,
|
|
pa_alsa_ucm_device *me,
|
|
pa_alsa_ucm_device *devices,
|
|
const char **dev_names,
|
|
int n) {
|
|
|
|
pa_alsa_ucm_device *d;
|
|
|
|
PA_LLIST_FOREACH(d, devices) {
|
|
const char *name;
|
|
int i;
|
|
|
|
if (d == me)
|
|
continue;
|
|
|
|
name = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME);
|
|
|
|
for (i = 0; i < n; i++)
|
|
if (pa_streq(dev_names[i], name))
|
|
pa_idxset_put(idxset, d, NULL);
|
|
}
|
|
}
|
|
|
|
/* Create a property list for this ucm device */
|
|
static int ucm_get_device_property(
|
|
pa_alsa_ucm_device *device,
|
|
snd_use_case_mgr_t *uc_mgr,
|
|
pa_alsa_ucm_verb *verb,
|
|
const char *device_name) {
|
|
|
|
const char *value;
|
|
const char **devices;
|
|
char *id;
|
|
int i;
|
|
int err;
|
|
uint32_t ui;
|
|
int n_confdev, n_suppdev;
|
|
|
|
for (i = 0; item[i].id; i++) {
|
|
id = pa_sprintf_malloc("=%s/%s", item[i].id, device_name);
|
|
err = snd_use_case_get(uc_mgr, id, &value);
|
|
pa_xfree(id);
|
|
if (err < 0)
|
|
continue;
|
|
|
|
pa_log_debug("Got %s for device %s: %s", item[i].id, device_name, value);
|
|
pa_proplist_sets(device->proplist, item[i].property, value);
|
|
free((void*)value);
|
|
}
|
|
|
|
/* get direction and channels */
|
|
value = pa_proplist_gets(device->proplist, PA_ALSA_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_ALSA_PROP_UCM_SINK);
|
|
if (!value) { /* take pcm from verb playback default */
|
|
value = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_SINK);
|
|
if (value) {
|
|
pa_log_debug("UCM playback device %s fetch pcm from verb default %s", device_name, value);
|
|
pa_proplist_sets(device->proplist, PA_ALSA_PROP_UCM_SINK, value);
|
|
} else
|
|
pa_log("UCM playback device %s fetch pcm failed", device_name);
|
|
}
|
|
}
|
|
|
|
value = pa_proplist_gets(device->proplist, PA_ALSA_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_ALSA_PROP_UCM_SOURCE);
|
|
if (!value) { /* take pcm from verb capture default */
|
|
value = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_SOURCE);
|
|
if (value) {
|
|
pa_log_debug("UCM capture device %s fetch pcm from verb default %s", device_name, value);
|
|
pa_proplist_sets(device->proplist, PA_ALSA_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_ALSA_PROP_UCM_PLAYBACK_PRIORITY);
|
|
if (value) {
|
|
/* get priority from ucm config */
|
|
if (pa_atou(value, &ui) == 0)
|
|
device->playback_priority = ui;
|
|
else
|
|
pa_log_debug("UCM playback priority %s for device %s error", value, device_name);
|
|
}
|
|
}
|
|
|
|
if (device->capture_channels) { /* source device */
|
|
value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_CAPTURE_PRIORITY);
|
|
if (value) {
|
|
/* get priority from ucm config */
|
|
if (pa_atou(value, &ui) == 0)
|
|
device->capture_priority = ui;
|
|
else
|
|
pa_log_debug("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 */
|
|
for (i = 0; dev_info[i].id; i++) {
|
|
if (strcasecmp(dev_info[i].id, device_name) == 0) {
|
|
PA_UCM_DEVICE_PRIORITY_SET(device, dev_info[i].priority);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
n_confdev = snd_use_case_get_list(uc_mgr, id, &devices);
|
|
pa_xfree(id);
|
|
|
|
if (n_confdev <= 0)
|
|
pa_log_debug("No %s for device %s", "_conflictingdevs", device_name);
|
|
else {
|
|
device->conflicting_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
|
|
ucm_add_devices_to_idxset(device->conflicting_devices, device, verb->devices, devices, n_confdev);
|
|
snd_use_case_free_list(devices, n_confdev);
|
|
}
|
|
|
|
id = pa_sprintf_malloc("%s/%s", "_supporteddevs", device_name);
|
|
n_suppdev = snd_use_case_get_list(uc_mgr, id, &devices);
|
|
pa_xfree(id);
|
|
|
|
if (n_suppdev <= 0)
|
|
pa_log_debug("No %s for device %s", "_supporteddevs", device_name);
|
|
else {
|
|
device->supported_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
|
|
ucm_add_devices_to_idxset(device->supported_devices, device, verb->devices, devices, n_suppdev);
|
|
snd_use_case_free_list(devices, n_suppdev);
|
|
}
|
|
|
|
return 0;
|
|
};
|
|
|
|
/* Create a property list for this ucm modifier */
|
|
static int ucm_get_modifier_property(pa_alsa_ucm_modifier *modifier, snd_use_case_mgr_t *uc_mgr, const char *modifier_name) {
|
|
const char *value;
|
|
char *id;
|
|
int i;
|
|
|
|
for (i = 0; item[i].id; i++) {
|
|
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 )
|
|
continue;
|
|
|
|
pa_log_debug("Got %s for modifier %s: %s", item[i].id, modifier_name, value);
|
|
pa_proplist_sets(modifier->proplist, item[i].property, value);
|
|
free((void*)value);
|
|
}
|
|
|
|
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_debug("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_debug("No %s for modifier %s", "_supporteddevs", modifier_name);
|
|
|
|
return 0;
|
|
};
|
|
|
|
/* Create a list of devices for this verb */
|
|
static int ucm_get_devices(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 = pa_xnew0(pa_alsa_ucm_device, 1);
|
|
|
|
d->proplist = pa_proplist_new();
|
|
pa_proplist_sets(d->proplist, PA_ALSA_PROP_UCM_NAME, pa_strnull(dev_list[i]));
|
|
pa_proplist_sets(d->proplist, PA_ALSA_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(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;
|
|
|
|
if (!mod_list[i]) {
|
|
pa_log_warn("Got a modifier with a null name. Skipping.");
|
|
continue;
|
|
}
|
|
|
|
m = pa_xnew0(pa_alsa_ucm_modifier, 1);
|
|
m->proplist = pa_proplist_new();
|
|
|
|
pa_proplist_sets(m->proplist, PA_ALSA_PROP_UCM_NAME, mod_list[i]);
|
|
pa_proplist_sets(m->proplist, PA_ALSA_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 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 (!pa_str_in_list_spaces(cur, role)) { /* does not exist */
|
|
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, bool is_sink) {
|
|
pa_alsa_ucm_device *d;
|
|
|
|
PA_LLIST_FOREACH(d, list) {
|
|
const char *dev_name = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME);
|
|
|
|
if (pa_streq(dev_name, name)) {
|
|
const char *sink = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_SINK);
|
|
const char *source = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_SOURCE);
|
|
|
|
if (is_sink && sink)
|
|
add_role_to_device(d, dev_name, role_name, role);
|
|
else if (!is_sink && source)
|
|
add_role_to_device(d, dev_name, role_name, role);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static char *modifier_name_to_role(const char *mod_name, bool *is_sink) {
|
|
char *sub = NULL, *tmp;
|
|
|
|
*is_sink = FALSE;
|
|
|
|
if (pa_startswith(mod_name, "Play")) {
|
|
*is_sink = TRUE;
|
|
sub = pa_xstrdup(mod_name + 4);
|
|
} else if (pa_startswith(mod_name, "Capture"))
|
|
sub = pa_xstrdup(mod_name + 7);
|
|
|
|
if (!sub || !*sub) {
|
|
pa_xfree(sub);
|
|
pa_log_warn("Can't match media roles for modifer %s", mod_name);
|
|
return NULL;
|
|
}
|
|
|
|
tmp = sub;
|
|
|
|
do {
|
|
*tmp = tolower(*tmp);
|
|
} while (*(++tmp));
|
|
|
|
return sub;
|
|
}
|
|
|
|
static void ucm_set_media_roles(pa_alsa_ucm_modifier *modifier, pa_alsa_ucm_device *list, const char *mod_name) {
|
|
int i;
|
|
bool is_sink = FALSE;
|
|
char *sub = NULL;
|
|
const char *role_name;
|
|
|
|
sub = modifier_name_to_role(mod_name, &is_sink);
|
|
if (!sub)
|
|
return;
|
|
|
|
modifier->action_direction = is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT;
|
|
modifier->media_role = sub;
|
|
|
|
role_name = is_sink ? PA_ALSA_PROP_UCM_PLAYBACK_ROLES : PA_ALSA_PROP_UCM_CAPTURE_ROLES;
|
|
for (i = 0; i < modifier->n_suppdev; i++) {
|
|
/* if modifier has no specific pcm, we add role intent to its supported devices */
|
|
if (!pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_SINK) &&
|
|
!pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_SOURCE))
|
|
add_media_role(modifier->supported_devices[i], list, role_name, sub, is_sink);
|
|
}
|
|
}
|
|
|
|
static void append_lost_relationship(pa_alsa_ucm_device *dev) {
|
|
uint32_t idx;
|
|
pa_alsa_ucm_device *d;
|
|
|
|
if (dev->conflicting_devices) {
|
|
PA_IDXSET_FOREACH(d, dev->conflicting_devices, idx) {
|
|
if (!d->conflicting_devices)
|
|
d->conflicting_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
|
|
|
|
if (pa_idxset_put(d->conflicting_devices, dev, NULL) == 0)
|
|
pa_log_warn("Add lost conflicting device %s to %s",
|
|
pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME),
|
|
pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME));
|
|
}
|
|
}
|
|
|
|
if (dev->supported_devices) {
|
|
PA_IDXSET_FOREACH(d, dev->supported_devices, idx) {
|
|
if (!d->supported_devices)
|
|
d->supported_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
|
|
|
|
if (pa_idxset_put(d->supported_devices, dev, NULL) == 0)
|
|
pa_log_warn("Add lost supported device %s to %s",
|
|
pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME),
|
|
pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME));
|
|
}
|
|
}
|
|
}
|
|
|
|
int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index)
|
|
{
|
|
char *card_name;
|
|
const char **verb_list;
|
|
int num_verbs, i, err = 0;
|
|
|
|
/* is UCM available for this card ? */
|
|
err = snd_card_get_name(card_index, &card_name);
|
|
if (err < 0) {
|
|
pa_log("Card can't get card_name from card_index %d", card_index);
|
|
goto name_fail;
|
|
}
|
|
|
|
err = snd_use_case_mgr_open(&ucm->ucm_mgr, card_name);
|
|
if (err < 0) {
|
|
pa_log("UCM not available for card %s", card_name);
|
|
goto ucm_mgr_fail;
|
|
}
|
|
|
|
pa_log_info("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(ucm->ucm_mgr, &verb_list);
|
|
if (num_verbs < 0) {
|
|
pa_log("UCM verb list not found for %s", card_name);
|
|
goto ucm_verb_fail;
|
|
}
|
|
|
|
/* get the properties of each UCM verb */
|
|
for (i = 0; i < num_verbs; i += 2) {
|
|
pa_alsa_ucm_verb *verb;
|
|
|
|
/* Get devices and modifiers for each verb */
|
|
err = pa_alsa_ucm_get_verb(ucm->ucm_mgr, verb_list[i], verb_list[i+1], &verb);
|
|
if (err < 0) {
|
|
pa_log("Failed to get the verb %s", verb_list[i]);
|
|
continue;
|
|
}
|
|
|
|
PA_LLIST_PREPEND(pa_alsa_ucm_verb, ucm->verbs, verb);
|
|
}
|
|
|
|
if (!ucm->verbs) {
|
|
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(ucm->ucm_mgr);
|
|
ucm->ucm_mgr = NULL;
|
|
}
|
|
|
|
ucm_mgr_fail:
|
|
free(card_name);
|
|
|
|
name_fail:
|
|
return err;
|
|
}
|
|
|
|
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) {
|
|
pa_alsa_ucm_device *d;
|
|
pa_alsa_ucm_modifier *mod;
|
|
pa_alsa_ucm_verb *verb;
|
|
int err = 0;
|
|
|
|
*p_verb = NULL;
|
|
pa_log_info("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_ALSA_PROP_UCM_NAME, pa_strnull(verb_name));
|
|
pa_proplist_sets(verb->proplist, PA_ALSA_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_ALSA_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)
|
|
append_lost_relationship(d);
|
|
|
|
PA_LLIST_FOREACH(mod, verb->modifiers) {
|
|
const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_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_debug("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_ucm_mapping_context *context,
|
|
bool is_sink,
|
|
pa_alsa_ucm_device **pdevices,
|
|
int num,
|
|
pa_hashmap *ports,
|
|
pa_card_profile *cp,
|
|
pa_core *core) {
|
|
|
|
pa_device_port *port;
|
|
int i;
|
|
unsigned priority;
|
|
char *name, *desc;
|
|
const char *dev_name;
|
|
const char *direction;
|
|
pa_alsa_ucm_device *dev;
|
|
|
|
dev = pdevices[0];
|
|
dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
|
|
|
|
name = pa_sprintf_malloc("%s%s", is_sink ? PA_UCM_PRE_TAG_OUTPUT : PA_UCM_PRE_TAG_INPUT, dev_name);
|
|
desc = num == 1 ? pa_xstrdup(pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_DESCRIPTION))
|
|
: pa_sprintf_malloc("Combination port for %s", dev_name);
|
|
|
|
priority = is_sink ? dev->playback_priority : dev->capture_priority;
|
|
|
|
for (i = 1; i < num; i++) {
|
|
char *tmp;
|
|
|
|
dev = pdevices[i];
|
|
dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_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 this true? */
|
|
priority += (is_sink ? dev->playback_priority : dev->capture_priority);
|
|
}
|
|
|
|
port = pa_hashmap_get(ports, name);
|
|
if (!port) {
|
|
port = pa_device_port_new(core, pa_strna(name), desc, 0);
|
|
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);
|
|
}
|
|
|
|
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_port_contains(const char *port_name, const char *dev_name, bool is_sink) {
|
|
int ret = 0;
|
|
const char *r;
|
|
const char *state = NULL;
|
|
int len;
|
|
|
|
if (!port_name || !dev_name)
|
|
return FALSE;
|
|
|
|
port_name += is_sink ? strlen(PA_UCM_PRE_TAG_OUTPUT) : strlen(PA_UCM_PRE_TAG_INPUT);
|
|
|
|
while ((r = pa_split_in_place(port_name, "+", &len, &state))) {
|
|
if (!strncmp(r, dev_name, len)) {
|
|
ret = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ucm_check_conformance(
|
|
pa_alsa_ucm_mapping_context *context,
|
|
pa_alsa_ucm_device **pdevices,
|
|
int dev_num,
|
|
pa_alsa_ucm_device *dev) {
|
|
|
|
uint32_t idx;
|
|
pa_alsa_ucm_device *d;
|
|
int i;
|
|
|
|
pa_assert(dev);
|
|
|
|
pa_log_debug("Check device %s conformance with %d other devices",
|
|
pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME), dev_num);
|
|
if (dev_num == 0) {
|
|
pa_log_debug("First device in combination, number 1");
|
|
return 1;
|
|
}
|
|
|
|
if (dev->conflicting_devices) { /* the device defines conflicting devices */
|
|
PA_IDXSET_FOREACH(d, dev->conflicting_devices, idx) {
|
|
for (i = 0; i < dev_num; i++) {
|
|
if (pdevices[i] == d) {
|
|
pa_log_debug("Conflicting device found");
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
} else if (dev->supported_devices) { /* the device defines supported devices */
|
|
for (i = 0; i < dev_num; i++) {
|
|
if (!ucm_device_exists(dev->supported_devices, pdevices[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;
|
|
}
|
|
|
|
static inline pa_alsa_ucm_device *get_next_device(pa_idxset *idxset, uint32_t *idx) {
|
|
pa_alsa_ucm_device *dev;
|
|
|
|
if (*idx == PA_IDXSET_INVALID)
|
|
dev = pa_idxset_first(idxset, idx);
|
|
else
|
|
dev = pa_idxset_next(idxset, idx);
|
|
|
|
return dev;
|
|
}
|
|
|
|
static void ucm_add_ports_combination(
|
|
pa_hashmap *hash,
|
|
pa_alsa_ucm_mapping_context *context,
|
|
bool is_sink,
|
|
pa_alsa_ucm_device **pdevices,
|
|
int dev_num,
|
|
uint32_t map_index,
|
|
pa_hashmap *ports,
|
|
pa_card_profile *cp,
|
|
pa_core *core) {
|
|
|
|
pa_alsa_ucm_device *dev;
|
|
uint32_t idx = map_index;
|
|
|
|
if ((dev = get_next_device(context->ucm_devices, &idx)) == NULL)
|
|
return;
|
|
|
|
/* check if device at map_index can combine with existing devices combination */
|
|
if (ucm_check_conformance(context, pdevices, dev_num, dev)) {
|
|
/* add device at map_index to devices combination */
|
|
pdevices[dev_num] = dev;
|
|
/* add current devices combination as a new port */
|
|
ucm_add_port_combination(hash, context, is_sink, pdevices, dev_num + 1, ports, cp, core);
|
|
/* try more elements combination */
|
|
ucm_add_ports_combination(hash, context, is_sink, pdevices, dev_num + 1, idx, ports, cp, core);
|
|
}
|
|
|
|
/* try other device with current elements number */
|
|
ucm_add_ports_combination(hash, context, is_sink, pdevices, dev_num, idx, ports, cp, core);
|
|
}
|
|
|
|
static char* merge_roles(const char *cur, const char *add) {
|
|
char *r, *ret;
|
|
const char *state = NULL;
|
|
|
|
if (add == NULL)
|
|
return pa_xstrdup(cur);
|
|
else if (cur == NULL)
|
|
return pa_xstrdup(add);
|
|
|
|
ret = pa_xstrdup(cur);
|
|
|
|
while ((r = pa_split_spaces(add, &state))) {
|
|
char *value;
|
|
|
|
if (!pa_str_in_list_spaces(ret, 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 pa_alsa_ucm_add_ports_combination(
|
|
pa_hashmap *p,
|
|
pa_alsa_ucm_mapping_context *context,
|
|
bool is_sink,
|
|
pa_hashmap *ports,
|
|
pa_card_profile *cp,
|
|
pa_core *core) {
|
|
|
|
pa_alsa_ucm_device **pdevices;
|
|
|
|
pa_assert(context->ucm_devices);
|
|
|
|
if (pa_idxset_size(context->ucm_devices) > 0) {
|
|
pdevices = pa_xnew(pa_alsa_ucm_device *, pa_idxset_size(context->ucm_devices));
|
|
ucm_add_ports_combination(p, context, is_sink, pdevices, 0, PA_IDXSET_INVALID, ports, cp, core);
|
|
pa_xfree(pdevices);
|
|
}
|
|
}
|
|
|
|
void pa_alsa_ucm_add_ports(
|
|
pa_hashmap **p,
|
|
pa_proplist *proplist,
|
|
pa_alsa_ucm_mapping_context *context,
|
|
bool is_sink,
|
|
pa_card *card) {
|
|
|
|
uint32_t idx;
|
|
char *merged_roles;
|
|
const char *role_name = is_sink ? PA_ALSA_PROP_UCM_PLAYBACK_ROLES : PA_ALSA_PROP_UCM_CAPTURE_ROLES;
|
|
pa_alsa_ucm_device *dev;
|
|
pa_alsa_ucm_modifier *mod;
|
|
char *tmp;
|
|
|
|
pa_assert(p);
|
|
pa_assert(*p);
|
|
|
|
/* add ports first */
|
|
pa_alsa_ucm_add_ports_combination(*p, context, is_sink, card->ports, NULL, card->core);
|
|
|
|
/* then set property PA_PROP_DEVICE_INTENDED_ROLES */
|
|
merged_roles = pa_xstrdup(pa_proplist_gets(proplist, PA_PROP_DEVICE_INTENDED_ROLES));
|
|
PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) {
|
|
const char *roles = pa_proplist_gets(dev->proplist, role_name);
|
|
tmp = merge_roles(merged_roles, roles);
|
|
pa_xfree(merged_roles);
|
|
merged_roles = tmp;
|
|
}
|
|
|
|
if (context->ucm_modifiers)
|
|
PA_IDXSET_FOREACH(mod, context->ucm_modifiers, idx) {
|
|
tmp = merge_roles(merged_roles, mod->media_role);
|
|
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", pa_proplist_gets(proplist, PA_PROP_DEVICE_STRING), pa_strnull(merged_roles));
|
|
pa_xfree(merged_roles);
|
|
}
|
|
|
|
/* Change UCM verb and device to match selected card profile */
|
|
int pa_alsa_ucm_set_profile(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 (!pa_streq(new_profile, old_profile))
|
|
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_ALSA_PROP_UCM_NAME);
|
|
if (pa_streq(verb_name, profile)) {
|
|
ucm->active_verb = verb;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port, bool is_sink) {
|
|
int i;
|
|
int ret = 0;
|
|
pa_alsa_ucm_config *ucm;
|
|
const char **enable_devs;
|
|
int enable_num = 0;
|
|
uint32_t idx;
|
|
pa_alsa_ucm_device *dev;
|
|
|
|
pa_assert(context && context->ucm);
|
|
|
|
ucm = context->ucm;
|
|
pa_assert(ucm->ucm_mgr);
|
|
|
|
enable_devs = pa_xnew(const char *, pa_idxset_size(context->ucm_devices));
|
|
|
|
/* first disable then enable */
|
|
PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) {
|
|
const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
|
|
|
|
if (ucm_port_contains(port->name, dev_name, is_sink))
|
|
enable_devs[enable_num++] = dev_name;
|
|
else {
|
|
pa_log_debug("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;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < enable_num; i++) {
|
|
pa_log_debug("Enable ucm device %s", enable_devs[i]);
|
|
if (snd_use_case_set(ucm->ucm_mgr, "_enadev", enable_devs[i]) < 0) {
|
|
pa_log("Failed to enable ucm device %s", enable_devs[i]);
|
|
ret = -1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
pa_idxset_put(m->ucm_context.ucm_devices, device, NULL);
|
|
|
|
new_desc = pa_proplist_gets(device->proplist, PA_ALSA_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("");
|
|
|
|
/* save mapping to ucm device */
|
|
if (m->direction == PA_ALSA_DIRECTION_OUTPUT)
|
|
device->playback_mapping = m;
|
|
else
|
|
device->capture_mapping = m;
|
|
}
|
|
|
|
static int ucm_create_mapping_direction(
|
|
pa_alsa_ucm_config *ucm,
|
|
pa_alsa_profile_set *ps,
|
|
pa_alsa_profile *p,
|
|
pa_alsa_ucm_device *device,
|
|
const char *verb_name,
|
|
const char *device_name,
|
|
const char *device_str,
|
|
bool 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 = pa_alsa_mapping_get(ps, mapping_name);
|
|
if (!m) {
|
|
pa_log("No mapping for %s", mapping_name);
|
|
pa_xfree(mapping_name);
|
|
return -1;
|
|
}
|
|
pa_log_debug("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) { /* new mapping */
|
|
m->ucm_context.ucm_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
|
|
m->ucm_context.ucm = ucm;
|
|
m->ucm_context.direction = is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT;
|
|
|
|
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(
|
|
pa_alsa_ucm_config *ucm,
|
|
pa_alsa_profile_set *ps,
|
|
pa_alsa_profile *p,
|
|
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, TRUE);
|
|
if (ret == 0 && source)
|
|
ret = ucm_create_mapping_direction(ucm, ps, p, device, verb_name, device_name, source, FALSE);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ucm_create_profile(
|
|
pa_alsa_ucm_config *ucm,
|
|
pa_alsa_profile_set *ps,
|
|
pa_alsa_ucm_verb *verb,
|
|
const char *verb_name,
|
|
const char *verb_desc) {
|
|
|
|
pa_alsa_profile *p;
|
|
pa_alsa_ucm_device *dev;
|
|
int i = 0;
|
|
const char *name, *sink, *source;
|
|
char *verb_cmp, *c;
|
|
|
|
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);
|
|
|
|
p->supported = TRUE;
|
|
pa_hashmap_put(ps->profiles, p->name, p);
|
|
|
|
/* TODO: get profile priority from ucm info or policy management */
|
|
c = verb_cmp = pa_xstrdup(verb_name);
|
|
while (*c) {
|
|
if (*c == '_') *c = ' ';
|
|
c++;
|
|
}
|
|
|
|
for (i = 0; verb_info[i].id; i++) {
|
|
if (strcasecmp(verb_info[i].id, verb_cmp) == 0) {
|
|
p->priority = verb_info[i].priority;
|
|
break;
|
|
}
|
|
}
|
|
|
|
pa_xfree(verb_cmp);
|
|
|
|
if (verb_info[i].id == NULL)
|
|
p->priority = 1000;
|
|
|
|
PA_LLIST_FOREACH(dev, verb->devices) {
|
|
name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
|
|
|
|
sink = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SINK);
|
|
source = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SOURCE);
|
|
|
|
ucm_create_mapping(ucm, ps, p, dev, verb_name, name, sink, source);
|
|
}
|
|
|
|
pa_alsa_profile_dump(p);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, int mode) {
|
|
snd_pcm_t* pcm;
|
|
pa_sample_spec try_ss = ucm->core->default_sample_spec;
|
|
pa_channel_map try_map;
|
|
snd_pcm_uframes_t try_period_size, try_buffer_size;
|
|
bool exact_channels = m->channel_map.channels > 0;
|
|
|
|
if (exact_channels) {
|
|
try_map = m->channel_map;
|
|
try_ss.channels = try_map.channels;
|
|
} else
|
|
pa_channel_map_init_extend(&try_map, try_ss.channels, PA_CHANNEL_MAP_ALSA);
|
|
|
|
try_period_size =
|
|
pa_usec_to_bytes(ucm->core->default_fragment_size_msec * PA_USEC_PER_MSEC, &try_ss) /
|
|
pa_frame_size(&try_ss);
|
|
try_buffer_size = ucm->core->default_n_fragments * try_period_size;
|
|
|
|
pcm = pa_alsa_open_by_device_string(m->device_strings[0], NULL, &try_ss,
|
|
&try_map, mode, &try_period_size, &try_buffer_size, 0, NULL, NULL, exact_channels);
|
|
|
|
if (pcm && !exact_channels)
|
|
m->channel_map = try_map;
|
|
|
|
return pcm;
|
|
}
|
|
|
|
static void profile_finalize_probing(pa_alsa_profile *p) {
|
|
pa_alsa_mapping *m;
|
|
uint32_t idx;
|
|
|
|
PA_IDXSET_FOREACH(m, p->output_mappings, idx) {
|
|
if (!m->output_pcm)
|
|
continue;
|
|
|
|
if (p->supported)
|
|
m->supported++;
|
|
|
|
snd_pcm_close(m->output_pcm);
|
|
m->output_pcm = NULL;
|
|
}
|
|
|
|
PA_IDXSET_FOREACH(m, p->input_mappings, idx) {
|
|
if (!m->input_pcm)
|
|
continue;
|
|
|
|
if (p->supported)
|
|
m->supported++;
|
|
|
|
snd_pcm_close(m->input_pcm);
|
|
m->input_pcm = NULL;
|
|
}
|
|
}
|
|
|
|
static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps) {
|
|
void *state;
|
|
pa_alsa_profile *p;
|
|
pa_alsa_mapping *m;
|
|
uint32_t idx;
|
|
|
|
PA_HASHMAP_FOREACH(p, ps->profiles, state) {
|
|
/* change verb */
|
|
pa_log_info("Set ucm verb to %s", p->name);
|
|
if ((snd_use_case_set(ucm->ucm_mgr, "_verb", p->name)) < 0) {
|
|
pa_log("Failed to set verb %s", p->name);
|
|
p->supported = FALSE;
|
|
continue;
|
|
}
|
|
PA_IDXSET_FOREACH(m, p->output_mappings, idx) {
|
|
m->output_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_PLAYBACK);
|
|
if (!m->output_pcm) {
|
|
p->supported = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
if (p->supported) {
|
|
PA_IDXSET_FOREACH(m, p->input_mappings, idx) {
|
|
m->input_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_CAPTURE);
|
|
if (!m->input_pcm) {
|
|
p->supported = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!p->supported) {
|
|
profile_finalize_probing(p);
|
|
continue;
|
|
}
|
|
|
|
pa_log_debug("Profile %s supported.", p->name);
|
|
|
|
profile_finalize_probing(p);
|
|
}
|
|
|
|
/* restore ucm state */
|
|
snd_use_case_set(ucm->ucm_mgr, "_verb", SND_USE_CASE_VERB_INACTIVE);
|
|
|
|
pa_alsa_profile_set_drop_unsupported(ps);
|
|
}
|
|
|
|
pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map) {
|
|
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_ALSA_PROP_UCM_NAME);
|
|
verb_desc = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_DESCRIPTION);
|
|
if (verb_name == NULL) {
|
|
pa_log("Verb with no name");
|
|
continue;
|
|
}
|
|
|
|
ucm_create_profile(ucm, ps, verb, verb_name, verb_desc);
|
|
}
|
|
|
|
ucm_probe_profile_set(ucm, ps);
|
|
ps->probed = TRUE;
|
|
|
|
return ps;
|
|
}
|
|
|
|
static void free_verb(pa_alsa_ucm_verb *verb) {
|
|
pa_alsa_ucm_device *di, *dn;
|
|
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->conflicting_devices)
|
|
pa_idxset_free(di->conflicting_devices, NULL, NULL);
|
|
if (di->supported_devices)
|
|
pa_idxset_free(di->supported_devices, NULL, NULL);
|
|
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 pa_alsa_ucm_free(pa_alsa_ucm_config *ucm) {
|
|
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 pa_alsa_ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context) {
|
|
pa_alsa_ucm_device *dev;
|
|
uint32_t idx;
|
|
|
|
if (context->ucm_devices) {
|
|
/* clear ucm device pointer to mapping */
|
|
PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) {
|
|
if (context->direction == PA_DIRECTION_OUTPUT)
|
|
dev->playback_mapping = NULL;
|
|
else
|
|
dev->capture_mapping = NULL;
|
|
}
|
|
|
|
pa_idxset_free(context->ucm_devices, NULL, NULL);
|
|
}
|
|
|
|
if (context->ucm_modifiers) {
|
|
pa_idxset_free(context->ucm_modifiers, NULL, NULL);
|
|
}
|
|
}
|