From d80d593271c3957f495ce19a7bb3fd62ddf67ced Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 6 Jul 2020 17:22:42 +0200 Subject: [PATCH] acp: add support for soft volume Add a set_soft_volume/mute event. If we can't configure the hardware volume completely, notify the remainder with the soft_volume/mute events. --- spa/plugins/alsa/acp/acp.c | 69 +++++++++++++++++++++++++++++-- spa/plugins/alsa/acp/acp.h | 4 ++ spa/plugins/alsa/acp/alsa-mixer.h | 1 + spa/plugins/alsa/acp/volume.h | 23 ++++++++--- 4 files changed, 87 insertions(+), 10 deletions(-) diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index bbf54af5d..59aae6b76 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -30,6 +30,8 @@ int _acp_log_level = 1; acp_log_func _acp_log_func; void *_acp_log_data; +#define VOLUME_ACCURACY (PA_VOLUME_NORM/100) /* don't require volume adjustments to be perfectly correct. don't necessarily extend granularity in software unless the differences get greater than this level */ + static void profile_free(void *data) { } @@ -53,6 +55,7 @@ static void init_device(pa_card *impl, pa_alsa_device *dev, pa_alsa_direction_t dev->device.format.format_mask = m->sample_spec.format; dev->device.format.rate_mask = m->sample_spec.rate; dev->device.format.channels = m->channel_map.channels; + pa_cvolume_set(&dev->real_volume, m->channel_map.channels, PA_VOLUME_NORM); for (i = 0; i < m->channel_map.channels; i++) dev->device.format.map[i]= m->channel_map.map[i]; dev->direction = direction; @@ -724,6 +727,7 @@ static int read_volume(pa_alsa_device *dev) static void set_volume(pa_alsa_device *dev, const pa_cvolume *v) { + pa_card *impl = dev->card; pa_cvolume r; dev->real_volume = *v; @@ -734,6 +738,46 @@ static void set_volume(pa_alsa_device *dev, const pa_cvolume *v) if (pa_alsa_path_set_volume(dev->mixer_path, dev->mixer_handle, &dev->mapping->channel_map, &r, false, true) < 0) return; + + /* Shift down by the base volume, so that 0dB becomes maximum volume */ + pa_sw_cvolume_multiply_scalar(&r, &r, dev->base_volume); + + dev->hardware_volume = r; + + if (dev->mixer_path->has_dB) { + pa_cvolume new_soft_volume; + bool accurate_enough; + + /* Match exactly what the user requested by software */ + pa_sw_cvolume_divide(&new_soft_volume, &dev->real_volume, &dev->hardware_volume); + + /* If the adjustment to do in software is only minimal we + * can skip it. That saves us CPU at the expense of a bit of + * accuracy */ + accurate_enough = + (pa_cvolume_min(&new_soft_volume) >= (PA_VOLUME_NORM - VOLUME_ACCURACY)) && + (pa_cvolume_max(&new_soft_volume) <= (PA_VOLUME_NORM + VOLUME_ACCURACY)); + + pa_log_debug("Requested volume: %d", pa_cvolume_max(&dev->real_volume)); + pa_log_debug("Got hardware volume: %d", pa_cvolume_max(&dev->hardware_volume)); + pa_log_debug("Calculated software volume: %d (accurate-enough=%s)", + pa_cvolume_max(&new_soft_volume), + pa_yes_no(accurate_enough)); + + if (!accurate_enough && impl->events && impl->events->set_soft_volume) { + uint32_t i, n_volumes = new_soft_volume.channels; + float volumes[n_volumes]; + for (i = 0; i < n_volumes; i++) + volumes[i] = ((float)new_soft_volume.values[i]) / PA_VOLUME_NORM; + impl->events->set_soft_volume(impl->user_data, &dev->device, volumes, n_volumes); + } + + } else { + pa_log_debug("Wrote hardware volume: %d", pa_cvolume_max(&r)); + /* We can't match exactly what the user requested, hence let's + * at least tell the user about it */ + dev->real_volume = r; + } } static int read_mute(pa_alsa_device *dev) @@ -1302,15 +1346,23 @@ int acp_device_set_port(struct acp_device *dev, uint32_t port_index) int acp_device_set_volume(struct acp_device *dev, const float *volume, uint32_t n_volume) { pa_alsa_device *d = (pa_alsa_device*)dev; - pa_cvolume v; + pa_card *impl = d->card; uint32_t i; - v.channels = d->mapping->channel_map.channels; + pa_cvolume v; if (n_volume == 0) return -EINVAL; + + v.channels = d->mapping->channel_map.channels; for (i = 0; i < v.channels; i++) v.values[i] = volume[i % n_volume] * PA_VOLUME_NORM; - if (d->set_volume) + + if (d->set_volume) { d->set_volume(d, &v); + } else { + d->real_volume = v; + if (impl->events && impl->events->set_soft_volume) + impl->events->set_soft_volume(impl->user_data, dev, volume, n_volume); + } return 0; } @@ -1330,8 +1382,16 @@ int acp_device_get_volume(struct acp_device *dev, float *volume, uint32_t n_volu int acp_device_set_mute(struct acp_device *dev, bool mute) { pa_alsa_device *d = (pa_alsa_device*)dev; - if (d->set_mute) + pa_card *impl = d->card; + if (d->muted == mute) + return 0; + if (d->set_mute) { d->set_mute(d, mute); + } else { + d->muted = mute; + if (impl->events && impl->events->set_soft_mute) + impl->events->set_soft_mute(impl->user_data, dev, mute); + } return 0; } @@ -1341,6 +1401,7 @@ int acp_device_get_mute(struct acp_device *dev, bool *mute) *mute = d->muted; return 0; } + void acp_set_log_func(acp_log_func func, void *data) { _acp_log_func = func; diff --git a/spa/plugins/alsa/acp/acp.h b/spa/plugins/alsa/acp/acp.h index 8a7b39e8c..161d5ca23 100644 --- a/spa/plugins/alsa/acp/acp.h +++ b/spa/plugins/alsa/acp/acp.h @@ -127,6 +127,10 @@ struct acp_card_events { void (*volume_changed) (void *data, struct acp_device *dev); void (*mute_changed) (void *data, struct acp_device *dev); + + void (*set_soft_volume) (void *data, struct acp_device *dev, + const float *volume, uint32_t n_volume); + void (*set_soft_mute) (void *data, struct acp_device *dev, bool mute); }; struct acp_port { diff --git a/spa/plugins/alsa/acp/alsa-mixer.h b/spa/plugins/alsa/acp/alsa-mixer.h index 4ee589338..c11d0d4fd 100644 --- a/spa/plugins/alsa/acp/alsa-mixer.h +++ b/spa/plugins/alsa/acp/alsa-mixer.h @@ -295,6 +295,7 @@ struct pa_alsa_device { unsigned muted:1; unsigned decibel_volume:1; pa_cvolume real_volume; + pa_cvolume hardware_volume; pa_volume_t base_volume; unsigned n_volume_steps; diff --git a/spa/plugins/alsa/acp/volume.h b/spa/plugins/alsa/acp/volume.h index fce449a54..b916bd29d 100644 --- a/spa/plugins/alsa/acp/volume.h +++ b/spa/plugins/alsa/acp/volume.h @@ -181,14 +181,25 @@ static inline int pa_cvolume_compatible_with_channel_map(const pa_cvolume *v, static inline pa_volume_t pa_cvolume_max(const pa_cvolume *a) { - pa_volume_t m = PA_VOLUME_MUTED; - unsigned c; - for (c = 0; c < a->channels; c++) - if (a->values[c] > m) - m = a->values[c]; - return m; + pa_volume_t m = PA_VOLUME_MUTED; + unsigned c; + for (c = 0; c < a->channels; c++) + if (a->values[c] > m) + m = a->values[c]; + return m; } +static inline pa_volume_t pa_cvolume_min(const pa_cvolume *a) +{ + pa_volume_t m = PA_VOLUME_MAX; + unsigned c; + for (c = 0; c < a->channels; c++) + if (a->values[c] < m) + m = a->values[c]; + return m; +} + + #ifdef __cplusplus } /* extern "C" */ #endif