alsa-ucm: Support Playback/CaptureVolume

This allows us to support the PlaybackVolume and CaptureVolume commands
in UCM, specifying a mixer control to use for hardware volume control.
This only works with ports corresponding to single devices at the
moment, and doesn't support stacking controls for combination ports.

The configuration is intended to provide a control (like Headphone
Playback Volume), but we try to resolve to a simple mixer control
(Headphone) to reuse existing volume paths.

On the UCM side, this also requires that when disabling the device for
the port, the volume should be reset to some default.

When enabling/disabling combination devices, things are a bit iffy since
we have no way to reset the volume before switching to a combination
device. It would be nice to have a combination-transition-sequence
command in UCM to handle this and other similar cases.

PlaybackSwitch and CaptureSwitch are yet to be implemented.
This commit is contained in:
Arun Raghavan 2016-05-03 18:22:10 +05:30 committed by Arun Raghavan
parent 1c240b7a12
commit 3dfccada46
7 changed files with 295 additions and 66 deletions

View file

@ -1598,7 +1598,7 @@ static void sink_set_mute_cb(pa_sink *s) {
static void mixer_volume_init(struct userdata *u) { static void mixer_volume_init(struct userdata *u) {
pa_assert(u); pa_assert(u);
if (!u->mixer_path->has_volume) { if (!u->mixer_path || !u->mixer_path->has_volume) {
pa_sink_set_write_volume_callback(u->sink, NULL); pa_sink_set_write_volume_callback(u->sink, NULL);
pa_sink_set_get_volume_callback(u->sink, NULL); pa_sink_set_get_volume_callback(u->sink, NULL);
pa_sink_set_set_volume_callback(u->sink, NULL); pa_sink_set_set_volume_callback(u->sink, NULL);
@ -1633,7 +1633,7 @@ static void mixer_volume_init(struct userdata *u) {
pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported"); pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported");
} }
if (!u->mixer_path->has_mute) { if (!u->mixer_path || !u->mixer_path->has_mute) {
pa_sink_set_get_mute_callback(u->sink, NULL); pa_sink_set_get_mute_callback(u->sink, NULL);
pa_sink_set_set_mute_callback(u->sink, NULL); pa_sink_set_set_mute_callback(u->sink, NULL);
pa_log_info("Driver does not support hardware mute control, falling back to software mute control."); pa_log_info("Driver does not support hardware mute control, falling back to software mute control.");
@ -1646,11 +1646,31 @@ static void mixer_volume_init(struct userdata *u) {
static int sink_set_port_ucm_cb(pa_sink *s, pa_device_port *p) { static int sink_set_port_ucm_cb(pa_sink *s, pa_device_port *p) {
struct userdata *u = s->userdata; struct userdata *u = s->userdata;
pa_alsa_ucm_port_data *data;
data = PA_DEVICE_PORT_DATA(p);
pa_assert(u); pa_assert(u);
pa_assert(p); pa_assert(p);
pa_assert(u->ucm_context); pa_assert(u->ucm_context);
u->mixer_path = data->path;
mixer_volume_init(u);
if (u->mixer_path) {
pa_alsa_path_select(u->mixer_path, NULL, u->mixer_handle, s->muted);
if (s->set_mute)
s->set_mute(s);
if (s->flags & PA_SINK_DEFERRED_VOLUME) {
if (s->write_volume)
s->write_volume(s);
} else {
if (s->set_volume)
s->set_volume(s);
}
}
return pa_alsa_ucm_set_port(u->ucm_context, p, true); return pa_alsa_ucm_set_port(u->ucm_context, p, true);
} }
@ -2079,6 +2099,11 @@ static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char
return; return;
} }
if (u->ucm_context) {
/* We just want to open the device */
return;
}
if (element) { if (element) {
if (!(u->mixer_path = pa_alsa_path_synthesize(element, PA_ALSA_DIRECTION_OUTPUT))) if (!(u->mixer_path = pa_alsa_path_synthesize(element, PA_ALSA_DIRECTION_OUTPUT)))
@ -2116,16 +2141,31 @@ static int setup_mixer(struct userdata *u, bool ignore_dB) {
return 0; return 0;
if (u->sink->active_port) { if (u->sink->active_port) {
pa_alsa_port_data *data; if (!u->ucm_context) {
pa_alsa_port_data *data;
/* We have a list of supported paths, so let's activate the /* We have a list of supported paths, so let's activate the
* one that has been chosen as active */ * one that has been chosen as active */
data = PA_DEVICE_PORT_DATA(u->sink->active_port); data = PA_DEVICE_PORT_DATA(u->sink->active_port);
u->mixer_path = data->path; u->mixer_path = data->path;
pa_alsa_path_select(data->path, data->setting, u->mixer_handle, u->sink->muted); pa_alsa_path_select(data->path, data->setting, u->mixer_handle, u->sink->muted);
} else {
pa_alsa_ucm_port_data *data;
/* First activate the port on the UCM side */
if (pa_alsa_ucm_set_port(u->ucm_context, u->sink->active_port, true) < 0)
return -1;
data = PA_DEVICE_PORT_DATA(u->sink->active_port);
/* Now activate volume controls, if any */
if (data->path) {
u->mixer_path = data->path;
pa_alsa_path_select(u->mixer_path, NULL, u->mixer_handle, u->sink->muted);
}
}
} else { } else {
if (!u->mixer_path && u->mixer_path_set) if (!u->mixer_path && u->mixer_path_set)
@ -2135,7 +2175,6 @@ static int setup_mixer(struct userdata *u, bool ignore_dB) {
/* Hmm, we have only a single path, then let's activate it */ /* Hmm, we have only a single path, then let's activate it */
pa_alsa_path_select(u->mixer_path, u->mixer_path->settings, u->mixer_handle, u->sink->muted); pa_alsa_path_select(u->mixer_path, u->mixer_path->settings, u->mixer_handle, u->sink->muted);
} else } else
return 0; return 0;
} }
@ -2466,8 +2505,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
/* ALSA might tweak the sample spec, so recalculate the frame size */ /* ALSA might tweak the sample spec, so recalculate the frame size */
frame_size = pa_frame_size(&ss); frame_size = pa_frame_size(&ss);
if (!u->ucm_context) find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB);
find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB);
pa_sink_new_data_init(&data); pa_sink_new_data_init(&data);
data.driver = driver; data.driver = driver;
@ -2524,7 +2562,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
} }
if (u->ucm_context) if (u->ucm_context)
pa_alsa_ucm_add_ports(&data.ports, data.proplist, u->ucm_context, true, card); pa_alsa_ucm_add_ports(&data.ports, data.proplist, u->ucm_context, true, card, u->pcm_handle, ignore_dB);
else if (u->mixer_path_set) else if (u->mixer_path_set)
pa_alsa_add_ports(&data, u->mixer_path_set, card); pa_alsa_add_ports(&data, u->mixer_path_set, card);
@ -2598,10 +2636,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
if (update_sw_params(u, false) < 0) if (update_sw_params(u, false) < 0)
goto fail; goto fail;
if (u->ucm_context) { if (setup_mixer(u, ignore_dB) < 0)
if (u->sink->active_port && pa_alsa_ucm_set_port(u->ucm_context, u->sink->active_port, true) < 0)
goto fail;
} else if (setup_mixer(u, ignore_dB) < 0)
goto fail; goto fail;
pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle); pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle);
@ -2725,7 +2760,8 @@ static void userdata_free(struct userdata *u) {
if (u->mixer_fdl) if (u->mixer_fdl)
pa_alsa_fdlist_free(u->mixer_fdl); pa_alsa_fdlist_free(u->mixer_fdl);
if (u->mixer_path && !u->mixer_path_set) /* Only free the mixer_path if the sink owns it */
if (u->mixer_path && !u->mixer_path_set && !u->ucm_context)
pa_alsa_path_free(u->mixer_path); pa_alsa_path_free(u->mixer_path);
if (u->mixer_handle) if (u->mixer_handle)

View file

@ -1469,7 +1469,7 @@ static void source_set_mute_cb(pa_source *s) {
static void mixer_volume_init(struct userdata *u) { static void mixer_volume_init(struct userdata *u) {
pa_assert(u); pa_assert(u);
if (!u->mixer_path->has_volume) { if (!u->mixer_path || !u->mixer_path->has_volume) {
pa_source_set_write_volume_callback(u->source, NULL); pa_source_set_write_volume_callback(u->source, NULL);
pa_source_set_get_volume_callback(u->source, NULL); pa_source_set_get_volume_callback(u->source, NULL);
pa_source_set_set_volume_callback(u->source, NULL); pa_source_set_set_volume_callback(u->source, NULL);
@ -1504,7 +1504,7 @@ static void mixer_volume_init(struct userdata *u) {
pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported"); pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported");
} }
if (!u->mixer_path->has_mute) { if (!u->mixer_path || !u->mixer_path->has_mute) {
pa_source_set_get_mute_callback(u->source, NULL); pa_source_set_get_mute_callback(u->source, NULL);
pa_source_set_set_mute_callback(u->source, NULL); pa_source_set_set_mute_callback(u->source, NULL);
pa_log_info("Driver does not support hardware mute control, falling back to software mute control."); pa_log_info("Driver does not support hardware mute control, falling back to software mute control.");
@ -1517,11 +1517,31 @@ static void mixer_volume_init(struct userdata *u) {
static int source_set_port_ucm_cb(pa_source *s, pa_device_port *p) { static int source_set_port_ucm_cb(pa_source *s, pa_device_port *p) {
struct userdata *u = s->userdata; struct userdata *u = s->userdata;
pa_alsa_ucm_port_data *data;
data = PA_DEVICE_PORT_DATA(p);
pa_assert(u); pa_assert(u);
pa_assert(p); pa_assert(p);
pa_assert(u->ucm_context); pa_assert(u->ucm_context);
u->mixer_path = data->path;
mixer_volume_init(u);
if (u->mixer_path) {
pa_alsa_path_select(u->mixer_path, NULL, u->mixer_handle, s->muted);
if (s->set_mute)
s->set_mute(s);
if (s->flags & PA_SOURCE_DEFERRED_VOLUME) {
if (s->write_volume)
s->write_volume(s);
} else {
if (s->set_volume)
s->set_volume(s);
}
}
return pa_alsa_ucm_set_port(u->ucm_context, p, false); return pa_alsa_ucm_set_port(u->ucm_context, p, false);
} }
@ -1785,6 +1805,11 @@ static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char
return; return;
} }
if (u->ucm_context) {
/* We just want to open the device */
return;
}
if (element) { if (element) {
if (!(u->mixer_path = pa_alsa_path_synthesize(element, PA_ALSA_DIRECTION_INPUT))) if (!(u->mixer_path = pa_alsa_path_synthesize(element, PA_ALSA_DIRECTION_INPUT)))
@ -1822,16 +1847,31 @@ static int setup_mixer(struct userdata *u, bool ignore_dB) {
return 0; return 0;
if (u->source->active_port) { if (u->source->active_port) {
pa_alsa_port_data *data; if (!u->ucm_context) {
pa_alsa_port_data *data;
/* We have a list of supported paths, so let's activate the /* We have a list of supported paths, so let's activate the
* one that has been chosen as active */ * one that has been chosen as active */
data = PA_DEVICE_PORT_DATA(u->source->active_port); data = PA_DEVICE_PORT_DATA(u->source->active_port);
u->mixer_path = data->path; u->mixer_path = data->path;
pa_alsa_path_select(data->path, data->setting, u->mixer_handle, u->source->muted); pa_alsa_path_select(data->path, data->setting, u->mixer_handle, u->source->muted);
} else {
pa_alsa_ucm_port_data *data;
/* First activate the port on the UCM side */
if (pa_alsa_ucm_set_port(u->ucm_context, u->source->active_port, false) < 0)
return -1;
data = PA_DEVICE_PORT_DATA(u->source->active_port);
/* Now activate volume controls, if any */
if (data->path) {
u->mixer_path = data->path;
pa_alsa_path_select(u->mixer_path, NULL, u->mixer_handle, u->source->muted);
}
}
} else { } else {
if (!u->mixer_path && u->mixer_path_set) if (!u->mixer_path && u->mixer_path_set)
@ -2152,8 +2192,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
/* ALSA might tweak the sample spec, so recalculate the frame size */ /* ALSA might tweak the sample spec, so recalculate the frame size */
frame_size = pa_frame_size(&ss); frame_size = pa_frame_size(&ss);
if (!u->ucm_context) find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB);
find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB);
pa_source_new_data_init(&data); pa_source_new_data_init(&data);
data.driver = driver; data.driver = driver;
@ -2210,7 +2249,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
} }
if (u->ucm_context) if (u->ucm_context)
pa_alsa_ucm_add_ports(&data.ports, data.proplist, u->ucm_context, false, card); pa_alsa_ucm_add_ports(&data.ports, data.proplist, u->ucm_context, false, card, u->pcm_handle, ignore_dB);
else if (u->mixer_path_set) else if (u->mixer_path_set)
pa_alsa_add_ports(&data, u->mixer_path_set, card); pa_alsa_add_ports(&data, u->mixer_path_set, card);
@ -2276,10 +2315,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
if (update_sw_params(u) < 0) if (update_sw_params(u) < 0)
goto fail; goto fail;
if (u->ucm_context) { if (setup_mixer(u, ignore_dB) < 0)
if (u->source->active_port && pa_alsa_ucm_set_port(u->ucm_context, u->source->active_port, false) < 0)
goto fail;
} else if (setup_mixer(u, ignore_dB) < 0)
goto fail; goto fail;
pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle); pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle);
@ -2368,7 +2404,7 @@ static void userdata_free(struct userdata *u) {
if (u->mixer_fdl) if (u->mixer_fdl)
pa_alsa_fdlist_free(u->mixer_fdl); pa_alsa_fdlist_free(u->mixer_fdl);
if (u->mixer_path && !u->mixer_path_set) if (u->mixer_path && !u->mixer_path_set && !u->ucm_context)
pa_alsa_path_free(u->mixer_path); pa_alsa_path_free(u->mixer_path);
if (u->mixer_handle) if (u->mixer_handle)

View file

@ -81,19 +81,11 @@ static void device_add_hw_mute_jack(pa_alsa_ucm_device *device, pa_alsa_jack *ja
static pa_alsa_ucm_device *verb_find_device(pa_alsa_ucm_verb *verb, const char *device_name); static pa_alsa_ucm_device *verb_find_device(pa_alsa_ucm_verb *verb, const char *device_name);
struct ucm_port {
pa_alsa_ucm_config *ucm;
pa_device_port *core_port;
/* A single port will be associated with multiple devices if it represents static void ucm_port_data_init(pa_alsa_ucm_port_data *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port,
* a combination of devices. */ pa_alsa_ucm_device **devices, unsigned n_devices);
pa_dynarray *devices; /* pa_alsa_ucm_device */ static void ucm_port_data_free(pa_device_port *port);
}; static void ucm_port_update_available(pa_alsa_ucm_port_data *port);
static void ucm_port_init(struct ucm_port *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port,
pa_alsa_ucm_device **devices, unsigned n_devices);
static void ucm_port_free(pa_device_port *port);
static void ucm_port_update_available(struct ucm_port *port);
static struct ucm_items item[] = { static struct ucm_items item[] = {
{"PlaybackPCM", PA_ALSA_PROP_UCM_SINK}, {"PlaybackPCM", PA_ALSA_PROP_UCM_SINK},
@ -303,6 +295,18 @@ static int ucm_get_device_property(
else else
pa_log_debug("UCM playback priority %s for device %s error", value, device_name); pa_log_debug("UCM playback priority %s for device %s error", value, device_name);
} }
value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_PLAYBACK_VOLUME);
if (value) {
/* Try to get the simple control name, and failing that, just use the name as is */
char *selem;
if (!(selem = pa_str_strip_suffix(value, " Playback Volume")))
if (!(selem = pa_str_strip_suffix(value, " Volume")))
selem = pa_xstrdup(value);
pa_hashmap_put(device->playback_volumes, pa_xstrdup(pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME)), selem);
}
} }
if (device->capture_channels) { /* source device */ if (device->capture_channels) { /* source device */
@ -324,6 +328,18 @@ static int ucm_get_device_property(
else else
pa_log_debug("UCM capture priority %s for device %s error", value, device_name); pa_log_debug("UCM capture priority %s for device %s error", value, device_name);
} }
value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_CAPTURE_VOLUME);
if (value) {
/* Try to get the simple control name, and failing that, just use the name as is */
char *selem;
if (!(selem = pa_str_strip_suffix(value, " Capture Volume")))
if (!(selem = pa_str_strip_suffix(value, " Volume")))
selem = pa_xstrdup(value);
pa_hashmap_put(device->capture_volumes, pa_xstrdup(pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME)), selem);
}
} }
if (PA_UCM_PLAYBACK_PRIORITY_UNSET(device) || PA_UCM_CAPTURE_PRIORITY_UNSET(device)) { if (PA_UCM_PLAYBACK_PRIORITY_UNSET(device) || PA_UCM_CAPTURE_PRIORITY_UNSET(device)) {
@ -427,6 +443,11 @@ static int ucm_get_devices(pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr) {
d->hw_mute_jacks = pa_dynarray_new(NULL); d->hw_mute_jacks = pa_dynarray_new(NULL);
d->available = PA_AVAILABLE_UNKNOWN; 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_xfree);
d->capture_volumes = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree,
pa_xfree);
PA_LLIST_PREPEND(pa_alsa_ucm_device, verb->devices, d); PA_LLIST_PREPEND(pa_alsa_ucm_device, verb->devices, d);
} }
@ -707,6 +728,46 @@ static int pa_alsa_ucm_device_cmp(const void *a, const void *b) {
return strcmp(pa_proplist_gets(d1->proplist, PA_ALSA_PROP_UCM_NAME), pa_proplist_gets(d2->proplist, PA_ALSA_PROP_UCM_NAME)); return strcmp(pa_proplist_gets(d1->proplist, PA_ALSA_PROP_UCM_NAME), pa_proplist_gets(d2->proplist, PA_ALSA_PROP_UCM_NAME));
} }
static void probe_volumes(pa_hashmap *hash, snd_pcm_t *pcm_handle, bool ignore_dB) {
pa_device_port *port;
pa_alsa_path *path;
pa_alsa_ucm_port_data *data;
snd_mixer_t *mixer_handle;
const char *profile;
void *state, *state2;
if (!(mixer_handle = pa_alsa_open_mixer_for_pcm(pcm_handle, NULL))) {
pa_log_error("Failed to find a working mixer device.");
goto fail;
}
PA_HASHMAP_FOREACH(port, hash, state) {
data = PA_DEVICE_PORT_DATA(port);
PA_HASHMAP_FOREACH_KV(profile, 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", data->path->name);
pa_hashmap_remove(data->paths, profile);
} else if (!path->has_volume) {
pa_log_warn("Path %s is not a volume control", data->path->name);
pa_hashmap_remove(data->paths, profile);
} else
pa_log_debug("Set up h/w volume using '%s' for %s:%s", path->name, profile, port->name);
}
}
snd_mixer_close(mixer_handle);
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 ucm_add_port_combination( static void ucm_add_port_combination(
pa_hashmap *hash, pa_hashmap *hash,
pa_alsa_ucm_mapping_context *context, pa_alsa_ucm_mapping_context *context,
@ -724,7 +785,10 @@ static void ucm_add_port_combination(
char *name, *desc; char *name, *desc;
const char *dev_name; const char *dev_name;
const char *direction; const char *direction;
const char *profile, *volume_element;
pa_alsa_ucm_device *sorted[num], *dev; pa_alsa_ucm_device *sorted[num], *dev;
pa_alsa_ucm_port_data *data;
void *state;
for (i = 0; i < num; i++) for (i = 0; i < num; i++)
sorted[i] = pdevices[i]; sorted[i] = pdevices[i];
@ -772,8 +836,6 @@ static void ucm_add_port_combination(
port = pa_hashmap_get(ports, name); port = pa_hashmap_get(ports, name);
if (!port) { if (!port) {
struct ucm_port *ucm_port;
pa_device_port_new_data port_data; pa_device_port_new_data port_data;
pa_device_port_new_data_init(&port_data); pa_device_port_new_data_init(&port_data);
@ -781,17 +843,33 @@ static void ucm_add_port_combination(
pa_device_port_new_data_set_description(&port_data, desc); pa_device_port_new_data_set_description(&port_data, desc);
pa_device_port_new_data_set_direction(&port_data, is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT); pa_device_port_new_data_set_direction(&port_data, is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT);
port = pa_device_port_new(core, &port_data, sizeof(struct ucm_port)); port = pa_device_port_new(core, &port_data, sizeof(pa_alsa_ucm_port_data));
port->impl_free = ucm_port_free;
pa_device_port_new_data_done(&port_data); pa_device_port_new_data_done(&port_data);
ucm_port = PA_DEVICE_PORT_DATA(port); data = PA_DEVICE_PORT_DATA(port);
ucm_port_init(ucm_port, context->ucm, port, pdevices, num); ucm_port_data_init(data, context->ucm, port, pdevices, num);
port->impl_free = ucm_port_data_free;
pa_hashmap_put(ports, port->name, port); pa_hashmap_put(ports, port->name, port);
pa_log_debug("Add port %s: %s", port->name, port->description); pa_log_debug("Add port %s: %s", port->name, port->description);
} }
if (num == 1) {
/* To keep things simple and not worry about stacking controls, we only support hardware volumes on non-combination
* ports. */
data = PA_DEVICE_PORT_DATA(port);
PA_HASHMAP_FOREACH_KV(profile, volume_element, is_sink ? dev->playback_volumes : dev->capture_volumes, state) {
pa_alsa_path *path = pa_alsa_path_synthesize(volume_element,
is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT);
if (!path)
pa_log_warn("Failed to set up volume control: %s", volume_element);
else
pa_hashmap_put(data->paths, pa_xstrdup(profile), path);
}
}
port->priority = priority; port->priority = priority;
pa_xfree(name); pa_xfree(name);
@ -971,7 +1049,9 @@ void pa_alsa_ucm_add_ports(
pa_proplist *proplist, pa_proplist *proplist,
pa_alsa_ucm_mapping_context *context, pa_alsa_ucm_mapping_context *context,
bool is_sink, bool is_sink,
pa_card *card) { pa_card *card,
snd_pcm_t *pcm_handle,
bool ignore_dB) {
uint32_t idx; uint32_t idx;
char *merged_roles; char *merged_roles;
@ -986,6 +1066,9 @@ void pa_alsa_ucm_add_ports(
/* add ports first */ /* add ports first */
pa_alsa_ucm_add_ports_combination(*p, context, is_sink, card->ports, NULL, card->core); pa_alsa_ucm_add_ports_combination(*p, context, is_sink, card->ports, NULL, card->core);
/* now set up volume paths if any */
probe_volumes(*p, pcm_handle, ignore_dB);
/* then set property PA_PROP_DEVICE_INTENDED_ROLES */ /* then set property PA_PROP_DEVICE_INTENDED_ROLES */
merged_roles = pa_xstrdup(pa_proplist_gets(proplist, PA_PROP_DEVICE_INTENDED_ROLES)); merged_roles = pa_xstrdup(pa_proplist_gets(proplist, PA_PROP_DEVICE_INTENDED_ROLES));
PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) { PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) {
@ -1010,10 +1093,13 @@ void pa_alsa_ucm_add_ports(
} }
/* Change UCM verb and device to match selected card profile */ /* Change UCM verb and device to match selected card profile */
int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, const char *new_profile, const char *old_profile) { int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, const char *new_profile, const char *old_profile) {
int ret = 0; int ret = 0;
const char *profile; const char *profile;
pa_alsa_ucm_verb *verb; pa_alsa_ucm_verb *verb;
pa_device_port *port;
pa_alsa_ucm_port_data *data;
void *state;
if (new_profile == old_profile) if (new_profile == old_profile)
return ret; return ret;
@ -1042,6 +1128,12 @@ int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, const char *new_profile, co
} }
} }
/* select volume controls on ports */
PA_HASHMAP_FOREACH(port, card->ports, state) {
data = PA_DEVICE_PORT_DATA(port);
data->path = pa_hashmap_get(data->paths, new_profile);
}
return ret; return ret;
} }
@ -1650,11 +1742,18 @@ static void free_verb(pa_alsa_ucm_verb *verb) {
if (di->ucm_ports) if (di->ucm_ports)
pa_dynarray_free(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_proplist_free(di->proplist);
if (di->conflicting_devices) if (di->conflicting_devices)
pa_idxset_free(di->conflicting_devices, NULL); pa_idxset_free(di->conflicting_devices, NULL);
if (di->supported_devices) if (di->supported_devices)
pa_idxset_free(di->supported_devices, NULL); pa_idxset_free(di->supported_devices, NULL);
pa_xfree(di); pa_xfree(di);
} }
@ -1785,7 +1884,7 @@ void pa_alsa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, pa_
} }
} }
static void device_add_ucm_port(pa_alsa_ucm_device *device, struct ucm_port *port) { static void device_add_ucm_port(pa_alsa_ucm_device *device, pa_alsa_ucm_port_data *port) {
pa_assert(device); pa_assert(device);
pa_assert(port); pa_assert(port);
@ -1813,7 +1912,7 @@ static void device_add_hw_mute_jack(pa_alsa_ucm_device *device, pa_alsa_jack *ja
} }
static void device_set_available(pa_alsa_ucm_device *device, pa_available_t available) { static void device_set_available(pa_alsa_ucm_device *device, pa_available_t available) {
struct ucm_port *port; pa_alsa_ucm_port_data *port;
unsigned idx; unsigned idx;
pa_assert(device); pa_assert(device);
@ -1847,8 +1946,8 @@ void pa_alsa_ucm_device_update_available(pa_alsa_ucm_device *device) {
device_set_available(device, available); device_set_available(device, available);
} }
static void ucm_port_init(struct ucm_port *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port, 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 **devices, unsigned n_devices) { pa_alsa_ucm_device **devices, unsigned n_devices) {
unsigned i; unsigned i;
pa_assert(ucm); pa_assert(ucm);
@ -1864,11 +1963,14 @@ static void ucm_port_init(struct ucm_port *port, pa_alsa_ucm_config *ucm, pa_dev
device_add_ucm_port(devices[i], port); device_add_ucm_port(devices[i], port);
} }
port->paths = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree,
(pa_free_cb_t) pa_alsa_path_free);
ucm_port_update_available(port); ucm_port_update_available(port);
} }
static void ucm_port_free(pa_device_port *port) { static void ucm_port_data_free(pa_device_port *port) {
struct ucm_port *ucm_port; pa_alsa_ucm_port_data *ucm_port;
pa_assert(port); pa_assert(port);
@ -1876,9 +1978,12 @@ static void ucm_port_free(pa_device_port *port) {
if (ucm_port->devices) if (ucm_port->devices)
pa_dynarray_free(ucm_port->devices); pa_dynarray_free(ucm_port->devices);
if (ucm_port->paths)
pa_hashmap_free(ucm_port->paths);
} }
static void ucm_port_update_available(struct ucm_port *port) { static void ucm_port_update_available(pa_alsa_ucm_port_data *port) {
pa_alsa_ucm_device *device; pa_alsa_ucm_device *device;
unsigned idx; unsigned idx;
pa_available_t available = PA_AVAILABLE_YES; pa_available_t available = PA_AVAILABLE_YES;
@ -1910,7 +2015,7 @@ pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_cha
return NULL; return NULL;
} }
int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, const char *new_profile, const char *old_profile) { int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, const char *new_profile, const char *old_profile) {
return -1; return -1;
} }
@ -1923,7 +2028,9 @@ void pa_alsa_ucm_add_ports(
pa_proplist *proplist, pa_proplist *proplist,
pa_alsa_ucm_mapping_context *context, pa_alsa_ucm_mapping_context *context,
bool is_sink, bool is_sink,
pa_card *card) { pa_card *card,
snd_pcm_t *pcm_handle,
bool ignore_dB) {
} }
void pa_alsa_ucm_add_ports_combination( void pa_alsa_ucm_add_ports_combination(

View file

@ -113,10 +113,11 @@ typedef struct pa_alsa_ucm_modifier pa_alsa_ucm_modifier;
typedef struct pa_alsa_ucm_device pa_alsa_ucm_device; typedef struct pa_alsa_ucm_device pa_alsa_ucm_device;
typedef struct pa_alsa_ucm_config pa_alsa_ucm_config; typedef struct pa_alsa_ucm_config pa_alsa_ucm_config;
typedef struct pa_alsa_ucm_mapping_context pa_alsa_ucm_mapping_context; typedef struct pa_alsa_ucm_mapping_context pa_alsa_ucm_mapping_context;
typedef struct pa_alsa_ucm_port_data pa_alsa_ucm_port_data;
int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index); int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index);
pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map); pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map);
int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, const char *new_profile, const char *old_profile); int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, const char *new_profile, const char *old_profile);
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); 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);
@ -125,7 +126,9 @@ void pa_alsa_ucm_add_ports(
pa_proplist *proplist, pa_proplist *proplist,
pa_alsa_ucm_mapping_context *context, pa_alsa_ucm_mapping_context *context,
bool is_sink, bool is_sink,
pa_card *card); pa_card *card,
snd_pcm_t *pcm_handle,
bool ignore_dB);
void pa_alsa_ucm_add_ports_combination( void pa_alsa_ucm_add_ports_combination(
pa_hashmap *hash, pa_hashmap *hash,
pa_alsa_ucm_mapping_context *context, pa_alsa_ucm_mapping_context *context,
@ -157,6 +160,11 @@ struct pa_alsa_ucm_device {
unsigned playback_channels; unsigned playback_channels;
unsigned capture_channels; unsigned capture_channels;
/* These may be different per verb, so we store this as a hashmap of verb -> volume_control. We might eventually want to
* make this a hashmap of verb -> per-verb-device-properties-struct. */
pa_hashmap *playback_volumes;
pa_hashmap *capture_volumes;
pa_alsa_mapping *playback_mapping; pa_alsa_mapping *playback_mapping;
pa_alsa_mapping *capture_mapping; pa_alsa_mapping *capture_mapping;
@ -224,4 +232,18 @@ struct pa_alsa_ucm_mapping_context {
pa_idxset *ucm_modifiers; pa_idxset *ucm_modifiers;
}; };
struct pa_alsa_ucm_port_data {
pa_alsa_ucm_config *ucm;
pa_device_port *core_port;
/* A single port will be associated with multiple devices if it represents
* a combination of devices. */
pa_dynarray *devices; /* pa_alsa_ucm_device */
/* profile -> pa_alsa_path for volume control */
pa_hashmap *paths;
/* Current path, set when activating profile */
pa_alsa_path *path;
};
#endif #endif

View file

@ -241,7 +241,7 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {
/* if UCM is available for this card then update the verb */ /* if UCM is available for this card then update the verb */
if (u->use_ucm) { if (u->use_ucm) {
if (pa_alsa_ucm_set_profile(&u->ucm, nd->profile ? nd->profile->name : NULL, if (pa_alsa_ucm_set_profile(&u->ucm, c, nd->profile ? nd->profile->name : NULL,
od->profile ? od->profile->name : NULL) < 0) { od->profile ? od->profile->name : NULL) < 0) {
ret = -1; ret = -1;
goto finish; goto finish;
@ -294,7 +294,7 @@ static void init_profile(struct userdata *u) {
if (d->profile && u->use_ucm) { if (d->profile && u->use_ucm) {
/* Set initial verb */ /* Set initial verb */
if (pa_alsa_ucm_set_profile(ucm, d->profile->name, NULL) < 0) { if (pa_alsa_ucm_set_profile(ucm, u->card, d->profile->name, NULL) < 0) {
pa_log("Failed to set ucm profile %s", d->profile->name); pa_log("Failed to set ucm profile %s", d->profile->name);
return; return;
} }

View file

@ -2893,6 +2893,32 @@ bool pa_str_in_list_spaces(const char *haystack, const char *needle) {
return false; return false;
} }
char* pa_str_strip_suffix(const char *str, const char *suffix) {
size_t str_l, suf_l, prefix;
char *ret;
pa_assert(str);
pa_assert(suffix);
str_l = strlen(str);
suf_l = strlen(suffix);
if (str_l < suf_l)
return NULL;
prefix = str_l - suf_l;
if (!pa_streq(&str[prefix], suffix))
return NULL;
ret = pa_xmalloc(prefix + 1);
strncpy(ret, str, prefix);
ret[prefix] = '\0';
return ret;
}
char *pa_get_user_name_malloc(void) { char *pa_get_user_name_malloc(void) {
ssize_t k; ssize_t k;
char *u; char *u;

View file

@ -232,6 +232,8 @@ static inline bool pa_safe_streq(const char *a, const char *b) {
bool pa_str_in_list_spaces(const char *needle, const char *haystack); bool pa_str_in_list_spaces(const char *needle, const char *haystack);
bool pa_str_in_list(const char *haystack, const char *delimiters, const char *needle); bool pa_str_in_list(const char *haystack, const char *delimiters, const char *needle);
char* pa_str_strip_suffix(const char *str, const char *suffix);
char *pa_get_host_name_malloc(void); char *pa_get_host_name_malloc(void);
char *pa_get_user_name_malloc(void); char *pa_get_user_name_malloc(void);