mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-05-30 21:37:53 -04:00
Merge branch 'spa-device-external-vol-control' into 'master'
spa: alsa: Add option to send external volume control events See merge request pipewire/pipewire!2836
This commit is contained in:
commit
eed1094fbd
24 changed files with 1311 additions and 99 deletions
|
|
@ -8,6 +8,7 @@
|
||||||
#include <spa/utils/defs.h>
|
#include <spa/utils/defs.h>
|
||||||
#include <spa/utils/hook.h>
|
#include <spa/utils/hook.h>
|
||||||
#include <spa/utils/dict.h>
|
#include <spa/utils/dict.h>
|
||||||
|
#include <spa/pod/command.h>
|
||||||
#include <spa/pod/event.h>
|
#include <spa/pod/event.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
@ -126,7 +127,35 @@ struct spa_device_events {
|
||||||
#define SPA_DEVICE_METHOD_SYNC 1
|
#define SPA_DEVICE_METHOD_SYNC 1
|
||||||
#define SPA_DEVICE_METHOD_ENUM_PARAMS 2
|
#define SPA_DEVICE_METHOD_ENUM_PARAMS 2
|
||||||
#define SPA_DEVICE_METHOD_SET_PARAM 3
|
#define SPA_DEVICE_METHOD_SET_PARAM 3
|
||||||
#define SPA_DEVICE_METHOD_NUM 4
|
#define SPA_DEVICE_METHOD_SEND_COMMAND 4
|
||||||
|
#define SPA_DEVICE_METHOD_NUM 5
|
||||||
|
|
||||||
|
/* object id of SPA_TYPE_COMMAND_Device */
|
||||||
|
enum spa_device_commands {
|
||||||
|
SPA_DEVICE_COMMAND_User,
|
||||||
|
SPA_DEVICE_COMMAND_VolumeControl, /**< Volume control for devices which do not
|
||||||
|
* support standard volume control via route
|
||||||
|
* props */
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SPA_DEVICE_COMMAND_ID(cmd) SPA_COMMAND_ID(cmd, SPA_TYPE_COMMAND_Device)
|
||||||
|
#define SPA_DEVICE_COMMAND_INIT(id) SPA_COMMAND_INIT(SPA_TYPE_COMMAND_Device, id)
|
||||||
|
|
||||||
|
|
||||||
|
/* properties for SPA_TYPE_COMMAND_Device */
|
||||||
|
enum spa_command_device {
|
||||||
|
SPA_COMMAND_DEVICE_START,
|
||||||
|
|
||||||
|
SPA_COMMAND_DEVICE_START_User = 0x1000,
|
||||||
|
SPA_COMMAND_DEVICE_extra, /** extra info (String) */
|
||||||
|
|
||||||
|
SPA_COMMAND_DEVICE_START_VolumeControl = 0x2000,
|
||||||
|
SPA_COMMAND_DEVICE_volumeUp, /** Send a volume up command. route id (Id) */
|
||||||
|
SPA_COMMAND_DEVICE_volumeDown, /** Send a volume down command. route id (Id) */
|
||||||
|
SPA_COMMAND_DEVICE_muteToggle, /** Send a mute toggle command. route id (Id) */
|
||||||
|
|
||||||
|
SPA_COMMAND_DEVICE_START_CUSTOM = 0x1000000,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* spa_device_methods:
|
* spa_device_methods:
|
||||||
|
|
@ -134,7 +163,7 @@ struct spa_device_events {
|
||||||
struct spa_device_methods {
|
struct spa_device_methods {
|
||||||
/* the version of the methods. This can be used to expand this
|
/* the version of the methods. This can be used to expand this
|
||||||
* structure in the future */
|
* structure in the future */
|
||||||
#define SPA_VERSION_DEVICE_METHODS 0
|
#define SPA_VERSION_DEVICE_METHODS 1
|
||||||
uint32_t version;
|
uint32_t version;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -226,6 +255,23 @@ struct spa_device_methods {
|
||||||
int (*set_param) (void *object,
|
int (*set_param) (void *object,
|
||||||
uint32_t id, uint32_t flags,
|
uint32_t id, uint32_t flags,
|
||||||
const struct spa_pod *param);
|
const struct spa_pod *param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a command to a device.
|
||||||
|
*
|
||||||
|
* This function must be called from the main thread.
|
||||||
|
*
|
||||||
|
* \param object a \ref spa_device
|
||||||
|
* \param command a \ref spa_command of type \ref spa_command_device
|
||||||
|
* \return 0 on success
|
||||||
|
* -EINVAL when node or command is NULL
|
||||||
|
* -ENOTSUP when this node can't process commands
|
||||||
|
* -EINVAL \a command is an invalid command
|
||||||
|
*
|
||||||
|
* \since 1
|
||||||
|
*/
|
||||||
|
int (*send_command) (void *object, const struct spa_command *command);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
SPA_API_DEVICE int spa_device_add_listener(struct spa_device *object,
|
SPA_API_DEVICE int spa_device_add_listener(struct spa_device *object,
|
||||||
|
|
@ -256,6 +302,12 @@ SPA_API_DEVICE int spa_device_set_param(struct spa_device *object,
|
||||||
return spa_api_method_r(int, -ENOTSUP, spa_device, &object->iface, set_param, 0,
|
return spa_api_method_r(int, -ENOTSUP, spa_device, &object->iface, set_param, 0,
|
||||||
id, flags, param);
|
id, flags, param);
|
||||||
}
|
}
|
||||||
|
SPA_API_DEVICE int spa_device_send_command(struct spa_device *object,
|
||||||
|
const struct spa_command *command)
|
||||||
|
{
|
||||||
|
return spa_api_method_r(int, -ENOTSUP, spa_device, &object->iface, send_command, 1,
|
||||||
|
command);
|
||||||
|
}
|
||||||
|
|
||||||
#define SPA_KEY_DEVICE_ENUM_API "device.enum.api" /**< the api used to discover this
|
#define SPA_KEY_DEVICE_ENUM_API "device.enum.api" /**< the api used to discover this
|
||||||
* device */
|
* device */
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ extern "C" {
|
||||||
/* object id of SPA_TYPE_EVENT_Device */
|
/* object id of SPA_TYPE_EVENT_Device */
|
||||||
enum spa_device_event {
|
enum spa_device_event {
|
||||||
SPA_DEVICE_EVENT_ObjectConfig,
|
SPA_DEVICE_EVENT_ObjectConfig,
|
||||||
|
SPA_DEVICE_EVENT_ExtVolumeControl,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define SPA_DEVICE_EVENT_ID(ev) SPA_EVENT_ID(ev, SPA_TYPE_EVENT_Device)
|
#define SPA_DEVICE_EVENT_ID(ev) SPA_EVENT_ID(ev, SPA_TYPE_EVENT_Device)
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,9 @@
|
||||||
#ifndef SPA_DEVICE_TYPE_INFO_H
|
#ifndef SPA_DEVICE_TYPE_INFO_H
|
||||||
#define SPA_DEVICE_TYPE_INFO_H
|
#define SPA_DEVICE_TYPE_INFO_H
|
||||||
|
|
||||||
#include <spa/utils/type-info.h>
|
#include <spa/utils/type.h>
|
||||||
|
|
||||||
|
#include <spa/monitor/device.h>
|
||||||
#include <spa/monitor/event.h>
|
#include <spa/monitor/event.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
@ -26,6 +27,7 @@ extern "C" {
|
||||||
|
|
||||||
static const struct spa_type_info spa_type_device_event_id[] = {
|
static const struct spa_type_info spa_type_device_event_id[] = {
|
||||||
{ SPA_DEVICE_EVENT_ObjectConfig, SPA_TYPE_EVENT_Device, SPA_TYPE_INFO_DEVICE_EVENT_ID_BASE "ObjectConfig", NULL },
|
{ SPA_DEVICE_EVENT_ObjectConfig, SPA_TYPE_EVENT_Device, SPA_TYPE_INFO_DEVICE_EVENT_ID_BASE "ObjectConfig", NULL },
|
||||||
|
{ SPA_DEVICE_EVENT_ExtVolumeControl, SPA_TYPE_EVENT_Device, SPA_TYPE_INFO_DEVICE_EVENT_ID_BASE "ExtVolumeControl", NULL },
|
||||||
{ 0, 0, NULL, NULL },
|
{ 0, 0, NULL, NULL },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -36,6 +38,27 @@ static const struct spa_type_info spa_type_device_event[] = {
|
||||||
{ 0, 0, NULL, NULL },
|
{ 0, 0, NULL, NULL },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define SPA_TYPE_INFO_DeviceCommand SPA_TYPE_INFO_COMMAND_BASE "Device"
|
||||||
|
#define SPA_TYPE_INFO_DEVICE_COMMAND_BASE SPA_TYPE_INFO_DeviceCommand ":"
|
||||||
|
|
||||||
|
static const struct spa_type_info spa_type_device_command_id[] = {
|
||||||
|
{ SPA_DEVICE_COMMAND_User, SPA_TYPE_COMMAND_Device, SPA_TYPE_INFO_DEVICE_COMMAND_BASE "User", NULL },
|
||||||
|
{ SPA_DEVICE_COMMAND_VolumeControl, SPA_TYPE_COMMAND_Device, SPA_TYPE_INFO_DEVICE_COMMAND_BASE "VolumeControl", NULL },
|
||||||
|
{ 0, 0, NULL, NULL },
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct spa_type_info spa_type_device_command[] = {
|
||||||
|
{ SPA_COMMAND_DEVICE_START, SPA_TYPE_Id, SPA_TYPE_INFO_DEVICE_COMMAND_BASE, spa_type_device_command_id },
|
||||||
|
|
||||||
|
{ SPA_COMMAND_DEVICE_extra, SPA_TYPE_String, SPA_TYPE_INFO_DEVICE_COMMAND_BASE "extra", NULL },
|
||||||
|
|
||||||
|
{ SPA_COMMAND_DEVICE_volumeUp, SPA_TYPE_Id, SPA_TYPE_INFO_DEVICE_COMMAND_BASE "volumeUp", NULL },
|
||||||
|
{ SPA_COMMAND_DEVICE_volumeDown, SPA_TYPE_Id, SPA_TYPE_INFO_DEVICE_COMMAND_BASE "volumeDown", NULL },
|
||||||
|
{ SPA_COMMAND_DEVICE_muteToggle, SPA_TYPE_Id, SPA_TYPE_INFO_DEVICE_COMMAND_BASE "muteToggle", NULL },
|
||||||
|
|
||||||
|
{ 0, 0, NULL, NULL },
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \}
|
* \}
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
29
spa/include/spa/param/audio/volume.h
Normal file
29
spa/include/spa/param/audio/volume.h
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
/* Simple Plugin API */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2026 Arun Raghavan */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#ifndef SPA_AUDIO_VOLUME_H
|
||||||
|
#define SPA_AUDIO_VOLUME_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** Flags for volume control capabilities */
|
||||||
|
enum spa_audio_volume_control_flags {
|
||||||
|
SPA_AUDIO_VOLUME_CONTROL_NONE = 0, /**<< No flags */
|
||||||
|
SPA_AUDIO_VOLUME_CONTROL_READ_VOLUME = (1 << 0), /**<< Volume value can be read */
|
||||||
|
SPA_AUDIO_VOLUME_CONTROL_WRITE_VOLUME_VALUE = (1 << 1), /**<< Volume value can be set */
|
||||||
|
SPA_AUDIO_VOLUME_CONTROL_WRITE_VOLUME_UPDOWN = (1 << 2), /**<< Volume value can incremented/decremented */
|
||||||
|
SPA_AUDIO_VOLUME_CONTROL_READ_MUTE = (1 << 3), /**<< Mute state can be read */
|
||||||
|
SPA_AUDIO_VOLUME_CONTROL_WRITE_MUTE_VALUE = (1 << 4), /**<< Mute state can be set */
|
||||||
|
SPA_AUDIO_VOLUME_CONTROL_WRITE_MUTE_TOGGLE = (1 << 5), /**<< Mute state can be toggled */
|
||||||
|
SPA_AUDIO_VOLUME_CONTROL_READ_BALANCE = (1 << 6), /**<< Per-channel volumes can be read */
|
||||||
|
SPA_AUDIO_VOLUME_CONTROL_WRITE_BALANCE = (1 << 7), /**<< Per-channel volumes can be set */
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
} /* extern "C" */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* SPA_AUDIO_VOLUME_H */
|
||||||
|
|
@ -66,6 +66,7 @@ static const struct spa_type_info spa_type_props[] = {
|
||||||
{ SPA_PROP_volumeRampTime, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "volumeRampTime", NULL },
|
{ SPA_PROP_volumeRampTime, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "volumeRampTime", NULL },
|
||||||
{ SPA_PROP_volumeRampStepTime, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "volumeRampStepTime", NULL },
|
{ SPA_PROP_volumeRampStepTime, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "volumeRampStepTime", NULL },
|
||||||
{ SPA_PROP_volumeRampScale, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE "volumeRampScale", spa_type_audio_volume_ramp_scale },
|
{ SPA_PROP_volumeRampScale, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE "volumeRampScale", spa_type_audio_volume_ramp_scale },
|
||||||
|
{ SPA_PROP_volumeControlFlags, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "volumeControlFlags", NULL },
|
||||||
|
|
||||||
{ SPA_PROP_brightness, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "brightness", NULL },
|
{ SPA_PROP_brightness, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "brightness", NULL },
|
||||||
{ SPA_PROP_contrast, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "contrast", NULL },
|
{ SPA_PROP_contrast, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "contrast", NULL },
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,8 @@ enum spa_prop {
|
||||||
* to ramp the */
|
* to ramp the */
|
||||||
SPA_PROP_volumeRampScale, /**< the scale or graph to used to ramp the
|
SPA_PROP_volumeRampScale, /**< the scale or graph to used to ramp the
|
||||||
* volume */
|
* volume */
|
||||||
|
SPA_PROP_volumeControlFlags, /**< Available volume control features
|
||||||
|
* (Id enum spa_audio_volume_control_flags) */
|
||||||
|
|
||||||
SPA_PROP_START_Video = 0x20000, /**< video related properties */
|
SPA_PROP_START_Video = 0x20000, /**< video related properties */
|
||||||
SPA_PROP_brightness,
|
SPA_PROP_brightness,
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ static const struct spa_type_info spa_types[] = {
|
||||||
{ SPA_TYPE_EVENT_Node, SPA_TYPE_Object, SPA_TYPE_INFO_EVENT_BASE "Node", spa_type_node_event },
|
{ SPA_TYPE_EVENT_Node, SPA_TYPE_Object, SPA_TYPE_INFO_EVENT_BASE "Node", spa_type_node_event },
|
||||||
|
|
||||||
{ SPA_TYPE_COMMAND_START, SPA_TYPE_Object, SPA_TYPE_INFO_Command, NULL },
|
{ SPA_TYPE_COMMAND_START, SPA_TYPE_Object, SPA_TYPE_INFO_Command, NULL },
|
||||||
{ SPA_TYPE_COMMAND_Device, SPA_TYPE_Object, SPA_TYPE_INFO_COMMAND_BASE "Device", NULL },
|
{ SPA_TYPE_COMMAND_Device, SPA_TYPE_Object, SPA_TYPE_INFO_COMMAND_BASE "Device", spa_type_device_command },
|
||||||
{ SPA_TYPE_COMMAND_Node, SPA_TYPE_Object, SPA_TYPE_INFO_COMMAND_BASE "Node", spa_type_node_command },
|
{ SPA_TYPE_COMMAND_Node, SPA_TYPE_Object, SPA_TYPE_INFO_COMMAND_BASE "Node", spa_type_node_command },
|
||||||
|
|
||||||
{ SPA_TYPE_OBJECT_START, SPA_TYPE_Object, SPA_TYPE_INFO_Object, NULL },
|
{ SPA_TYPE_OBJECT_START, SPA_TYPE_Object, SPA_TYPE_INFO_Object, NULL },
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include "acp.h"
|
#include "acp.h"
|
||||||
#include "alsa-mixer.h"
|
#include "alsa-mixer.h"
|
||||||
#include "alsa-ucm.h"
|
#include "alsa-ucm.h"
|
||||||
|
#include "ext-volume.h"
|
||||||
|
|
||||||
#include <spa/utils/string.h>
|
#include <spa/utils/string.h>
|
||||||
#include <spa/utils/json.h>
|
#include <spa/utils/json.h>
|
||||||
|
|
@ -1246,6 +1247,28 @@ static void init_eld_ctls(pa_card *impl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ext_volume_notifier_cb (void *data, struct spa_event *event) {
|
||||||
|
struct pa_card *impl = data;
|
||||||
|
|
||||||
|
if (impl->events && impl->events->ext_vol_event_available)
|
||||||
|
impl->events->ext_vol_event_available(impl->user_data, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void init_ext_volume(pa_card *impl)
|
||||||
|
{
|
||||||
|
struct acp_card *card = &impl->card;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
if (!impl->ext_volume_ctrl)
|
||||||
|
return;
|
||||||
|
|
||||||
|
res = spa_acp_ext_volume_init(&card->ext_volume, impl->name, ext_volume_notifier_cb, impl);
|
||||||
|
if (res < 0) {
|
||||||
|
pa_log_notice("failed to init ACP external volume: %s", snd_strerror(res));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t acp_card_find_best_profile_index(struct acp_card *card, const char *name)
|
uint32_t acp_card_find_best_profile_index(struct acp_card *card, const char *name)
|
||||||
{
|
{
|
||||||
uint32_t i;
|
uint32_t i;
|
||||||
|
|
@ -1357,9 +1380,34 @@ static int read_volume(pa_alsa_device *dev)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dev->mixer_handle)
|
if (impl->card.ext_volume.initialized &&
|
||||||
return 0;
|
(impl->card.ext_volume.flags & SPA_AUDIO_VOLUME_CONTROL_READ_VOLUME)) {
|
||||||
|
pa_cvolume ext_vol;
|
||||||
|
|
||||||
|
if (!dev->active_port)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* Externally managed volume */
|
||||||
|
if ((res = spa_acp_ext_volume_read_volume(&impl->card.ext_volume, impl->name,
|
||||||
|
dev->active_port->name, dev->active_port->port.index, &ext_vol)) < 0) {
|
||||||
|
pa_log_error("Could not read volume: %s", snd_strerror(res));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FIXME: scale to the range from capabilities */
|
||||||
|
if (ext_vol.channels == 1) {
|
||||||
|
r.channels = dev->device.format.channels;
|
||||||
|
for (unsigned int i = 0; i < r.channels; i++)
|
||||||
|
r.values[i] = ext_vol.values[0];
|
||||||
|
} else if (ext_vol.channels == dev->device.format.channels) {
|
||||||
|
r = ext_vol;
|
||||||
|
} else {
|
||||||
|
pa_log_error("Mismatch channel count: device %u != volume %u",
|
||||||
|
dev->device.format.channels, ext_vol.channels);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else if (dev->mixer_handle) {
|
||||||
|
/* ALSA mixer for volume */
|
||||||
if (dev->mixer_path->has_volume_mute && dev->muted) {
|
if (dev->mixer_path->has_volume_mute && dev->muted) {
|
||||||
/* Shift up by the base volume */
|
/* Shift up by the base volume */
|
||||||
pa_sw_cvolume_divide_scalar(&r, &dev->hardware_volume, dev->base_volume);
|
pa_sw_cvolume_divide_scalar(&r, &dev->hardware_volume, dev->base_volume);
|
||||||
|
|
@ -1373,6 +1421,10 @@ static int read_volume(pa_alsa_device *dev)
|
||||||
/* Shift down by the base volume, so that 0dB becomes maximum volume */
|
/* Shift down by the base volume, so that 0dB becomes maximum volume */
|
||||||
pa_sw_cvolume_multiply_scalar(&r, &r, dev->base_volume);
|
pa_sw_cvolume_multiply_scalar(&r, &r, dev->base_volume);
|
||||||
|
|
||||||
|
} else
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
|
||||||
if (pa_cvolume_equal(&dev->hardware_volume, &r))
|
if (pa_cvolume_equal(&dev->hardware_volume, &r))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
|
@ -1394,12 +1446,10 @@ static int read_volume(pa_alsa_device *dev)
|
||||||
|
|
||||||
static void set_volume(pa_alsa_device *dev, const pa_cvolume *v)
|
static void set_volume(pa_alsa_device *dev, const pa_cvolume *v)
|
||||||
{
|
{
|
||||||
|
pa_card *impl = dev->card;
|
||||||
pa_cvolume r;
|
pa_cvolume r;
|
||||||
bool write_to_hw;
|
bool write_to_hw;
|
||||||
|
|
||||||
if (v != &dev->real_volume)
|
|
||||||
dev->real_volume = *v;
|
|
||||||
|
|
||||||
if (dev->ucm_context) {
|
if (dev->ucm_context) {
|
||||||
if (!dev->active_port)
|
if (!dev->active_port)
|
||||||
return;
|
return;
|
||||||
|
|
@ -1408,8 +1458,55 @@ static void set_volume(pa_alsa_device *dev, const pa_cvolume *v)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dev->mixer_handle)
|
if (impl->card.ext_volume.initialized) {
|
||||||
|
if (!dev->active_port)
|
||||||
return;
|
return;
|
||||||
|
if (impl->card.ext_volume.flags & SPA_AUDIO_VOLUME_CONTROL_WRITE_VOLUME_VALUE) {
|
||||||
|
/* External volume control by value */
|
||||||
|
if (spa_acp_ext_volume_write_volume_absolute(&impl->card.ext_volume, impl->name,
|
||||||
|
dev->active_port->name, dev->active_port->port.index, &dev->real_volume) < 0) {
|
||||||
|
pa_log_error("Could not write volume");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update volume if we were successful */
|
||||||
|
if (v != &dev->real_volume)
|
||||||
|
dev->real_volume = *v;
|
||||||
|
} else if ((impl->card.ext_volume.flags & SPA_AUDIO_VOLUME_CONTROL_READ_VOLUME) &&
|
||||||
|
(impl->card.ext_volume.flags & SPA_AUDIO_VOLUME_CONTROL_WRITE_VOLUME_UPDOWN)) {
|
||||||
|
/* External volume control by increment/decrement only */
|
||||||
|
pa_volume_t cur, new;
|
||||||
|
float step;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
cur = pa_cvolume_max(&dev->real_volume);
|
||||||
|
new = pa_cvolume_max(v);
|
||||||
|
|
||||||
|
if (cur < new)
|
||||||
|
step = 1;
|
||||||
|
else if (cur > new)
|
||||||
|
step = -1;
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (spa_acp_ext_volume_write_volume_relative(&impl->card.ext_volume, impl->name,
|
||||||
|
dev->active_port->name, dev->active_port->port.index, step) < 0) {
|
||||||
|
pa_log_error("Could not write volume");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update volume if we were successful */
|
||||||
|
dev->real_volume.channels = v->channels;
|
||||||
|
for (i = 0; i < (int)v->channels; i++)
|
||||||
|
dev->real_volume.values[i] = v->values[i] + (int)step;
|
||||||
|
} else {
|
||||||
|
pa_log_debug("Ignoring volume setting, ext volume control does not support it");
|
||||||
|
}
|
||||||
|
} else if (dev->mixer_handle) {
|
||||||
|
/* ALSA mixer control for volume */
|
||||||
|
|
||||||
|
if (v != &dev->real_volume)
|
||||||
|
dev->real_volume = *v;
|
||||||
|
|
||||||
/* Shift up by the base volume */
|
/* Shift up by the base volume */
|
||||||
pa_sw_cvolume_divide_scalar(&r, &dev->real_volume, dev->base_volume);
|
pa_sw_cvolume_divide_scalar(&r, &dev->real_volume, dev->base_volume);
|
||||||
|
|
@ -1455,6 +1552,7 @@ static void set_volume(pa_alsa_device *dev, const pa_cvolume *v)
|
||||||
* at least tell the user about it */
|
* at least tell the user about it */
|
||||||
dev->real_volume = r;
|
dev->real_volume = r;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int read_mute(pa_alsa_device *dev)
|
static int read_mute(pa_alsa_device *dev)
|
||||||
|
|
@ -1471,9 +1569,19 @@ static int read_mute(pa_alsa_device *dev)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dev->mixer_handle)
|
if (impl->card.ext_volume.initialized &&
|
||||||
return 0;
|
(impl->card.ext_volume.flags & SPA_AUDIO_VOLUME_CONTROL_READ_MUTE)) {
|
||||||
|
if (!dev->active_port)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* Externally managed mute state */
|
||||||
|
if (spa_acp_ext_volume_read_mute(&impl->card.ext_volume, impl->name,
|
||||||
|
dev->active_port->name, dev->active_port->port.index, &mute) < 0) {
|
||||||
|
pa_log_error("Could not read mute state");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else if (dev->mixer_handle) {
|
||||||
|
/* ALSA mixer for mute state */
|
||||||
if (dev->mixer_path->has_volume_mute) {
|
if (dev->mixer_path->has_volume_mute) {
|
||||||
pa_cvolume mute_vol;
|
pa_cvolume mute_vol;
|
||||||
pa_cvolume r;
|
pa_cvolume r;
|
||||||
|
|
@ -1486,6 +1594,8 @@ static int read_mute(pa_alsa_device *dev)
|
||||||
if ((res = pa_alsa_path_get_mute(dev->mixer_path, dev->mixer_handle, &mute)) < 0)
|
if ((res = pa_alsa_path_get_mute(dev->mixer_path, dev->mixer_handle, &mute)) < 0)
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
} else
|
||||||
|
return 0;
|
||||||
|
|
||||||
if (mute == dev->muted)
|
if (mute == dev->muted)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -1501,7 +1611,7 @@ static int read_mute(pa_alsa_device *dev)
|
||||||
|
|
||||||
static void set_mute(pa_alsa_device *dev, bool mute)
|
static void set_mute(pa_alsa_device *dev, bool mute)
|
||||||
{
|
{
|
||||||
dev->muted = mute;
|
pa_card *impl = dev->card;
|
||||||
|
|
||||||
if (dev->ucm_context) {
|
if (dev->ucm_context) {
|
||||||
if (!dev->active_port)
|
if (!dev->active_port)
|
||||||
|
|
@ -1511,8 +1621,34 @@ static void set_mute(pa_alsa_device *dev, bool mute)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dev->mixer_handle)
|
if (impl->card.ext_volume.initialized) {
|
||||||
|
if (!dev->active_port)
|
||||||
return;
|
return;
|
||||||
|
if ((impl->card.ext_volume.flags & SPA_AUDIO_VOLUME_CONTROL_WRITE_MUTE_VALUE)) {
|
||||||
|
/* Externally managed mute state by value*/
|
||||||
|
if (spa_acp_ext_volume_write_mute_value(&impl->card.ext_volume, impl->name,
|
||||||
|
dev->active_port->name, dev->active_port->port.index, mute) < 0) {
|
||||||
|
pa_log_error("Could not write mute state");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev->muted = mute;
|
||||||
|
} else if ((impl->card.ext_volume.flags & SPA_AUDIO_VOLUME_CONTROL_READ_MUTE) &&
|
||||||
|
(impl->card.ext_volume.flags & SPA_AUDIO_VOLUME_CONTROL_WRITE_MUTE_TOGGLE)) {
|
||||||
|
/* Externally managed mute state toggle */
|
||||||
|
if (spa_acp_ext_volume_write_mute_toggle(&impl->card.ext_volume, impl->name,
|
||||||
|
dev->active_port->name, dev->active_port->port.index) < 0) {
|
||||||
|
pa_log_error("Could not write mute toggle");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev->muted = mute;
|
||||||
|
} else {
|
||||||
|
pa_log_debug("Ignoring mute setting, ext volume control does not support it");
|
||||||
|
}
|
||||||
|
} else if (dev->mixer_handle) {
|
||||||
|
/* ALSA mixer for mute state */
|
||||||
|
dev->muted = mute;
|
||||||
|
|
||||||
if (dev->mixer_path->has_volume_mute) {
|
if (dev->mixer_path->has_volume_mute) {
|
||||||
pa_cvolume r;
|
pa_cvolume r;
|
||||||
|
|
@ -1533,13 +1669,22 @@ static void set_mute(pa_alsa_device *dev, bool mute)
|
||||||
} else {
|
} else {
|
||||||
pa_alsa_path_set_mute(dev->mixer_path, dev->mixer_handle, mute);
|
pa_alsa_path_set_mute(dev->mixer_path, dev->mixer_handle, mute);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void mixer_volume_init(pa_card *impl, pa_alsa_device *dev)
|
static void mixer_volume_init(pa_card *impl, pa_alsa_device *dev)
|
||||||
{
|
{
|
||||||
pa_assert(dev);
|
pa_assert(dev);
|
||||||
|
|
||||||
if (impl->soft_mixer || !dev->mixer_path || !dev->mixer_path->has_volume) {
|
if (impl->ext_volume_ctrl) {
|
||||||
|
dev->device.flags |= ACP_DEVICE_HW_VOLUME;
|
||||||
|
if (impl->card.ext_volume.flags & SPA_AUDIO_VOLUME_CONTROL_READ_VOLUME)
|
||||||
|
dev->read_volume = read_volume;
|
||||||
|
if (impl->card.ext_volume.flags &
|
||||||
|
(SPA_AUDIO_VOLUME_CONTROL_WRITE_VOLUME_VALUE |
|
||||||
|
SPA_AUDIO_VOLUME_CONTROL_WRITE_VOLUME_UPDOWN))
|
||||||
|
dev->set_volume = set_volume;
|
||||||
|
} else if (impl->soft_mixer || !dev->mixer_path || !dev->mixer_path->has_volume) {
|
||||||
dev->read_volume = NULL;
|
dev->read_volume = NULL;
|
||||||
dev->set_volume = NULL;
|
dev->set_volume = NULL;
|
||||||
pa_log_info("Driver does not support hardware volume control, "
|
pa_log_info("Driver does not support hardware volume control, "
|
||||||
|
|
@ -1587,7 +1732,15 @@ static void mixer_volume_init(pa_card *impl, pa_alsa_device *dev)
|
||||||
dev->device.base_volume = (float)pa_sw_volume_to_linear(dev->base_volume);
|
dev->device.base_volume = (float)pa_sw_volume_to_linear(dev->base_volume);
|
||||||
dev->device.volume_step = 1.0f / dev->n_volume_steps;
|
dev->device.volume_step = 1.0f / dev->n_volume_steps;
|
||||||
|
|
||||||
if (impl->soft_mixer || !dev->mixer_path ||
|
if (impl->ext_volume_ctrl) {
|
||||||
|
dev->device.flags |= ACP_DEVICE_HW_MUTE;
|
||||||
|
if (impl->card.ext_volume.flags & SPA_AUDIO_VOLUME_CONTROL_READ_MUTE)
|
||||||
|
dev->read_mute = read_mute;
|
||||||
|
if (impl->card.ext_volume.flags &
|
||||||
|
(SPA_AUDIO_VOLUME_CONTROL_WRITE_MUTE_VALUE |
|
||||||
|
SPA_AUDIO_VOLUME_CONTROL_WRITE_MUTE_TOGGLE))
|
||||||
|
dev->set_mute = set_mute;
|
||||||
|
} else if (impl->soft_mixer || !dev->mixer_path ||
|
||||||
(!dev->mixer_path->has_mute && !dev->mixer_path->has_volume_mute)) {
|
(!dev->mixer_path->has_mute && !dev->mixer_path->has_volume_mute)) {
|
||||||
dev->read_mute = NULL;
|
dev->read_mute = NULL;
|
||||||
dev->set_mute = NULL;
|
dev->set_mute = NULL;
|
||||||
|
|
@ -1656,6 +1809,12 @@ static int setup_mixer(pa_card *impl, pa_alsa_device *dev, bool ignore_dB)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (impl->ext_volume_ctrl) {
|
||||||
|
/* We've been told to use an external service to manage volume */
|
||||||
|
init_ext_volume(impl);
|
||||||
|
dev->device.ext_volume_flags = impl->card.ext_volume.flags;
|
||||||
|
}
|
||||||
|
|
||||||
mixer_volume_init(impl, dev);
|
mixer_volume_init(impl, dev);
|
||||||
|
|
||||||
/* Will we need to register callbacks? */
|
/* Will we need to register callbacks? */
|
||||||
|
|
@ -1975,6 +2134,8 @@ struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props)
|
||||||
impl->disable_pro_audio = spa_atob(s);
|
impl->disable_pro_audio = spa_atob(s);
|
||||||
if ((s = acp_dict_lookup(props, "api.acp.use-eld-channels")) != NULL)
|
if ((s = acp_dict_lookup(props, "api.acp.use-eld-channels")) != NULL)
|
||||||
impl->use_eld_channels = spa_atob(s);
|
impl->use_eld_channels = spa_atob(s);
|
||||||
|
if ((s = acp_dict_lookup(props, "api.alsa.external-volume-control")) != NULL)
|
||||||
|
impl->ext_volume_ctrl = spa_atob(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if SND_LIB_VERSION < 0x10207
|
#if SND_LIB_VERSION < 0x10207
|
||||||
|
|
@ -2325,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;
|
||||||
|
|
@ -2360,6 +2539,7 @@ int acp_device_set_mute(struct acp_device *dev, bool mute)
|
||||||
|
|
||||||
if (d->set_mute) {
|
if (d->set_mute) {
|
||||||
d->set_mute(d, mute);
|
d->set_mute(d, mute);
|
||||||
|
mute = d->muted;
|
||||||
} else {
|
} else {
|
||||||
d->muted = mute;
|
d->muted = mute;
|
||||||
}
|
}
|
||||||
|
|
@ -2377,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;
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,10 @@
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <spa/param/audio/volume.h>
|
||||||
|
|
||||||
|
#include "ext-volume.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#else
|
#else
|
||||||
|
|
@ -188,6 +192,8 @@ struct acp_card_events {
|
||||||
|
|
||||||
void (*volume_changed) (void *data, struct acp_device *dev);
|
void (*volume_changed) (void *data, struct acp_device *dev);
|
||||||
void (*mute_changed) (void *data, struct acp_device *dev);
|
void (*mute_changed) (void *data, struct acp_device *dev);
|
||||||
|
|
||||||
|
void (*ext_vol_event_available) (void *data, struct spa_event *event);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct acp_port {
|
struct acp_port {
|
||||||
|
|
@ -239,6 +245,9 @@ struct acp_device {
|
||||||
int64_t latency_ns;
|
int64_t latency_ns;
|
||||||
uint32_t codecs[32];
|
uint32_t codecs[32];
|
||||||
uint32_t n_codecs;
|
uint32_t n_codecs;
|
||||||
|
|
||||||
|
uint32_t ext_volume_flags;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct acp_card_profile {
|
struct acp_card_profile {
|
||||||
|
|
@ -277,6 +286,8 @@ struct acp_card {
|
||||||
struct acp_port **ports;
|
struct acp_port **ports;
|
||||||
uint32_t preferred_input_port_index;
|
uint32_t preferred_input_port_index;
|
||||||
uint32_t preferred_output_port_index;
|
uint32_t preferred_output_port_index;
|
||||||
|
|
||||||
|
struct spa_acp_ext_volume ext_volume;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props);
|
struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props);
|
||||||
|
|
@ -299,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,
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,8 @@ struct pa_card {
|
||||||
|
|
||||||
const struct acp_card_events *events;
|
const struct acp_card_events *events;
|
||||||
void *user_data;
|
void *user_data;
|
||||||
|
|
||||||
|
bool ext_volume_ctrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool pa_alsa_device_init_description(pa_proplist *p, pa_card *card);
|
bool pa_alsa_device_init_description(pa_proplist *p, pa_card *card);
|
||||||
|
|
|
||||||
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;
|
||||||
|
}
|
||||||
69
spa/plugins/alsa/acp/ext-volume.h
Normal file
69
spa/plugins/alsa/acp/ext-volume.h
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
/* External Volume Control */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2026 Arun Raghavan */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2026 Julian Bouzas */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#ifndef ACP_EXT_VOLUME_H
|
||||||
|
#define ACP_EXT_VOLUME_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <spa/param/audio/volume.h>
|
||||||
|
#include <spa/pod/event.h>
|
||||||
|
|
||||||
|
#define MAX_PORTS 256
|
||||||
|
|
||||||
|
typedef struct pa_cvolume pa_cvolume;
|
||||||
|
|
||||||
|
typedef void (*ext_volume_notifier_t) (void *data, struct spa_event *event);
|
||||||
|
|
||||||
|
struct caps_reply {
|
||||||
|
int res;
|
||||||
|
enum spa_audio_volume_control_flags caps;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mute_reply {
|
||||||
|
int res;
|
||||||
|
bool mute;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct vols_reply {
|
||||||
|
int res;
|
||||||
|
uint32_t channels;
|
||||||
|
double values[SPA_AUDIO_MAX_CHANNELS];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct spa_acp_ext_volume {
|
||||||
|
bool initialized;
|
||||||
|
enum spa_audio_volume_control_flags flags;
|
||||||
|
|
||||||
|
ext_volume_notifier_t notifier_cb;
|
||||||
|
void *notifier_data;
|
||||||
|
|
||||||
|
struct caps_reply caps_reply;
|
||||||
|
struct mute_reply mute_replies[MAX_PORTS];
|
||||||
|
struct vols_reply vols_replies[MAX_PORTS];
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
#endif /* ACP_EXT_VOLUME_H */
|
||||||
|
|
@ -5,6 +5,7 @@ acp_sources = [
|
||||||
'alsa-ucm.c',
|
'alsa-ucm.c',
|
||||||
'alsa-util.c',
|
'alsa-util.c',
|
||||||
'conf-parser.c',
|
'conf-parser.c',
|
||||||
|
'ext-volume.c',
|
||||||
]
|
]
|
||||||
|
|
||||||
acp_c_args = [
|
acp_c_args = [
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,8 @@ struct impl {
|
||||||
#define IDX_Profile 1
|
#define IDX_Profile 1
|
||||||
#define IDX_EnumRoute 2
|
#define IDX_EnumRoute 2
|
||||||
#define IDX_Route 3
|
#define IDX_Route 3
|
||||||
struct spa_param_info params[4];
|
#define IDX_Props 4
|
||||||
|
struct spa_param_info params[5];
|
||||||
|
|
||||||
struct spa_hook_list hooks;
|
struct spa_hook_list hooks;
|
||||||
|
|
||||||
|
|
@ -490,6 +491,11 @@ static struct spa_pod *build_route(struct spa_pod_builder *b, uint32_t id,
|
||||||
dev->n_codecs, dev->codecs);
|
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_pop(b, &f[1]);
|
||||||
}
|
}
|
||||||
spa_pod_builder_prop(b, SPA_PARAM_ROUTE_devices, 0);
|
spa_pod_builder_prop(b, SPA_PARAM_ROUTE_devices, 0);
|
||||||
|
|
@ -507,6 +513,41 @@ static struct spa_pod *build_route(struct spa_pod_builder *b, uint32_t id,
|
||||||
return spa_pod_builder_pop(b, &f[0]);
|
return spa_pod_builder_pop(b, &f[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct spa_pod *build_props(struct spa_pod_builder *b, struct acp_card *card, struct acp_port *p)
|
||||||
|
{
|
||||||
|
struct spa_pod_frame f[4];
|
||||||
|
|
||||||
|
spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Props, SPA_PARAM_Props);
|
||||||
|
spa_pod_builder_prop(b, SPA_PROP_params, 0);
|
||||||
|
spa_pod_builder_push_struct(b, &f[1]);
|
||||||
|
|
||||||
|
spa_pod_builder_string(b, "ext-control-caps-reply");
|
||||||
|
spa_pod_builder_push_struct(b, &f[2]);
|
||||||
|
spa_pod_builder_int(b, card->ext_volume.caps_reply.res);
|
||||||
|
spa_pod_builder_int(b, card->ext_volume.caps_reply.caps);
|
||||||
|
spa_pod_builder_pop(b, &f[2]);
|
||||||
|
|
||||||
|
spa_pod_builder_string(b, "ext-control-vols-reply");
|
||||||
|
spa_pod_builder_push_struct(b, &f[2]);
|
||||||
|
spa_pod_builder_string(b, p->name);
|
||||||
|
spa_pod_builder_int(b, card->ext_volume.vols_replies[p->index].res);
|
||||||
|
spa_pod_builder_push_array(b, &f[3]);
|
||||||
|
for (uint32_t i = 0; i < card->ext_volume.vols_replies[p->index].channels; i++)
|
||||||
|
spa_pod_builder_double(b, card->ext_volume.vols_replies[p->index].values[i]);
|
||||||
|
spa_pod_builder_pop(b, &f[3]);
|
||||||
|
spa_pod_builder_pop(b, &f[2]);
|
||||||
|
|
||||||
|
spa_pod_builder_string(b, "ext-control-mute-reply");
|
||||||
|
spa_pod_builder_push_struct(b, &f[2]);
|
||||||
|
spa_pod_builder_string(b, p->name);
|
||||||
|
spa_pod_builder_int(b, card->ext_volume.mute_replies[p->index].res);
|
||||||
|
spa_pod_builder_bool(b, card->ext_volume.mute_replies[p->index].mute);
|
||||||
|
spa_pod_builder_pop(b, &f[2]);
|
||||||
|
|
||||||
|
spa_pod_builder_pop(b, &f[1]);
|
||||||
|
return spa_pod_builder_pop(b, &f[0]);
|
||||||
|
}
|
||||||
|
|
||||||
static struct acp_port *find_port_for_device(struct acp_card *card, struct acp_device *dev)
|
static struct acp_port *find_port_for_device(struct acp_card *card, struct acp_device *dev)
|
||||||
{
|
{
|
||||||
uint32_t i;
|
uint32_t i;
|
||||||
|
|
@ -601,6 +642,16 @@ static int impl_enum_params(void *object, int seq,
|
||||||
return -errno;
|
return -errno;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case SPA_PARAM_Props:
|
||||||
|
if (result.index >= card->n_ports)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
p = card->ports[result.index];
|
||||||
|
if (SPA_FLAG_IS_SET(p->flags, ACP_PORT_HIDDEN))
|
||||||
|
goto next;
|
||||||
|
param = build_props(&b.b, card, p);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return -ENOENT;
|
return -ENOENT;
|
||||||
}
|
}
|
||||||
|
|
@ -793,6 +844,165 @@ static bool check_active_profile_port(struct impl *this, uint32_t device, uint32
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int parse_ext_control_caps_reply (struct acp_card *card, struct spa_pod_struct *reply)
|
||||||
|
{
|
||||||
|
int changed = 0;
|
||||||
|
struct spa_pod *it;
|
||||||
|
bool res_parsed = false, caps_parsed = false;
|
||||||
|
int res = 0, caps = 0;
|
||||||
|
int field = 0;
|
||||||
|
|
||||||
|
SPA_POD_STRUCT_FOREACH(reply, it) {
|
||||||
|
switch (field++) {
|
||||||
|
case 0:
|
||||||
|
if (spa_pod_get_int(it, &res) >= 0)
|
||||||
|
res_parsed = true;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (spa_pod_get_int(it, &caps) >= 0)
|
||||||
|
caps_parsed = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res_parsed && caps_parsed) {
|
||||||
|
card->ext_volume.caps_reply.res = res;
|
||||||
|
card->ext_volume.caps_reply.caps = caps;
|
||||||
|
changed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_ext_control_vols_reply (struct acp_card *card, struct spa_pod_struct *reply)
|
||||||
|
{
|
||||||
|
int changed = 0;
|
||||||
|
struct spa_pod *it;
|
||||||
|
const char *route_name = NULL;
|
||||||
|
bool res_parsed = false, vols_parsed = false;
|
||||||
|
int res = 0;
|
||||||
|
uint32_t channels;
|
||||||
|
int64_t *vols;
|
||||||
|
uint32_t port_index = ACP_INVALID_INDEX;
|
||||||
|
int field = 0;
|
||||||
|
|
||||||
|
SPA_POD_STRUCT_FOREACH(reply, it) {
|
||||||
|
switch (field++) {
|
||||||
|
case 0:
|
||||||
|
spa_pod_get_string(it, &route_name);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (spa_pod_get_int(it, &res) >= 0)
|
||||||
|
res_parsed = true;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
vols_parsed = true;
|
||||||
|
channels = SPA_POD_ARRAY_N_VALUES(SPA_POD_BODY(it));
|
||||||
|
vols = SPA_POD_ARRAY_VALUES(SPA_POD_BODY(it));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
port_index = find_route_by_name(card, route_name);
|
||||||
|
|
||||||
|
if (port_index != ACP_INVALID_INDEX && res_parsed && vols_parsed) {
|
||||||
|
card->ext_volume.vols_replies[port_index].res = res;
|
||||||
|
card->ext_volume.vols_replies[port_index].channels = channels;
|
||||||
|
for (uint32_t i = 0; i < channels; i++)
|
||||||
|
card->ext_volume.vols_replies[port_index].values[i] = vols[i];
|
||||||
|
changed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_ext_control_mute_reply (struct acp_card *card, struct spa_pod_struct *reply)
|
||||||
|
{
|
||||||
|
int changed = 0;
|
||||||
|
struct spa_pod *it;
|
||||||
|
const char *route_name = NULL;
|
||||||
|
bool res_parsed = false, mute_parsed = false, mute = false;
|
||||||
|
int res = 0;
|
||||||
|
uint32_t port_index = ACP_INVALID_INDEX;
|
||||||
|
int field = 0;
|
||||||
|
|
||||||
|
SPA_POD_STRUCT_FOREACH(reply, it) {
|
||||||
|
switch (field++) {
|
||||||
|
case 0:
|
||||||
|
spa_pod_get_string(it, &route_name);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (spa_pod_get_int(it, &res) >= 0)
|
||||||
|
res_parsed = true;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (spa_pod_get_bool(it, &mute) >= 0)
|
||||||
|
mute_parsed = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
port_index = find_route_by_name(card, route_name);
|
||||||
|
|
||||||
|
if (port_index != ACP_INVALID_INDEX && res_parsed && mute_parsed) {
|
||||||
|
card->ext_volume.mute_replies[port_index].res = res;
|
||||||
|
card->ext_volume.mute_replies[port_index].mute = mute;
|
||||||
|
changed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_prop_params(struct impl *this, struct spa_pod *params)
|
||||||
|
{
|
||||||
|
struct acp_card *card = this->card;
|
||||||
|
struct spa_pod_parser prs;
|
||||||
|
struct spa_pod_frame f;
|
||||||
|
int changed = 0;
|
||||||
|
|
||||||
|
if (params == NULL)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
spa_pod_parser_pod(&prs, params);
|
||||||
|
if (spa_pod_parser_push_struct(&prs, &f) < 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
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, "ext-control-caps-reply") && spa_pod_is_struct(pod)) {
|
||||||
|
parse_ext_control_caps_reply(card, (struct spa_pod_struct *)pod);
|
||||||
|
} else if (spa_streq(name, "ext-control-vols-reply") && spa_pod_is_struct(pod)) {
|
||||||
|
parse_ext_control_vols_reply(card, (struct spa_pod_struct *)pod);
|
||||||
|
} else if (spa_streq(name, "ext-control-mute-reply") && spa_pod_is_struct(pod)) {
|
||||||
|
parse_ext_control_mute_reply(card, (struct spa_pod_struct *)pod);
|
||||||
|
} else
|
||||||
|
continue;
|
||||||
|
|
||||||
|
changed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed > 0) {
|
||||||
|
this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS;
|
||||||
|
this->params[IDX_Props].user++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
static int impl_set_param(void *object,
|
static int impl_set_param(void *object,
|
||||||
uint32_t id, uint32_t flags,
|
uint32_t id, uint32_t flags,
|
||||||
const struct spa_pod *param)
|
const struct spa_pod *param)
|
||||||
|
|
@ -879,18 +1089,99 @@ static int impl_set_param(void *object,
|
||||||
emit_info(this, false);
|
emit_info(this, false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case SPA_PARAM_Props:
|
||||||
|
{
|
||||||
|
struct spa_pod *params = NULL;
|
||||||
|
|
||||||
|
if (param == NULL)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if ((res = spa_pod_parse_object(param,
|
||||||
|
SPA_TYPE_OBJECT_Props, NULL,
|
||||||
|
SPA_PROP_params, SPA_POD_OPT_Pod(¶ms))) < 0) {
|
||||||
|
spa_log_warn(this->log, "can't parse props");
|
||||||
|
spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, param);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_prop_params(this, params);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return -ENOENT;
|
return -ENOENT;
|
||||||
}
|
}
|
||||||
return 0;
|
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.initialized)
|
||||||
|
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 = {
|
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)
|
||||||
|
|
@ -1083,6 +1374,12 @@ static void on_mute_changed(void *data, struct acp_device *dev)
|
||||||
spa_device_emit_event(&this->hooks, event);
|
spa_device_emit_event(&this->hooks, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void on_ext_vol_event_available(void *data, struct spa_event *event)
|
||||||
|
{
|
||||||
|
struct impl *this = data;
|
||||||
|
spa_device_emit_event(&this->hooks, event);
|
||||||
|
}
|
||||||
|
|
||||||
static const struct acp_card_events card_events = {
|
static const struct acp_card_events card_events = {
|
||||||
ACP_VERSION_CARD_EVENTS,
|
ACP_VERSION_CARD_EVENTS,
|
||||||
.props_changed = card_props_changed,
|
.props_changed = card_props_changed,
|
||||||
|
|
@ -1092,6 +1389,7 @@ static const struct acp_card_events card_events = {
|
||||||
.port_available = card_port_available,
|
.port_available = card_port_available,
|
||||||
.volume_changed = on_volume_changed,
|
.volume_changed = on_volume_changed,
|
||||||
.mute_changed = on_mute_changed,
|
.mute_changed = on_mute_changed,
|
||||||
|
.ext_vol_event_available = on_ext_vol_event_available,
|
||||||
};
|
};
|
||||||
|
|
||||||
static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface)
|
static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface)
|
||||||
|
|
|
||||||
|
|
@ -200,6 +200,36 @@ static int device_demarshal_set_param(void *object, const struct pw_protocol_nat
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int device_marshal_send_command(void *object,
|
||||||
|
const struct spa_command *command)
|
||||||
|
{
|
||||||
|
struct pw_resource *resource = object;
|
||||||
|
struct spa_pod_builder *b;
|
||||||
|
|
||||||
|
b = pw_protocol_native_begin_resource(resource, SPA_DEVICE_METHOD_SEND_COMMAND, NULL);
|
||||||
|
|
||||||
|
spa_pod_builder_add_struct(b,
|
||||||
|
SPA_POD_Pod(command));
|
||||||
|
|
||||||
|
return pw_protocol_native_end_resource(resource, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int device_demarshal_send_command(void *object, const struct pw_protocol_native_message *msg)
|
||||||
|
{
|
||||||
|
struct pw_proxy *proxy = object;
|
||||||
|
struct spa_pod_parser prs;
|
||||||
|
struct spa_command *command;
|
||||||
|
|
||||||
|
spa_pod_parser_init(&prs, msg->data, msg->size);
|
||||||
|
if (spa_pod_parser_get_struct(&prs,
|
||||||
|
SPA_POD_Pod(&command)) < 0)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
pw_proxy_notify(proxy, struct spa_device_methods, send_command, 1,
|
||||||
|
command);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void device_marshal_info(void *data,
|
static void device_marshal_info(void *data,
|
||||||
const struct spa_device_info *info)
|
const struct spa_device_info *info)
|
||||||
{
|
{
|
||||||
|
|
@ -482,7 +512,8 @@ static const struct spa_device_methods pw_protocol_native_device_method_marshal
|
||||||
.add_listener = &device_marshal_add_listener,
|
.add_listener = &device_marshal_add_listener,
|
||||||
.sync = &device_marshal_sync,
|
.sync = &device_marshal_sync,
|
||||||
.enum_params = &device_marshal_enum_params,
|
.enum_params = &device_marshal_enum_params,
|
||||||
.set_param = &device_marshal_set_param
|
.set_param = &device_marshal_set_param,
|
||||||
|
.send_command = &device_marshal_send_command,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct pw_protocol_native_demarshal
|
static const struct pw_protocol_native_demarshal
|
||||||
|
|
@ -492,6 +523,7 @@ pw_protocol_native_device_method_demarshal[SPA_DEVICE_METHOD_NUM] =
|
||||||
[SPA_DEVICE_METHOD_SYNC] = { &device_demarshal_sync, 0 },
|
[SPA_DEVICE_METHOD_SYNC] = { &device_demarshal_sync, 0 },
|
||||||
[SPA_DEVICE_METHOD_ENUM_PARAMS] = { &device_demarshal_enum_params, 0 },
|
[SPA_DEVICE_METHOD_ENUM_PARAMS] = { &device_demarshal_enum_params, 0 },
|
||||||
[SPA_DEVICE_METHOD_SET_PARAM] = { &device_demarshal_set_param, 0 },
|
[SPA_DEVICE_METHOD_SET_PARAM] = { &device_demarshal_set_param, 0 },
|
||||||
|
[SPA_DEVICE_METHOD_SEND_COMMAND] = { &device_demarshal_send_command, 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct spa_device_events pw_protocol_native_device_event_marshal = {
|
static const struct spa_device_events pw_protocol_native_device_event_marshal = {
|
||||||
|
|
|
||||||
|
|
@ -1056,6 +1056,34 @@ static int device_demarshal_set_param(void *object, const struct pw_protocol_nat
|
||||||
return pw_resource_notify(resource, struct pw_device_methods, set_param, 0, id, flags, param);
|
return pw_resource_notify(resource, struct pw_device_methods, set_param, 0, id, flags, param);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int device_marshal_send_command(void *object, const struct spa_command *command)
|
||||||
|
{
|
||||||
|
struct pw_proxy *proxy = object;
|
||||||
|
struct spa_pod_builder *b;
|
||||||
|
|
||||||
|
b = pw_protocol_native_begin_proxy(proxy, PW_NODE_METHOD_SEND_COMMAND, NULL);
|
||||||
|
spa_pod_builder_add_struct(b,
|
||||||
|
SPA_POD_Pod(command));
|
||||||
|
return pw_protocol_native_end_proxy(proxy, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int device_demarshal_send_command(void *object, const struct pw_protocol_native_message *msg)
|
||||||
|
{
|
||||||
|
struct pw_resource *resource = object;
|
||||||
|
struct spa_pod_parser prs;
|
||||||
|
struct spa_command *command;
|
||||||
|
|
||||||
|
spa_pod_parser_init(&prs, msg->data, msg->size);
|
||||||
|
if (spa_pod_parser_get_struct(&prs,
|
||||||
|
SPA_POD_Pod(&command)) < 0)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (command == NULL)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
return pw_resource_notify(resource, struct pw_device_methods, send_command, 1, command);
|
||||||
|
}
|
||||||
|
|
||||||
static int factory_method_marshal_add_listener(void *object,
|
static int factory_method_marshal_add_listener(void *object,
|
||||||
struct spa_hook *listener,
|
struct spa_hook *listener,
|
||||||
const struct pw_factory_events *events,
|
const struct pw_factory_events *events,
|
||||||
|
|
@ -2110,6 +2138,7 @@ static const struct pw_device_methods pw_protocol_native_device_method_marshal =
|
||||||
.subscribe_params = &device_marshal_subscribe_params,
|
.subscribe_params = &device_marshal_subscribe_params,
|
||||||
.enum_params = &device_marshal_enum_params,
|
.enum_params = &device_marshal_enum_params,
|
||||||
.set_param = &device_marshal_set_param,
|
.set_param = &device_marshal_set_param,
|
||||||
|
.send_command = &device_marshal_send_command,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct pw_protocol_native_demarshal
|
static const struct pw_protocol_native_demarshal
|
||||||
|
|
@ -2118,6 +2147,7 @@ pw_protocol_native_device_method_demarshal[PW_DEVICE_METHOD_NUM] = {
|
||||||
[PW_DEVICE_METHOD_SUBSCRIBE_PARAMS] = { &device_demarshal_subscribe_params, 0, },
|
[PW_DEVICE_METHOD_SUBSCRIBE_PARAMS] = { &device_demarshal_subscribe_params, 0, },
|
||||||
[PW_DEVICE_METHOD_ENUM_PARAMS] = { &device_demarshal_enum_params, 0, },
|
[PW_DEVICE_METHOD_ENUM_PARAMS] = { &device_demarshal_enum_params, 0, },
|
||||||
[PW_DEVICE_METHOD_SET_PARAM] = { &device_demarshal_set_param, PW_PERM_W, },
|
[PW_DEVICE_METHOD_SET_PARAM] = { &device_demarshal_set_param, PW_PERM_W, },
|
||||||
|
[PW_DEVICE_METHOD_SEND_COMMAND] = { &device_demarshal_send_command, PW_PERM_W, },
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct pw_device_events pw_protocol_native_device_event_marshal = {
|
static const struct pw_device_events pw_protocol_native_device_event_marshal = {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
#include <regex.h>
|
#include <regex.h>
|
||||||
#include <malloc.h>
|
#include <malloc.h>
|
||||||
|
|
||||||
|
#include <spa/monitor/device.h>
|
||||||
#include <spa/param/props.h>
|
#include <spa/param/props.h>
|
||||||
#include <spa/pod/builder.h>
|
#include <spa/pod/builder.h>
|
||||||
#include <spa/pod/pod.h>
|
#include <spa/pod/pod.h>
|
||||||
|
|
@ -23,8 +24,62 @@
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "manager.h"
|
#include "manager.h"
|
||||||
#include "module.h"
|
#include "module.h"
|
||||||
|
#include "pipewire/properties.h"
|
||||||
#include "message-handler.h"
|
#include "message-handler.h"
|
||||||
|
|
||||||
|
static int sink_object_message_handler(struct client *client, struct pw_manager_object *o, const char *message, const char *params, FILE *response)
|
||||||
|
{
|
||||||
|
struct device_info dev_info;
|
||||||
|
struct pw_manager_object *card = NULL;
|
||||||
|
char buf[1024];
|
||||||
|
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
|
||||||
|
struct spa_pod_frame f[1];
|
||||||
|
struct spa_command *command;
|
||||||
|
uint32_t card_id, command_id;
|
||||||
|
|
||||||
|
pw_log_debug(": sink %p object message:'%s' params:'%s'", o, message, params);
|
||||||
|
|
||||||
|
get_device_info(o, &dev_info, PW_DIRECTION_OUTPUT, false);
|
||||||
|
|
||||||
|
if (spa_streq(message, "volume-up"))
|
||||||
|
command_id = SPA_COMMAND_DEVICE_volumeUp;
|
||||||
|
else if (spa_streq(message, "volume-down"))
|
||||||
|
command_id = SPA_COMMAND_DEVICE_volumeDown;
|
||||||
|
else if (spa_streq(message, "mute-toggle"))
|
||||||
|
command_id = SPA_COMMAND_DEVICE_muteToggle;
|
||||||
|
else {
|
||||||
|
fprintf(response, "Unknown message %s (must be volume-up, volume-down or mute-toggle)", message);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
card_id = pw_properties_get_uint32(o->props, PW_KEY_DEVICE_ID, SPA_ID_INVALID);
|
||||||
|
|
||||||
|
spa_list_for_each(o, &client->manager->object_list, link) {
|
||||||
|
if (o->id == card_id) {
|
||||||
|
card = o;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!card) {
|
||||||
|
pw_log_error("Could not find card");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
spa_pod_builder_push_object(&b, &f[0],
|
||||||
|
SPA_TYPE_COMMAND_Device, SPA_DEVICE_COMMAND_VolumeControl);
|
||||||
|
spa_pod_builder_add(&b,
|
||||||
|
command_id, SPA_POD_Id(dev_info.active_port), 0);
|
||||||
|
|
||||||
|
command = (struct spa_command *)spa_pod_builder_pop(&b, &f[0]);
|
||||||
|
|
||||||
|
pw_device_send_command((struct pw_device *)card->proxy, command);
|
||||||
|
|
||||||
|
fprintf(response, "true");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int bluez_card_object_message_handler(struct client *client, struct pw_manager_object *o, const char *message, const char *params, FILE *response)
|
static int bluez_card_object_message_handler(struct client *client, struct pw_manager_object *o, const char *message, const char *params, FILE *response)
|
||||||
{
|
{
|
||||||
struct transport_codec_info codecs[64];
|
struct transport_codec_info codecs[64];
|
||||||
|
|
@ -289,4 +344,14 @@ void register_object_message_handlers(struct pw_manager_object *o)
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pw_manager_object_is_sink(o)) {
|
||||||
|
str = pw_properties_get(o->props, PW_KEY_NODE_NAME);
|
||||||
|
if (str) {
|
||||||
|
free(o->message_object_path);
|
||||||
|
o->message_object_path = spa_aprintf("/sink/%s/volume-control", str);
|
||||||
|
o->message_handler = sink_object_message_handler;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3740,7 +3740,7 @@ static int fill_card_info(struct client *client, struct message *m,
|
||||||
}
|
}
|
||||||
|
|
||||||
static int fill_sink_info_proplist(struct message *m, const struct spa_dict *sink_props,
|
static int fill_sink_info_proplist(struct message *m, const struct spa_dict *sink_props,
|
||||||
const struct pw_manager_object *card)
|
const struct device_info *dev_info, const struct pw_manager_object *card)
|
||||||
{
|
{
|
||||||
struct pw_device_info *card_info = card ? card->info : NULL;
|
struct pw_device_info *card_info = card ? card->info : NULL;
|
||||||
spa_autoptr(pw_properties) props = NULL;
|
spa_autoptr(pw_properties) props = NULL;
|
||||||
|
|
@ -3754,6 +3754,26 @@ static int fill_sink_info_proplist(struct message *m, const struct spa_dict *sin
|
||||||
sink_props = &props->dict;
|
sink_props = &props->dict;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dev_info->volume_info.flags & VOLUME_CONTROL_MASK) {
|
||||||
|
props = pw_properties_new_dict(sink_props);
|
||||||
|
if (props == NULL)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
#define SET_PROP(k, f) pw_properties_set(props, k, dev_info->volume_info.flags & (f) ? "true" : "false")
|
||||||
|
|
||||||
|
SET_PROP("device.volume.read-volume", VOLUME_READ);
|
||||||
|
SET_PROP("device.volume.write-volume-value", VOLUME_WRITE);
|
||||||
|
SET_PROP("device.volume.write-volume-updown", VOLUME_UPDOWN);
|
||||||
|
SET_PROP("device.volume.read-mute", VOLUME_READ_MUTE);
|
||||||
|
SET_PROP("device.volume.write-mute-value", VOLUME_WRITE_MUTE);
|
||||||
|
SET_PROP("device.volume.write-mute-toggle", VOLUME_TOGGLE_MUTE);
|
||||||
|
SET_PROP("device.volume.read-balance", VOLUME_READ_BALANCE);
|
||||||
|
SET_PROP("device.volume.write-balance", VOLUME_WRITE_BALANCE);
|
||||||
|
|
||||||
|
pw_properties_add(props, card_info->props);
|
||||||
|
sink_props = &props->dict;
|
||||||
|
}
|
||||||
|
|
||||||
message_put(m, TAG_PROPLIST, sink_props, TAG_INVALID);
|
message_put(m, TAG_PROPLIST, sink_props, TAG_INVALID);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -3852,7 +3872,7 @@ static int fill_sink_info(struct client *client, struct message *m,
|
||||||
|
|
||||||
if (client->version >= 13) {
|
if (client->version >= 13) {
|
||||||
int res;
|
int res;
|
||||||
if ((res = fill_sink_info_proplist(m, info->props, card)) < 0)
|
if ((res = fill_sink_info_proplist(m, info->props, &dev_info, card)) < 0)
|
||||||
return res;
|
return res;
|
||||||
message_put(m,
|
message_put(m,
|
||||||
TAG_USEC, 0LL, /* requested latency */
|
TAG_USEC, 0LL, /* requested latency */
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include <spa/param/props.h>
|
#include <spa/param/props.h>
|
||||||
#include <spa/param/audio/raw.h>
|
#include <spa/param/audio/raw.h>
|
||||||
|
#include <spa/param/audio/volume.h>
|
||||||
#include <spa/pod/iter.h>
|
#include <spa/pod/iter.h>
|
||||||
#include <spa/utils/defs.h>
|
#include <spa/utils/defs.h>
|
||||||
#include <pipewire/log.h>
|
#include <pipewire/log.h>
|
||||||
|
|
@ -89,6 +90,29 @@ int volume_parse_param(const struct spa_pod *param, struct volume_info *info, bo
|
||||||
info->map.channels = spa_pod_copy_array(&prop->value, SPA_TYPE_Id,
|
info->map.channels = spa_pod_copy_array(&prop->value, SPA_TYPE_Id,
|
||||||
info->map.map, SPA_N_ELEMENTS(info->map.map));
|
info->map.map, SPA_N_ELEMENTS(info->map.map));
|
||||||
break;
|
break;
|
||||||
|
case SPA_PROP_volumeControlFlags:
|
||||||
|
{
|
||||||
|
uint32_t flags;
|
||||||
|
if (spa_pod_get_id(&prop->value, &flags) >= 0) {
|
||||||
|
SPA_FLAG_UPDATE(info->flags, VOLUME_READ,
|
||||||
|
flags & SPA_AUDIO_VOLUME_CONTROL_READ_VOLUME);
|
||||||
|
SPA_FLAG_UPDATE(info->flags, VOLUME_WRITE,
|
||||||
|
flags & SPA_AUDIO_VOLUME_CONTROL_WRITE_VOLUME_VALUE);
|
||||||
|
SPA_FLAG_UPDATE(info->flags, VOLUME_UPDOWN,
|
||||||
|
flags & SPA_AUDIO_VOLUME_CONTROL_WRITE_VOLUME_UPDOWN);
|
||||||
|
SPA_FLAG_UPDATE(info->flags, VOLUME_READ_MUTE,
|
||||||
|
flags & SPA_AUDIO_VOLUME_CONTROL_READ_MUTE);
|
||||||
|
SPA_FLAG_UPDATE(info->flags, VOLUME_WRITE_MUTE,
|
||||||
|
flags & SPA_AUDIO_VOLUME_CONTROL_WRITE_MUTE_VALUE);
|
||||||
|
SPA_FLAG_UPDATE(info->flags, VOLUME_TOGGLE_MUTE,
|
||||||
|
flags & SPA_AUDIO_VOLUME_CONTROL_WRITE_MUTE_TOGGLE);
|
||||||
|
SPA_FLAG_UPDATE(info->flags, VOLUME_READ_BALANCE,
|
||||||
|
flags & SPA_AUDIO_VOLUME_CONTROL_READ_BALANCE);
|
||||||
|
SPA_FLAG_UPDATE(info->flags, VOLUME_WRITE_BALANCE,
|
||||||
|
flags & SPA_AUDIO_VOLUME_CONTROL_WRITE_BALANCE);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,15 @@ struct volume_info {
|
||||||
uint32_t steps;
|
uint32_t steps;
|
||||||
#define VOLUME_HW_VOLUME (1<<0)
|
#define VOLUME_HW_VOLUME (1<<0)
|
||||||
#define VOLUME_HW_MUTE (1<<1)
|
#define VOLUME_HW_MUTE (1<<1)
|
||||||
|
#define VOLUME_READ (1<<2)
|
||||||
|
#define VOLUME_WRITE (1<<3)
|
||||||
|
#define VOLUME_UPDOWN (1<<4)
|
||||||
|
#define VOLUME_READ_MUTE (1<<5)
|
||||||
|
#define VOLUME_WRITE_MUTE (1<<6)
|
||||||
|
#define VOLUME_TOGGLE_MUTE (1<<7)
|
||||||
|
#define VOLUME_READ_BALANCE (1<<8)
|
||||||
|
#define VOLUME_WRITE_BALANCE (1<<9)
|
||||||
|
#define VOLUME_CONTROL_MASK (0x01FC)
|
||||||
uint32_t flags;
|
uint32_t flags;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#ifndef PIPEWIRE_DEVICE_H
|
#ifndef PIPEWIRE_DEVICE_H
|
||||||
#define PIPEWIRE_DEVICE_H
|
#define PIPEWIRE_DEVICE_H
|
||||||
|
|
||||||
|
#include <spa/pod/command.h>
|
||||||
#include <spa/utils/defs.h>
|
#include <spa/utils/defs.h>
|
||||||
#include <spa/utils/hook.h>
|
#include <spa/utils/hook.h>
|
||||||
|
|
||||||
|
|
@ -92,11 +93,12 @@ struct pw_device_events {
|
||||||
#define PW_DEVICE_METHOD_SUBSCRIBE_PARAMS 1
|
#define PW_DEVICE_METHOD_SUBSCRIBE_PARAMS 1
|
||||||
#define PW_DEVICE_METHOD_ENUM_PARAMS 2
|
#define PW_DEVICE_METHOD_ENUM_PARAMS 2
|
||||||
#define PW_DEVICE_METHOD_SET_PARAM 3
|
#define PW_DEVICE_METHOD_SET_PARAM 3
|
||||||
#define PW_DEVICE_METHOD_NUM 4
|
#define PW_DEVICE_METHOD_SEND_COMMAND 4
|
||||||
|
#define PW_DEVICE_METHOD_NUM 5
|
||||||
|
|
||||||
/** Device methods */
|
/** Device methods */
|
||||||
struct pw_device_methods {
|
struct pw_device_methods {
|
||||||
#define PW_VERSION_DEVICE_METHODS 0
|
#define PW_VERSION_DEVICE_METHODS 1
|
||||||
uint32_t version;
|
uint32_t version;
|
||||||
|
|
||||||
int (*add_listener) (void *object,
|
int (*add_listener) (void *object,
|
||||||
|
|
@ -143,6 +145,15 @@ struct pw_device_methods {
|
||||||
*/
|
*/
|
||||||
int (*set_param) (void *object, uint32_t id, uint32_t flags,
|
int (*set_param) (void *object, uint32_t id, uint32_t flags,
|
||||||
const struct spa_pod *param);
|
const struct spa_pod *param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a command to the device
|
||||||
|
*
|
||||||
|
* \param command the command to send
|
||||||
|
*
|
||||||
|
* This requires X and W permissions on the device.
|
||||||
|
*/
|
||||||
|
int (*send_command) (void *object, const struct spa_command *command);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** \copydoc pw_device_methods.add_listener
|
/** \copydoc pw_device_methods.add_listener
|
||||||
|
|
@ -183,6 +194,13 @@ PW_API_DEVICE_IMPL int pw_device_set_param(struct pw_device *object, uint32_t id
|
||||||
pw_device, (struct spa_interface*)object, set_param, 0,
|
pw_device, (struct spa_interface*)object, set_param, 0,
|
||||||
id, flags, param);
|
id, flags, param);
|
||||||
}
|
}
|
||||||
|
/** \copydoc pw_device_methods.send_command
|
||||||
|
* \sa pw_device_methods.send_command */
|
||||||
|
PW_API_DEVICE_IMPL int pw_device_send_command(struct pw_device *object, const struct spa_command *command)
|
||||||
|
{
|
||||||
|
return spa_api_method_r(int, -ENOTSUP,
|
||||||
|
pw_device, (struct spa_interface*)object, send_command, 0, command);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \}
|
* \}
|
||||||
|
|
|
||||||
|
|
@ -533,11 +533,30 @@ static int device_set_param(void *object, uint32_t id, uint32_t flags,
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int device_send_command(void *object, const struct spa_command *command)
|
||||||
|
{
|
||||||
|
struct resource_data *data = object;
|
||||||
|
struct pw_impl_device *device = data->device;
|
||||||
|
uint32_t id = SPA_DEVICE_COMMAND_ID(command);
|
||||||
|
int res;
|
||||||
|
|
||||||
|
pw_log_debug("%p: got command %d (%s)", device, id,
|
||||||
|
spa_debug_type_find_name(spa_type_device_command_id, id));
|
||||||
|
|
||||||
|
switch (id) {
|
||||||
|
default:
|
||||||
|
res = spa_device_send_command(device->device, command);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
static const struct pw_device_methods device_methods = {
|
static const struct pw_device_methods device_methods = {
|
||||||
PW_VERSION_DEVICE_METHODS,
|
PW_VERSION_DEVICE_METHODS,
|
||||||
.subscribe_params = device_subscribe_params,
|
.subscribe_params = device_subscribe_params,
|
||||||
.enum_params = device_enum_params,
|
.enum_params = device_enum_params,
|
||||||
.set_param = device_set_param
|
.set_param = device_set_param,
|
||||||
|
.send_command = device_send_command,
|
||||||
};
|
};
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,7 @@ static void test_device_abi(void)
|
||||||
const struct spa_pod *filter);
|
const struct spa_pod *filter);
|
||||||
int (*set_param) (void *object, uint32_t id, uint32_t flags,
|
int (*set_param) (void *object, uint32_t id, uint32_t flags,
|
||||||
const struct spa_pod *param);
|
const struct spa_pod *param);
|
||||||
|
int (*send_command) (void *object, const struct spa_command *command);
|
||||||
} methods = { PW_VERSION_DEVICE_METHODS, };
|
} methods = { PW_VERSION_DEVICE_METHODS, };
|
||||||
struct {
|
struct {
|
||||||
uint32_t version;
|
uint32_t version;
|
||||||
|
|
@ -167,7 +168,8 @@ static void test_device_abi(void)
|
||||||
TEST_FUNC(m, methods, subscribe_params);
|
TEST_FUNC(m, methods, subscribe_params);
|
||||||
TEST_FUNC(m, methods, enum_params);
|
TEST_FUNC(m, methods, enum_params);
|
||||||
TEST_FUNC(m, methods, set_param);
|
TEST_FUNC(m, methods, set_param);
|
||||||
spa_assert_se(PW_VERSION_DEVICE_METHODS == 0);
|
TEST_FUNC(m, methods, send_command);
|
||||||
|
spa_assert_se(PW_VERSION_DEVICE_METHODS == 1);
|
||||||
spa_assert_se(sizeof(m) == sizeof(methods));
|
spa_assert_se(sizeof(m) == sizeof(methods));
|
||||||
|
|
||||||
TEST_FUNC(e, events, version);
|
TEST_FUNC(e, events, version);
|
||||||
|
|
|
||||||
|
|
@ -2059,6 +2059,8 @@ static bool do_send_command(struct data *data, const char *cmd, char *args, char
|
||||||
|
|
||||||
if (spa_streq(global->type, PW_TYPE_INTERFACE_Node)) {
|
if (spa_streq(global->type, PW_TYPE_INTERFACE_Node)) {
|
||||||
ti = spa_debug_type_find_short(spa_type_node_command_id, a[1]);
|
ti = spa_debug_type_find_short(spa_type_node_command_id, a[1]);
|
||||||
|
} else if (spa_streq(global->type, PW_TYPE_INTERFACE_Device)) {
|
||||||
|
ti = spa_debug_type_find_short(spa_type_device_command_id, a[1]);
|
||||||
} else {
|
} else {
|
||||||
*error = spa_aprintf("send-command not implemented on object %d type:%s",
|
*error = spa_aprintf("send-command not implemented on object %d type:%s",
|
||||||
atoi(a[0]), global->type);
|
atoi(a[0]), global->type);
|
||||||
|
|
@ -2066,7 +2068,7 @@ static bool do_send_command(struct data *data, const char *cmd, char *args, char
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ti == NULL) {
|
if (ti == NULL) {
|
||||||
*error = spa_aprintf("%s: unknown node command type: %s", cmd, a[1]);
|
*error = spa_aprintf("%s: unknown command type: %s", cmd, a[1]);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ((res = spa_json_to_pod(&b.b, 0, ti, a[2], strlen(a[2]))) < 0) {
|
if ((res = spa_json_to_pod(&b.b, 0, ti, a[2], strlen(a[2]))) < 0) {
|
||||||
|
|
@ -2079,7 +2081,12 @@ static bool do_send_command(struct data *data, const char *cmd, char *args, char
|
||||||
}
|
}
|
||||||
spa_debug_pod(0, NULL, pod);
|
spa_debug_pod(0, NULL, pod);
|
||||||
|
|
||||||
|
if (spa_streq(global->type, PW_TYPE_INTERFACE_Node)) {
|
||||||
pw_node_send_command((struct pw_node*)global->proxy, (struct spa_command*)pod);
|
pw_node_send_command((struct pw_node*)global->proxy, (struct spa_command*)pod);
|
||||||
|
} else if (spa_streq(global->type, PW_TYPE_INTERFACE_Device)) {
|
||||||
|
pw_device_send_command((struct pw_device*)global->proxy, (struct spa_command*)pod);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue