diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c index 767482e4a5042453ea2693fa5e6c17153011a5d8..535799bae1d26061cc246e11d27a3956d32bf0a6 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 */ @@ -2436,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; } @@ -2465,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; }