mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-05-30 21:37:53 -04:00
299 lines
10 KiB
C
299 lines
10 KiB
C
|
|
/* 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;
|
||
|
|
}
|