Merge branch 'alsa-ext-vol' into 'master'

Add an external volume control mechanism

See merge request pipewire/pipewire!2722
This commit is contained in:
Arun Raghavan 2026-05-21 15:57:15 +00:00
commit 937ca2a125
33 changed files with 2367 additions and 103 deletions

View file

@ -21,6 +21,7 @@
#include <spa/support/loop.h>
#include <spa/support/plugin.h>
#include <spa/support/i18n.h>
#include <spa/support/varlink.h>
#include <spa/monitor/device.h>
#include <spa/monitor/utils.h>
#include <spa/monitor/event.h>
@ -36,6 +37,7 @@
#include "acp/acp.h"
extern struct spa_i18n *acp_i18n;
extern struct spa_varlink *acp_varlink;
#define MAX_POLL 16
#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS
@ -63,6 +65,7 @@ struct impl {
struct spa_log *log;
struct spa_loop *loop;
struct spa_varlink *varlink;
uint32_t info_all;
struct spa_device_info info;
@ -490,6 +493,11 @@ static struct spa_pod *build_route(struct spa_pod_builder *b, uint32_t id,
dev->n_codecs, dev->codecs);
}
if (dev->ext_volume_flags) {
spa_pod_builder_prop(b, SPA_PROP_volumeControlFlags, 0);
spa_pod_builder_id(b, dev->ext_volume_flags);
}
spa_pod_builder_pop(b, &f[1]);
}
spa_pod_builder_prop(b, SPA_PARAM_ROUTE_devices, 0);
@ -885,12 +893,74 @@ static int impl_set_param(void *object,
return 0;
}
static int impl_send_command(void *object, const struct spa_command *command)
{
struct impl *this = object;
struct acp_card *card = this->card;
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.client == NULL)
return -ENOTSUP;
SPA_POD_OBJECT_FOREACH(&command->body, prop) {
struct acp_port *port;
struct acp_device *dev = NULL;
uint32_t id, i;
if (spa_pod_get_id(&prop->value, &id) < 0 || id > card->n_ports)
return -EINVAL;
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;
}
}
if (dev == NULL)
return -EINVAL;
switch (prop->key) {
case SPA_COMMAND_DEVICE_volumeUp:
acp_device_set_volume_updown(dev, true);
break;
case SPA_COMMAND_DEVICE_volumeDown:
acp_device_set_volume_updown(dev, false);
break;
case SPA_COMMAND_DEVICE_muteToggle:
acp_device_toggle_mute(dev);
break;
default:
return -ENOTSUP;
}
}
break;
}
default:
return -ENOTSUP;
}
return 0;
}
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)
@ -1163,6 +1233,7 @@ impl_init(const struct spa_handle_factory *factory,
this->loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop);
acp_i18n = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_I18N);
acp_varlink = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Varlink);
if (this->loop == NULL) {
spa_log_error(this->log, "a Loop interface is needed");
return -EINVAL;