/* External Volume Control */ /* SPDX-FileCopyrightText: Copyright © 2026 Arun Raghavan */ /* SPDX-FileCopyrightText: Copyright © 2026 Julian Bouzas */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #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; }