From 08bd69f9bd6ffbaba13dcc1cbea52e9c8eb5039c Mon Sep 17 00:00:00 2001 From: Julian Bouzas Date: Fri, 5 Jun 2026 08:01:30 -0400 Subject: [PATCH] spa: alsa: Support volume control commands for external volume control --- spa/plugins/alsa/acp/acp.c | 35 +++++ spa/plugins/alsa/acp/acp.h | 2 + spa/plugins/alsa/alsa-acp-device.c | 215 +++++++++++++++++++++++++++++ 3 files changed, 252 insertions(+) diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index 5c9bd119c..2441a01c2 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -2486,6 +2486,24 @@ int acp_device_set_volume(struct acp_device *dev, const float *volume, uint32_t return 0; } +int acp_device_set_volume_updown(struct acp_device *dev, bool up) +{ + pa_alsa_device *d = (pa_alsa_device*)dev; + pa_card *impl = d->card; + + if (!impl->card.ext_volume.initialized) + return -EINVAL; + + if (!d->active_port) + return -EINVAL; + + pa_log_info("Volume %s", up ? "up" : "down"); + + /* TODO: use step size from capabilities */ + return spa_acp_ext_volume_write_volume_relative(&impl->card.ext_volume, impl->name, + d->active_port->name, d->active_port->port.index, up ? 1.0 : -1.0); +} + static int get_volume(pa_cvolume *v, float *volume, uint32_t n_volume) { uint32_t i; @@ -2539,6 +2557,23 @@ int acp_device_get_mute(struct acp_device *dev, bool *mute) return 0; } +int acp_device_toggle_mute(struct acp_device *dev) +{ + pa_alsa_device *d = (pa_alsa_device*)dev; + pa_card *impl = d->card; + + if (!impl->card.ext_volume.initialized) + return -EINVAL; + + if (!d->active_port) + return -EINVAL; + + pa_log_info("Toggle mute"); + + return spa_acp_ext_volume_write_mute_toggle(&impl->card.ext_volume, impl->name, + d->active_port->name, d->active_port->port.index); +} + void acp_set_log_func(acp_log_func func, void *data) { _acp_log_func = func; diff --git a/spa/plugins/alsa/acp/acp.h b/spa/plugins/alsa/acp/acp.h index 7d7e90f21..6c4a8fa13 100644 --- a/spa/plugins/alsa/acp/acp.h +++ b/spa/plugins/alsa/acp/acp.h @@ -310,9 +310,11 @@ uint32_t acp_device_find_best_port_index(struct acp_device *dev, const char *nam int acp_device_set_port(struct acp_device *dev, uint32_t port_index, uint32_t flags); int acp_device_set_volume(struct acp_device *dev, const float *volume, uint32_t n_volume); +int acp_device_set_volume_updown(struct acp_device *dev, bool up); int acp_device_get_soft_volume(struct acp_device *dev, float *volume, uint32_t n_volume); int acp_device_get_volume(struct acp_device *dev, float *volume, uint32_t n_volume); int acp_device_set_mute(struct acp_device *dev, bool mute); +int acp_device_toggle_mute(struct acp_device *dev); int acp_device_get_mute(struct acp_device *dev, bool *mute); typedef void (*acp_log_func) (void *data, diff --git a/spa/plugins/alsa/alsa-acp-device.c b/spa/plugins/alsa/alsa-acp-device.c index 61390dfef..0dc6d57fb 100644 --- a/spa/plugins/alsa/alsa-acp-device.c +++ b/spa/plugins/alsa/alsa-acp-device.c @@ -890,12 +890,227 @@ static int impl_set_param(void *object, return 0; } +static struct acp_device * find_acp_device_for_command_value(struct acp_card *card, const struct spa_pod *value) +{ + struct acp_port *port; + struct acp_device *dev = NULL; + uint32_t id, i; + + if (spa_pod_get_id(value, &id) < 0 || id > card->n_ports) + return NULL; + + port = card->ports[id]; + + for (i = 0; i < port->n_devices; i++) { + if (port->devices[i]->flags & ACP_DEVICE_ACTIVE) { + dev = port->devices[i]; + break; + } + } + + return dev; +} + +static int handle_volume_updown_command(struct acp_card *card, const struct spa_pod *value, bool up) +{ + struct acp_device *dev = find_acp_device_for_command_value (card, value); + if (dev == NULL) + return -EINVAL; + + acp_device_set_volume_updown(dev, up); + return 0; +} + +static int handle_mute_toggle_command(struct acp_card *card, const struct spa_pod *value) +{ + struct acp_device *dev = find_acp_device_for_command_value (card, value); + if (dev == NULL) + return -EINVAL; + + acp_device_toggle_mute(dev); + return 0; +} + +static int handle_update_capabilities_command(struct acp_card *card, const struct spa_pod *value) +{ + int caps = 0; + + if (spa_pod_get_int(value, &caps) < 0) + return -EINVAL; + + card->ext_volume.read_caps = caps; + return 0; +} + +static int handle_update_volume_command(struct acp_card *card, const struct spa_pod *value) +{ + struct spa_pod *params = NULL; + struct spa_pod_parser prs; + struct spa_pod_frame f; + const char* route = NULL; + uint32_t channels; + int64_t *vols; + bool route_parsed = false, volume_parsed = false; + uint32_t port_index = ACP_INVALID_INDEX; + + if (spa_pod_parse_object(value, SPA_TYPE_OBJECT_Props, NULL, + SPA_PROP_params, SPA_POD_OPT_Pod(¶ms)) < 0) { + return -EINVAL; + } + + + spa_pod_parser_pod(&prs, params); + if (spa_pod_parser_push_struct(&prs, &f) < 0) + return -EINVAL; + + while (true) { + const char *name; + struct spa_pod *pod; + + if (spa_pod_parser_get_string(&prs, &name) < 0) + break; + + if (spa_pod_parser_get_pod(&prs, &pod) < 0) + break; + + if (spa_streq(name, "route")) { + if (spa_pod_get_string(pod, &route) < 0) + return -EINVAL; + route_parsed = true; + } else if (spa_streq(name, "volume")) { + if (!spa_pod_is_array(pod)) + return -EINVAL; + channels = SPA_POD_ARRAY_N_VALUES(SPA_POD_BODY(pod)); + vols = SPA_POD_ARRAY_VALUES(SPA_POD_BODY(pod)); + volume_parsed = true; + } + } + + if (!route_parsed || !volume_parsed) + return -EINVAL; + + port_index = find_route_by_name(card, route); + if (port_index == ACP_INVALID_INDEX) + return -EINVAL; + + card->ext_volume.read_volumes[port_index].channels = channels; + for (uint32_t i = 0; i < channels; i++) + card->ext_volume.read_volumes[port_index].values[i] = vols[i]; + + return 0; +} + +static int handle_update_mute_command(struct acp_card *card, const struct spa_pod *value) +{ + struct spa_pod *params = NULL; + struct spa_pod_parser prs; + struct spa_pod_frame f; + const char* route = NULL; + bool mute = false; + bool route_parsed = false, mute_parsed = false; + uint32_t port_index = ACP_INVALID_INDEX; + + if (spa_pod_parse_object(value, SPA_TYPE_OBJECT_Props, NULL, + SPA_PROP_params, SPA_POD_OPT_Pod(¶ms)) < 0) { + return -EINVAL; + } + + spa_pod_parser_pod(&prs, params); + if (spa_pod_parser_push_struct(&prs, &f) < 0) + return -EINVAL; + + while (true) { + const char *name; + struct spa_pod *pod; + + if (spa_pod_parser_get_string(&prs, &name) < 0) + break; + + if (spa_pod_parser_get_pod(&prs, &pod) < 0) + break; + + if (spa_streq(name, "route")) { + if (spa_pod_get_string(pod, &route) < 0) + return -EINVAL; + route_parsed = true; + } else if (spa_streq(name, "mute")) { + if (spa_pod_get_bool(pod, &mute) < 0) + return -EINVAL; + mute_parsed = true; + } + } + + if (!route_parsed || !mute_parsed) + return -EINVAL; + + port_index = find_route_by_name(card, route); + if (port_index == ACP_INVALID_INDEX) + return -EINVAL; + + card->ext_volume.read_mutes[port_index] = mute; + + return 0; +} + +static int impl_send_command(void *object, const struct spa_command *command) +{ + struct impl *this = object; + struct acp_card *card = this->card; + int res = 0; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(command != NULL, -EINVAL); + + switch (SPA_DEVICE_COMMAND_ID(command)) { + case SPA_DEVICE_COMMAND_VolumeControl: + { + const struct spa_pod_prop *prop; + + /* We support some volume control commands for external volumes */ + if (!card->ext_volume.initialized) + return -ENOTSUP; + + SPA_POD_OBJECT_FOREACH(&command->body, prop) { + switch (prop->key) { + case SPA_COMMAND_DEVICE_volumeUp: + res = handle_volume_updown_command(card, &prop->value, true); + break; + case SPA_COMMAND_DEVICE_volumeDown: + res = handle_volume_updown_command(card, &prop->value, false); + break; + case SPA_COMMAND_DEVICE_muteToggle: + res = handle_mute_toggle_command(card, &prop->value); + break; + case SPA_COMMAND_DEVICE_updateCapabilities: + res = handle_update_capabilities_command (card, &prop->value); + break; + case SPA_COMMAND_DEVICE_updateVolume: + res = handle_update_volume_command (card, &prop->value); + break; + case SPA_COMMAND_DEVICE_updateMute: + res = handle_update_mute_command (card, &prop->value); + break; + default: + return -ENOTSUP; + } + } + + break; + } + default: + return -ENOTSUP; + } + + return res; +} + static const struct spa_device_methods impl_device = { SPA_VERSION_DEVICE_METHODS, .add_listener = impl_add_listener, .sync = impl_sync, .enum_params = impl_enum_params, .set_param = impl_set_param, + .send_command = impl_send_command, }; static void card_props_changed(void *data)