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);
if ((str = pa_proplist_gets(impl->proplist, name)))
spa_atof(str, &dp->volume_range[0]);
spa_atof(str, &dp->hard_volume_limit[0]);
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);
if ((str = pa_proplist_gets(impl->proplist, name)))
spa_atof(str, &dp->volume_range[1]);
spa_atof(str, &dp->hard_volume_limit[1]);
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_as_dict(dp->proplist, &dp->port.props);
pa_dynarray_append(&impl->out.ports, dp);
}
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;
snprintf(name, sizeof(name), "api.acp.device.%s.min-volume", dev->device.name);
if ((str = pa_proplist_gets(impl->proplist, name)))
spa_atof(str, &vol_range[0]);
spa_atof(str, &vol_limit[0]);
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);
if ((str = pa_proplist_gets(impl->proplist, name)))
spa_atof(str, &vol_range[1]);
spa_atof(str, &vol_limit[1]);
else
vol_range[1] = impl->volume_range[1];
vol_limit[1] = impl->hard_volume_limit[1];
PA_HASHMAP_FOREACH(dp, dev->ports, state) {
if (n_ports == 0) {
vol_range[0] = dp->volume_range[0];
vol_range[1] = dp->volume_range[1];
vol_limit[0] = dp->hard_volume_limit[0];
vol_limit[1] = dp->hard_volume_limit[1];
} else {
vol_range[0] = fminf(vol_range[0], dp->volume_range[0]);
vol_range[1] = fmaxf(vol_range[1], dp->volume_range[1]);
vol_limit[0] = fminf(vol_limit[0], dp->hard_volume_limit[0]);
vol_limit[1] = fmaxf(vol_limit[1], dp->hard_volume_limit[1]);
}
pa_dynarray_append(&dev->port_array, dp);
pa_dynarray_append(&dp->devices, dev);
n_ports++;
}
if (n_ports == 0) {
pa_proplist_setf(dev->proplist, "channelmix.min-volume", "%f", vol_range[0]);
pa_proplist_setf(dev->proplist, "channelmix.max-volume", "%f", vol_range[1]);
pa_proplist_setf(dev->proplist, "channelmix.min-volume", "%f", vol_limit[0]);
pa_proplist_setf(dev->proplist, "channelmix.max-volume", "%f", vol_limit[1]);
}
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->rate = DEFAULT_RATE;
impl->pro_channels = DEFAULT_CHANNELS;
impl->volume_range[0] = 0.0f;
impl->volume_range[1] = FLT_MAX;
impl->hard_volume_limit[0] = 0.0f;
impl->hard_volume_limit[1] = FLT_MAX;
if (props) {
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)
impl->use_eld_channels = spa_atob(s);
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)
spa_atof(s, &impl->volume_range[1]);
spa_atof(s, &impl->hard_volume_limit[1]);
}
#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_card *impl = d->card;
pa_device_port *p;
float *volume_range;
if ((p = d->active_port) != NULL)
volume_range = p->volume_range;
else
volume_range = impl->volume_range;
float *soft_volume_limit, *hard_volume_limit;
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)
*min = volume_range[0];
*min = fmaxf(soft_volume_limit[0], hard_volume_limit[0]);
if (max)
*max = volume_range[1];
*max = fminf(soft_volume_limit[1], hard_volume_limit[1]);
return 0;
}
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_card *impl = d->card;
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;
volume_range[0] = min;
volume_range[1] = max;
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;
}
soft_volume_limit[0] = fmaxf(min, hard_volume_limit[0]);
soft_volume_limit[1] = fminf(max, hard_volume_limit[1]);
return 0;
}
@ -2381,7 +2388,7 @@ int acp_device_set_volume(struct acp_device *dev, const float *volume, uint32_t
uint32_t i;
pa_cvolume v, old_volume;
pa_device_port *p;
float *volume_range;
float *volume_limit;
if (n_volume == 0)
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;
if ((p = d->active_port) != NULL)
volume_range = p->volume_range;
volume_limit = p->soft_volume_limit;
else
volume_range = impl->volume_range;
volume_limit = impl->hard_volume_limit;
v.channels = d->mapping->channel_map.channels;
for (i = 0; i < v.channels; i++)
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",
d->set_volume ? "hardware" : "software",

View file

@ -52,7 +52,7 @@ struct pa_card {
bool use_eld_channels;
uint32_t rate;
uint32_t pro_channels;
float volume_range[2];
float hard_volume_limit[2];
pa_alsa_ucm_config ucm;
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 ?
ACP_DIRECTION_PLAYBACK : ACP_DIRECTION_CAPTURE;
p->type = data->type;
p->volume_range[0] = 0.0f;
p->volume_range[1] = FLT_MAX;
p->hard_volume_limit[0] = 0.0f;
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();
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;
int64_t latency_offset;
float volume_range[2];
float hard_volume_limit[2];
float soft_volume_limit[2];
pa_proplist *proplist;
pa_hashmap *profiles;