mirror of
https://github.com/alsa-project/alsa-lib.git
synced 2026-03-04 01:40:09 -05:00
ucm: implement DeviceVariant configuration extension
It may be useful for the channel count specification for example. Signed-off-by: Jaroslav Kysela <perex@perex.cz>
This commit is contained in:
parent
554efca497
commit
3149ca0f1c
3 changed files with 323 additions and 63 deletions
308
src/ucm/parser.c
308
src/ucm/parser.c
|
|
@ -1549,75 +1549,16 @@ static int parse_modifier(snd_use_case_mgr_t *uc_mgr,
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Parse Device Use Cases
|
* Parse device configuration fields
|
||||||
*
|
|
||||||
* # Each device is described in new section. N devices are allowed
|
|
||||||
* SectionDevice."Headphones" {
|
|
||||||
* Comment "Headphones connected to 3.5mm jack"
|
|
||||||
*
|
|
||||||
* SupportedDevice [
|
|
||||||
* "x"
|
|
||||||
* "y"
|
|
||||||
* ]
|
|
||||||
*
|
|
||||||
* ConflictingDevice [
|
|
||||||
* "x"
|
|
||||||
* "y"
|
|
||||||
* ]
|
|
||||||
*
|
|
||||||
* EnableSequence [
|
|
||||||
* ....
|
|
||||||
* ]
|
|
||||||
*
|
|
||||||
* DisableSequence [
|
|
||||||
* ...
|
|
||||||
* ]
|
|
||||||
*
|
|
||||||
* TransitionSequence."ToDevice" [
|
|
||||||
* ...
|
|
||||||
* ]
|
|
||||||
*
|
|
||||||
* Value {
|
|
||||||
* PlaybackVolume "name='Master Playback Volume',index=2"
|
|
||||||
* PlaybackSwitch "name='Master Playback Switch',index=2"
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*/
|
*/
|
||||||
static int parse_device(snd_use_case_mgr_t *uc_mgr,
|
static int parse_device_fields(snd_use_case_mgr_t *uc_mgr,
|
||||||
snd_config_t *cfg,
|
snd_config_t *cfg,
|
||||||
void *data1, void *data2)
|
struct use_case_device *device)
|
||||||
{
|
{
|
||||||
struct use_case_verb *verb = data1;
|
|
||||||
char *name;
|
|
||||||
struct use_case_device *device;
|
|
||||||
snd_config_iterator_t i, next;
|
snd_config_iterator_t i, next;
|
||||||
snd_config_t *n;
|
snd_config_t *n;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if (parse_get_safe_name(uc_mgr, cfg, data2, &name) < 0)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
device = calloc(1, sizeof(*device));
|
|
||||||
if (device == NULL) {
|
|
||||||
free(name);
|
|
||||||
return -ENOMEM;
|
|
||||||
}
|
|
||||||
INIT_LIST_HEAD(&device->enable_list);
|
|
||||||
INIT_LIST_HEAD(&device->disable_list);
|
|
||||||
INIT_LIST_HEAD(&device->transition_list);
|
|
||||||
INIT_LIST_HEAD(&device->dev_list.list);
|
|
||||||
INIT_LIST_HEAD(&device->value_list);
|
|
||||||
list_add_tail(&device->list, &verb->device_list);
|
|
||||||
device->name = name;
|
|
||||||
device->orig_name = strdup(name);
|
|
||||||
if (device->orig_name == NULL)
|
|
||||||
return -ENOMEM;
|
|
||||||
|
|
||||||
/* in-place evaluation */
|
|
||||||
err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
|
|
||||||
if (err < 0)
|
|
||||||
return err;
|
|
||||||
|
|
||||||
snd_config_for_each(i, next, cfg) {
|
snd_config_for_each(i, next, cfg) {
|
||||||
const char *id;
|
const char *id;
|
||||||
n = snd_config_iterator_entry(i);
|
n = snd_config_iterator_entry(i);
|
||||||
|
|
@ -1695,6 +1636,246 @@ static int parse_device(snd_use_case_mgr_t *uc_mgr,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper function to copy, evaluate and optionally merge configuration trees.
|
||||||
|
*/
|
||||||
|
static int uc_mgr_config_copy_eval_merge(snd_use_case_mgr_t *uc_mgr,
|
||||||
|
snd_config_t **dst,
|
||||||
|
snd_config_t *src,
|
||||||
|
snd_config_t *merge_from)
|
||||||
|
{
|
||||||
|
snd_config_t *tmp = NULL;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = snd_config_copy(&tmp, src);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
err = uc_mgr_evaluate_inplace(uc_mgr, tmp);
|
||||||
|
if (err < 0) {
|
||||||
|
snd_config_delete(tmp);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (merge_from) {
|
||||||
|
err = uc_mgr_config_tree_merge(uc_mgr, tmp, merge_from, NULL, NULL);
|
||||||
|
if (err < 0) {
|
||||||
|
snd_config_delete(tmp);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*dst = tmp;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Parse Device Use Cases
|
||||||
|
*
|
||||||
|
* # Each device is described in new section. N devices are allowed
|
||||||
|
* SectionDevice."Headphones" {
|
||||||
|
* Comment "Headphones connected to 3.5mm jack"
|
||||||
|
*
|
||||||
|
* SupportedDevice [
|
||||||
|
* "x"
|
||||||
|
* "y"
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
* ConflictingDevice [
|
||||||
|
* "x"
|
||||||
|
* "y"
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
* EnableSequence [
|
||||||
|
* ....
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
* DisableSequence [
|
||||||
|
* ...
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
* TransitionSequence."ToDevice" [
|
||||||
|
* ...
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
* Value {
|
||||||
|
* PlaybackVolume "name='Master Playback Volume',index=2"
|
||||||
|
* PlaybackSwitch "name='Master Playback Switch',index=2"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
||||||
|
static int parse_device_by_name(snd_use_case_mgr_t *uc_mgr,
|
||||||
|
snd_config_t *cfg,
|
||||||
|
struct use_case_verb *verb,
|
||||||
|
const char *name,
|
||||||
|
struct use_case_device **ret_device)
|
||||||
|
{
|
||||||
|
struct use_case_device *device;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
device = calloc(1, sizeof(*device));
|
||||||
|
if (device == NULL)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
INIT_LIST_HEAD(&device->enable_list);
|
||||||
|
INIT_LIST_HEAD(&device->disable_list);
|
||||||
|
INIT_LIST_HEAD(&device->transition_list);
|
||||||
|
INIT_LIST_HEAD(&device->dev_list.list);
|
||||||
|
INIT_LIST_HEAD(&device->value_list);
|
||||||
|
INIT_LIST_HEAD(&device->variants);
|
||||||
|
INIT_LIST_HEAD(&device->variant_list);
|
||||||
|
list_add_tail(&device->list, &verb->device_list);
|
||||||
|
device->name = strdup(name);
|
||||||
|
if (device->name == NULL) {
|
||||||
|
free(device);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
device->orig_name = strdup(name);
|
||||||
|
if (device->orig_name == NULL)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
err = parse_device_fields(uc_mgr, cfg, device);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (ret_device)
|
||||||
|
*ret_device = device;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_device(snd_use_case_mgr_t *uc_mgr,
|
||||||
|
snd_config_t *cfg,
|
||||||
|
void *data1, void *data2)
|
||||||
|
{
|
||||||
|
struct use_case_verb *verb = data1;
|
||||||
|
char *name, *colon;
|
||||||
|
const char *variant_label = NULL;
|
||||||
|
struct use_case_device *device = NULL;
|
||||||
|
snd_config_t *primary_cfg_copy = NULL;
|
||||||
|
snd_config_t *device_variant = NULL;
|
||||||
|
snd_config_t *merged_cfg = NULL;
|
||||||
|
snd_config_iterator_t i, next;
|
||||||
|
snd_config_t *n;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (parse_get_safe_name(uc_mgr, cfg, data2, &name) < 0)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (uc_mgr->conf_format >= 8 && (colon = strchr(name, ':'))) {
|
||||||
|
variant_label = colon + 1;
|
||||||
|
|
||||||
|
err = snd_config_search(cfg, "DeviceVariant", &device_variant);
|
||||||
|
if (err == 0) {
|
||||||
|
snd_config_t *variant_cfg = NULL;
|
||||||
|
|
||||||
|
/* Save a copy of the primary config for creating variant devices */
|
||||||
|
err = snd_config_copy(&primary_cfg_copy, cfg);
|
||||||
|
if (err < 0) {
|
||||||
|
free(name);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = snd_config_search(device_variant, variant_label, &variant_cfg);
|
||||||
|
if (err == 0) {
|
||||||
|
err = uc_mgr_config_copy_eval_merge(uc_mgr, &merged_cfg, cfg, variant_cfg);
|
||||||
|
if (err < 0) {
|
||||||
|
free(name);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
cfg = merged_cfg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* in-place evaluation */
|
||||||
|
if (cfg != merged_cfg) {
|
||||||
|
err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
|
||||||
|
if (err < 0) {
|
||||||
|
free(name);
|
||||||
|
goto __error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = parse_device_by_name(uc_mgr, cfg, verb, name, &device);
|
||||||
|
free(name);
|
||||||
|
if (err < 0)
|
||||||
|
goto __error;
|
||||||
|
|
||||||
|
if (merged_cfg) {
|
||||||
|
snd_config_delete(merged_cfg);
|
||||||
|
merged_cfg = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device_variant == NULL)
|
||||||
|
goto __end;
|
||||||
|
|
||||||
|
if (device->dev_list.type == DEVLIST_SUPPORTED) {
|
||||||
|
snd_error(UCM, "DeviceVariant cannot be used with SupportedDevice");
|
||||||
|
err = -EINVAL;
|
||||||
|
goto __error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snd_config_get_type(device_variant) != SND_CONFIG_TYPE_COMPOUND) {
|
||||||
|
snd_error(UCM, "compound type expected for DeviceVariant");
|
||||||
|
err = -EINVAL;
|
||||||
|
goto __error;
|
||||||
|
}
|
||||||
|
|
||||||
|
colon = strchr(device->name, ':');
|
||||||
|
if (!colon) {
|
||||||
|
snd_error(UCM, "DeviceVariant requires ':' in device name");
|
||||||
|
err = -EINVAL;
|
||||||
|
goto __error;
|
||||||
|
}
|
||||||
|
|
||||||
|
snd_config_for_each(i, next, device_variant) {
|
||||||
|
const char *variant_name;
|
||||||
|
char variant_device_name[128];
|
||||||
|
struct use_case_device *variant = NULL;
|
||||||
|
|
||||||
|
n = snd_config_iterator_entry(i);
|
||||||
|
|
||||||
|
if (snd_config_get_id(n, &variant_name) < 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Create variant device name: base:variant_name */
|
||||||
|
snprintf(variant_device_name, sizeof(variant_device_name),
|
||||||
|
"%.*s:%s", (int)(colon - device->name),
|
||||||
|
device->name, variant_name);
|
||||||
|
|
||||||
|
err = uc_mgr_config_copy_eval_merge(uc_mgr, &merged_cfg, primary_cfg_copy, n);
|
||||||
|
if (err < 0)
|
||||||
|
goto __error;
|
||||||
|
|
||||||
|
err = parse_device_by_name(uc_mgr, merged_cfg, verb,
|
||||||
|
variant_device_name, &variant);
|
||||||
|
snd_config_delete(merged_cfg);
|
||||||
|
merged_cfg = NULL;
|
||||||
|
if (err < 0)
|
||||||
|
goto __error;
|
||||||
|
|
||||||
|
/* Link variant to primary device */
|
||||||
|
list_add(&variant->variant_list, &device->variants);
|
||||||
|
|
||||||
|
err = uc_mgr_put_to_dev_list(&device->dev_list, variant->name);
|
||||||
|
if (err < 0)
|
||||||
|
goto __error;
|
||||||
|
if (device->dev_list.type == DEVLIST_NONE)
|
||||||
|
device->dev_list.type = DEVLIST_CONFLICTING;
|
||||||
|
}
|
||||||
|
|
||||||
|
__end:
|
||||||
|
err = 0;
|
||||||
|
__error:
|
||||||
|
if (merged_cfg)
|
||||||
|
snd_config_delete(merged_cfg);
|
||||||
|
if (primary_cfg_copy)
|
||||||
|
snd_config_delete(primary_cfg_copy);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Parse Device Rename/Delete Command
|
* Parse Device Rename/Delete Command
|
||||||
*
|
*
|
||||||
|
|
@ -1843,6 +2024,7 @@ static int verb_dev_list_add(struct use_case_verb *verb,
|
||||||
|
|
||||||
list_for_each(pos, &verb->device_list) {
|
list_for_each(pos, &verb->device_list) {
|
||||||
device = list_entry(pos, struct use_case_device, list);
|
device = list_entry(pos, struct use_case_device, list);
|
||||||
|
|
||||||
if (strcmp(device->name, dst) != 0)
|
if (strcmp(device->name, dst) != 0)
|
||||||
continue;
|
continue;
|
||||||
if (device->dev_list.type != dst_type) {
|
if (device->dev_list.type != dst_type) {
|
||||||
|
|
|
||||||
|
|
@ -914,6 +914,78 @@ SectionDevice."Speaker" {
|
||||||
}
|
}
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
|
### Device Variants
|
||||||
|
|
||||||
|
Starting with **Syntax 8**, devices can define variants using the *DeviceVariant* block.
|
||||||
|
Device variants provide a convenient way to define multiple related devices with different
|
||||||
|
configurations (such as different channel counts) in a single device definition.
|
||||||
|
|
||||||
|
When a device name contains a colon (':') character and the device configuration includes
|
||||||
|
*DeviceVariant* blocks, the UCM parser handles variant configuration in two ways:
|
||||||
|
|
||||||
|
1. **Primary device configuration**: If the text after the colon (variant label) matches a
|
||||||
|
variant identifier in the *DeviceVariant* block, that variant's configuration is merged
|
||||||
|
with the primary device configuration before parsing. This allows the primary device to
|
||||||
|
inherit base configuration while overriding specific values from the variant.
|
||||||
|
|
||||||
|
2. **Additional variant devices**: The UCM parser automatically creates multiple distinct
|
||||||
|
UCM devices:
|
||||||
|
- The base device (with the name specified in the *Device* or *SectionDevice* block)
|
||||||
|
- One additional device for each *DeviceVariant* block
|
||||||
|
|
||||||
|
Each variant device name is constructed by combining the base device name with the variant
|
||||||
|
identifier. Variant devices are automatically added to the base device's conflicting device
|
||||||
|
list, since these configurations are mutually exclusive (e.g., you cannot use 2.0, 5.1, and
|
||||||
|
7.1 speaker configurations simultaneously).
|
||||||
|
|
||||||
|
Example - Speaker with multiple channel configurations:
|
||||||
|
|
||||||
|
~~~{.html}
|
||||||
|
Device."Speaker:2.0" {
|
||||||
|
Value {
|
||||||
|
PlaybackChannels 2
|
||||||
|
}
|
||||||
|
DeviceVariant."5.1".Value {
|
||||||
|
PlaybackChannels 6
|
||||||
|
}
|
||||||
|
DeviceVariant."7.1".Value {
|
||||||
|
PlaybackChannels 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
~~~
|
||||||
|
|
||||||
|
This configuration creates three UCM devices:
|
||||||
|
- **Speaker:2.0** - 2 playback channels (base device)
|
||||||
|
- **Speaker:5.1** - 6 playback channels (variant)
|
||||||
|
- **Speaker:7.1** - 8 playback channels (variant)
|
||||||
|
|
||||||
|
The variant devices (**Speaker:5.1** and **Speaker:7.1**) inherit all configuration from the
|
||||||
|
base device and override only the values specified in their *DeviceVariant* block. The devices
|
||||||
|
are automatically marked as conflicting with each other.
|
||||||
|
|
||||||
|
Example - HDMI output with different sample rates:
|
||||||
|
|
||||||
|
~~~{.html}
|
||||||
|
SectionDevice."HDMI:LowRate" {
|
||||||
|
Comment "HDMI output - standard rate"
|
||||||
|
EnableSequence [
|
||||||
|
cset "name='HDMI Switch' on"
|
||||||
|
]
|
||||||
|
Value {
|
||||||
|
PlaybackPCM "hw:${CardId},3"
|
||||||
|
PlaybackRate 48000
|
||||||
|
}
|
||||||
|
DeviceVariant."HighRate" {
|
||||||
|
Comment "HDMI output - high sample rate"
|
||||||
|
Value {
|
||||||
|
PlaybackRate 192000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
~~~
|
||||||
|
|
||||||
|
This creates two devices: **HDMI:LowRate** (48kHz) and **HDMI:HighRate** (192kHz).
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -183,6 +183,12 @@ struct use_case_device {
|
||||||
|
|
||||||
/* cached priority for sorting (LONG_MIN if not determined) */
|
/* cached priority for sorting (LONG_MIN if not determined) */
|
||||||
long sort_priority;
|
long sort_priority;
|
||||||
|
|
||||||
|
/* list of variant devices */
|
||||||
|
struct list_head variants;
|
||||||
|
|
||||||
|
/* list link for variant devices */
|
||||||
|
struct list_head variant_list;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue