On 09/20/2013 01:57 PM, Arun Raghavan wrote: > 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. > > On the UCM side, this also requires that when disabling the device for > the port, the volume should be reset to some default. Overall, this seems to be a very well written patch, good work! Just two comments further down. > 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. > --- > src/modules/alsa/alsa-sink.c | 69 +++++++++++++++----- > src/modules/alsa/alsa-source.c | 34 ++++++---- > src/modules/alsa/alsa-ucm.c | 126 ++++++++++++++++++++++++++++++++++-- > src/modules/alsa/alsa-ucm.h | 19 +++++- > src/modules/alsa/module-alsa-card.c | 4 +- > 5 files changed, 215 insertions(+), 37 deletions(-) > > diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c > index d3d5b19..3b64448 100644 > --- a/src/modules/alsa/alsa-sink.c > +++ b/src/modules/alsa/alsa-sink.c > @@ -1422,7 +1422,7 @@ static void sink_set_mute_cb(pa_sink *s) { > static void mixer_volume_init(struct userdata *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_get_volume_callback(u->sink, NULL); > pa_sink_set_set_volume_callback(u->sink, NULL); > @@ -1457,7 +1457,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"); > } > > - 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_set_mute_callback(u->sink, NULL); > pa_log_info("Driver does not support hardware mute control, falling back to software mute control."); > @@ -1470,10 +1470,31 @@ static void mixer_volume_init(struct userdata *u) { > > static int sink_set_port_ucm_cb(pa_sink *s, pa_device_port *p) { > struct userdata *u = s->userdata; > + pa_alsa_ucm_port_data *data; > + > + data = PA_DEVICE_PORT_DATA(p); > > pa_assert(u); > pa_assert(p); > pa_assert(u->ucm_context); > + pa_assert(u->mixer_handle); > + > + u->mixer_path = data->path; > + mixer_volume_init(u); > + > + if (data->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); > } > @@ -1896,7 +1917,7 @@ static void set_sink_name(pa_sink_new_data *data, pa_modargs *ma, const char *de > static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char *element, bool ignore_dB) { > snd_hctl_t *hctl; > > - if (!mapping && !element) > + if (!u->ucm_context && (!mapping && !element)) > return; > > if (!(u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device, &hctl))) { > @@ -1904,6 +1925,11 @@ static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char > return; > } > > + if (u->ucm_context) { > + /* We just want to open the device */ > + return; > + } > + > if (element) { > > if (!(u->mixer_path = pa_alsa_path_synthesize(element, PA_ALSA_DIRECTION_OUTPUT))) > @@ -1941,16 +1967,31 @@ static int setup_mixer(struct userdata *u, bool ignore_dB) { > return 0; > > 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 > - * one that has been chosen as active */ > + /* We have a list of supported paths, so let's activate the > + * one that has been chosen as active */ > > - data = PA_DEVICE_PORT_DATA(u->sink->active_port); > - u->mixer_path = data->path; > + data = PA_DEVICE_PORT_DATA(u->sink->active_port); > + 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(data->path, NULL, u->mixer_handle, u->sink->muted); > + } > + } > } else { > > if (!u->mixer_path && u->mixer_path_set) > @@ -2244,8 +2285,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 */ > 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); > data.driver = driver; > @@ -2295,7 +2335,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca > } > > 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) > pa_alsa_add_ports(&data, u->mixer_path_set, card); > > @@ -2366,10 +2406,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca > if (update_sw_params(u) < 0) > goto fail; > > - if (u->ucm_context) { > - 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) > + if (setup_mixer(u, ignore_dB) < 0) > goto fail; > > pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle); > diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c > index 8416ba8..9b80b9e 100644 > --- a/src/modules/alsa/alsa-source.c > +++ b/src/modules/alsa/alsa-source.c > @@ -1652,16 +1652,31 @@ static int setup_mixer(struct userdata *u, bool ignore_dB) { > return 0; > > 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 > - * one that has been chosen as active */ > + /* We have a list of supported paths, so let's activate the > + * one that has been chosen as active */ > > - data = PA_DEVICE_PORT_DATA(u->source->active_port); > - u->mixer_path = data->path; > + data = PA_DEVICE_PORT_DATA(u->source->active_port); > + 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, true) < 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(data->path, NULL, u->mixer_handle, u->source->muted); > + } > + } > } else { > > if (!u->mixer_path && u->mixer_path_set) > @@ -1994,7 +2009,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p > } > > 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) > pa_alsa_add_ports(&data, u->mixer_path_set, card); > > @@ -2057,10 +2072,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p > if (update_sw_params(u) < 0) > goto fail; > > - if (u->ucm_context) { > - 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) > + if (setup_mixer(u, ignore_dB) < 0) > goto fail; > > pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle); > diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c > index 47ff926..822f760 100644 > --- a/src/modules/alsa/alsa-ucm.c > +++ b/src/modules/alsa/alsa-ucm.c > @@ -278,6 +278,17 @@ static int ucm_get_device_property( > else > pa_log_debug("UCM playback priority %s for device %s error", value, device_name); > } > + > + /* Volume control element */ > + if (!device->playback_volume) > + device->playback_volume = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree, > + pa_xfree); > + > + value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_PLAYBACK_VOLUME); > + if (value) { > + pa_hashmap_put(device->playback_volume, pa_xstrdup(pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME)), > + pa_xstrdup(value)); > + } > } > > if (device->capture_channels) { /* source device */ > @@ -299,6 +310,17 @@ static int ucm_get_device_property( > else > pa_log_debug("UCM capture priority %s for device %s error", value, device_name); > } > + > + /* Volume control element */ > + if (!device->capture_volume) > + device->capture_volume = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree, > + pa_xfree); > + > + value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_CAPTURE_VOLUME); > + if (value) { > + pa_hashmap_put(device->capture_volume, pa_xstrdup(pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME)), > + pa_xstrdup(value)); > + } > } > > if (PA_UCM_PLAYBACK_PRIORITY_UNSET(device) || PA_UCM_CAPTURE_PRIORITY_UNSET(device)) { > @@ -671,6 +693,57 @@ 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)); > } > > +static void ucm_port_data_free(pa_device_port *port) { > + pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(port); > + > + if (data->paths) { > + pa_hashmap_free(data->paths); > + } > + > + data->path = NULL; Any reason why you set data->path to null but not data->paths? > +} > + > +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; > + snd_hctl_t *hctl; > + const char *profile; > + void *state, *state2; > + > + if (!(mixer_handle = pa_alsa_open_mixer_for_pcm(pcm_handle, NULL, &hctl))) { > + pa_log_error("Failed to find a working mixer device."); > + goto fail; > + } You probably want to not do "pa_alsa_open_mixer_for_pcm" here, but instead try to use whatever the ucm file specifies in "cset" - in most cases there wouldn't be a difference, but who knows... (This might also apply to more places) > + > + 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, mixer_handle, hctl, ignore_dB) < 0) { > + pa_log_warn("Could not probe path: %s, using s/w volume", data->path->name); > + pa_hashmap_remove(data->paths, profile); > + pa_alsa_path_free(path); > + } else > + pa_log_debug("Set up h/w volume using '%s' for %s:%s", path->name, profile, port->name); > + } > + } > + > + return; > + > +fail: > + /* We could not probe the paths we created. Free them and rever to software volumes. */ > + PA_HASHMAP_FOREACH(port, hash, state) { > + data = PA_DEVICE_PORT_DATA(port); > + > + if (data->path) { > + pa_alsa_path_free(data->path); > + data->path = NULL; > + } > + } > +} > + > static void ucm_add_port_combination( > pa_hashmap *hash, > pa_alsa_ucm_mapping_context *context, > @@ -688,7 +761,10 @@ static void ucm_add_port_combination( > char *name, *desc; > const char *dev_name; > const char *direction; > + const char *profile, *volume; > pa_alsa_ucm_device *sorted[num], *dev; > + pa_alsa_ucm_port_data *data; > + void *state; > > for (i = 0; i < num; i++) > sorted[i] = pdevices[i]; > @@ -743,15 +819,35 @@ static void ucm_add_port_combination( > 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); > > - port = pa_device_port_new(core, &port_data, 0); > + port = pa_device_port_new(core, &port_data, sizeof(pa_alsa_ucm_port_data)); > pa_device_port_new_data_done(&port_data); > - pa_assert(port); > + > + data = PA_DEVICE_PORT_DATA(port); > + data->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); > + 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); > port->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); > } > > + 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, is_sink ? dev->playback_volume : dev->capture_volume, state) { > + pa_alsa_path *path = pa_alsa_path_synthesize(volume, > + is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT); > + > + if (!path) > + pa_log_warn("Failed to set up volume control: %s", volume); > + else > + pa_hashmap_put(data->paths, pa_xstrdup(profile), path); > + } > + } > + > port->priority = priority; > > pa_xfree(name); > @@ -931,7 +1027,9 @@ void pa_alsa_ucm_add_ports( > pa_proplist *proplist, > pa_alsa_ucm_mapping_context *context, > bool is_sink, > - pa_card *card) { > + pa_card *card, > + snd_pcm_t *pcm_handle, > + bool ignore_dB) { > > uint32_t idx; > char *merged_roles; > @@ -946,6 +1044,9 @@ void pa_alsa_ucm_add_ports( > /* add ports first */ > 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 */ > merged_roles = pa_xstrdup(pa_proplist_gets(proplist, PA_PROP_DEVICE_INTENDED_ROLES)); > PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) { > @@ -970,10 +1071,13 @@ void pa_alsa_ucm_add_ports( > } > > /* 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; > const char *profile; > pa_alsa_ucm_verb *verb; > + pa_device_port *port; > + pa_alsa_ucm_port_data *data; > + void *state; > > if (new_profile == old_profile) > return ret; > @@ -1002,6 +1106,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; > } > > @@ -1551,6 +1661,8 @@ static void free_verb(pa_alsa_ucm_verb *verb) { > > PA_LLIST_FOREACH_SAFE(di, dn, verb->devices) { > PA_LLIST_REMOVE(pa_alsa_ucm_device, verb->devices, di); > + pa_hashmap_free(di->playback_volume); > + pa_hashmap_free(di->capture_volume); > pa_proplist_free(di->proplist); > if (di->conflicting_devices) > pa_idxset_free(di->conflicting_devices, NULL); > @@ -1684,7 +1796,7 @@ pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_cha > 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; > } > > @@ -1697,7 +1809,9 @@ void pa_alsa_ucm_add_ports( > pa_proplist *proplist, > pa_alsa_ucm_mapping_context *context, > bool is_sink, > - pa_card *card) { > + pa_card *card, > + snd_pcm_t *pcm_handle, > + bool ignore_dB) { > } > > void pa_alsa_ucm_add_ports_combination( > diff --git a/src/modules/alsa/alsa-ucm.h b/src/modules/alsa/alsa-ucm.h > index 2fae6c4..36a491f 100644 > --- a/src/modules/alsa/alsa-ucm.h > +++ b/src/modules/alsa/alsa-ucm.h > @@ -91,10 +91,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_config pa_alsa_ucm_config; > 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); > 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); > > @@ -103,7 +104,9 @@ void pa_alsa_ucm_add_ports( > pa_proplist *proplist, > pa_alsa_ucm_mapping_context *context, > bool is_sink, > - pa_card *card); > + pa_card *card, > + snd_pcm_t *pcm_handle, > + bool ignore_dB); > void pa_alsa_ucm_add_ports_combination( > pa_hashmap *hash, > pa_alsa_ucm_mapping_context *context, > @@ -135,6 +138,11 @@ struct pa_alsa_ucm_device { > unsigned playback_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_volume; > + pa_hashmap *capture_volume; > + > pa_alsa_mapping *playback_mapping; > pa_alsa_mapping *capture_mapping; > > @@ -194,4 +202,11 @@ struct pa_alsa_ucm_mapping_context { > pa_idxset *ucm_modifiers; > }; > > +struct pa_alsa_ucm_port_data { > + /* profile -> pa_alsa_path for volume control */ > + pa_hashmap *paths; > + /* Current path, set when activating profile */ > + pa_alsa_path *path; > +}; > + > #endif > diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c > index 96e88e5..970344a 100644 > --- a/src/modules/alsa/module-alsa-card.c > +++ b/src/modules/alsa/module-alsa-card.c > @@ -244,7 +244,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 (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) > return -1; > } > @@ -294,7 +294,7 @@ static void init_profile(struct userdata *u) { > > if (d->profile && u->use_ucm) { > /* 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); > return; > } > -- David Henningsson, Canonical Ltd. https://launchpad.net/~diwic