From a01a897345e2aa02c4db0575b79c2e23e70468e9 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Mon, 23 Nov 2020 23:10:44 +0100 Subject: [PATCH 1/2] bluetooth: Use software volume for >100%, balance and finer control This compensates discrepancies between requested volume and actual volume represented by the 127-step A2DP range or 15-step HSP/HFP range, including setting volume above 100% without any additional logic; the delta simply becomes larger. The delta is calculated per channel against "mono" hardware volume on the remote to also take care of differences in balance. The remote uses the maximum volume of all channels and software is used to attenuate below that (or above, if one or more channels are set above 100%). Note that this does _NOT_ take the actual volume on the remote into account yet! Most headphones don't use the full range, and instead round to the nearest multiple of some arbitrarily chosen step size. They do reply with this value, which we should consequently retrieve and deal with. --- src/modules/bluetooth/module-bluez5-device.c | 50 ++++++++++++++++---- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c index 767482e4a..791dc7fea 100644 --- a/src/modules/bluetooth/module-bluez5-device.c +++ b/src/modules/bluetooth/module-bluez5-device.c @@ -924,9 +924,38 @@ static int source_set_state_in_io_thread_cb(pa_source *s, pa_source_state_t new_ return 0; } +/* Use software volume to compensate for lack in hardware granularity, + * provide stereo balance, and >100% amplification */ +static bool set_software_volume_compensation(const pa_cvolume *real_volume, + const pa_volume_t hardware_volume, + pa_cvolume *soft_volume) { + char hw_volume_str[PA_VOLUME_SNPRINT_VERBOSE_MAX]; + char volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX]; + bool different = false; + + pa_assert(real_volume->channels == soft_volume->channels); + + for (int i = 0; i < soft_volume->channels; ++i) { + /* Calculate in unsigned space since hardware volume can never exceed real_volume */ + pa_volume_t delta = PA_VOLUME_NORM + real_volume->values[i] - hardware_volume; + soft_volume->values[i] = delta; + different |= delta != PA_VOLUME_NORM; + } + + if (different) { + pa_log_debug("Remote all-channel hardware volume %s does not match requested %s", + pa_volume_snprint_verbose(hw_volume_str, sizeof(hw_volume_str), hardware_volume, false), + pa_cvolume_snprint_verbose(volume_str, sizeof(volume_str), real_volume, NULL, false)); + pa_log_debug("Compensating with %s", + pa_cvolume_snprint_verbose(volume_str, sizeof(volume_str), soft_volume, NULL, false)); + } + + return different; +} + /* Run from main thread */ static void source_set_volume_cb(pa_source *s) { - pa_volume_t volume; + pa_volume_t volume, hardware_volume; struct userdata *u; pa_assert(s); @@ -940,10 +969,14 @@ static void source_set_volume_cb(pa_source *s) { pa_assert(u->transport); pa_assert(u->transport->set_source_volume); - /* In the AG role, send a command to change microphone gain on the HS/HF */ - volume = u->transport->set_source_volume(u->transport, pa_cvolume_max(&s->real_volume)); + volume = pa_cvolume_max(&s->real_volume); - pa_cvolume_set(&s->real_volume, u->decoder_sample_spec.channels, volume); + /* In the AG role, send a command to change microphone gain on the HS/HF */ + // TODO: Round up for the below compensation to work properly. + hardware_volume = u->transport->set_source_volume(u->transport, volume); + // TODO: This does not take into account any rounding that might also happen on the headphones + // For that we need to know which _incoming_ `Volume` change belongs to a requested volume. + set_software_volume_compensation(&s->real_volume, hardware_volume, &s->soft_volume); } /* Run from main thread */ @@ -1163,7 +1196,7 @@ static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state, /* Run from main thread */ static void sink_set_volume_cb(pa_sink *s) { - pa_volume_t volume; + pa_volume_t volume, hardware_volume; struct userdata *u; pa_assert(s); @@ -1177,10 +1210,11 @@ static void sink_set_volume_cb(pa_sink *s) { pa_assert(u->transport); pa_assert(u->transport->set_sink_volume); - /* In the AG role, send a command to change speaker gain on the HS/HF */ - volume = u->transport->set_sink_volume(u->transport, pa_cvolume_max(&s->real_volume)); + volume = pa_cvolume_max(&s->real_volume); - pa_cvolume_set(&s->real_volume, u->encoder_sample_spec.channels, volume); + /* In the AG role, send a command to change speaker gain on the HS/HF */ + hardware_volume = u->transport->set_sink_volume(u->transport, volume); + set_software_volume_compensation(&s->real_volume, hardware_volume, &s->soft_volume); } /* Run from main thread */ -- GitLab From 79f45c141cccbfa795134b69c1295898a9147b79 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Sun, 28 Mar 2021 22:44:57 +0200 Subject: [PATCH 2/2] DEBATE: Reset software volume on remote changes --- src/modules/bluetooth/module-bluez5-device.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c index 791dc7fea..535799bae 100644 --- a/src/modules/bluetooth/module-bluez5-device.c +++ b/src/modules/bluetooth/module-bluez5-device.c @@ -2470,8 +2470,11 @@ static pa_hook_result_t transport_sink_volume_changed_cb(pa_bluetooth_discovery pa_cvolume_set(&v, u->encoder_sample_spec.channels, volume); if (pa_bluetooth_profile_should_attenuate_volume(t->profile)) pa_sink_set_volume(u->sink, &v, true, true); - else + else { + /* Reset local attenuation */ + pa_sink_set_soft_volume(u->sink, NULL); pa_sink_volume_changed(u->sink, &v); + } return PA_HOOK_OK; } @@ -2499,8 +2502,11 @@ static pa_hook_result_t transport_source_volume_changed_cb(pa_bluetooth_discover if (pa_bluetooth_profile_should_attenuate_volume(t->profile)) pa_source_set_volume(u->source, &v, true, true); - else + else { + /* Reset local attenuation */ + pa_source_set_soft_volume(u->source, NULL); pa_source_volume_changed(u->source, &v); + } return PA_HOOK_OK; } -- GitLab