bluetooth: Update source software volume on AVRCP SetAbsoluteVolume
authorMarijn Suijten <marijns95@gmail.com>
Sat, 27 Mar 2021 09:53:07 +0000 (10:53 +0100)
committerPulseAudio Marge Bot <pulseaudio-maintainers@lists.freedesktop.org>
Mon, 17 May 2021 14:50:03 +0000 (14:50 +0000)
The A2DP spec mandates that the audio rendering device - the device
receiving audio, in our case a `pa_source` - is responsible for
performing attenuation:

AVRCP v1.6.2, ยง5.8:
    The SetAbsoluteVolume command is used to set an absolute volume to be used by the rendering device.

BlueZ models this call as a change of the `Volume` property on the
`org.bluez.MediaTransport1` interface.  Supporting Absolute Volume is
optional but BlueZ unconditionally reports feature category 2 in its
profile, mandating support.  Hence remote devices (ie. a phone) playing
back audio to a machine running PulseAudio assume volume is to be
changed through SetAbsoluteVolume, without performing any local
attenuation.

Future changes will implement this feature the other way around: setting
an initial value for the `Volume` property as well as propagating
`pa_source` volume changes back to the peer.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/239>

src/modules/bluetooth/bluez5-util.c
src/modules/bluetooth/bluez5-util.h

index f6d73a8..4453e87 100644 (file)
     " </interface>"                                                     \
     "</node>"
 
+static pa_volume_t a2dp_gain_to_volume(uint16_t gain) {
+    pa_volume_t volume = (pa_volume_t) ((
+        gain * PA_VOLUME_NORM
+        /* Round to closest by adding half the denominator */
+        + A2DP_MAX_GAIN / 2
+    ) / A2DP_MAX_GAIN);
+
+    if (volume > PA_VOLUME_NORM)
+        volume = PA_VOLUME_NORM;
+
+    return volume;
+}
+
 struct pa_bluetooth_discovery {
     PA_REFCNT_DECLARE;
 
@@ -496,6 +509,24 @@ void pa_bluetooth_transport_set_state(pa_bluetooth_transport *t, pa_bluetooth_tr
     }
 }
 
+static void pa_bluetooth_transport_remote_volume_changed(pa_bluetooth_transport *t, uint16_t gain) {
+    pa_volume_t volume;
+
+    pa_assert(t);
+
+    volume = a2dp_gain_to_volume(gain);
+
+    /* increment volume by one to correct rounding errors */
+    if (volume < PA_VOLUME_NORM)
+        volume++;
+
+    if (t->source_volume == volume)
+        return;
+
+    t->source_volume = volume;
+    pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_SOURCE_VOLUME_CHANGED), t);
+}
+
 void pa_bluetooth_transport_put(pa_bluetooth_transport *t) {
     pa_assert(t);
 
@@ -679,6 +710,8 @@ static void parse_transport_property(pa_bluetooth_transport *t, DBusMessageIter
     if (key == NULL)
         return;
 
+    pa_log_debug("Transport property %s changed", key);
+
     dbus_message_iter_recurse(i, &variant_i);
 
     switch (dbus_message_iter_get_arg_type(&variant_i)) {
@@ -701,6 +734,17 @@ static void parse_transport_property(pa_bluetooth_transport *t, DBusMessageIter
 
             break;
         }
+
+        case DBUS_TYPE_UINT16: {
+            uint16_t value;
+            dbus_message_iter_get_basic(&variant_i, &value);
+
+            if (pa_streq(key, "Volume")) {
+                if (t->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE)
+                    pa_bluetooth_transport_remote_volume_changed(t, value);
+            }
+            break;
+        }
     }
 
     return;
index 7bbfe62..c08a7a3 100644 (file)
@@ -52,6 +52,7 @@
 #define PA_BLUETOOTH_UUID_HFP_HF      "0000111e-0000-1000-8000-00805f9b34fb"
 #define PA_BLUETOOTH_UUID_HFP_AG      "0000111f-0000-1000-8000-00805f9b34fb"
 
+#define A2DP_MAX_GAIN 127
 #define HSP_MAX_GAIN 15
 
 typedef struct pa_bluetooth_transport pa_bluetooth_transport;