mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-06-13 14:33:03 -04:00
spa: alsa: Support volume control commands for external volume control
This commit is contained in:
parent
eb9a751257
commit
08bd69f9bd
3 changed files with 252 additions and 0 deletions
|
|
@ -2486,6 +2486,24 @@ int acp_device_set_volume(struct acp_device *dev, const float *volume, uint32_t
|
||||||
return 0;
|
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)
|
static int get_volume(pa_cvolume *v, float *volume, uint32_t n_volume)
|
||||||
{
|
{
|
||||||
uint32_t i;
|
uint32_t i;
|
||||||
|
|
@ -2539,6 +2557,23 @@ int acp_device_get_mute(struct acp_device *dev, bool *mute)
|
||||||
return 0;
|
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)
|
void acp_set_log_func(acp_log_func func, void *data)
|
||||||
{
|
{
|
||||||
_acp_log_func = func;
|
_acp_log_func = func;
|
||||||
|
|
|
||||||
|
|
@ -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_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(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_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_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_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);
|
int acp_device_get_mute(struct acp_device *dev, bool *mute);
|
||||||
|
|
||||||
typedef void (*acp_log_func) (void *data,
|
typedef void (*acp_log_func) (void *data,
|
||||||
|
|
|
||||||
|
|
@ -890,12 +890,227 @@ static int impl_set_param(void *object,
|
||||||
return 0;
|
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 = {
|
static const struct spa_device_methods impl_device = {
|
||||||
SPA_VERSION_DEVICE_METHODS,
|
SPA_VERSION_DEVICE_METHODS,
|
||||||
.add_listener = impl_add_listener,
|
.add_listener = impl_add_listener,
|
||||||
.sync = impl_sync,
|
.sync = impl_sync,
|
||||||
.enum_params = impl_enum_params,
|
.enum_params = impl_enum_params,
|
||||||
.set_param = impl_set_param,
|
.set_param = impl_set_param,
|
||||||
|
.send_command = impl_send_command,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void card_props_changed(void *data)
|
static void card_props_changed(void *data)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue