pipewire/spa/plugins/alsa/acp/alsa-ucm.c

2912 lines
96 KiB
C
Raw Normal View History

/***
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, see <http://www.gnu.org/licenses/>.
***/
#include "config.h"
#include <ctype.h>
#include <sys/types.h>
#include <limits.h>
#include <alsa/asoundlib.h>
#ifdef HAVE_VALGRIND_MEMCHECK_H
#include <valgrind/memcheck.h>
#endif
#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)
#define PA_UCM_IS_MODIFIER_MAPPING(m) ((pa_proplist_gets((m)->proplist, PA_ALSA_PROP_UCM_MODIFIER)) != NULL)
#ifdef HAVE_ALSA_UCM
struct ucm_type {
const char *prefix;
pa_device_port_type_t type;
};
struct ucm_items {
const char *id;
const char *property;
};
struct ucm_info {
const char *id;
unsigned priority;
};
static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *device);
static void device_set_jack(pa_alsa_ucm_device *device, pa_alsa_jack *jack);
static void device_add_hw_mute_jack(pa_alsa_ucm_device *device, pa_alsa_jack *jack);
static pa_alsa_ucm_device *verb_find_device(pa_alsa_ucm_verb *verb, const char *device_name);
static void ucm_port_data_init(pa_alsa_ucm_port_data *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port,
pa_alsa_ucm_device *device);
static void ucm_port_data_free(pa_device_port *port);
static struct ucm_type types[] = {
{"None", PA_DEVICE_PORT_TYPE_UNKNOWN},
{"Speaker", PA_DEVICE_PORT_TYPE_SPEAKER},
{"Line", PA_DEVICE_PORT_TYPE_LINE},
{"Mic", PA_DEVICE_PORT_TYPE_MIC},
{"Headphones", PA_DEVICE_PORT_TYPE_HEADPHONES},
{"Headset", PA_DEVICE_PORT_TYPE_HEADSET},
{"Handset", PA_DEVICE_PORT_TYPE_HANDSET},
{"Bluetooth", PA_DEVICE_PORT_TYPE_BLUETOOTH},
{"Earpiece", PA_DEVICE_PORT_TYPE_EARPIECE},
{"SPDIF", PA_DEVICE_PORT_TYPE_SPDIF},
{"HDMI", PA_DEVICE_PORT_TYPE_HDMI},
{NULL, 0}
};
static struct ucm_items item[] = {
{"PlaybackPCM", PA_ALSA_PROP_UCM_SINK},
{"CapturePCM", PA_ALSA_PROP_UCM_SOURCE},
{"PlaybackCTL", PA_ALSA_PROP_UCM_PLAYBACK_CTL_DEVICE},
{"PlaybackVolume", PA_ALSA_PROP_UCM_PLAYBACK_VOLUME},
{"PlaybackSwitch", PA_ALSA_PROP_UCM_PLAYBACK_SWITCH},
{"PlaybackMixer", PA_ALSA_PROP_UCM_PLAYBACK_MIXER_DEVICE},
{"PlaybackMixerElem", PA_ALSA_PROP_UCM_PLAYBACK_MIXER_ELEM},
{"PlaybackMasterElem", PA_ALSA_PROP_UCM_PLAYBACK_MASTER_ELEM},
{"PlaybackMasterType", PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE},
{"PlaybackPriority", PA_ALSA_PROP_UCM_PLAYBACK_PRIORITY},
{"PlaybackRate", PA_ALSA_PROP_UCM_PLAYBACK_RATE},
{"PlaybackChannels", PA_ALSA_PROP_UCM_PLAYBACK_CHANNELS},
{"CaptureCTL", PA_ALSA_PROP_UCM_CAPTURE_CTL_DEVICE},
{"CaptureVolume", PA_ALSA_PROP_UCM_CAPTURE_VOLUME},
{"CaptureSwitch", PA_ALSA_PROP_UCM_CAPTURE_SWITCH},
{"CaptureMixer", PA_ALSA_PROP_UCM_CAPTURE_MIXER_DEVICE},
{"CaptureMixerElem", PA_ALSA_PROP_UCM_CAPTURE_MIXER_ELEM},
{"CaptureMasterElem", PA_ALSA_PROP_UCM_CAPTURE_MASTER_ELEM},
{"CaptureMasterType", PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE},
{"CapturePriority", PA_ALSA_PROP_UCM_CAPTURE_PRIORITY},
{"CaptureRate", PA_ALSA_PROP_UCM_CAPTURE_RATE},
{"CaptureChannels", PA_ALSA_PROP_UCM_CAPTURE_CHANNELS},
{"TQ", PA_ALSA_PROP_UCM_QOS},
{"JackCTL", PA_ALSA_PROP_UCM_JACK_DEVICE},
{"JackControl", PA_ALSA_PROP_UCM_JACK_CONTROL},
{"JackHWMute", PA_ALSA_PROP_UCM_JACK_HW_MUTE},
{NULL, NULL},
};
/* UCM verb info - this should eventually be part of policy management */
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}
};
static char *ucm_verb_value(
snd_use_case_mgr_t *uc_mgr,
const char *verb_name,
const char *id) {
const char *value;
char *_id = pa_sprintf_malloc("=%s//%s", id, verb_name);
int err = snd_use_case_get(uc_mgr, _id, &value);
pa_xfree(_id);
if (err < 0)
return NULL;
pa_log_debug("Got %s for verb %s: %s", id, verb_name, value);
/* Use the cast here to allow free() call without casting for callers.
* The snd_use_case_get() returns mallocated string.
* See the Note: in use-case.h for snd_use_case_get().
*/
return (char *)value;
}
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);
}
}
/* Split a string into words. Like pa_split_spaces() but handle '' and "". */
static char *ucm_split_devnames(const char *c, const char **state) {
const char *current = *state ? *state : c;
char h;
size_t l;
if (!*current || *c == 0)
return NULL;
current += strspn(current, "\n\r \t");
h = *current;
if (h == '\'' || h =='"') {
c = ++current;
for (l = 0; *c && *c != h; l++) c++;
if (*c != h)
return NULL;
*state = c + 1;
} else {
l = strcspn(current, "\n\r \t");
*state = current+l;
}
return pa_xstrndup(current, l);
}
static void ucm_volume_free(pa_alsa_ucm_volume *vol) {
pa_assert(vol);
pa_xfree(vol->mixer_elem);
pa_xfree(vol->master_elem);
pa_xfree(vol->master_type);
pa_xfree(vol);
}
/* Get the volume identifier */
static char *ucm_get_mixer_id(
pa_alsa_ucm_device *device,
const char *mprop,
const char *cprop,
const char *cid)
{
#if SND_LIB_VERSION >= 0x10201 /* alsa-lib-1.2.1+ check */
snd_ctl_elem_id_t *ctl;
int err;
#endif
const char *value;
char *value2;
int index;
/* mixer element as first, if it's found, return it without modifications */
value = pa_proplist_gets(device->proplist, mprop);
if (value)
return pa_xstrdup(value);
/* fallback, get the control element identifier */
/* and try to do some heuristic to determine the mixer element name */
value = pa_proplist_gets(device->proplist, cprop);
if (value == NULL)
return NULL;
#if SND_LIB_VERSION >= 0x10201 /* alsa-lib-1.2.1+ check */
/* The new parser may return also element index. */
snd_ctl_elem_id_alloca(&ctl);
err = snd_use_case_parse_ctl_elem_id(ctl, cid, value);
if (err < 0)
return NULL;
value = snd_ctl_elem_id_get_name(ctl);
index = snd_ctl_elem_id_get_index(ctl);
#else
#warning "Upgrade to alsa-lib 1.2.1!"
index = 0;
#endif
if (!(value2 = pa_str_strip_suffix(value, " Playback Volume")))
if (!(value2 = pa_str_strip_suffix(value, " Capture Volume")))
if (!(value2 = pa_str_strip_suffix(value, " Volume")))
value2 = pa_xstrdup(value);
if (index > 0) {
char *mix = pa_sprintf_malloc("'%s',%d", value2, index);
pa_xfree(value2);
return mix;
}
return value2;
}
/* Get the volume identifier */
static pa_alsa_ucm_volume *ucm_get_mixer_volume(
pa_alsa_ucm_device *device,
const char *mprop,
const char *cprop,
const char *cid,
const char *masterid,
const char *mastertype)
{
pa_alsa_ucm_volume *vol;
char *mixer_elem;
mixer_elem = ucm_get_mixer_id(device, mprop, cprop, cid);
if (mixer_elem == NULL)
return NULL;
vol = pa_xnew0(pa_alsa_ucm_volume, 1);
if (vol == NULL) {
pa_xfree(mixer_elem);
return NULL;
}
vol->mixer_elem = mixer_elem;
vol->master_elem = pa_xstrdup(pa_proplist_gets(device->proplist, masterid));
vol->master_type = pa_xstrdup(pa_proplist_gets(device->proplist, mastertype));
return vol;
}
/* Get the ALSA mixer device for the UCM device */
static const char *get_mixer_device(pa_alsa_ucm_device *dev, bool is_sink)
{
const char *dev_name;
if (is_sink) {
dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_PLAYBACK_MIXER_DEVICE);
if (!dev_name)
dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_PLAYBACK_CTL_DEVICE);
} else {
dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_CAPTURE_MIXER_DEVICE);
if (!dev_name)
dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_CAPTURE_CTL_DEVICE);
}
return dev_name;
}
/* Get the ALSA mixer device for the UCM jack */
static const char *get_jack_mixer_device(pa_alsa_ucm_device *dev, bool is_sink) {
const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_JACK_DEVICE);
if (!dev_name)
return get_mixer_device(dev, is_sink);
return dev_name;
}
static PA_PRINTF_FUNC(2,3) const char *ucm_get_string(snd_use_case_mgr_t *uc_mgr, const char *fmt, ...)
{
char *id;
const char *value;
va_list args;
int err;
va_start(args, fmt);
id = pa_vsprintf_malloc(fmt, args);
va_end(args);
err = snd_use_case_get(uc_mgr, id, &value);
if (err >= 0)
pa_log_debug("Got %s: %s", id, value);
pa_xfree(id);
if (err < 0) {
errno = -err;
return NULL;
}
return value;
}
static pa_alsa_ucm_split *ucm_get_split_channels(pa_alsa_ucm_device *device, snd_use_case_mgr_t *uc_mgr, const char *prefix) {
pa_alsa_ucm_split *split;
const char *value;
const char *device_name;
int i;
uint32_t hw_channels;
device_name = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_NAME);
if (!device_name)
return NULL;
value = ucm_get_string(uc_mgr, "%sChannels/%s", prefix, device_name);
if (pa_atou(value, &hw_channels) < 0)
return NULL;
split = pa_xnew0(pa_alsa_ucm_split, 1);
for (i = 0; i < PA_CHANNELS_MAX; i++) {
uint32_t idx;
snd_pcm_chmap_t *map;
value = ucm_get_string(uc_mgr, "%sChannel%d/%s", prefix, i, device_name);
if (pa_atou(value, &idx) < 0)
break;
if (idx >= hw_channels)
goto fail;
value = ucm_get_string(uc_mgr, "%sChannelPos%d/%s", prefix, i, device_name);
if (!value)
goto fail;
map = snd_pcm_chmap_parse_string(value);
if (!map)
goto fail;
if (map->channels == 1) {
pa_log_debug("Split %s channel %d -> device %s channel %d: %s (%d)",
prefix, (int)idx, device_name, i, value, map->pos[0]);
split->idx[i] = idx;
split->pos[i] = map->pos[0];
free(map);
} else {
free(map);
goto fail;
}
}
if (i == 0) {
pa_xfree(split);
return NULL;
}
split->channels = i;
split->hw_channels = hw_channels;
return split;
fail:
pa_log_warn("Invalid SplitPCM ALSA UCM rule for device %s", device_name);
pa_xfree(split);
return 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, *s;
int i;
int err;
uint32_t ui;
int n_confdev, n_suppdev;
pa_alsa_ucm_volume *vol;
/* determine the device type */
device->type = PA_DEVICE_PORT_TYPE_UNKNOWN;
id = s = pa_xstrdup(device_name);
while (s && *s && isalpha(*s)) s++;
if (s)
*s = '\0';
for (i = 0; types[i].prefix; i++)
if (pa_streq(id, types[i].prefix)) {
device->type = types[i].type;
break;
}
pa_xfree(id);
/* set properties */
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 && pa_channels_valid(ui))
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 */
pa_log("UCM playback device %s fetch pcm failed", device_name);
}
if (pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SINK) &&
device->playback_channels == 0) {
pa_log_info("UCM file does not specify 'PlaybackChannels' "
"for device %s, assuming stereo.", device_name);
device->playback_channels = 2;
}
value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_CAPTURE_CHANNELS);
if (value) { /* input */
/* get channels */
if (pa_atou(value, &ui) == 0 && pa_channels_valid(ui))
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 */
pa_log("UCM capture device %s fetch pcm failed", device_name);
}
if (pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SOURCE) &&
device->capture_channels == 0) {
pa_log_info("UCM file does not specify 'CaptureChannels' "
"for device %s, assuming stereo.", device_name);
device->capture_channels = 2;
}
/* get rate and priority of device */
if (device->playback_channels) { /* sink device */
/* get rate */
if ((value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_PLAYBACK_RATE))) {
if (pa_atou(value, &ui) == 0 && pa_sample_rate_valid(ui)) {
pa_log_debug("UCM playback device %s rate %d", device_name, ui);
device->playback_rate = ui;
} else
pa_log_debug("UCM playback device %s has bad rate %s", device_name, value);
}
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);
}
vol = ucm_get_mixer_volume(device,
PA_ALSA_PROP_UCM_PLAYBACK_MIXER_ELEM,
PA_ALSA_PROP_UCM_PLAYBACK_VOLUME,
"PlaybackVolume",
PA_ALSA_PROP_UCM_PLAYBACK_MASTER_ELEM,
PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE);
if (vol)
pa_hashmap_put(device->playback_volumes, pa_xstrdup(pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME)), vol);
}
if (device->capture_channels) { /* source device */
/* get rate */
if ((value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_CAPTURE_RATE))) {
if (pa_atou(value, &ui) == 0 && pa_sample_rate_valid(ui)) {
pa_log_debug("UCM capture device %s rate %d", device_name, ui);
device->capture_rate = ui;
} else
pa_log_debug("UCM capture device %s has bad rate %s", device_name, value);
}
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);
}
vol = ucm_get_mixer_volume(device,
PA_ALSA_PROP_UCM_CAPTURE_MIXER_ELEM,
PA_ALSA_PROP_UCM_CAPTURE_VOLUME,
"CaptureVolume",
PA_ALSA_PROP_UCM_CAPTURE_MASTER_ELEM,
PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE);
if (vol)
pa_hashmap_put(device->capture_volumes, pa_xstrdup(pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME)), vol);
}
device->playback_split = ucm_get_split_channels(device, uc_mgr, "Playback");
device->capture_split = ucm_get_split_channels(device, uc_mgr, "Capture");
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);
device->conflicting_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
if (n_confdev <= 0)
pa_log_debug("No %s for device %s", "_conflictingdevs", device_name);
else {
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);
device->supported_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
if (n_suppdev <= 0)
pa_log_debug("No %s for device %s", "_supporteddevs", device_name);
else {
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,
pa_alsa_ucm_verb *verb,
const char *modifier_name) {
const char *value;
char *id;
int i;
const char **devices;
int n_confdev, n_suppdev;
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);
n_confdev = snd_use_case_get_list(uc_mgr, id, &devices);
pa_xfree(id);
modifier->conflicting_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
if (n_confdev <= 0)
pa_log_debug("No %s for modifier %s", "_conflictingdevs", modifier_name);
else {
ucm_add_devices_to_idxset(modifier->conflicting_devices, NULL, verb->devices, devices, n_confdev);
snd_use_case_free_list(devices, n_confdev);
}
id = pa_sprintf_malloc("%s/%s", "_supporteddevs", modifier_name);
n_suppdev = snd_use_case_get_list(uc_mgr, id, &devices);
pa_xfree(id);
modifier->supported_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
if (n_suppdev <= 0)
pa_log_debug("No %s for modifier %s", "_supporteddevs", modifier_name);
else {
ucm_add_devices_to_idxset(modifier->supported_devices, NULL, verb->devices, devices, n_suppdev);
snd_use_case_free_list(devices, n_suppdev);
}
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]));
d->ucm_ports = pa_dynarray_new(NULL);
d->hw_mute_jacks = pa_dynarray_new(NULL);
d->available = PA_AVAILABLE_UNKNOWN;
d->playback_volumes = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree,
(pa_free_cb_t) ucm_volume_free);
d->capture_volumes = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree,
(pa_free_cb_t) ucm_volume_free);
PA_LLIST_PREPEND(pa_alsa_ucm_device, verb->devices, d);
}
snd_use_case_free_list(dev_list, num_dev);
return 0;
};
static long ucm_device_status(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *dev) {
const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
char *devstatus;
long status = 0;
if (!ucm->active_verb) {
pa_log_error("Failed to get status for UCM device %s: no UCM verb set", dev_name);
return -1;
}
devstatus = pa_sprintf_malloc("_devstatus/%s", dev_name);
if (snd_use_case_geti(ucm->ucm_mgr, devstatus, &status) < 0) {
pa_log_debug("Failed to get status for UCM device %s", dev_name);
status = -1;
}
pa_xfree(devstatus);
return status;
}
static int ucm_device_disable(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *dev) {
const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
if (!ucm->active_verb) {
pa_log_error("Failed to disable UCM device %s: no UCM verb set", dev_name);
return -1;
}
/* If any of dev's conflicting devices is enabled, trying to disable
* dev gives an error despite the fact that it's already disabled.
* Check that dev is enabled to avoid this error. */
if (ucm_device_status(ucm, dev) == 0) {
pa_log_debug("UCM device %s is already disabled", dev_name);
return 0;
}
pa_log_debug("Disabling 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);
return -1;
}
return 0;
}
static int ucm_device_enable(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *dev) {
const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
if (!ucm->active_verb) {
pa_log_error("Failed to enable UCM device %s: no UCM verb set", dev_name);
return -1;
}
/* We don't need to enable devices that are already enabled */
if (ucm_device_status(ucm, dev) > 0) {
pa_log_debug("UCM device %s is already enabled", dev_name);
return 0;
}
pa_log_debug("Enabling UCM device %s", dev_name);
if (snd_use_case_set(ucm->ucm_mgr, "_enadev", dev_name) < 0) {
pa_log("Failed to enable UCM device %s", dev_name);
return -1;
}
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 long ucm_modifier_status(pa_alsa_ucm_config *ucm, pa_alsa_ucm_modifier *mod) {
const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME);
char *modstatus;
long status = 0;
if (!ucm->active_verb) {
pa_log_error("Failed to get status for UCM modifier %s: no UCM verb set", mod_name);
return -1;
}
modstatus = pa_sprintf_malloc("_modstatus/%s", mod_name);
if (snd_use_case_geti(ucm->ucm_mgr, modstatus, &status) < 0) {
pa_log_debug("Failed to get status for UCM modifier %s", mod_name);
status = -1;
}
pa_xfree(modstatus);
return status;
}
static int ucm_modifier_disable(pa_alsa_ucm_config *ucm, pa_alsa_ucm_modifier *mod) {
const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME);
if (!ucm->active_verb) {
pa_log_error("Failed to disable UCM modifier %s: no UCM verb set", mod_name);
return -1;
}
/* We don't need to disable modifiers that are already disabled */
if (ucm_modifier_status(ucm, mod) == 0) {
pa_log_debug("UCM modifier %s is already disabled", mod_name);
return 0;
}
pa_log_debug("Disabling UCM modifier %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);
return -1;
}
return 0;
}
static int ucm_modifier_enable(pa_alsa_ucm_config *ucm, pa_alsa_ucm_modifier *mod) {
const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME);
if (!ucm->active_verb) {
pa_log_error("Failed to enable UCM modifier %s: no UCM verb set", mod_name);
return -1;
}
/* We don't need to enable modifiers that are already enabled */
if (ucm_modifier_status(ucm, mod) > 0) {
pa_log_debug("UCM modifier %s is already enabled", mod_name);
return 0;
}
pa_log_debug("Enabling UCM modifier %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);
return -1;
}
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(pa_alsa_ucm_device *dev, const char *role_name, const char *role, bool is_sink) {
const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
const char *sink = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SINK);
const char *source = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SOURCE);
if (is_sink && sink)
add_role_to_device(dev, dev_name, role_name, role);
else if (!is_sink && source)
add_role_to_device(dev, dev_name, role_name, role);
}
static char *modifier_name_to_role(const char *mod_name, bool *is_sink) {
char *sub = NULL, *tmp, *pos;
*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);
pos = sub;
while (pos && *pos == ' ') pos++;
if (!pos || !*pos) {
pa_xfree(sub);
pa_log_warn("Can't match media roles for modifier %s", mod_name);
return NULL;
}
tmp = pos;
do {
*tmp = tolower(*tmp);
} while (*(++tmp));
tmp = pa_xstrdup(pos);
pa_xfree(sub);
return tmp;
}
static void ucm_set_media_roles(pa_alsa_ucm_modifier *modifier, const char *mod_name) {
pa_alsa_ucm_device *dev;
bool is_sink = false;
char *sub = NULL;
const char *role_name;
uint32_t idx;
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;
PA_IDXSET_FOREACH(dev, modifier->supported_devices, idx) {
/* 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(dev, role_name, sub, is_sink);
}
}
static void append_lost_relationship(pa_alsa_ucm_device *dev) {
uint32_t idx;
pa_alsa_ucm_device *d;
PA_IDXSET_FOREACH(d, dev->conflicting_devices, idx)
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));
PA_IDXSET_FOREACH(d, dev->supported_devices, idx)
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;
2020-09-30 12:56:05 +02:00
const char **verb_list, *value;
int num_verbs, i, err = 0;
const char *split_prefix = ucm->split_enable ? "<<<SplitPCM=1>>>" : "";
/* support multiple card instances, address card directly by index */
card_name = pa_sprintf_malloc("%shw:%i", split_prefix, card_index);
if (card_name == NULL)
2020-09-30 12:56:05 +02:00
return -PA_ALSA_ERR_UNSPECIFIED;
err = snd_use_case_mgr_open(&ucm->ucm_mgr, card_name);
if (err < 0) {
char *ucm_card_name;
/* fallback longname: is UCM available for this card ? */
pa_xfree(card_name);
err = snd_card_get_name(card_index, &ucm_card_name);
if (err < 0) {
pa_log("Card can't get card_name from card_index %d", card_index);
2020-09-30 12:56:05 +02:00
err = -PA_ALSA_ERR_UNSPECIFIED;
goto name_fail;
}
card_name = pa_sprintf_malloc("%s%s", split_prefix, ucm_card_name);
free(ucm_card_name);
if (card_name == NULL) {
err = -PA_ALSA_ERR_UNSPECIFIED;
goto name_fail;
}
err = snd_use_case_mgr_open(&ucm->ucm_mgr, card_name);
if (err < 0) {
pa_log_info("UCM not available for card %s", card_name);
2020-09-30 12:56:05 +02:00
err = -PA_ALSA_ERR_UCM_OPEN;
goto ucm_mgr_fail;
}
}
2020-09-30 12:56:05 +02:00
err = snd_use_case_get(ucm->ucm_mgr, "=Linked", &value);
if (err >= 0) {
if (strcasecmp(value, "true") == 0 || strcasecmp(value, "1") == 0) {
free((void *)value);
pa_log_info("Empty (linked) UCM for card %s", card_name);
err = -PA_ALSA_ERR_UCM_LINKED;
goto ucm_verb_fail;
}
free((void *)value);
}
pa_log_info("UCM available for card %s", card_name);
2022-01-03 17:21:28 +01:00
if (snd_use_case_get(ucm->ucm_mgr, "_alibpref", &value) == 0) {
if (value[0]) {
ucm->alib_prefix = pa_xstrdup(value);
pa_log_debug("UCM _alibpref=%s", ucm->alib_prefix);
}
free((void *)value);
}
/* get a list of all UCM verbs 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);
2020-09-30 12:56:05 +02:00
err = -PA_ALSA_ERR_UNSPECIFIED;
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);
2020-09-30 12:56:05 +02:00
err = -PA_ALSA_ERR_UCM_NO_VERB;
}
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:
pa_xfree(card_name);
name_fail:
return err;
}
static void ucm_verb_set_split_leaders(pa_alsa_ucm_verb *verb) {
pa_alsa_ucm_device *d, *d2;
/* Set first virtual device in each split HW PCM as the split leader */
PA_LLIST_FOREACH(d, verb->devices) {
if (d->playback_split)
d->playback_split->leader = true;
if (d->capture_split)
d->capture_split->leader = true;
}
PA_LLIST_FOREACH(d, verb->devices) {
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 (d->playback_split) {
if (!sink)
d->playback_split->leader = false;
if (d->playback_split->leader) {
PA_LLIST_FOREACH(d2, verb->devices) {
const char *sink2 = pa_proplist_gets(d2->proplist, PA_ALSA_PROP_UCM_SINK);
if (d == d2 || !d2->playback_split || !sink || !sink2 || !pa_streq(sink, sink2))
continue;
d2->playback_split->leader = false;
}
}
}
if (d->capture_split) {
if (!source)
d->capture_split->leader = false;
if (d->capture_split->leader) {
PA_LLIST_FOREACH(d2, verb->devices) {
const char *source2 = pa_proplist_gets(d2->proplist, PA_ALSA_PROP_UCM_SOURCE);
if (d == d2 || !d2->capture_split || !source || !source2 || !pa_streq(source, source2))
continue;
d2->capture_split->leader = false;
}
}
}
}
}
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;
char *value;
unsigned ui;
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));
value = ucm_verb_value(uc_mgr, verb_name, "Priority");
if (value && !pa_atou(value, &ui))
verb->priority = ui > 10000 ? 10000 : ui;
free(value);
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);
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);
}
ucm_verb_set_split_leaders(verb);
/* 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, verb, 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, mod_name);
}
*p_verb = verb;
return 0;
}
static int pa_alsa_ucm_device_cmp(const void *a, const void *b) {
const pa_alsa_ucm_device *d1 = *(pa_alsa_ucm_device **)a;
const pa_alsa_ucm_device *d2 = *(pa_alsa_ucm_device **)b;
return strcmp(pa_proplist_gets(d1->proplist, PA_ALSA_PROP_UCM_NAME), pa_proplist_gets(d2->proplist, PA_ALSA_PROP_UCM_NAME));
}
static void set_eld_devices(pa_hashmap *hash)
{
pa_device_port *port;
pa_alsa_ucm_port_data *data;
pa_alsa_ucm_device *dev;
void *state;
PA_HASHMAP_FOREACH(port, hash, state) {
data = PA_DEVICE_PORT_DATA(port);
dev = data->device;
data->eld_device = dev->eld_device;
Revert "alsa: UCM conflicting devices changes" If needs some more work This reverts commit aae770f18b7f6c94195fb3f2e2a61308029086af. This reverts commit 2216956e05689079db5ec05f793158e6479060da. This reverts commit 1d3c4501eff3392e0cd858d980c742c7f88c95e7. This reverts commit c7ca02460761273de0d1a8432c5defaafed43664. This reverts commit 8e4a5f5d5ed3d8b3081956f451c85c23aa74fc15. This reverts commit 8b20675ad12bc9daea84f6d4314ec12fccf5882c. This reverts commit ebe6f8b51a31ff7bd71d560991a12283f710b937. This reverts commit 4b97da1277b709e9749b568319f5eb403ff0f362. This reverts commit 7c46a656c734d4688cac229cd9d49af5ad0609fa. This reverts commit 96ed03e1fc5b1f0a287069c42031d7961239c896. This reverts commit 43770c533c71bfaf2c7e82d47d50bfd0533b53e0. This reverts commit ccb2fce05452185858d22de06d659452702f05d8. This reverts commit 93d0ddc8b327f9aaf11d71193f1d50d64bf1144b. This reverts commit 815e8c8fa09a88269b674786a152fe36849b933d. This reverts commit 97b99a0f7c2dd059c08c6ccd143cc1be06111ad1. This reverts commit 7e4d87402b17cd881e9140c37285b175c622ea24. This reverts commit 168b3ee22c1545b738634c4ae89ae4afbaf68cc4. This reverts commit 5128cdba319a2ced0f2e559ad594b8cbeb97a072. This reverts commit 31cde774c54931109da99bc452c858a208d94d6c. This reverts commit b02c8ba153f21459f1a120d4c1ca1bde6a98f4fb. This reverts commit 68ac72d09857901a33be22922669fc8674a4ab9c. This reverts commit 96cfc9bc1ac63414e7b3a6b928be5a29c03e1431. This reverts commit e2d642a20b1bc4e70f7273ddfc3da376a342e10f. This reverts commit 80fc80c34348bdc247bb1a12bcc174db772208a4. This reverts commit 3a68905c7c45677f64213ff563fd68bce6d14a9f. This reverts commit 7e64680a65e4dc2c755e79e55f43f7596b31e1d9. This reverts commit 8d1e38a2816e113121f2355a5827b0a681a3d41b. This reverts commit 591188195c60bb0bba4019d76547ece5386496ac. This reverts commit 973fe0cb79c9ef508778c7a2bb98bdfebeb70dfe. This reverts commit 4d08c9ad609d47c16d03ae3d5230c14d8d52f49e. This reverts commit 3dce92e58090de98e9f1470d9ba69da1f1b590db. This reverts commit 6b5e232c5c4a8e3d2d1c442c87ab03cc40350978. This reverts commit b1286c62c90167d6f6b6c17ad9bdfbb5d0254f60.
2023-04-12 12:30:56 +02:00
if (data->eld_mixer_device_name)
pa_xfree(data->eld_mixer_device_name);
data->eld_mixer_device_name = pa_xstrdup(dev->eld_mixer_device_name);
}
}
static void update_mixer_paths(pa_hashmap *ports, const char *verb_name) {
pa_device_port *port;
pa_alsa_ucm_port_data *data;
void *state;
/* select volume controls on ports */
PA_HASHMAP_FOREACH(port, ports, state) {
pa_log_info("Updating mixer path for %s: %s", verb_name, port->name);
data = PA_DEVICE_PORT_DATA(port);
data->path = pa_hashmap_get(data->paths, verb_name);
}
}
static void probe_volumes(pa_hashmap *hash, bool is_sink, snd_pcm_t *pcm_handle, pa_hashmap *mixers, bool ignore_dB) {
pa_device_port *port;
pa_alsa_path *path;
pa_alsa_ucm_port_data *data;
pa_alsa_ucm_device *dev;
snd_mixer_t *mixer_handle;
const char *verb_name, *mdev;
void *state, *state2;
PA_HASHMAP_FOREACH(port, hash, state) {
data = PA_DEVICE_PORT_DATA(port);
dev = data->device;
mdev = get_mixer_device(dev, is_sink);
2020-07-13 14:39:27 +02:00
if (mdev == NULL || !(mixer_handle = pa_alsa_open_mixer_by_name(mixers, mdev, true))) {
pa_log_error("Failed to find a working mixer device (%s).", mdev);
goto fail;
}
PA_HASHMAP_FOREACH_KV(verb_name, path, data->paths, state2) {
if (pa_alsa_path_probe(path, NULL, mixer_handle, ignore_dB) < 0) {
pa_log_warn("Could not probe path: %s, using s/w volume", path->name);
pa_hashmap_remove(data->paths, verb_name);
} else if (!path->has_volume && !path->has_mute) {
pa_log_warn("Path %s is not a volume or mute control", path->name);
pa_hashmap_remove(data->paths, verb_name);
} else
pa_log_debug("Set up h/w %s using '%s' for %s:%s", path->has_volume ? "volume" : "mute",
path->name, verb_name, port->name);
}
}
return;
fail:
/* We could not probe the paths we created. Free them and revert to software volumes. */
PA_HASHMAP_FOREACH(port, hash, state) {
data = PA_DEVICE_PORT_DATA(port);
pa_hashmap_remove_all(data->paths);
}
}
static void proplist_set_icon_name(
pa_proplist *proplist,
pa_device_port_type_t type,
bool is_sink) {
const char *icon;
if (is_sink) {
switch (type) {
case PA_DEVICE_PORT_TYPE_HEADPHONES:
icon = "audio-headphones";
break;
case PA_DEVICE_PORT_TYPE_HDMI:
icon = "video-display";
break;
case PA_DEVICE_PORT_TYPE_SPEAKER:
default:
icon = "audio-speakers";
break;
}
} else {
switch (type) {
case PA_DEVICE_PORT_TYPE_HEADSET:
icon = "audio-headset";
break;
case PA_DEVICE_PORT_TYPE_MIC:
default:
icon = "audio-input-microphone";
break;
}
}
pa_proplist_sets(proplist, "device.icon_name", icon);
}
static char *devset_name(pa_idxset *devices, const char *sep) {
int i = 0;
int num = pa_idxset_size(devices);
pa_alsa_ucm_device *sorted[num], *dev;
char *dev_names = NULL;
char *tmp = NULL;
uint32_t idx;
PA_IDXSET_FOREACH(dev, devices, idx) {
sorted[i] = dev;
i++;
}
/* Sort by alphabetical order so as to have a deterministic naming scheme */
qsort(&sorted[0], num, sizeof(pa_alsa_ucm_device *), pa_alsa_ucm_device_cmp);
for (i = 0; i < num; i++) {
dev = sorted[i];
const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
if (!dev_names) {
dev_names = pa_xstrdup(dev_name);
} else {
tmp = pa_sprintf_malloc("%s%s%s", dev_names, sep, dev_name);
pa_xfree(dev_names);
dev_names = tmp;
}
}
return dev_names;
}
PA_UNUSED static char *devset_description(pa_idxset *devices, const char *sep) {
int i = 0;
int num = pa_idxset_size(devices);
pa_alsa_ucm_device *sorted[num], *dev;
char *dev_descs = NULL;
char *tmp = NULL;
uint32_t idx;
PA_IDXSET_FOREACH(dev, devices, idx) {
sorted[i] = dev;
i++;
}
/* Sort by alphabetical order to match devset_name() */
qsort(&sorted[0], num, sizeof(pa_alsa_ucm_device *), pa_alsa_ucm_device_cmp);
for (i = 0; i < num; i++) {
dev = sorted[i];
const char *dev_desc = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_DESCRIPTION);
if (!dev_descs) {
dev_descs = pa_xstrdup(dev_desc);
} else {
tmp = pa_sprintf_malloc("%s%s%s", dev_descs, sep, dev_desc);
pa_xfree(dev_descs);
dev_descs = tmp;
}
}
return dev_descs;
}
/* If invert is true, uses the formula 1/p = 1/p1 + 1/p2 + ... 1/pn.
* This way, the result will always be less than the individual components,
* yet higher components will lead to higher result. */
static unsigned devset_playback_priority(pa_idxset *devices, bool invert) {
pa_alsa_ucm_device *dev;
uint32_t idx;
double priority = 0;
PA_IDXSET_FOREACH(dev, devices, idx) {
if (dev->playback_priority > 0 && invert)
priority += 1.0 / dev->playback_priority;
else
priority += dev->playback_priority;
}
if (priority > 0 && invert)
return (unsigned)(1.0 / priority);
return (unsigned) priority;
}
static unsigned devset_capture_priority(pa_idxset *devices, bool invert) {
pa_alsa_ucm_device *dev;
uint32_t idx;
double priority = 0;
PA_IDXSET_FOREACH(dev, devices, idx) {
if (dev->capture_priority > 0 && invert)
priority += 1.0 / dev->capture_priority;
else
priority += dev->capture_priority;
}
if (priority > 0 && invert)
return (unsigned)(1.0 / priority);
return (unsigned) priority;
}
static void ucm_add_port_props(
pa_device_port *port,
bool is_sink)
{
proplist_set_icon_name(port->proplist, port->type, is_sink);
}
void pa_alsa_ucm_add_port(
pa_hashmap *hash,
pa_alsa_ucm_mapping_context *context,
bool is_sink,
pa_hashmap *ports,
pa_card_profile *cp,
pa_core *core) {
pa_device_port *port;
unsigned priority;
char *name, *desc;
const char *dev_name;
const char *direction;
const char *verb_name;
pa_alsa_ucm_device *dev;
pa_alsa_ucm_port_data *data;
pa_alsa_ucm_volume *vol;
pa_alsa_jack *jack;
pa_device_port_type_t type;
void *state;
dev = context->ucm_device;
if (!dev)
return;
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 = pa_xstrdup(pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_DESCRIPTION));
priority = is_sink ? dev->playback_priority : dev->capture_priority;
jack = ucm_get_jack(context->ucm, dev);
type = dev->type;
port = pa_hashmap_get(ports, name);
if (!port) {
pa_device_port_new_data port_data;
pa_device_port_new_data_init(&port_data);
pa_device_port_new_data_set_name(&port_data, name);
pa_device_port_new_data_set_description(&port_data, desc);
pa_device_port_new_data_set_type(&port_data, type);
pa_device_port_new_data_set_direction(&port_data, is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT);
if (jack)
pa_device_port_new_data_set_availability_group(&port_data, jack->name);
port = pa_device_port_new(core, &port_data, sizeof(pa_alsa_ucm_port_data));
pa_device_port_new_data_done(&port_data);
data = PA_DEVICE_PORT_DATA(port);
ucm_port_data_init(data, context->ucm, port, dev);
port->impl_free = ucm_port_data_free;
pa_hashmap_put(ports, port->name, port);
pa_log_debug("Add port %s: %s", port->name, port->description);
ucm_add_port_props(port, is_sink);
}
2023-03-21 10:07:39 +01:00
data = PA_DEVICE_PORT_DATA(port);
PA_HASHMAP_FOREACH_KV(verb_name, vol, is_sink ? dev->playback_volumes : dev->capture_volumes, state) {
if (pa_hashmap_get(data->paths, verb_name))
continue;
pa_alsa_path *path = pa_alsa_path_synthesize(vol->mixer_elem,
is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT);
if (!path)
pa_log_warn("Failed to set up volume control: %s", vol->mixer_elem);
else {
if (vol->master_elem) {
pa_alsa_element *e = pa_alsa_element_get(path, vol->master_elem, false);
e->switch_use = PA_ALSA_SWITCH_MUTE;
e->volume_use = PA_ALSA_VOLUME_MERGE;
}
pa_hashmap_put(data->paths, pa_xstrdup(verb_name), path);
/* Add path also to already created empty path set */
if (is_sink)
pa_hashmap_put(dev->playback_mapping->output_path_set->paths, pa_xstrdup(vol->mixer_elem), path);
else
pa_hashmap_put(dev->capture_mapping->input_path_set->paths, pa_xstrdup(vol->mixer_elem), path);
}
}
port->priority = priority;
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 profile %s to port %s.", cp->name, port->name);
pa_hashmap_put(port->profiles, cp->name, cp);
}
if (hash) {
pa_hashmap_put(hash, port->name, port);
}
/* ELD devices */
set_eld_devices(ports);
}
static bool devset_supports_device(pa_idxset *devices, pa_alsa_ucm_device *dev) {
alsa-ucm: Consider devices using the same PCM as incompatible Although it's a valid UCM configuration to have multiple devices using the same PlaybackPCM or CapturePCM, it's unclear how PulseAudio should handle the cases where multiple of these devices are enabled. Some options I can think of are: - Merge all devices sharing the same PCM into the same mapping, open only one PCM substream for this mapping, and add 'combination ports' that enable combinations of the devices. This has been the case until recently, although the combination port logic was broken. A problem with this is that we can't independently control device volumes. We most likely cannot use hardware volumes either. - Have one mapping for each device in the same profile, and open one PCM substream for each mapping. This is the current state, and it fails when there are fewer substreams than devices. Otherwise it works, but it's still confusing, as sound directed to a device-specific mapping might end up playing at multiple devices. - Make multiple profiles each with combinations of upto-substream-count devices, and have one mapping/substream per device. This still causes the confusion mentioned above. And it's likely that the substream count will almost always be one, where this case degenerates into the last one. - Have one mapping for each device in the same profile, but open only one PCM substream. I assume this is possible with software mixing, but it is still confusing like the above, and probably less performant. - Generate multiple profiles each with one of the shared-PCM devices, again with one mapping/substream for that one device. The trade-off with this is that we can't use multiple of these devices at the same time. However, this doesn't have the output device confusion, combination port's volume problems, or the substream count limitation. This patch takes a short-cut to achieve the last option, by considering shared-PCM devices implicitly conflicting with each other. Link: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/commit/45509ff9bc92345fef4ecf1003bee6fe632c3bcb Signed-off-by: Alper Nebi Yasak <alpernebiyasak@gmail.com>
2023-11-28 00:16:52 +03:00
const char *sink, *sink2, *source, *source2;
pa_alsa_ucm_device *d;
uint32_t idx;
pa_assert(devices);
Revert "alsa: UCM conflicting devices changes" If needs some more work This reverts commit aae770f18b7f6c94195fb3f2e2a61308029086af. This reverts commit 2216956e05689079db5ec05f793158e6479060da. This reverts commit 1d3c4501eff3392e0cd858d980c742c7f88c95e7. This reverts commit c7ca02460761273de0d1a8432c5defaafed43664. This reverts commit 8e4a5f5d5ed3d8b3081956f451c85c23aa74fc15. This reverts commit 8b20675ad12bc9daea84f6d4314ec12fccf5882c. This reverts commit ebe6f8b51a31ff7bd71d560991a12283f710b937. This reverts commit 4b97da1277b709e9749b568319f5eb403ff0f362. This reverts commit 7c46a656c734d4688cac229cd9d49af5ad0609fa. This reverts commit 96ed03e1fc5b1f0a287069c42031d7961239c896. This reverts commit 43770c533c71bfaf2c7e82d47d50bfd0533b53e0. This reverts commit ccb2fce05452185858d22de06d659452702f05d8. This reverts commit 93d0ddc8b327f9aaf11d71193f1d50d64bf1144b. This reverts commit 815e8c8fa09a88269b674786a152fe36849b933d. This reverts commit 97b99a0f7c2dd059c08c6ccd143cc1be06111ad1. This reverts commit 7e4d87402b17cd881e9140c37285b175c622ea24. This reverts commit 168b3ee22c1545b738634c4ae89ae4afbaf68cc4. This reverts commit 5128cdba319a2ced0f2e559ad594b8cbeb97a072. This reverts commit 31cde774c54931109da99bc452c858a208d94d6c. This reverts commit b02c8ba153f21459f1a120d4c1ca1bde6a98f4fb. This reverts commit 68ac72d09857901a33be22922669fc8674a4ab9c. This reverts commit 96cfc9bc1ac63414e7b3a6b928be5a29c03e1431. This reverts commit e2d642a20b1bc4e70f7273ddfc3da376a342e10f. This reverts commit 80fc80c34348bdc247bb1a12bcc174db772208a4. This reverts commit 3a68905c7c45677f64213ff563fd68bce6d14a9f. This reverts commit 7e64680a65e4dc2c755e79e55f43f7596b31e1d9. This reverts commit 8d1e38a2816e113121f2355a5827b0a681a3d41b. This reverts commit 591188195c60bb0bba4019d76547ece5386496ac. This reverts commit 973fe0cb79c9ef508778c7a2bb98bdfebeb70dfe. This reverts commit 4d08c9ad609d47c16d03ae3d5230c14d8d52f49e. This reverts commit 3dce92e58090de98e9f1470d9ba69da1f1b590db. This reverts commit 6b5e232c5c4a8e3d2d1c442c87ab03cc40350978. This reverts commit b1286c62c90167d6f6b6c17ad9bdfbb5d0254f60.
2023-04-12 12:30:56 +02:00
pa_assert(dev);
2023-03-21 10:07:39 +01:00
/* Can add anything to empty group */
if (pa_idxset_isempty(devices))
return true;
/* Device already selected */
if (pa_idxset_contains(devices, dev))
return true;
/* No conflicting device must already be selected */
if (!pa_idxset_isdisjoint(devices, dev->conflicting_devices))
return false;
/* No already selected device must be unsupported */
if (!pa_idxset_isempty(dev->supported_devices))
if (!pa_idxset_issubset(devices, dev->supported_devices))
return false;
alsa-ucm: Consider devices using the same PCM as incompatible Although it's a valid UCM configuration to have multiple devices using the same PlaybackPCM or CapturePCM, it's unclear how PulseAudio should handle the cases where multiple of these devices are enabled. Some options I can think of are: - Merge all devices sharing the same PCM into the same mapping, open only one PCM substream for this mapping, and add 'combination ports' that enable combinations of the devices. This has been the case until recently, although the combination port logic was broken. A problem with this is that we can't independently control device volumes. We most likely cannot use hardware volumes either. - Have one mapping for each device in the same profile, and open one PCM substream for each mapping. This is the current state, and it fails when there are fewer substreams than devices. Otherwise it works, but it's still confusing, as sound directed to a device-specific mapping might end up playing at multiple devices. - Make multiple profiles each with combinations of upto-substream-count devices, and have one mapping/substream per device. This still causes the confusion mentioned above. And it's likely that the substream count will almost always be one, where this case degenerates into the last one. - Have one mapping for each device in the same profile, but open only one PCM substream. I assume this is possible with software mixing, but it is still confusing like the above, and probably less performant. - Generate multiple profiles each with one of the shared-PCM devices, again with one mapping/substream for that one device. The trade-off with this is that we can't use multiple of these devices at the same time. However, this doesn't have the output device confusion, combination port's volume problems, or the substream count limitation. This patch takes a short-cut to achieve the last option, by considering shared-PCM devices implicitly conflicting with each other. Link: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/commit/45509ff9bc92345fef4ecf1003bee6fe632c3bcb Signed-off-by: Alper Nebi Yasak <alpernebiyasak@gmail.com>
2023-11-28 00:16:52 +03:00
sink = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SINK);
source = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SOURCE);
PA_IDXSET_FOREACH(d, devices, idx) {
/* Must not be unsupported by any selected device */
if (!pa_idxset_isempty(d->supported_devices))
if (!pa_idxset_contains(d->supported_devices, dev))
return false;
/* PlaybackPCM must not be the same as any selected device, except when both split */
alsa-ucm: Consider devices using the same PCM as incompatible Although it's a valid UCM configuration to have multiple devices using the same PlaybackPCM or CapturePCM, it's unclear how PulseAudio should handle the cases where multiple of these devices are enabled. Some options I can think of are: - Merge all devices sharing the same PCM into the same mapping, open only one PCM substream for this mapping, and add 'combination ports' that enable combinations of the devices. This has been the case until recently, although the combination port logic was broken. A problem with this is that we can't independently control device volumes. We most likely cannot use hardware volumes either. - Have one mapping for each device in the same profile, and open one PCM substream for each mapping. This is the current state, and it fails when there are fewer substreams than devices. Otherwise it works, but it's still confusing, as sound directed to a device-specific mapping might end up playing at multiple devices. - Make multiple profiles each with combinations of upto-substream-count devices, and have one mapping/substream per device. This still causes the confusion mentioned above. And it's likely that the substream count will almost always be one, where this case degenerates into the last one. - Have one mapping for each device in the same profile, but open only one PCM substream. I assume this is possible with software mixing, but it is still confusing like the above, and probably less performant. - Generate multiple profiles each with one of the shared-PCM devices, again with one mapping/substream for that one device. The trade-off with this is that we can't use multiple of these devices at the same time. However, this doesn't have the output device confusion, combination port's volume problems, or the substream count limitation. This patch takes a short-cut to achieve the last option, by considering shared-PCM devices implicitly conflicting with each other. Link: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/commit/45509ff9bc92345fef4ecf1003bee6fe632c3bcb Signed-off-by: Alper Nebi Yasak <alpernebiyasak@gmail.com>
2023-11-28 00:16:52 +03:00
sink2 = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_SINK);
if (sink && sink2 && pa_streq(sink, sink2)) {
if (!(dev->playback_split && d->playback_split))
return false;
}
alsa-ucm: Consider devices using the same PCM as incompatible Although it's a valid UCM configuration to have multiple devices using the same PlaybackPCM or CapturePCM, it's unclear how PulseAudio should handle the cases where multiple of these devices are enabled. Some options I can think of are: - Merge all devices sharing the same PCM into the same mapping, open only one PCM substream for this mapping, and add 'combination ports' that enable combinations of the devices. This has been the case until recently, although the combination port logic was broken. A problem with this is that we can't independently control device volumes. We most likely cannot use hardware volumes either. - Have one mapping for each device in the same profile, and open one PCM substream for each mapping. This is the current state, and it fails when there are fewer substreams than devices. Otherwise it works, but it's still confusing, as sound directed to a device-specific mapping might end up playing at multiple devices. - Make multiple profiles each with combinations of upto-substream-count devices, and have one mapping/substream per device. This still causes the confusion mentioned above. And it's likely that the substream count will almost always be one, where this case degenerates into the last one. - Have one mapping for each device in the same profile, but open only one PCM substream. I assume this is possible with software mixing, but it is still confusing like the above, and probably less performant. - Generate multiple profiles each with one of the shared-PCM devices, again with one mapping/substream for that one device. The trade-off with this is that we can't use multiple of these devices at the same time. However, this doesn't have the output device confusion, combination port's volume problems, or the substream count limitation. This patch takes a short-cut to achieve the last option, by considering shared-PCM devices implicitly conflicting with each other. Link: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/commit/45509ff9bc92345fef4ecf1003bee6fe632c3bcb Signed-off-by: Alper Nebi Yasak <alpernebiyasak@gmail.com>
2023-11-28 00:16:52 +03:00
/* CapturePCM must not be the same as any selected device, except when both split */
alsa-ucm: Consider devices using the same PCM as incompatible Although it's a valid UCM configuration to have multiple devices using the same PlaybackPCM or CapturePCM, it's unclear how PulseAudio should handle the cases where multiple of these devices are enabled. Some options I can think of are: - Merge all devices sharing the same PCM into the same mapping, open only one PCM substream for this mapping, and add 'combination ports' that enable combinations of the devices. This has been the case until recently, although the combination port logic was broken. A problem with this is that we can't independently control device volumes. We most likely cannot use hardware volumes either. - Have one mapping for each device in the same profile, and open one PCM substream for each mapping. This is the current state, and it fails when there are fewer substreams than devices. Otherwise it works, but it's still confusing, as sound directed to a device-specific mapping might end up playing at multiple devices. - Make multiple profiles each with combinations of upto-substream-count devices, and have one mapping/substream per device. This still causes the confusion mentioned above. And it's likely that the substream count will almost always be one, where this case degenerates into the last one. - Have one mapping for each device in the same profile, but open only one PCM substream. I assume this is possible with software mixing, but it is still confusing like the above, and probably less performant. - Generate multiple profiles each with one of the shared-PCM devices, again with one mapping/substream for that one device. The trade-off with this is that we can't use multiple of these devices at the same time. However, this doesn't have the output device confusion, combination port's volume problems, or the substream count limitation. This patch takes a short-cut to achieve the last option, by considering shared-PCM devices implicitly conflicting with each other. Link: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/commit/45509ff9bc92345fef4ecf1003bee6fe632c3bcb Signed-off-by: Alper Nebi Yasak <alpernebiyasak@gmail.com>
2023-11-28 00:16:52 +03:00
source2 = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_SOURCE);
if (source && source2 && pa_streq(source, source2)) {
if (!(dev->capture_split && d->capture_split))
return false;
}
alsa-ucm: Consider devices using the same PCM as incompatible Although it's a valid UCM configuration to have multiple devices using the same PlaybackPCM or CapturePCM, it's unclear how PulseAudio should handle the cases where multiple of these devices are enabled. Some options I can think of are: - Merge all devices sharing the same PCM into the same mapping, open only one PCM substream for this mapping, and add 'combination ports' that enable combinations of the devices. This has been the case until recently, although the combination port logic was broken. A problem with this is that we can't independently control device volumes. We most likely cannot use hardware volumes either. - Have one mapping for each device in the same profile, and open one PCM substream for each mapping. This is the current state, and it fails when there are fewer substreams than devices. Otherwise it works, but it's still confusing, as sound directed to a device-specific mapping might end up playing at multiple devices. - Make multiple profiles each with combinations of upto-substream-count devices, and have one mapping/substream per device. This still causes the confusion mentioned above. And it's likely that the substream count will almost always be one, where this case degenerates into the last one. - Have one mapping for each device in the same profile, but open only one PCM substream. I assume this is possible with software mixing, but it is still confusing like the above, and probably less performant. - Generate multiple profiles each with one of the shared-PCM devices, again with one mapping/substream for that one device. The trade-off with this is that we can't use multiple of these devices at the same time. However, this doesn't have the output device confusion, combination port's volume problems, or the substream count limitation. This patch takes a short-cut to achieve the last option, by considering shared-PCM devices implicitly conflicting with each other. Link: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/commit/45509ff9bc92345fef4ecf1003bee6fe632c3bcb Signed-off-by: Alper Nebi Yasak <alpernebiyasak@gmail.com>
2023-11-28 00:16:52 +03:00
}
return true;
Revert "alsa: UCM conflicting devices changes" If needs some more work This reverts commit aae770f18b7f6c94195fb3f2e2a61308029086af. This reverts commit 2216956e05689079db5ec05f793158e6479060da. This reverts commit 1d3c4501eff3392e0cd858d980c742c7f88c95e7. This reverts commit c7ca02460761273de0d1a8432c5defaafed43664. This reverts commit 8e4a5f5d5ed3d8b3081956f451c85c23aa74fc15. This reverts commit 8b20675ad12bc9daea84f6d4314ec12fccf5882c. This reverts commit ebe6f8b51a31ff7bd71d560991a12283f710b937. This reverts commit 4b97da1277b709e9749b568319f5eb403ff0f362. This reverts commit 7c46a656c734d4688cac229cd9d49af5ad0609fa. This reverts commit 96ed03e1fc5b1f0a287069c42031d7961239c896. This reverts commit 43770c533c71bfaf2c7e82d47d50bfd0533b53e0. This reverts commit ccb2fce05452185858d22de06d659452702f05d8. This reverts commit 93d0ddc8b327f9aaf11d71193f1d50d64bf1144b. This reverts commit 815e8c8fa09a88269b674786a152fe36849b933d. This reverts commit 97b99a0f7c2dd059c08c6ccd143cc1be06111ad1. This reverts commit 7e4d87402b17cd881e9140c37285b175c622ea24. This reverts commit 168b3ee22c1545b738634c4ae89ae4afbaf68cc4. This reverts commit 5128cdba319a2ced0f2e559ad594b8cbeb97a072. This reverts commit 31cde774c54931109da99bc452c858a208d94d6c. This reverts commit b02c8ba153f21459f1a120d4c1ca1bde6a98f4fb. This reverts commit 68ac72d09857901a33be22922669fc8674a4ab9c. This reverts commit 96cfc9bc1ac63414e7b3a6b928be5a29c03e1431. This reverts commit e2d642a20b1bc4e70f7273ddfc3da376a342e10f. This reverts commit 80fc80c34348bdc247bb1a12bcc174db772208a4. This reverts commit 3a68905c7c45677f64213ff563fd68bce6d14a9f. This reverts commit 7e64680a65e4dc2c755e79e55f43f7596b31e1d9. This reverts commit 8d1e38a2816e113121f2355a5827b0a681a3d41b. This reverts commit 591188195c60bb0bba4019d76547ece5386496ac. This reverts commit 973fe0cb79c9ef508778c7a2bb98bdfebeb70dfe. This reverts commit 4d08c9ad609d47c16d03ae3d5230c14d8d52f49e. This reverts commit 3dce92e58090de98e9f1470d9ba69da1f1b590db. This reverts commit 6b5e232c5c4a8e3d2d1c442c87ab03cc40350978. This reverts commit b1286c62c90167d6f6b6c17ad9bdfbb5d0254f60.
2023-04-12 12:30:56 +02:00
}
/* Iterates nonempty subsets of UCM devices that can be simultaneously
* used, including subsets of previously returned subsets. At start,
* *state should be NULL. It's not safe to modify the devices argument
* until iteration ends. The returned idxsets must be freed by the
* caller. */
static pa_idxset *iterate_device_subsets(pa_idxset *devices, void **state) {
uint32_t idx;
Revert "alsa: UCM conflicting devices changes" If needs some more work This reverts commit aae770f18b7f6c94195fb3f2e2a61308029086af. This reverts commit 2216956e05689079db5ec05f793158e6479060da. This reverts commit 1d3c4501eff3392e0cd858d980c742c7f88c95e7. This reverts commit c7ca02460761273de0d1a8432c5defaafed43664. This reverts commit 8e4a5f5d5ed3d8b3081956f451c85c23aa74fc15. This reverts commit 8b20675ad12bc9daea84f6d4314ec12fccf5882c. This reverts commit ebe6f8b51a31ff7bd71d560991a12283f710b937. This reverts commit 4b97da1277b709e9749b568319f5eb403ff0f362. This reverts commit 7c46a656c734d4688cac229cd9d49af5ad0609fa. This reverts commit 96ed03e1fc5b1f0a287069c42031d7961239c896. This reverts commit 43770c533c71bfaf2c7e82d47d50bfd0533b53e0. This reverts commit ccb2fce05452185858d22de06d659452702f05d8. This reverts commit 93d0ddc8b327f9aaf11d71193f1d50d64bf1144b. This reverts commit 815e8c8fa09a88269b674786a152fe36849b933d. This reverts commit 97b99a0f7c2dd059c08c6ccd143cc1be06111ad1. This reverts commit 7e4d87402b17cd881e9140c37285b175c622ea24. This reverts commit 168b3ee22c1545b738634c4ae89ae4afbaf68cc4. This reverts commit 5128cdba319a2ced0f2e559ad594b8cbeb97a072. This reverts commit 31cde774c54931109da99bc452c858a208d94d6c. This reverts commit b02c8ba153f21459f1a120d4c1ca1bde6a98f4fb. This reverts commit 68ac72d09857901a33be22922669fc8674a4ab9c. This reverts commit 96cfc9bc1ac63414e7b3a6b928be5a29c03e1431. This reverts commit e2d642a20b1bc4e70f7273ddfc3da376a342e10f. This reverts commit 80fc80c34348bdc247bb1a12bcc174db772208a4. This reverts commit 3a68905c7c45677f64213ff563fd68bce6d14a9f. This reverts commit 7e64680a65e4dc2c755e79e55f43f7596b31e1d9. This reverts commit 8d1e38a2816e113121f2355a5827b0a681a3d41b. This reverts commit 591188195c60bb0bba4019d76547ece5386496ac. This reverts commit 973fe0cb79c9ef508778c7a2bb98bdfebeb70dfe. This reverts commit 4d08c9ad609d47c16d03ae3d5230c14d8d52f49e. This reverts commit 3dce92e58090de98e9f1470d9ba69da1f1b590db. This reverts commit 6b5e232c5c4a8e3d2d1c442c87ab03cc40350978. This reverts commit b1286c62c90167d6f6b6c17ad9bdfbb5d0254f60.
2023-04-12 12:30:56 +02:00
pa_alsa_ucm_device *dev;
pa_assert(devices);
pa_assert(state);
if (*state == NULL) {
/* First iteration, start adding from first device */
*state = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
dev = pa_idxset_first(devices, &idx);
Revert "alsa: UCM conflicting devices changes" If needs some more work This reverts commit aae770f18b7f6c94195fb3f2e2a61308029086af. This reverts commit 2216956e05689079db5ec05f793158e6479060da. This reverts commit 1d3c4501eff3392e0cd858d980c742c7f88c95e7. This reverts commit c7ca02460761273de0d1a8432c5defaafed43664. This reverts commit 8e4a5f5d5ed3d8b3081956f451c85c23aa74fc15. This reverts commit 8b20675ad12bc9daea84f6d4314ec12fccf5882c. This reverts commit ebe6f8b51a31ff7bd71d560991a12283f710b937. This reverts commit 4b97da1277b709e9749b568319f5eb403ff0f362. This reverts commit 7c46a656c734d4688cac229cd9d49af5ad0609fa. This reverts commit 96ed03e1fc5b1f0a287069c42031d7961239c896. This reverts commit 43770c533c71bfaf2c7e82d47d50bfd0533b53e0. This reverts commit ccb2fce05452185858d22de06d659452702f05d8. This reverts commit 93d0ddc8b327f9aaf11d71193f1d50d64bf1144b. This reverts commit 815e8c8fa09a88269b674786a152fe36849b933d. This reverts commit 97b99a0f7c2dd059c08c6ccd143cc1be06111ad1. This reverts commit 7e4d87402b17cd881e9140c37285b175c622ea24. This reverts commit 168b3ee22c1545b738634c4ae89ae4afbaf68cc4. This reverts commit 5128cdba319a2ced0f2e559ad594b8cbeb97a072. This reverts commit 31cde774c54931109da99bc452c858a208d94d6c. This reverts commit b02c8ba153f21459f1a120d4c1ca1bde6a98f4fb. This reverts commit 68ac72d09857901a33be22922669fc8674a4ab9c. This reverts commit 96cfc9bc1ac63414e7b3a6b928be5a29c03e1431. This reverts commit e2d642a20b1bc4e70f7273ddfc3da376a342e10f. This reverts commit 80fc80c34348bdc247bb1a12bcc174db772208a4. This reverts commit 3a68905c7c45677f64213ff563fd68bce6d14a9f. This reverts commit 7e64680a65e4dc2c755e79e55f43f7596b31e1d9. This reverts commit 8d1e38a2816e113121f2355a5827b0a681a3d41b. This reverts commit 591188195c60bb0bba4019d76547ece5386496ac. This reverts commit 973fe0cb79c9ef508778c7a2bb98bdfebeb70dfe. This reverts commit 4d08c9ad609d47c16d03ae3d5230c14d8d52f49e. This reverts commit 3dce92e58090de98e9f1470d9ba69da1f1b590db. This reverts commit 6b5e232c5c4a8e3d2d1c442c87ab03cc40350978. This reverts commit b1286c62c90167d6f6b6c17ad9bdfbb5d0254f60.
2023-04-12 12:30:56 +02:00
} else {
/* Backtrack the most recent device we added and skip it */
dev = pa_idxset_steal_last(*state, NULL);
pa_idxset_get_by_data(devices, dev, &idx);
if (dev)
dev = pa_idxset_next(devices, &idx);
}
/* Try adding devices we haven't decided on yet */
for (; dev; dev = pa_idxset_next(devices, &idx)) {
if (devset_supports_device(*state, dev))
pa_idxset_put(*state, dev, NULL);
}
Revert "alsa: UCM conflicting devices changes" If needs some more work This reverts commit aae770f18b7f6c94195fb3f2e2a61308029086af. This reverts commit 2216956e05689079db5ec05f793158e6479060da. This reverts commit 1d3c4501eff3392e0cd858d980c742c7f88c95e7. This reverts commit c7ca02460761273de0d1a8432c5defaafed43664. This reverts commit 8e4a5f5d5ed3d8b3081956f451c85c23aa74fc15. This reverts commit 8b20675ad12bc9daea84f6d4314ec12fccf5882c. This reverts commit ebe6f8b51a31ff7bd71d560991a12283f710b937. This reverts commit 4b97da1277b709e9749b568319f5eb403ff0f362. This reverts commit 7c46a656c734d4688cac229cd9d49af5ad0609fa. This reverts commit 96ed03e1fc5b1f0a287069c42031d7961239c896. This reverts commit 43770c533c71bfaf2c7e82d47d50bfd0533b53e0. This reverts commit ccb2fce05452185858d22de06d659452702f05d8. This reverts commit 93d0ddc8b327f9aaf11d71193f1d50d64bf1144b. This reverts commit 815e8c8fa09a88269b674786a152fe36849b933d. This reverts commit 97b99a0f7c2dd059c08c6ccd143cc1be06111ad1. This reverts commit 7e4d87402b17cd881e9140c37285b175c622ea24. This reverts commit 168b3ee22c1545b738634c4ae89ae4afbaf68cc4. This reverts commit 5128cdba319a2ced0f2e559ad594b8cbeb97a072. This reverts commit 31cde774c54931109da99bc452c858a208d94d6c. This reverts commit b02c8ba153f21459f1a120d4c1ca1bde6a98f4fb. This reverts commit 68ac72d09857901a33be22922669fc8674a4ab9c. This reverts commit 96cfc9bc1ac63414e7b3a6b928be5a29c03e1431. This reverts commit e2d642a20b1bc4e70f7273ddfc3da376a342e10f. This reverts commit 80fc80c34348bdc247bb1a12bcc174db772208a4. This reverts commit 3a68905c7c45677f64213ff563fd68bce6d14a9f. This reverts commit 7e64680a65e4dc2c755e79e55f43f7596b31e1d9. This reverts commit 8d1e38a2816e113121f2355a5827b0a681a3d41b. This reverts commit 591188195c60bb0bba4019d76547ece5386496ac. This reverts commit 973fe0cb79c9ef508778c7a2bb98bdfebeb70dfe. This reverts commit 4d08c9ad609d47c16d03ae3d5230c14d8d52f49e. This reverts commit 3dce92e58090de98e9f1470d9ba69da1f1b590db. This reverts commit 6b5e232c5c4a8e3d2d1c442c87ab03cc40350978. This reverts commit b1286c62c90167d6f6b6c17ad9bdfbb5d0254f60.
2023-04-12 12:30:56 +02:00
if (pa_idxset_isempty(*state)) {
/* No more choices to backtrack on, therefore no more subsets to
* return after this. Don't return the empty set, instead clean
* up and end iteration. */
pa_idxset_free(*state, NULL);
*state = NULL;
return NULL;
}
return pa_idxset_copy(*state, NULL);
}
/* This a wrapper around iterate_device_subsets() that only returns the
* biggest possible groups and not any of their subsets. */
static pa_idxset *iterate_maximal_device_subsets(pa_idxset *devices, void **state) {
uint32_t idx;
pa_alsa_ucm_device *dev;
pa_idxset *subset = NULL;
pa_assert(devices);
pa_assert(state);
while (subset == NULL && (subset = iterate_device_subsets(devices, state))) {
/* Skip this group if it's incomplete, by checking if we can add any
* other device. If we can, this iteration is a subset of another
* group that we already returned or eventually return. */
PA_IDXSET_FOREACH(dev, devices, idx) {
if (subset && !pa_idxset_contains(subset, dev) && devset_supports_device(subset, dev)) {
pa_idxset_free(subset, NULL);
subset = NULL;
}
}
}
return subset;
}
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(
pa_hashmap **p,
pa_proplist *proplist,
pa_alsa_ucm_mapping_context *context,
bool is_sink,
pa_card *card,
snd_pcm_t *pcm_handle,
bool ignore_dB) {
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_port(*p, context, is_sink, card->ports, NULL, card->core);
/* now set up volume paths if any */
probe_volumes(*p, is_sink, pcm_handle, context->ucm->mixers, ignore_dB);
/* probe_volumes() removes per-verb paths from ports if probing them
* fails. The path for the current verb is cached in
2022-01-03 17:21:28 +01:00
* pa_alsa_ucm_port_data.path, which is not cleared by probe_volumes() if
* the path gets removed, so we have to call update_mixer_paths() here to
* unset the cached path if needed. */
if (context->ucm->active_verb) {
const char *verb_name;
verb_name = pa_proplist_gets(context->ucm->active_verb->proplist, PA_ALSA_PROP_UCM_NAME);
update_mixer_paths(*p, verb_name);
}
/* then set property PA_PROP_DEVICE_INTENDED_ROLES */
merged_roles = pa_xstrdup(pa_proplist_gets(proplist, PA_PROP_DEVICE_INTENDED_ROLES));
dev = context->ucm_device;
if (dev) {
const char *roles = pa_proplist_gets(dev->proplist, role_name);
tmp = merge_roles(merged_roles, roles);
pa_xfree(merged_roles);
merged_roles = tmp;
}
mod = context->ucm_modifier;
if (mod) {
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, pa_card *card, pa_alsa_profile *new_profile, pa_alsa_profile *old_profile) {
int ret = 0;
const char *verb_name, *profile_name;
pa_alsa_ucm_verb *verb;
pa_alsa_mapping *map;
uint32_t idx;
if (new_profile == old_profile)
return 0;
if (new_profile == NULL) {
verb = NULL;
profile_name = SND_USE_CASE_VERB_INACTIVE;
verb_name = SND_USE_CASE_VERB_INACTIVE;
} else {
verb = new_profile->ucm_context.verb;
profile_name = new_profile->name;
verb_name = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME);
}
pa_log_info("Set profile to %s", profile_name);
if (ucm->active_verb != verb) {
/* change verb */
pa_log_info("Set UCM verb to %s", verb_name);
if ((snd_use_case_set(ucm->ucm_mgr, "_verb", verb_name)) < 0) {
pa_log("Failed to set verb %s", verb_name);
ret = -1;
}
} else if (ucm->active_verb) {
/* Disable modifiers not in new profile. Has to be done before
* devices, because _dismod fails if a modifier's supported
* devices are disabled. */
PA_IDXSET_FOREACH(map, old_profile->input_mappings, idx)
if (new_profile && !pa_idxset_contains(new_profile->input_mappings, map))
if (map->ucm_context.ucm_modifier && ucm_modifier_disable(ucm, map->ucm_context.ucm_modifier) < 0)
ret = -1;
PA_IDXSET_FOREACH(map, old_profile->output_mappings, idx)
if (new_profile && !pa_idxset_contains(new_profile->output_mappings, map))
if (map->ucm_context.ucm_modifier && ucm_modifier_disable(ucm, map->ucm_context.ucm_modifier) < 0)
ret = -1;
/* Disable devices not in new profile */
PA_IDXSET_FOREACH(map, old_profile->input_mappings, idx)
if (new_profile && !pa_idxset_contains(new_profile->input_mappings, map))
if (map->ucm_context.ucm_device && ucm_device_disable(ucm, map->ucm_context.ucm_device) < 0)
ret = -1;
PA_IDXSET_FOREACH(map, old_profile->output_mappings, idx)
if (new_profile && !pa_idxset_contains(new_profile->output_mappings, map))
if (map->ucm_context.ucm_device && ucm_device_disable(ucm, map->ucm_context.ucm_device) < 0)
ret = -1;
}
ucm->active_verb = verb;
2023-03-21 10:07:39 +01:00
update_mixer_paths(card->ports, verb_name);
return ret;
}
int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port) {
pa_alsa_ucm_config *ucm;
pa_alsa_ucm_device *dev;
pa_alsa_ucm_port_data *data;
const char *dev_name, *ucm_dev_name;
pa_assert(context && context->ucm);
ucm = context->ucm;
pa_assert(ucm->ucm_mgr);
data = PA_DEVICE_PORT_DATA(port);
dev = data->device;
pa_assert(dev);
if (context->ucm_device) {
dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
ucm_dev_name = pa_proplist_gets(context->ucm_device->proplist, PA_ALSA_PROP_UCM_NAME);
if (!pa_streq(dev_name, ucm_dev_name)) {
pa_log_error("Failed to set port %s with wrong UCM context: %s", dev_name, ucm_dev_name);
return -1;
}
}
return ucm_device_enable(ucm, dev);
}
static void ucm_add_mapping(pa_alsa_profile *p, pa_alsa_mapping *m) {
pa_alsa_path_set *ps;
/* create empty path set for the future path additions */
ps = pa_xnew0(pa_alsa_path_set, 1);
ps->direction = m->direction;
ps->paths = pa_hashmap_new_full(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func, pa_xfree,
(pa_free_cb_t) pa_alsa_path_free);
switch (m->direction) {
case PA_ALSA_DIRECTION_ANY:
pa_idxset_put(p->output_mappings, m, NULL);
pa_idxset_put(p->input_mappings, m, NULL);
m->output_path_set = ps;
m->input_path_set = ps;
break;
case PA_ALSA_DIRECTION_OUTPUT:
pa_idxset_put(p->output_mappings, m, NULL);
m->output_path_set = ps;
break;
case PA_ALSA_DIRECTION_INPUT:
pa_idxset_put(p->input_mappings, m, NULL);
m->input_path_set = ps;
break;
}
}
static void alsa_mapping_add_ucm_device(pa_alsa_mapping *m, pa_alsa_ucm_device *device) {
char *cur_desc;
const char *new_desc, *mdev;
bool is_sink = m->direction == PA_ALSA_DIRECTION_OUTPUT;
m->ucm_context.ucm_device = device;
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 (is_sink)
device->playback_mapping = m;
else
device->capture_mapping = m;
proplist_set_icon_name(m->proplist, device->type, is_sink);
mdev = get_mixer_device(device, is_sink);
if (mdev)
pa_proplist_sets(m->proplist, "alsa.mixer_device", mdev);
}
static void alsa_mapping_add_ucm_modifier(pa_alsa_mapping *m, pa_alsa_ucm_modifier *modifier) {
char *cur_desc;
const char *new_desc, *mod_name, *channel_str;
uint32_t channels = 0;
m->ucm_context.ucm_modifier = modifier;
new_desc = pa_proplist_gets(modifier->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);
m->description = m->description ? m->description : pa_xstrdup("");
/* Modifier sinks should not be routed to by default */
m->priority = 0;
mod_name = pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_NAME);
pa_proplist_sets(m->proplist, PA_ALSA_PROP_UCM_MODIFIER, mod_name);
/* save mapping to ucm modifier */
if (m->direction == PA_ALSA_DIRECTION_OUTPUT) {
modifier->playback_mapping = m;
channel_str = pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_PLAYBACK_CHANNELS);
} else {
modifier->capture_mapping = m;
channel_str = pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_CAPTURE_CHANNELS);
}
if (channel_str) {
/* FIXME: channel_str is unsanitized input from the UCM configuration,
* we should do proper error handling instead of asserting.
* https://bugs.freedesktop.org/show_bug.cgi?id=71823 */
pa_assert_se(pa_atou(channel_str, &channels) == 0 && pa_channels_valid(channels));
pa_log_debug("Got channel count %" PRIu32 " for modifier", channels);
}
if (channels)
pa_channel_map_init_extend(&m->channel_map, channels, PA_CHANNEL_MAP_ALSA);
else
pa_channel_map_init(&m->channel_map);
}
alsa-ucm: Make one input/output mapping per UCM device PulseAudio combines UCM devices that have the same PlaybackPCM or CapturePCM value into a single mapping with multiple ports. It also creates ports in the same mapping for each valid combination of those UCM devices. Since mappings are the things we put in profiles, we can put in a profile either all devices of a joint mapping or none of them. This causes some complications with device conflicts. For example, a different UCM device might be marked as conflicting with some (but not all) of the devices in a joint mapping. In this case we can do one of three things: - Include all devices in one profile, and hope the conflicting device isn't chosen as the mapping's active port. We shouldn't do this as it puts conflicting devices in the same profile. - Make one profile with the joint group, and one with the other device. This is somewhat acceptable as we have no conflicts, but we sacrifice some compatible combinations of devices. - Do not group the devices into the same mapping, and make one profile for each compatible combination of devices. This appears to be the best option, one where we can always have the maximum number of working devices. This patch chooses the third option and makes one input and/or output mapping per UCM device, by using UCM device names instead of PCM device strings in the mapping names. Link: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/commit/a50330a4a5ea7619f4de41c6d907a36a5e5ce022 Signed-off-by: Alper Nebi Yasak <alpernebiyasak@gmail.com>
2023-11-27 23:25:19 +03:00
static pa_alsa_mapping* ucm_alsa_mapping_get(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps, const char *verb_name, const char *ucm_name, bool is_sink) {
2021-07-21 13:11:17 +02:00
pa_alsa_mapping *m;
char *mapping_name;
alsa-ucm: Make one input/output mapping per UCM device PulseAudio combines UCM devices that have the same PlaybackPCM or CapturePCM value into a single mapping with multiple ports. It also creates ports in the same mapping for each valid combination of those UCM devices. Since mappings are the things we put in profiles, we can put in a profile either all devices of a joint mapping or none of them. This causes some complications with device conflicts. For example, a different UCM device might be marked as conflicting with some (but not all) of the devices in a joint mapping. In this case we can do one of three things: - Include all devices in one profile, and hope the conflicting device isn't chosen as the mapping's active port. We shouldn't do this as it puts conflicting devices in the same profile. - Make one profile with the joint group, and one with the other device. This is somewhat acceptable as we have no conflicts, but we sacrifice some compatible combinations of devices. - Do not group the devices into the same mapping, and make one profile for each compatible combination of devices. This appears to be the best option, one where we can always have the maximum number of working devices. This patch chooses the third option and makes one input and/or output mapping per UCM device, by using UCM device names instead of PCM device strings in the mapping names. Link: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/commit/a50330a4a5ea7619f4de41c6d907a36a5e5ce022 Signed-off-by: Alper Nebi Yasak <alpernebiyasak@gmail.com>
2023-11-27 23:25:19 +03:00
mapping_name = pa_sprintf_malloc("Mapping %s: %s: %s", verb_name, ucm_name, is_sink ? "sink" : "source");
2021-07-21 13:11:17 +02:00
m = pa_alsa_mapping_get(ps, mapping_name);
if (!m)
pa_log("No mapping for %s", mapping_name);
pa_xfree(mapping_name);
return m;
}
static const struct {
enum snd_pcm_chmap_position pos;
pa_channel_position_t channel;
} chmap_info[] = {
[SND_CHMAP_MONO] = { SND_CHMAP_MONO, PA_CHANNEL_POSITION_MONO },
[SND_CHMAP_FL] = { SND_CHMAP_FL, PA_CHANNEL_POSITION_FRONT_LEFT },
[SND_CHMAP_FR] = { SND_CHMAP_FR, PA_CHANNEL_POSITION_FRONT_RIGHT },
[SND_CHMAP_RL] = { SND_CHMAP_RL, PA_CHANNEL_POSITION_REAR_LEFT },
[SND_CHMAP_RR] = { SND_CHMAP_RR, PA_CHANNEL_POSITION_REAR_RIGHT },
[SND_CHMAP_FC] = { SND_CHMAP_FC, PA_CHANNEL_POSITION_FRONT_CENTER },
[SND_CHMAP_LFE] = { SND_CHMAP_LFE, PA_CHANNEL_POSITION_LFE },
[SND_CHMAP_SL] = { SND_CHMAP_SL, PA_CHANNEL_POSITION_SIDE_LEFT },
[SND_CHMAP_SR] = { SND_CHMAP_SR, PA_CHANNEL_POSITION_SIDE_RIGHT },
[SND_CHMAP_RC] = { SND_CHMAP_RC, PA_CHANNEL_POSITION_REAR_CENTER },
[SND_CHMAP_FLC] = { SND_CHMAP_FLC, PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER },
[SND_CHMAP_FRC] = { SND_CHMAP_FRC, PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER },
/* XXX: missing channel positions, mapped to aux... */
/* [SND_CHMAP_RLC] = { SND_CHMAP_RLC, PA_CHANNEL_POSITION_REAR_LEFT_OF_CENTER }, */
/* [SND_CHMAP_RRC] = { SND_CHMAP_RRC, PA_CHANNEL_POSITION_REAR_RIGHT_OF_CENTER }, */
/* [SND_CHMAP_FLW] = { SND_CHMAP_FLW, PA_CHANNEL_POSITION_FRONT_LEFT_WIDE }, */
/* [SND_CHMAP_FRW] = { SND_CHMAP_FRW, PA_CHANNEL_POSITION_FRONT_RIGHT_WIDE }, */
/* [SND_CHMAP_FLH] = { SND_CHMAP_FLH, PA_CHANNEL_POSITION_FRONT_LEFT_HIGH }, */
/* [SND_CHMAP_FCH] = { SND_CHMAP_FCH, PA_CHANNEL_POSITION_FRONT_CENTER_HIGH }, */
/* [SND_CHMAP_FRH] = { SND_CHMAP_FRH, PA_CHANNEL_POSITION_FRONT_RIGHT_HIGH }, */
[SND_CHMAP_TC] = { SND_CHMAP_TC, PA_CHANNEL_POSITION_TOP_CENTER },
[SND_CHMAP_TFL] = { SND_CHMAP_TFL, PA_CHANNEL_POSITION_TOP_FRONT_LEFT },
[SND_CHMAP_TFR] = { SND_CHMAP_TFR, PA_CHANNEL_POSITION_TOP_FRONT_RIGHT },
[SND_CHMAP_TFC] = { SND_CHMAP_TFC, PA_CHANNEL_POSITION_TOP_FRONT_CENTER },
[SND_CHMAP_TRL] = { SND_CHMAP_TRL, PA_CHANNEL_POSITION_TOP_REAR_LEFT },
[SND_CHMAP_TRR] = { SND_CHMAP_TRR, PA_CHANNEL_POSITION_TOP_REAR_RIGHT },
[SND_CHMAP_TRC] = { SND_CHMAP_TRC, PA_CHANNEL_POSITION_TOP_REAR_CENTER },
/* [SND_CHMAP_TFLC] = { SND_CHMAP_TFLC, PA_CHANNEL_POSITION_TOP_FRONT_LEFT_OF_CENTER }, */
/* [SND_CHMAP_TFRC] = { SND_CHMAP_TFRC, PA_CHANNEL_POSITION_TOP_FRONT_RIGHT_OF_CENTER }, */
/* [SND_CHMAP_TSL] = { SND_CHMAP_TSL, PA_CHANNEL_POSITION_TOP_SIDE_LEFT }, */
/* [SND_CHMAP_TSR] = { SND_CHMAP_TSR, PA_CHANNEL_POSITION_TOP_SIDE_RIGHT }, */
/* [SND_CHMAP_LLFE] = { SND_CHMAP_LLFE, PA_CHANNEL_POSITION_LEFT_LFE }, */
/* [SND_CHMAP_RLFE] = { SND_CHMAP_RLFE, PA_CHANNEL_POSITION_RIGHT_LFE }, */
/* [SND_CHMAP_BC] = { SND_CHMAP_BC, PA_CHANNEL_POSITION_BOTTOM_CENTER }, */
/* [SND_CHMAP_BLC] = { SND_CHMAP_BLC, PA_CHANNEL_POSITION_BOTTOM_LEFT_OF_CENTER }, */
/* [SND_CHMAP_BRC] = { SND_CHMAP_BRC, PA_CHANNEL_POSITION_BOTTOM_RIGHT_OF_CENTER }, */
};
static void ucm_split_to_channel_map(pa_channel_map *m, const pa_alsa_ucm_split *s)
{
const int n = sizeof(chmap_info) / sizeof(chmap_info[0]);
int i;
int aux = 0;
for (i = 0; i < s->channels; ++i) {
int p = s->pos[i];
if (p >= 0 && p < n && (int)chmap_info[p].pos == p)
m->map[i] = chmap_info[p].channel;
else
m->map[i] = PA_CHANNEL_POSITION_AUX0 + aux++;
if (aux >= 32)
break;
}
m->channels = i;
}
static int ucm_create_mapping_direction(
pa_alsa_ucm_config *ucm,
pa_alsa_profile_set *ps,
pa_alsa_ucm_device *device,
const char *verb_name,
const char *device_name,
const char *device_str,
bool is_sink) {
pa_alsa_mapping *m;
unsigned priority, rate, channels;
alsa-ucm: Make one input/output mapping per UCM device PulseAudio combines UCM devices that have the same PlaybackPCM or CapturePCM value into a single mapping with multiple ports. It also creates ports in the same mapping for each valid combination of those UCM devices. Since mappings are the things we put in profiles, we can put in a profile either all devices of a joint mapping or none of them. This causes some complications with device conflicts. For example, a different UCM device might be marked as conflicting with some (but not all) of the devices in a joint mapping. In this case we can do one of three things: - Include all devices in one profile, and hope the conflicting device isn't chosen as the mapping's active port. We shouldn't do this as it puts conflicting devices in the same profile. - Make one profile with the joint group, and one with the other device. This is somewhat acceptable as we have no conflicts, but we sacrifice some compatible combinations of devices. - Do not group the devices into the same mapping, and make one profile for each compatible combination of devices. This appears to be the best option, one where we can always have the maximum number of working devices. This patch chooses the third option and makes one input and/or output mapping per UCM device, by using UCM device names instead of PCM device strings in the mapping names. Link: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/commit/a50330a4a5ea7619f4de41c6d907a36a5e5ce022 Signed-off-by: Alper Nebi Yasak <alpernebiyasak@gmail.com>
2023-11-27 23:25:19 +03:00
m = ucm_alsa_mapping_get(ucm, ps, verb_name, device_name, is_sink);
2021-07-21 13:11:17 +02:00
if (!m)
return -1;
2021-07-21 13:11:17 +02:00
pa_log_debug("UCM mapping: %s dev %s", m->name, device_name);
priority = is_sink ? device->playback_priority : device->capture_priority;
rate = is_sink ? device->playback_rate : device->capture_rate;
channels = is_sink ? device->playback_channels : device->capture_channels;
if (!m->ucm_context.ucm_device) { /* new mapping */
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;
if (rate)
m->sample_spec.rate = rate;
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);
if (is_sink && device->playback_split) {
m->split = pa_xmemdup(device->playback_split, sizeof(*m->split));
ucm_split_to_channel_map(&m->channel_map, m->split);
} else if (!is_sink && device->capture_split) {
m->split = pa_xmemdup(device->capture_split, sizeof(*m->split));
ucm_split_to_channel_map(&m->channel_map, m->split);
}
alsa_mapping_add_ucm_device(m, device);
return 0;
}
static int ucm_create_mapping_for_modifier(
pa_alsa_ucm_config *ucm,
pa_alsa_profile_set *ps,
pa_alsa_ucm_modifier *modifier,
const char *verb_name,
const char *mod_name,
const char *device_str,
bool is_sink) {
pa_alsa_mapping *m;
alsa-ucm: Make one input/output mapping per UCM device PulseAudio combines UCM devices that have the same PlaybackPCM or CapturePCM value into a single mapping with multiple ports. It also creates ports in the same mapping for each valid combination of those UCM devices. Since mappings are the things we put in profiles, we can put in a profile either all devices of a joint mapping or none of them. This causes some complications with device conflicts. For example, a different UCM device might be marked as conflicting with some (but not all) of the devices in a joint mapping. In this case we can do one of three things: - Include all devices in one profile, and hope the conflicting device isn't chosen as the mapping's active port. We shouldn't do this as it puts conflicting devices in the same profile. - Make one profile with the joint group, and one with the other device. This is somewhat acceptable as we have no conflicts, but we sacrifice some compatible combinations of devices. - Do not group the devices into the same mapping, and make one profile for each compatible combination of devices. This appears to be the best option, one where we can always have the maximum number of working devices. This patch chooses the third option and makes one input and/or output mapping per UCM device, by using UCM device names instead of PCM device strings in the mapping names. Link: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/commit/a50330a4a5ea7619f4de41c6d907a36a5e5ce022 Signed-off-by: Alper Nebi Yasak <alpernebiyasak@gmail.com>
2023-11-27 23:25:19 +03:00
m = ucm_alsa_mapping_get(ucm, ps, verb_name, mod_name, is_sink);
2021-07-21 13:11:17 +02:00
if (!m)
return -1;
2021-07-21 13:11:17 +02:00
pa_log_info("UCM mapping: %s modifier %s", m->name, mod_name);
if (!m->ucm_context.ucm_device && !m->ucm_context.ucm_modifier) { /* new mapping */
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;
/* Modifier sinks should not be routed to by default */
m->priority = 0;
}
alsa_mapping_add_ucm_modifier(m, modifier);
return 0;
}
static int ucm_create_mapping(
pa_alsa_ucm_config *ucm,
pa_alsa_profile_set *ps,
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, device, verb_name, device_name, sink, true);
if (ret == 0 && source)
ret = ucm_create_mapping_direction(ucm, ps, device, verb_name, device_name, source, false);
return ret;
}
static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *device) {
pa_alsa_jack *j;
const char *device_name;
const char *jack_control;
const char *mixer_device_name;
char *name;
pa_assert(ucm);
pa_assert(device);
device_name = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_NAME);
jack_control = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_JACK_CONTROL);
if (jack_control) {
#if SND_LIB_VERSION >= 0x10201
snd_ctl_elem_id_t *ctl;
int err, index;
snd_ctl_elem_id_alloca(&ctl);
err = snd_use_case_parse_ctl_elem_id(ctl, "JackControl", jack_control);
if (err < 0)
return NULL;
jack_control = snd_ctl_elem_id_get_name(ctl);
index = snd_ctl_elem_id_get_index(ctl);
if (index > 0) {
pa_log("[%s] Invalid JackControl index value: \"%s\",%d", device_name, jack_control, index);
return NULL;
}
#else
#warning "Upgrade to alsa-lib 1.2.1!"
#endif
if (!pa_endswith(jack_control, " Jack")) {
pa_log("[%s] Invalid JackControl value: \"%s\"", device_name, jack_control);
return NULL;
}
/* pa_alsa_jack_new() expects a jack name without " Jack" at the
* end, so drop the trailing " Jack". */
name = pa_xstrndup(jack_control, strlen(jack_control) - 5);
} else {
/* The jack control hasn't been explicitly configured, fail. */
return NULL;
}
PA_LLIST_FOREACH(j, ucm->jacks)
if (pa_streq(j->name, name))
goto finish;
mixer_device_name = get_jack_mixer_device(device, true);
if (!mixer_device_name)
mixer_device_name = get_jack_mixer_device(device, false);
if (!mixer_device_name) {
pa_log("[%s] No mixer device name for JackControl \"%s\"", device_name, jack_control);
2020-11-23 03:35:30 -05:00
j = NULL;
goto finish;
}
2021-01-05 10:08:31 +01:00
j = pa_alsa_jack_new(NULL, mixer_device_name, name, 0);
PA_LLIST_PREPEND(pa_alsa_jack, ucm->jacks, j);
finish:
pa_xfree(name);
return j;
}
static int ucm_create_profile(
pa_alsa_ucm_config *ucm,
pa_alsa_profile_set *ps,
pa_alsa_ucm_verb *verb,
pa_idxset *mappings,
const char *profile_name,
const char *profile_desc,
unsigned int profile_priority) {
pa_alsa_profile *p;
pa_alsa_mapping *map;
uint32_t idx;
pa_assert(ps);
if (pa_hashmap_get(ps->profiles, profile_name)) {
pa_log("Profile %s already exists", profile_name);
return -1;
}
p = pa_xnew0(pa_alsa_profile, 1);
p->profile_set = ps;
p->name = pa_xstrdup(profile_name);
p->description = pa_xstrdup(profile_desc);
p->priority = profile_priority;
p->ucm_context.verb = verb;
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);
PA_IDXSET_FOREACH(map, mappings, idx)
ucm_add_mapping(p, map);
pa_alsa_profile_dump(p);
return 0;
}
static int ucm_create_verb_profiles(
pa_alsa_ucm_config *ucm,
pa_alsa_profile_set *ps,
pa_alsa_ucm_verb *verb,
const char *verb_name,
const char *verb_desc) {
pa_idxset *verb_devices, *p_devices, *p_mappings;
pa_alsa_ucm_device *dev;
pa_alsa_ucm_modifier *mod;
int i = 0;
int n_profiles = 0;
const char *name, *sink, *source;
char *p_name, *p_desc, *tmp;
unsigned int verb_priority, p_priority;
uint32_t idx;
void *state = NULL;
/* TODO: get profile priority from policy management */
verb_priority = verb->priority;
if (verb_priority == 0) {
char *verb_cmp, *c;
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) {
verb_priority = verb_info[i].priority;
break;
}
}
pa_xfree(verb_cmp);
}
PA_LLIST_FOREACH(dev, verb->devices) {
pa_alsa_jack *jack;
const char *jack_hw_mute;
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, dev, verb_name, name, sink, source);
jack = ucm_get_jack(ucm, dev);
if (jack)
device_set_jack(dev, jack);
/* JackHWMute contains a list of device names. Each listed device must
* be associated with the jack object that we just created. */
jack_hw_mute = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_JACK_HW_MUTE);
if (jack_hw_mute && !jack) {
pa_log("[%s] JackHWMute set, but JackControl is missing", name);
jack_hw_mute = NULL;
}
if (jack_hw_mute) {
char *hw_mute_device_name;
const char *state = NULL;
while ((hw_mute_device_name = ucm_split_devnames(jack_hw_mute, &state))) {
pa_alsa_ucm_verb *verb2;
bool device_found = false;
/* Search the referenced device from all verbs. If there are
* multiple verbs that have a device with this name, we add the
* hw mute association to each of those devices. */
PA_LLIST_FOREACH(verb2, ucm->verbs) {
pa_alsa_ucm_device *hw_mute_device;
hw_mute_device = verb_find_device(verb2, hw_mute_device_name);
if (hw_mute_device) {
device_found = true;
device_add_hw_mute_jack(hw_mute_device, jack);
}
}
if (!device_found)
pa_log("[%s] JackHWMute references an unknown device: %s", name, hw_mute_device_name);
pa_xfree(hw_mute_device_name);
}
}
}
/* Now find modifiers that have their own PlaybackPCM and create
* separate sinks for them. */
PA_LLIST_FOREACH(mod, verb->modifiers) {
name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME);
sink = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_SINK);
source = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_SOURCE);
if (sink)
ucm_create_mapping_for_modifier(ucm, ps, mod, verb_name, name, sink, true);
else if (source)
ucm_create_mapping_for_modifier(ucm, ps, mod, verb_name, name, source, false);
}
verb_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
PA_LLIST_FOREACH(dev, verb->devices)
pa_idxset_put(verb_devices, dev, NULL);
while ((p_devices = iterate_maximal_device_subsets(verb_devices, &state))) {
p_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
/* Add the mappings that include our selected devices */
PA_IDXSET_FOREACH(dev, p_devices, idx) {
if (dev->playback_mapping)
pa_idxset_put(p_mappings, dev->playback_mapping, NULL);
if (dev->capture_mapping)
pa_idxset_put(p_mappings, dev->capture_mapping, NULL);
}
/* Add mappings only for the modifiers that can work with our
* device selection */
PA_LLIST_FOREACH(mod, verb->modifiers)
if (pa_idxset_isempty(mod->supported_devices) || pa_idxset_issubset(mod->supported_devices, p_devices))
if (pa_idxset_isdisjoint(mod->conflicting_devices, p_devices)) {
if (mod->playback_mapping)
pa_idxset_put(p_mappings, mod->playback_mapping, NULL);
if (mod->capture_mapping)
pa_idxset_put(p_mappings, mod->capture_mapping, NULL);
}
/* If we'll have multiple profiles for this verb, their names
* must be unique. Use a list of chosen devices to disambiguate
* them. If the profile contains all devices of a verb, we'll
* generate only onle profile whose name should be the verb
* name. GUIs usually show the profile description instead of
* the name, add the device names to those as well. */
tmp = devset_name(p_devices, ", ");
if (pa_idxset_equals(p_devices, verb_devices)) {
p_name = pa_xstrdup(verb_name);
p_desc = pa_xstrdup(verb_desc);
} else {
p_name = pa_sprintf_malloc("%s (%s)", verb_name, tmp);
p_desc = pa_sprintf_malloc("%s (%s)", verb_desc, tmp);
}
/* Make sure profiles with higher-priority devices are
* prioritized. */
p_priority = verb_priority + devset_playback_priority(p_devices, false) + devset_capture_priority(p_devices, false);
if (ucm_create_profile(ucm, ps, verb, p_mappings, p_name, p_desc, p_priority) == 0) {
pa_log_debug("Created profile %s for UCM verb %s", p_name, verb_name);
n_profiles++;
}
pa_xfree(tmp);
pa_xfree(p_name);
pa_xfree(p_desc);
pa_idxset_free(p_mappings, NULL);
pa_idxset_free(p_devices, NULL);
}
pa_idxset_free(verb_devices, NULL);
if (n_profiles == 0) {
pa_log("UCM verb %s created no profiles", verb_name);
return -1;
}
return 0;
}
static void mapping_init_eld(pa_alsa_mapping *m, snd_pcm_t *pcm)
{
pa_alsa_ucm_mapping_context *context = &m->ucm_context;
pa_alsa_ucm_device *dev;
2022-01-03 17:21:28 +01:00
char *mdev, *alib_prefix;
snd_pcm_info_t *info;
int pcm_card, pcm_device;
snd_pcm_info_alloca(&info);
if (snd_pcm_info(pcm, info) < 0)
return;
if ((pcm_card = snd_pcm_info_get_card(info)) < 0)
return;
if ((pcm_device = snd_pcm_info_get_device(info)) < 0)
return;
2022-01-03 17:21:28 +01:00
alib_prefix = context->ucm->alib_prefix;
dev = context->ucm_device;
mdev = pa_sprintf_malloc("%shw:%i", alib_prefix ? alib_prefix : "", pcm_card);
if (mdev == NULL)
return;
dev->eld_mixer_device_name = mdev;
dev->eld_device = pcm_device;
}
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->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 (!m->split) {
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);
} else {
if (!m->split->leader) {
errno = EINVAL;
return NULL;
}
exact_channels = true;
try_ss.channels = m->split->hw_channels;
pa_channel_map_init_extend(&try_map, try_ss.channels, PA_CHANNEL_MAP_AUX);
}
try_period_size =
pa_usec_to_bytes(ucm->default_fragment_size_msec * PA_USEC_PER_MSEC, &try_ss) /
pa_frame_size(&try_ss);
try_buffer_size = ucm->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, NULL, NULL, exact_channels);
if (pcm) {
if (!exact_channels)
m->channel_map = try_map;
mapping_init_eld(m, pcm);
}
return pcm;
}
static void pa_alsa_init_proplist_split_pcm(pa_idxset *mappings, pa_alsa_mapping *leader, pa_direction_t direction)
{
pa_proplist *props = pa_proplist_new();
uint32_t idx;
pa_alsa_mapping *m;
if (direction == PA_DIRECTION_OUTPUT)
pa_alsa_init_proplist_pcm(NULL, props, leader->output_pcm);
else
pa_alsa_init_proplist_pcm(NULL, props, leader->input_pcm);
PA_IDXSET_FOREACH(m, mappings, idx) {
if (!m->split)
continue;
if (!pa_streq(m->device_strings[0], leader->device_strings[0]))
continue;
if (direction == PA_DIRECTION_OUTPUT)
pa_proplist_update(m->output_proplist, PA_UPDATE_REPLACE, props);
else
pa_proplist_update(m->input_proplist, PA_UPDATE_REPLACE, props);
}
pa_proplist_free(props);
}
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 (p->supported)
m->supported++;
if (!m->output_pcm)
continue;
if (!m->split)
pa_alsa_init_proplist_pcm(NULL, m->output_proplist, m->output_pcm);
else
pa_alsa_init_proplist_split_pcm(p->output_mappings, m, PA_DIRECTION_OUTPUT);
pa_alsa_close(&m->output_pcm);
}
PA_IDXSET_FOREACH(m, p->input_mappings, idx) {
if (p->supported)
m->supported++;
if (!m->input_pcm)
continue;
if (!m->split)
pa_alsa_init_proplist_pcm(NULL, m->input_proplist, m->input_pcm);
else
pa_alsa_init_proplist_split_pcm(p->input_mappings, m, PA_DIRECTION_INPUT);
pa_alsa_close(&m->input_pcm);
}
}
static void ucm_mapping_jack_probe(pa_alsa_mapping *m, pa_hashmap *mixers) {
snd_mixer_t *mixer_handle;
pa_alsa_ucm_mapping_context *context = &m->ucm_context;
pa_alsa_ucm_device *dev;
bool has_control;
dev = context->ucm_device;
if (!dev->jack || !dev->jack->mixer_device_name)
return;
mixer_handle = pa_alsa_open_mixer_by_name(mixers, dev->jack->mixer_device_name, true);
if (!mixer_handle) {
pa_log_error("Unable to determine open mixer device '%s' for jack %s", dev->jack->mixer_device_name, dev->jack->name);
return;
Revert "alsa: UCM conflicting devices changes" If needs some more work This reverts commit aae770f18b7f6c94195fb3f2e2a61308029086af. This reverts commit 2216956e05689079db5ec05f793158e6479060da. This reverts commit 1d3c4501eff3392e0cd858d980c742c7f88c95e7. This reverts commit c7ca02460761273de0d1a8432c5defaafed43664. This reverts commit 8e4a5f5d5ed3d8b3081956f451c85c23aa74fc15. This reverts commit 8b20675ad12bc9daea84f6d4314ec12fccf5882c. This reverts commit ebe6f8b51a31ff7bd71d560991a12283f710b937. This reverts commit 4b97da1277b709e9749b568319f5eb403ff0f362. This reverts commit 7c46a656c734d4688cac229cd9d49af5ad0609fa. This reverts commit 96ed03e1fc5b1f0a287069c42031d7961239c896. This reverts commit 43770c533c71bfaf2c7e82d47d50bfd0533b53e0. This reverts commit ccb2fce05452185858d22de06d659452702f05d8. This reverts commit 93d0ddc8b327f9aaf11d71193f1d50d64bf1144b. This reverts commit 815e8c8fa09a88269b674786a152fe36849b933d. This reverts commit 97b99a0f7c2dd059c08c6ccd143cc1be06111ad1. This reverts commit 7e4d87402b17cd881e9140c37285b175c622ea24. This reverts commit 168b3ee22c1545b738634c4ae89ae4afbaf68cc4. This reverts commit 5128cdba319a2ced0f2e559ad594b8cbeb97a072. This reverts commit 31cde774c54931109da99bc452c858a208d94d6c. This reverts commit b02c8ba153f21459f1a120d4c1ca1bde6a98f4fb. This reverts commit 68ac72d09857901a33be22922669fc8674a4ab9c. This reverts commit 96cfc9bc1ac63414e7b3a6b928be5a29c03e1431. This reverts commit e2d642a20b1bc4e70f7273ddfc3da376a342e10f. This reverts commit 80fc80c34348bdc247bb1a12bcc174db772208a4. This reverts commit 3a68905c7c45677f64213ff563fd68bce6d14a9f. This reverts commit 7e64680a65e4dc2c755e79e55f43f7596b31e1d9. This reverts commit 8d1e38a2816e113121f2355a5827b0a681a3d41b. This reverts commit 591188195c60bb0bba4019d76547ece5386496ac. This reverts commit 973fe0cb79c9ef508778c7a2bb98bdfebeb70dfe. This reverts commit 4d08c9ad609d47c16d03ae3d5230c14d8d52f49e. This reverts commit 3dce92e58090de98e9f1470d9ba69da1f1b590db. This reverts commit 6b5e232c5c4a8e3d2d1c442c87ab03cc40350978. This reverts commit b1286c62c90167d6f6b6c17ad9bdfbb5d0254f60.
2023-04-12 12:30:56 +02:00
}
has_control = pa_alsa_mixer_find_card(mixer_handle, &dev->jack->alsa_id, 0) != NULL;
pa_alsa_jack_set_has_control(dev->jack, has_control);
pa_log_info("UCM jack %s has_control=%d", dev->jack->name, dev->jack->has_control);
}
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;
const char *verb_name;
uint32_t idx;
PA_HASHMAP_FOREACH(p, ps->profiles, state) {
pa_log_info("Probing profile %s", p->name);
/* change verb */
verb_name = pa_proplist_gets(p->ucm_context.verb->proplist, PA_ALSA_PROP_UCM_NAME);
pa_log_info("Set ucm verb to %s", verb_name);
if ((snd_use_case_set(ucm->ucm_mgr, "_verb", verb_name)) < 0) {
pa_log("Failed to set verb %s", verb_name);
p->supported = false;
continue;
}
PA_IDXSET_FOREACH(m, p->output_mappings, idx) {
if (PA_UCM_IS_MODIFIER_MAPPING(m)) {
/* Skip jack probing on modifier PCMs since we expect this to
* only be controlled on the main device/verb PCM. */
continue;
}
if (m->split && !m->split->leader)
continue;
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) {
if (PA_UCM_IS_MODIFIER_MAPPING(m)) {
/* Skip jack probing on modifier PCMs since we expect this to
* only be controlled on the main device/verb PCM. */
continue;
}
if (m->split && !m->split->leader)
continue;
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);
PA_IDXSET_FOREACH(m, p->output_mappings, idx)
if (!PA_UCM_IS_MODIFIER_MAPPING(m))
ucm_mapping_jack_probe(m, ucm->mixers);
PA_IDXSET_FOREACH(m, p->input_mappings, idx)
if (!PA_UCM_IS_MODIFIER_MAPPING(m))
ucm_mapping_jack_probe(m, ucm->mixers);
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_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
(pa_free_cb_t) pa_alsa_mapping_free);
ps->profiles = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
(pa_free_cb_t) pa_alsa_profile_free);
ps->decibel_fixes = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
/* create profiles 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_verb_profiles(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);
if (di->hw_mute_jacks)
pa_dynarray_free(di->hw_mute_jacks);
if (di->ucm_ports)
pa_dynarray_free(di->ucm_ports);
if (di->playback_volumes)
pa_hashmap_free(di->playback_volumes);
if (di->capture_volumes)
pa_hashmap_free(di->capture_volumes);
pa_proplist_free(di->proplist);
pa_idxset_free(di->conflicting_devices, NULL);
pa_idxset_free(di->supported_devices, NULL);
pa_xfree(di->eld_mixer_device_name);
pa_xfree(di->playback_split);
pa_xfree(di->capture_split);
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);
pa_idxset_free(mi->conflicting_devices, NULL);
pa_idxset_free(mi->supported_devices, NULL);
pa_xfree(mi->media_role);
pa_xfree(mi);
}
pa_proplist_free(verb->proplist);
pa_xfree(verb);
}
static pa_alsa_ucm_device *verb_find_device(pa_alsa_ucm_verb *verb, const char *device_name) {
pa_alsa_ucm_device *device;
pa_assert(verb);
pa_assert(device_name);
PA_LLIST_FOREACH(device, verb->devices) {
const char *name;
name = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_NAME);
if (pa_streq(name, device_name))
return device;
}
return NULL;
}
void pa_alsa_ucm_free(pa_alsa_ucm_config *ucm) {
pa_alsa_ucm_verb *vi, *vn;
pa_alsa_jack *ji, *jn;
PA_LLIST_FOREACH_SAFE(vi, vn, ucm->verbs) {
PA_LLIST_REMOVE(pa_alsa_ucm_verb, ucm->verbs, vi);
free_verb(vi);
}
PA_LLIST_FOREACH_SAFE(ji, jn, ucm->jacks) {
PA_LLIST_REMOVE(pa_alsa_jack, ucm->jacks, ji);
pa_alsa_jack_free(ji);
}
if (ucm->ucm_mgr) {
snd_use_case_mgr_close(ucm->ucm_mgr);
ucm->ucm_mgr = NULL;
}
2022-01-03 17:21:28 +01:00
pa_xfree(ucm->alib_prefix);
ucm->alib_prefix = NULL;
}
void pa_alsa_ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context) {
pa_alsa_ucm_device *dev;
pa_alsa_ucm_modifier *mod;
dev = context->ucm_device;
if (dev) {
/* clear ucm device pointer to mapping */
if (context->direction == PA_DIRECTION_OUTPUT)
dev->playback_mapping = NULL;
else
dev->capture_mapping = NULL;
}
mod = context->ucm_modifier;
if (mod) {
if (context->direction == PA_DIRECTION_OUTPUT)
mod->playback_mapping = NULL;
else
mod->capture_mapping = NULL;
}
}
/* Enable the modifier when the first stream with matched role starts */
void pa_alsa_ucm_roled_stream_begin(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) {
pa_alsa_ucm_modifier *mod;
if (!ucm->active_verb)
return;
PA_LLIST_FOREACH(mod, ucm->active_verb->modifiers) {
if ((mod->action_direction == dir) && (pa_streq(mod->media_role, role))) {
if (mod->enabled_counter == 0) {
ucm_modifier_enable(ucm, mod);
}
mod->enabled_counter++;
break;
}
}
}
/* Disable the modifier when the last stream with matched role ends */
void pa_alsa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) {
pa_alsa_ucm_modifier *mod;
if (!ucm->active_verb)
return;
PA_LLIST_FOREACH(mod, ucm->active_verb->modifiers) {
if ((mod->action_direction == dir) && (pa_streq(mod->media_role, role))) {
mod->enabled_counter--;
if (mod->enabled_counter == 0)
ucm_modifier_disable(ucm, mod);
break;
}
}
}
static void device_set_jack(pa_alsa_ucm_device *device, pa_alsa_jack *jack) {
pa_assert(device);
pa_assert(jack);
device->jack = jack;
pa_alsa_jack_add_ucm_device(jack, device);
pa_alsa_ucm_device_update_available(device);
}
static void device_add_hw_mute_jack(pa_alsa_ucm_device *device, pa_alsa_jack *jack) {
pa_assert(device);
pa_assert(jack);
pa_dynarray_append(device->hw_mute_jacks, jack);
pa_alsa_jack_add_ucm_hw_mute_device(jack, device);
pa_alsa_ucm_device_update_available(device);
}
static void device_set_available(pa_alsa_ucm_device *device, pa_available_t available) {
pa_alsa_ucm_port_data *port;
unsigned idx;
pa_assert(device);
if (available == device->available)
return;
device->available = available;
PA_DYNARRAY_FOREACH(port, device->ucm_ports, idx)
pa_device_port_set_available(port->core_port, port->device->available);
}
void pa_alsa_ucm_device_update_available(pa_alsa_ucm_device *device) {
pa_available_t available = PA_AVAILABLE_UNKNOWN;
pa_alsa_jack *jack;
unsigned idx;
pa_assert(device);
if (device->jack && device->jack->has_control)
available = device->jack->plugged_in ? PA_AVAILABLE_YES : PA_AVAILABLE_NO;
PA_DYNARRAY_FOREACH(jack, device->hw_mute_jacks, idx) {
if (jack->plugged_in) {
available = PA_AVAILABLE_NO;
break;
}
}
device_set_available(device, available);
}
static void ucm_port_data_init(pa_alsa_ucm_port_data *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port,
pa_alsa_ucm_device *device) {
pa_assert(ucm);
pa_assert(core_port);
pa_assert(device);
port->ucm = ucm;
port->core_port = core_port;
port->eld_device = -1;
port->device = device;
pa_dynarray_append(device->ucm_ports, port);
port->paths = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree, NULL);
pa_device_port_set_available(port->core_port, port->device->available);
}
static void ucm_port_data_free(pa_device_port *port) {
pa_alsa_ucm_port_data *ucm_port;
pa_assert(port);
ucm_port = PA_DEVICE_PORT_DATA(port);
if (ucm_port->paths)
pa_hashmap_free(ucm_port->paths);
pa_xfree(ucm_port->eld_mixer_device_name);
}
long pa_alsa_ucm_port_device_status(pa_alsa_ucm_port_data *data) {
return ucm_device_status(data->ucm, data->device);
}
#else /* HAVE_ALSA_UCM */
/* Dummy functions for systems without UCM support */
int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index) {
pa_log_info("UCM not available.");
return -1;
}
pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map) {
return NULL;
}
int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, pa_alsa_profile *new_profile, pa_alsa_profile *old_profile) {
return -1;
}
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) {
return -1;
}
void pa_alsa_ucm_add_ports(
pa_hashmap **hash,
pa_proplist *proplist,
pa_alsa_ucm_mapping_context *context,
bool is_sink,
pa_card *card,
snd_pcm_t *pcm_handle,
bool ignore_dB) {
}
void pa_alsa_ucm_add_port(
pa_hashmap *hash,
pa_alsa_ucm_mapping_context *context,
bool is_sink,
pa_hashmap *ports,
pa_card_profile *cp,
pa_core *core) {
}
int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port) {
return -1;
}
void pa_alsa_ucm_free(pa_alsa_ucm_config *ucm) {
}
void pa_alsa_ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context) {
}
void pa_alsa_ucm_roled_stream_begin(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) {
}
void pa_alsa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) {
}
long pa_alsa_ucm_port_device_status(pa_alsa_ucm_port_data *data) {
return -1;
}
#endif