pipewire/spa/plugins/alsa/acp/ext-volume.c

299 lines
10 KiB
C
Raw Normal View History

/* 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;
}