mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-05-30 21:37:53 -04:00
spa: alsa: Add a mechanism for external volume control
Currently enabled at device creation and delegated to an external entity via spa_device events.
This commit is contained in:
parent
122bfd712b
commit
ef6f5194e3
9 changed files with 867 additions and 86 deletions
298
spa/plugins/alsa/acp/ext-volume.c
Normal file
298
spa/plugins/alsa/acp/ext-volume.c
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
/* External Volume Control */
|
||||
/* SPDX-FileCopyrightText: Copyright © 2026 Arun Raghavan */
|
||||
/* SPDX-FileCopyrightText: Copyright © 2026 Julian Bouzas */
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include <spa/utils/cleanup.h>
|
||||
#include <spa/utils/result.h>
|
||||
#include <spa/pod/builder.h>
|
||||
#include <spa/monitor/event.h>
|
||||
#include <spa/param/props.h>
|
||||
|
||||
#include "alsa-mixer.h"
|
||||
#include "channelmap.h"
|
||||
#include "volume.h"
|
||||
#include "ext-volume.h"
|
||||
|
||||
int
|
||||
spa_acp_ext_volume_init(struct spa_acp_ext_volume *ext_volume, const char *device,
|
||||
ext_volume_notifier_t notifier_cb, void *notifier_data)
|
||||
{
|
||||
struct spa_event *ev;
|
||||
uint8_t buffer[4096];
|
||||
struct spa_pod_builder b = { 0 };
|
||||
struct spa_pod_frame f[3];
|
||||
|
||||
ext_volume->initialized = false;
|
||||
ext_volume->flags = 0;
|
||||
|
||||
ext_volume->notifier_cb = notifier_cb;
|
||||
ext_volume->notifier_data = notifier_data;
|
||||
|
||||
/* Create event */
|
||||
spa_pod_builder_init(&b, buffer, sizeof(buffer));
|
||||
spa_pod_builder_push_object(&b, &f[0],
|
||||
SPA_TYPE_EVENT_Device, SPA_DEVICE_EVENT_ExtVolumeControl);
|
||||
spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Props, 0);
|
||||
spa_pod_builder_push_object(&b, &f[1],
|
||||
SPA_TYPE_OBJECT_Props, SPA_EVENT_DEVICE_Props);
|
||||
spa_pod_builder_prop(&b, SPA_PROP_params, 0);
|
||||
spa_pod_builder_push_struct(&b, &f[2]);
|
||||
spa_pod_builder_string(&b, "GetCapabilities");
|
||||
spa_pod_builder_string(&b, device ? device : "");
|
||||
spa_pod_builder_pop(&b, &f[2]);
|
||||
spa_pod_builder_pop(&b, &f[1]);
|
||||
ev = (struct spa_event *)spa_pod_builder_pop(&b, &f[0]);
|
||||
|
||||
pa_log_info("sending 'GetCapabilities' ext-vol-control event to %s", device);
|
||||
|
||||
/* Emit */
|
||||
ext_volume->caps_reply.res = -EINVAL;
|
||||
ext_volume->notifier_cb (ext_volume->notifier_data, ev);
|
||||
|
||||
pa_log_info("reply after emitting event: res:%d caps:%d",
|
||||
ext_volume->caps_reply.res, ext_volume->caps_reply.caps);
|
||||
|
||||
/* Check reply */
|
||||
if (ext_volume->caps_reply.res < 0)
|
||||
return ext_volume->caps_reply.res;
|
||||
|
||||
ext_volume->flags = ext_volume->caps_reply.caps;
|
||||
ext_volume->initialized = true;
|
||||
return ext_volume->caps_reply.res;
|
||||
}
|
||||
|
||||
int
|
||||
spa_acp_ext_volume_read_volume(struct spa_acp_ext_volume *ext_volume,
|
||||
const char *device, const char *port_name, uint32_t port_index, pa_cvolume *cvol)
|
||||
{
|
||||
struct spa_event *ev;
|
||||
uint8_t buffer[4096];
|
||||
struct spa_pod_builder b = { 0 };
|
||||
struct spa_pod_frame f[3];
|
||||
|
||||
/* Create event */
|
||||
spa_pod_builder_init(&b, buffer, sizeof(buffer));
|
||||
spa_pod_builder_push_object(&b, &f[0],
|
||||
SPA_TYPE_EVENT_Device, SPA_DEVICE_EVENT_ExtVolumeControl);
|
||||
spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Props, 0);
|
||||
spa_pod_builder_push_object(&b, &f[1],
|
||||
SPA_TYPE_OBJECT_Props, SPA_EVENT_DEVICE_Props);
|
||||
spa_pod_builder_prop(&b, SPA_PROP_params, 0);
|
||||
spa_pod_builder_push_struct(&b, &f[2]);
|
||||
spa_pod_builder_string(&b, "ReadVolume");
|
||||
spa_pod_builder_string(&b, device ? device : "");
|
||||
spa_pod_builder_string(&b, port_name ? port_name : "");
|
||||
spa_pod_builder_pop(&b, &f[2]);
|
||||
spa_pod_builder_pop(&b, &f[1]);
|
||||
ev = (struct spa_event *)spa_pod_builder_pop(&b, &f[0]);
|
||||
|
||||
pa_log_info("sending 'ReadVolume' ext-vol-control event to %s on port %s", device, port_name);
|
||||
|
||||
/* Emit */
|
||||
ext_volume->vols_replies[port_index].res = -EINVAL;
|
||||
ext_volume->notifier_cb (ext_volume->notifier_data, ev);
|
||||
|
||||
pa_log_info("reply after emitting event: res:%d channels:%u",
|
||||
ext_volume->vols_replies[port_index].res, ext_volume->vols_replies[port_index].channels);
|
||||
|
||||
/* Check reply */
|
||||
if (ext_volume->vols_replies[port_index].res < 0)
|
||||
return ext_volume->vols_replies[port_index].res;
|
||||
|
||||
cvol->channels = ext_volume->vols_replies[port_index].channels;
|
||||
for (uint32_t i = 0; i < cvol->channels && i < PA_CHANNELS_MAX && i < SPA_AUDIO_MAX_CHANNELS; i++) {
|
||||
double v = ext_volume->vols_replies[port_index].values[i];
|
||||
cvol->values[i] = lrint(v * PA_VOLUME_NORM);
|
||||
}
|
||||
return ext_volume->vols_replies[port_index].res;
|
||||
}
|
||||
|
||||
int
|
||||
spa_acp_ext_volume_write_volume_absolute(struct spa_acp_ext_volume *ext_volume,
|
||||
const char *device, const char *port_name, uint32_t port_index, pa_cvolume *cvol)
|
||||
{
|
||||
struct spa_event *ev;
|
||||
uint8_t buffer[4096];
|
||||
struct spa_pod_builder b = { 0 };
|
||||
struct spa_pod_frame f[4];
|
||||
|
||||
/* Create event */
|
||||
spa_pod_builder_init(&b, buffer, sizeof(buffer));
|
||||
spa_pod_builder_push_object(&b, &f[0],
|
||||
SPA_TYPE_EVENT_Device, SPA_DEVICE_EVENT_ExtVolumeControl);
|
||||
spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Props, 0);
|
||||
spa_pod_builder_push_object(&b, &f[1],
|
||||
SPA_TYPE_OBJECT_Props, SPA_EVENT_DEVICE_Props);
|
||||
spa_pod_builder_prop(&b, SPA_PROP_params, 0);
|
||||
spa_pod_builder_push_struct(&b, &f[2]);
|
||||
spa_pod_builder_string(&b, "WriteVolumeAbsolute");
|
||||
spa_pod_builder_string(&b, device ? device : "");
|
||||
spa_pod_builder_string(&b, port_name ? port_name : "");
|
||||
|
||||
/* FIXME: scale to the range from capabilities */
|
||||
spa_pod_builder_push_array(&b, &f[3]);
|
||||
if (ext_volume->flags & SPA_AUDIO_VOLUME_CONTROL_WRITE_BALANCE) {
|
||||
/* Write all channels */
|
||||
for (uint32_t i = 0; i < cvol->channels; i++)
|
||||
spa_pod_builder_double(&b, cvol->values[i] / PA_VOLUME_NORM);
|
||||
} else {
|
||||
/* Single volume */
|
||||
spa_pod_builder_double(&b, pa_cvolume_max(cvol) / PA_VOLUME_NORM);
|
||||
}
|
||||
spa_pod_builder_pop(&b, &f[3]);
|
||||
|
||||
spa_pod_builder_pop(&b, &f[2]);
|
||||
spa_pod_builder_pop(&b, &f[1]);
|
||||
ev = (struct spa_event *)spa_pod_builder_pop(&b, &f[0]);
|
||||
|
||||
pa_log_info("sending 'WriteVolumeAbsolute' ext-vol-control event to %s on port %s", device, port_name);
|
||||
|
||||
/* Emit */
|
||||
ext_volume->notifier_cb (ext_volume->notifier_data, ev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
spa_acp_ext_volume_write_volume_relative(struct spa_acp_ext_volume *ext_volume,
|
||||
const char *device, const char *port_name, uint32_t port_index, float step)
|
||||
{
|
||||
struct spa_event *ev;
|
||||
uint8_t buffer[4096];
|
||||
struct spa_pod_builder b = { 0 };
|
||||
struct spa_pod_frame f[3];
|
||||
|
||||
/* Create event */
|
||||
spa_pod_builder_init(&b, buffer, sizeof(buffer));
|
||||
spa_pod_builder_push_object(&b, &f[0],
|
||||
SPA_TYPE_EVENT_Device, SPA_DEVICE_EVENT_ExtVolumeControl);
|
||||
spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Props, 0);
|
||||
spa_pod_builder_push_object(&b, &f[1],
|
||||
SPA_TYPE_OBJECT_Props, SPA_EVENT_DEVICE_Props);
|
||||
spa_pod_builder_prop(&b, SPA_PROP_params, 0);
|
||||
spa_pod_builder_push_struct(&b, &f[2]);
|
||||
spa_pod_builder_string(&b, "WriteVolumeRelative");
|
||||
spa_pod_builder_string(&b, device ? device : "");
|
||||
spa_pod_builder_string(&b, port_name ? port_name : "");
|
||||
spa_pod_builder_float(&b, step);
|
||||
spa_pod_builder_pop(&b, &f[2]);
|
||||
spa_pod_builder_pop(&b, &f[1]);
|
||||
ev = (struct spa_event *)spa_pod_builder_pop(&b, &f[0]);
|
||||
|
||||
pa_log_info("sending 'WriteVolumeRelative' ext-vol-control event to %s on port %s", device, port_name);
|
||||
|
||||
/* Emit */
|
||||
ext_volume->notifier_cb (ext_volume->notifier_data, ev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
spa_acp_ext_volume_read_mute(struct spa_acp_ext_volume *ext_volume,
|
||||
const char *device, const char *port_name, uint32_t port_index, bool *mute)
|
||||
{
|
||||
struct spa_event *ev;
|
||||
uint8_t buffer[4096];
|
||||
struct spa_pod_builder b = { 0 };
|
||||
struct spa_pod_frame f[3];
|
||||
|
||||
/* Create event */
|
||||
spa_pod_builder_init(&b, buffer, sizeof(buffer));
|
||||
spa_pod_builder_push_object(&b, &f[0],
|
||||
SPA_TYPE_EVENT_Device, SPA_DEVICE_EVENT_ExtVolumeControl);
|
||||
spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Props, 0);
|
||||
spa_pod_builder_push_object(&b, &f[1],
|
||||
SPA_TYPE_OBJECT_Props, SPA_EVENT_DEVICE_Props);
|
||||
spa_pod_builder_prop(&b, SPA_PROP_params, 0);
|
||||
spa_pod_builder_push_struct(&b, &f[2]);
|
||||
spa_pod_builder_string(&b, "ReadMute");
|
||||
spa_pod_builder_string(&b, device ? device : "");
|
||||
spa_pod_builder_string(&b, port_name ? port_name : "");
|
||||
spa_pod_builder_pop(&b, &f[2]);
|
||||
spa_pod_builder_pop(&b, &f[1]);
|
||||
ev = (struct spa_event *)spa_pod_builder_pop(&b, &f[0]);
|
||||
|
||||
pa_log_info("sending 'ReadMute' ext-vol-control event to %s on port %s", device, port_name);
|
||||
|
||||
/* Emit */
|
||||
ext_volume->mute_replies[port_index].res = EINVAL;
|
||||
ext_volume->notifier_cb (ext_volume->notifier_data, ev);
|
||||
|
||||
pa_log_info("reply after emitting event: res:%d mute:%u",
|
||||
ext_volume->mute_replies[port_index].res, ext_volume->mute_replies[port_index].mute);
|
||||
|
||||
/* Check reply */
|
||||
if (ext_volume->mute_replies[port_index].res < 0)
|
||||
return ext_volume->mute_replies[port_index].res;
|
||||
|
||||
*mute = ext_volume->mute_replies[port_index].mute;
|
||||
return ext_volume->mute_replies[port_index].res;
|
||||
}
|
||||
|
||||
int
|
||||
spa_acp_ext_volume_write_mute_value(struct spa_acp_ext_volume *ext_volume,
|
||||
const char *device, const char *port_name, uint32_t port_index, bool mute)
|
||||
{
|
||||
struct spa_event *ev;
|
||||
uint8_t buffer[4096];
|
||||
struct spa_pod_builder b = { 0 };
|
||||
struct spa_pod_frame f[3];
|
||||
|
||||
/* Create event */
|
||||
spa_pod_builder_init(&b, buffer, sizeof(buffer));
|
||||
spa_pod_builder_push_object(&b, &f[0],
|
||||
SPA_TYPE_EVENT_Device, SPA_DEVICE_EVENT_ExtVolumeControl);
|
||||
spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Props, 0);
|
||||
spa_pod_builder_push_object(&b, &f[1],
|
||||
SPA_TYPE_OBJECT_Props, SPA_EVENT_DEVICE_Props);
|
||||
spa_pod_builder_prop(&b, SPA_PROP_params, 0);
|
||||
spa_pod_builder_push_struct(&b, &f[2]);
|
||||
spa_pod_builder_string(&b, "WriteMuteValue");
|
||||
spa_pod_builder_string(&b, device ? device : "");
|
||||
spa_pod_builder_string(&b, port_name ? port_name : "");
|
||||
spa_pod_builder_bool(&b, mute);
|
||||
spa_pod_builder_pop(&b, &f[2]);
|
||||
spa_pod_builder_pop(&b, &f[1]);
|
||||
ev = (struct spa_event *)spa_pod_builder_pop(&b, &f[0]);
|
||||
|
||||
pa_log_info("sending 'WriteMuteValue' ext-vol-control event to %s on port %s", device, port_name);
|
||||
|
||||
/* Emit */
|
||||
ext_volume->notifier_cb (ext_volume->notifier_data, ev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
spa_acp_ext_volume_write_mute_toggle(struct spa_acp_ext_volume *ext_volume,
|
||||
const char *device, const char *port_name, uint32_t port_index)
|
||||
{
|
||||
struct spa_event *ev;
|
||||
uint8_t buffer[4096];
|
||||
struct spa_pod_builder b = { 0 };
|
||||
struct spa_pod_frame f[3];
|
||||
|
||||
/* Create event */
|
||||
spa_pod_builder_init(&b, buffer, sizeof(buffer));
|
||||
spa_pod_builder_push_object(&b, &f[0],
|
||||
SPA_TYPE_EVENT_Device, SPA_DEVICE_EVENT_ExtVolumeControl);
|
||||
spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Props, 0);
|
||||
spa_pod_builder_push_object(&b, &f[1],
|
||||
SPA_TYPE_OBJECT_Props, SPA_EVENT_DEVICE_Props);
|
||||
spa_pod_builder_prop(&b, SPA_PROP_params, 0);
|
||||
spa_pod_builder_push_struct(&b, &f[2]);
|
||||
spa_pod_builder_string(&b, "WriteMuteToggle");
|
||||
spa_pod_builder_string(&b, device ? device : "");
|
||||
spa_pod_builder_string(&b, port_name ? port_name : "");
|
||||
spa_pod_builder_pop(&b, &f[2]);
|
||||
spa_pod_builder_pop(&b, &f[1]);
|
||||
ev = (struct spa_event *)spa_pod_builder_pop(&b, &f[0]);
|
||||
|
||||
pa_log_info("sending 'WriteMuteToggle' ext-vol-control event to %s on port %s", device, port_name);
|
||||
|
||||
/* Emit */
|
||||
ext_volume->notifier_cb (ext_volume->notifier_data, ev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue