acp: make hard and soft volume limit

Make a hard volume limit property that is configured with the config and
can never be changed.

Make a soft volume limit property that can be changed at runtime.

The used volume limit is a combination of the soft and hard limit.
This commit is contained in:
Wim Taymans 2026-06-08 17:00:40 +02:00
parent 1a534cd907
commit 64d3ed4710
4 changed files with 53 additions and 43 deletions

View file

@ -644,51 +644,53 @@ static void add_profiles(pa_card *impl)
snprintf(name, sizeof(name), "api.acp.port.%s.min-volume", dp->name); snprintf(name, sizeof(name), "api.acp.port.%s.min-volume", dp->name);
if ((str = pa_proplist_gets(impl->proplist, name))) if ((str = pa_proplist_gets(impl->proplist, name)))
spa_atof(str, &dp->volume_range[0]); spa_atof(str, &dp->hard_volume_limit[0]);
else else
dp->volume_range[0] = impl->volume_range[0]; dp->hard_volume_limit[0] = impl->hard_volume_limit[0];
snprintf(name, sizeof(name), "api.acp.port.%s.max-volume", dp->name); snprintf(name, sizeof(name), "api.acp.port.%s.max-volume", dp->name);
if ((str = pa_proplist_gets(impl->proplist, name))) if ((str = pa_proplist_gets(impl->proplist, name)))
spa_atof(str, &dp->volume_range[1]); spa_atof(str, &dp->hard_volume_limit[1]);
else else
dp->volume_range[1] = impl->volume_range[1]; dp->hard_volume_limit[1] = impl->hard_volume_limit[1];
dp->soft_volume_limit[0] = dp->hard_volume_limit[0];
dp->soft_volume_limit[1] = dp->hard_volume_limit[1];
pa_proplist_setf(dp->proplist, "card.profile.port", "%u", dp->port.index); pa_proplist_setf(dp->proplist, "card.profile.port", "%u", dp->port.index);
pa_proplist_as_dict(dp->proplist, &dp->port.props); pa_proplist_as_dict(dp->proplist, &dp->port.props);
pa_dynarray_append(&impl->out.ports, dp); pa_dynarray_append(&impl->out.ports, dp);
} }
PA_DYNARRAY_FOREACH(dev, &impl->out.devices, idx) { PA_DYNARRAY_FOREACH(dev, &impl->out.devices, idx) {
float vol_range[2] = { 0.0f, FLT_MAX }; float vol_limit[2] = { 0.0f, FLT_MAX };
n_ports = 0; n_ports = 0;
snprintf(name, sizeof(name), "api.acp.device.%s.min-volume", dev->device.name); snprintf(name, sizeof(name), "api.acp.device.%s.min-volume", dev->device.name);
if ((str = pa_proplist_gets(impl->proplist, name))) if ((str = pa_proplist_gets(impl->proplist, name)))
spa_atof(str, &vol_range[0]); spa_atof(str, &vol_limit[0]);
else else
vol_range[0] = impl->volume_range[0]; vol_limit[0] = impl->hard_volume_limit[0];
snprintf(name, sizeof(name), "api.acp.device.%s.max-volume", dev->device.name); snprintf(name, sizeof(name), "api.acp.device.%s.max-volume", dev->device.name);
if ((str = pa_proplist_gets(impl->proplist, name))) if ((str = pa_proplist_gets(impl->proplist, name)))
spa_atof(str, &vol_range[1]); spa_atof(str, &vol_limit[1]);
else else
vol_range[1] = impl->volume_range[1]; vol_limit[1] = impl->hard_volume_limit[1];
PA_HASHMAP_FOREACH(dp, dev->ports, state) { PA_HASHMAP_FOREACH(dp, dev->ports, state) {
if (n_ports == 0) { if (n_ports == 0) {
vol_range[0] = dp->volume_range[0]; vol_limit[0] = dp->hard_volume_limit[0];
vol_range[1] = dp->volume_range[1]; vol_limit[1] = dp->hard_volume_limit[1];
} else { } else {
vol_range[0] = fminf(vol_range[0], dp->volume_range[0]); vol_limit[0] = fminf(vol_limit[0], dp->hard_volume_limit[0]);
vol_range[1] = fmaxf(vol_range[1], dp->volume_range[1]); vol_limit[1] = fmaxf(vol_limit[1], dp->hard_volume_limit[1]);
} }
pa_dynarray_append(&dev->port_array, dp); pa_dynarray_append(&dev->port_array, dp);
pa_dynarray_append(&dp->devices, dev); pa_dynarray_append(&dp->devices, dev);
n_ports++; n_ports++;
} }
if (n_ports == 0) { if (n_ports == 0) {
pa_proplist_setf(dev->proplist, "channelmix.min-volume", "%f", vol_range[0]); pa_proplist_setf(dev->proplist, "channelmix.min-volume", "%f", vol_limit[0]);
pa_proplist_setf(dev->proplist, "channelmix.max-volume", "%f", vol_range[1]); pa_proplist_setf(dev->proplist, "channelmix.max-volume", "%f", vol_limit[1]);
} }
pa_proplist_as_dict(dev->proplist, &dev->device.props); pa_proplist_as_dict(dev->proplist, &dev->device.props);
@ -1990,8 +1992,8 @@ struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props)
impl->ignore_dB = false; impl->ignore_dB = false;
impl->rate = DEFAULT_RATE; impl->rate = DEFAULT_RATE;
impl->pro_channels = DEFAULT_CHANNELS; impl->pro_channels = DEFAULT_CHANNELS;
impl->volume_range[0] = 0.0f; impl->hard_volume_limit[0] = 0.0f;
impl->volume_range[1] = FLT_MAX; impl->hard_volume_limit[1] = FLT_MAX;
if (props) { if (props) {
if ((s = acp_dict_lookup(props, "api.alsa.use-ucm")) != NULL) if ((s = acp_dict_lookup(props, "api.alsa.use-ucm")) != NULL)
@ -2021,9 +2023,9 @@ struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props)
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.acp.min-volume")) != NULL) if ((s = acp_dict_lookup(props, "api.acp.min-volume")) != NULL)
spa_atof(s, &impl->volume_range[0]); spa_atof(s, &impl->hard_volume_limit[0]);
if ((s = acp_dict_lookup(props, "api.acp.max-volume")) != NULL) if ((s = acp_dict_lookup(props, "api.acp.max-volume")) != NULL)
spa_atof(s, &impl->volume_range[1]); spa_atof(s, &impl->hard_volume_limit[1]);
} }
#if SND_LIB_VERSION < 0x10207 #if SND_LIB_VERSION < 0x10207
@ -2344,17 +2346,19 @@ int acp_device_get_volume_limit(struct acp_device *dev, float *min, float *max)
pa_alsa_device *d = (pa_alsa_device*)dev; pa_alsa_device *d = (pa_alsa_device*)dev;
pa_card *impl = d->card; pa_card *impl = d->card;
pa_device_port *p; pa_device_port *p;
float *volume_range; float *soft_volume_limit, *hard_volume_limit;
if ((p = d->active_port) != NULL)
volume_range = p->volume_range;
else
volume_range = impl->volume_range;
if ((p = d->active_port) != NULL) {
soft_volume_limit = p->soft_volume_limit;
hard_volume_limit = p->hard_volume_limit;
} else {
soft_volume_limit = impl->hard_volume_limit;
hard_volume_limit = impl->hard_volume_limit;
}
if (min) if (min)
*min = volume_range[0]; *min = fmaxf(soft_volume_limit[0], hard_volume_limit[0]);
if (max) if (max)
*max = volume_range[1]; *max = fminf(soft_volume_limit[1], hard_volume_limit[1]);
return 0; return 0;
} }
int acp_device_set_volume_limit(struct acp_device *dev, float min, float max) int acp_device_set_volume_limit(struct acp_device *dev, float min, float max)
@ -2362,15 +2366,18 @@ int acp_device_set_volume_limit(struct acp_device *dev, float min, float max)
pa_alsa_device *d = (pa_alsa_device*)dev; pa_alsa_device *d = (pa_alsa_device*)dev;
pa_card *impl = d->card; pa_card *impl = d->card;
pa_device_port *p; pa_device_port *p;
float *volume_range; float *soft_volume_limit, *hard_volume_limit;
if ((p = d->active_port) != NULL) if ((p = d->active_port) != NULL) {
volume_range = p->volume_range; soft_volume_limit = p->soft_volume_limit;
else hard_volume_limit = p->hard_volume_limit;
volume_range = impl->volume_range; }
else {
volume_range[0] = min; soft_volume_limit = impl->hard_volume_limit;
volume_range[1] = max; hard_volume_limit = impl->hard_volume_limit;
}
soft_volume_limit[0] = fmaxf(min, hard_volume_limit[0]);
soft_volume_limit[1] = fminf(max, hard_volume_limit[1]);
return 0; return 0;
} }
@ -2381,7 +2388,7 @@ int acp_device_set_volume(struct acp_device *dev, const float *volume, uint32_t
uint32_t i; uint32_t i;
pa_cvolume v, old_volume; pa_cvolume v, old_volume;
pa_device_port *p; pa_device_port *p;
float *volume_range; float *volume_limit;
if (n_volume == 0) if (n_volume == 0)
return -EINVAL; return -EINVAL;
@ -2389,14 +2396,14 @@ int acp_device_set_volume(struct acp_device *dev, const float *volume, uint32_t
old_volume = d->real_volume; old_volume = d->real_volume;
if ((p = d->active_port) != NULL) if ((p = d->active_port) != NULL)
volume_range = p->volume_range; volume_limit = p->soft_volume_limit;
else else
volume_range = impl->volume_range; volume_limit = impl->hard_volume_limit;
v.channels = d->mapping->channel_map.channels; v.channels = d->mapping->channel_map.channels;
for (i = 0; i < v.channels; i++) for (i = 0; i < v.channels; i++)
v.values[i] = pa_sw_volume_from_linear( v.values[i] = pa_sw_volume_from_linear(
SPA_CLAMPF(volume[i % n_volume], volume_range[0], volume_range[1])); SPA_CLAMPF(volume[i % n_volume], volume_limit[0], volume_limit[1]));
pa_log_info("Set %s volume: min:%d max:%d", pa_log_info("Set %s volume: min:%d max:%d",
d->set_volume ? "hardware" : "software", d->set_volume ? "hardware" : "software",

View file

@ -52,7 +52,7 @@ struct pa_card {
bool use_eld_channels; bool use_eld_channels;
uint32_t rate; uint32_t rate;
uint32_t pro_channels; uint32_t pro_channels;
float volume_range[2]; float hard_volume_limit[2];
pa_alsa_ucm_config ucm; pa_alsa_ucm_config ucm;
pa_alsa_profile_set *profile_set; pa_alsa_profile_set *profile_set;

View file

@ -142,8 +142,10 @@ pa_device_port *pa_device_port_new(pa_core *c, pa_device_port_new_data *data, si
p->port.direction = data->direction == PA_DIRECTION_OUTPUT ? p->port.direction = data->direction == PA_DIRECTION_OUTPUT ?
ACP_DIRECTION_PLAYBACK : ACP_DIRECTION_CAPTURE; ACP_DIRECTION_PLAYBACK : ACP_DIRECTION_CAPTURE;
p->type = data->type; p->type = data->type;
p->volume_range[0] = 0.0f; p->hard_volume_limit[0] = 0.0f;
p->volume_range[1] = FLT_MAX; p->hard_volume_limit[1] = FLT_MAX;
p->soft_volume_limit[0] = p->hard_volume_limit[0];
p->soft_volume_limit[1] = p->hard_volume_limit[1];
p->proplist = pa_proplist_new(); p->proplist = pa_proplist_new();
pa_proplist_sets(p->proplist, ACP_KEY_PORT_TYPE, str_port_type(data->type)); pa_proplist_sets(p->proplist, ACP_KEY_PORT_TYPE, str_port_type(data->type));

View file

@ -76,7 +76,8 @@ struct pa_device_port {
pa_direction_t direction; pa_direction_t direction;
int64_t latency_offset; int64_t latency_offset;
float volume_range[2]; float hard_volume_limit[2];
float soft_volume_limit[2];
pa_proplist *proplist; pa_proplist *proplist;
pa_hashmap *profiles; pa_hashmap *profiles;