[pulseaudio-discuss] Implement event Volume has changed (org.bluez.MediaTransport1) in order to take into account volume changes of A2DP sources.

Submitted by Mathieu Tournier on March 11, 2016, 10:16 a.m.

Details

Message ID 1457691378-11250-1-git-send-email-mathieutournier@gmail.com
State New
Headers show
Series "Implement event Volume has changed (org.bluez.MediaTransport1) in order to take into account volume changes of A2DP sources." ( rev: 1 ) in PulseAudio

Not browsing as part of any series.

Commit Message

Mathieu Tournier March 11, 2016, 10:16 a.m.
Using A2DP and a bluetooth audio source, Pulseaudio doesn't take into account the volume modification (for device sending the event via AVRCP (ex:Apple devices))
This work aims to integrate bluez event Volume (org.bluez.MediaTransport1) for A2DP source in pulseaudio.
This permits to take into account A2DP source volume changes (sent using AVRCP protocol).

Change has been tested with an iphone and an ipad and is fully working.
---
 src/modules/bluetooth/bluez5-util.c          | 15 ++++++++++++++
 src/modules/bluetooth/bluez5-util.h          | 13 ++++++++----
 src/modules/bluetooth/module-bluez5-device.c | 30 ++++++++++++++++++++++++++++
 3 files changed, 54 insertions(+), 4 deletions(-)

Patch hide | download patch | download mbox

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 03c76bf..c216c6e 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -185,6 +185,14 @@  void pa_bluetooth_transport_set_state(pa_bluetooth_transport *t, pa_bluetooth_tr
         pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], t->device);
 }
 
+void pa_bluetooth_transport_set_source_volume(pa_bluetooth_transport *t,uint16_t volume) {
+
+    pa_assert(t);
+    t->source_volume = volume;
+    pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_A2DP_SOURCE_VOLUME_CHANGED], t);
+
+}
+
 void pa_bluetooth_transport_put(pa_bluetooth_transport *t) {
     pa_assert(t);
 
@@ -340,6 +348,13 @@  static void parse_transport_property(pa_bluetooth_transport *t, DBusMessageIter
 
                 pa_bluetooth_transport_set_state(t, state);
             }
+        case DBUS_TYPE_UINT16: {
+            uint16_t uintValue;
+            dbus_message_iter_get_basic(&variant_i, &uintValue);
+
+                if (pa_streq(key, "Volume"))
+                    pa_bluetooth_transport_set_source_volume(t,uintValue);
+            }
 
             break;
         }
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index d66e8a3..3380074 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -36,10 +36,11 @@  typedef struct pa_bluetooth_discovery pa_bluetooth_discovery;
 typedef struct pa_bluetooth_backend pa_bluetooth_backend;
 
 typedef enum pa_bluetooth_hook {
-    PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED,          /* Call data: pa_bluetooth_device */
-    PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED,            /* Call data: pa_bluetooth_transport */
-    PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED,  /* Call data: pa_bluetooth_transport */
-    PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED,     /* Call data: pa_bluetooth_transport */
+    PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED,            /* Call data: pa_bluetooth_device */
+    PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED,              /* Call data: pa_bluetooth_transport */
+    PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED,    /* Call data: pa_bluetooth_transport */
+    PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED,       /* Call data: pa_bluetooth_transport */
+    PA_BLUETOOTH_HOOK_TRANSPORT_A2DP_SOURCE_VOLUME_CHANGED, /* Call data: pa_bluetooth_transport */
     PA_BLUETOOTH_HOOK_MAX
 } pa_bluetooth_hook_t;
 
@@ -63,6 +64,7 @@  typedef void (*pa_bluetooth_transport_release_cb)(pa_bluetooth_transport *t);
 typedef void (*pa_bluetooth_transport_destroy_cb)(pa_bluetooth_transport *t);
 typedef void (*pa_bluetooth_transport_set_speaker_gain_cb)(pa_bluetooth_transport *t, uint16_t gain);
 typedef void (*pa_bluetooth_transport_set_microphone_gain_cb)(pa_bluetooth_transport *t, uint16_t gain);
+typedef void (*pa_bluetooth_transport_set_a2dp_source_volume_cb)(pa_bluetooth_transport *t, uint16_t volume);
 
 struct pa_bluetooth_transport {
     pa_bluetooth_device *device;
@@ -77,6 +79,7 @@  struct pa_bluetooth_transport {
 
     uint16_t microphone_gain;
     uint16_t speaker_gain;
+    uint16_t source_volume;
 
     pa_bluetooth_transport_state_t state;
 
@@ -85,6 +88,7 @@  struct pa_bluetooth_transport {
     pa_bluetooth_transport_destroy_cb destroy;
     pa_bluetooth_transport_set_speaker_gain_cb set_speaker_gain;
     pa_bluetooth_transport_set_microphone_gain_cb set_microphone_gain;
+    pa_bluetooth_transport_set_a2dp_source_volume_cb set_source_volume;
     void *userdata;
 };
 
@@ -139,6 +143,7 @@  pa_bluetooth_transport *pa_bluetooth_transport_new(pa_bluetooth_device *d, const
                                                    pa_bluetooth_profile_t p, const uint8_t *config, size_t size);
 
 void pa_bluetooth_transport_set_state(pa_bluetooth_transport *t, pa_bluetooth_transport_state_t state);
+void pa_bluetooth_transport_set_source_volume(pa_bluetooth_transport *t, uint16_t volume);
 void pa_bluetooth_transport_put(pa_bluetooth_transport *t);
 void pa_bluetooth_transport_unlink(pa_bluetooth_transport *t);
 void pa_bluetooth_transport_free(pa_bluetooth_transport *t);
diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 84e6d55..2aa7277 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -65,6 +65,7 @@  PA_MODULE_USAGE("path=<device object path>");
 #define BITPOOL_DEC_LIMIT 32
 #define BITPOOL_DEC_STEP 5
 #define HSP_MAX_GAIN 15
+#define A2DP_MAX_VOLUME 127
 
 static const char* const valid_modargs[] = {
     "path",
@@ -104,6 +105,7 @@  struct userdata {
     pa_hook_slot *transport_state_changed_slot;
     pa_hook_slot *transport_speaker_gain_changed_slot;
     pa_hook_slot *transport_microphone_gain_changed_slot;
+    pa_hook_slot *transport_a2dp_source_volume_changed_slot;
 
     pa_bluetooth_discovery *discovery;
     pa_bluetooth_device *device;
@@ -2102,6 +2104,29 @@  static pa_hook_result_t transport_microphone_gain_changed_cb(pa_bluetooth_discov
     return PA_HOOK_OK;
 }
 
+static pa_hook_result_t transport_a2dp_source_volume_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, struct userdata *u) {
+    pa_volume_t volume;
+    pa_cvolume v;
+
+    pa_assert(t);
+    pa_assert(u);
+
+    if (t != u->transport)
+      return PA_HOOK_OK;
+
+    volume = (pa_volume_t) (t->source_volume * PA_VOLUME_NORM / A2DP_MAX_VOLUME);
+
+    /* increment volume by one to correct rounding errors */
+    if (volume < PA_VOLUME_NORM)
+        volume++;
+
+    pa_cvolume_set(&v, u->sample_spec.channels, volume);
+    pa_source_set_volume(u->source, &v, true, true);
+    pa_source_volume_changed(u->source, &v);
+
+    return PA_HOOK_OK;
+}
+
 /* Run from main thread context */
 static int device_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
     struct bluetooth_msg *m = BLUETOOTH_MSG(obj);
@@ -2172,6 +2197,8 @@  int pa__init(pa_module* m) {
     u->transport_microphone_gain_changed_slot =
         pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED), PA_HOOK_NORMAL, (pa_hook_cb_t) transport_microphone_gain_changed_cb, u);
 
+    u->transport_a2dp_source_volume_changed_slot =
+        pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_A2DP_SOURCE_VOLUME_CHANGED), PA_HOOK_NORMAL, (pa_hook_cb_t) transport_a2dp_source_volume_changed_cb, u);
 
     if (add_card(u) < 0)
         goto fail;
@@ -2231,6 +2258,9 @@  void pa__done(pa_module *m) {
     if (u->transport_microphone_gain_changed_slot)
         pa_hook_slot_free(u->transport_microphone_gain_changed_slot);
 
+    if (u->transport_a2dp_source_volume_changed_slot)
+        pa_hook_slot_free(u->transport_a2dp_source_volume_changed_slot);
+
     if (u->sbc_info.buffer)
         pa_xfree(u->sbc_info.buffer);
 

Comments

Hi all,

I just remembered i forgot to add some infos to that patch :

Here is a copy and paste of the bluez signal generated when the volume of
the a2dp source is changed.

signal sender=:1.2 -> dest=(null destination) serial=154
path=/org/bluez/hci0/dev_B4_18_D1_9C_2B_B6/fd3; interface=org.freede
sktop.DBus.Properties; member=PropertiesChanged
  string "org.bluez.MediaTransport1"
  array [
     dict entry(
        string "Volume"
        variant             uint16 31
     )
  ]
  array [
  ]

I hope this attachment will make the review easier.

Thanks a lot and regards,
Mathieu Tournier

On Fri, 11 Mar 2016 at 11:16 Mathieu Tournier <mathieutournier@gmail.com>
wrote:

> Using A2DP and a bluetooth audio source, Pulseaudio doesn't take into
> account the volume modification (for device sending the event via AVRCP
> (ex:Apple devices))
> This work aims to integrate bluez event Volume (org.bluez.MediaTransport1)
> for A2DP source in pulseaudio.
> This permits to take into account A2DP source volume changes (sent using
> AVRCP protocol).
>
> Change has been tested with an iphone and an ipad and is fully working.
> ---
>  src/modules/bluetooth/bluez5-util.c          | 15 ++++++++++++++
>  src/modules/bluetooth/bluez5-util.h          | 13 ++++++++----
>  src/modules/bluetooth/module-bluez5-device.c | 30
> ++++++++++++++++++++++++++++
>  3 files changed, 54 insertions(+), 4 deletions(-)
>
> diff --git a/src/modules/bluetooth/bluez5-util.c
> b/src/modules/bluetooth/bluez5-util.c
> index 03c76bf..c216c6e 100644
> --- a/src/modules/bluetooth/bluez5-util.c
> +++ b/src/modules/bluetooth/bluez5-util.c
> @@ -185,6 +185,14 @@ void
> pa_bluetooth_transport_set_state(pa_bluetooth_transport *t, pa_bluetooth_tr
>
>  pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED],
> t->device);
>  }
>
> +void pa_bluetooth_transport_set_source_volume(pa_bluetooth_transport
> *t,uint16_t volume) {
> +
> +    pa_assert(t);
> +    t->source_volume = volume;
> +
> pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_A2DP_SOURCE_VOLUME_CHANGED],
> t);
> +
> +}
> +
>  void pa_bluetooth_transport_put(pa_bluetooth_transport *t) {
>      pa_assert(t);
>
> @@ -340,6 +348,13 @@ static void
> parse_transport_property(pa_bluetooth_transport *t, DBusMessageIter
>
>                  pa_bluetooth_transport_set_state(t, state);
>              }
> +        case DBUS_TYPE_UINT16: {
> +            uint16_t uintValue;
> +            dbus_message_iter_get_basic(&variant_i, &uintValue);
> +
> +                if (pa_streq(key, "Volume"))
> +                    pa_bluetooth_transport_set_source_volume(t,uintValue);
> +            }
>
>              break;
>          }
> diff --git a/src/modules/bluetooth/bluez5-util.h
> b/src/modules/bluetooth/bluez5-util.h
> index d66e8a3..3380074 100644
> --- a/src/modules/bluetooth/bluez5-util.h
> +++ b/src/modules/bluetooth/bluez5-util.h
> @@ -36,10 +36,11 @@ typedef struct pa_bluetooth_discovery
> pa_bluetooth_discovery;
>  typedef struct pa_bluetooth_backend pa_bluetooth_backend;
>
>  typedef enum pa_bluetooth_hook {
> -    PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED,          /* Call data:
> pa_bluetooth_device */
> -    PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED,            /* Call data:
> pa_bluetooth_transport */
> -    PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED,  /* Call data:
> pa_bluetooth_transport */
> -    PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED,     /* Call data:
> pa_bluetooth_transport */
> +    PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED,            /* Call data:
> pa_bluetooth_device */
> +    PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED,              /* Call data:
> pa_bluetooth_transport */
> +    PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED,    /* Call data:
> pa_bluetooth_transport */
> +    PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED,       /* Call data:
> pa_bluetooth_transport */
> +    PA_BLUETOOTH_HOOK_TRANSPORT_A2DP_SOURCE_VOLUME_CHANGED, /* Call data:
> pa_bluetooth_transport */
>      PA_BLUETOOTH_HOOK_MAX
>  } pa_bluetooth_hook_t;
>
> @@ -63,6 +64,7 @@ typedef void
> (*pa_bluetooth_transport_release_cb)(pa_bluetooth_transport *t);
>  typedef void (*pa_bluetooth_transport_destroy_cb)(pa_bluetooth_transport
> *t);
>  typedef void
> (*pa_bluetooth_transport_set_speaker_gain_cb)(pa_bluetooth_transport *t,
> uint16_t gain);
>  typedef void
> (*pa_bluetooth_transport_set_microphone_gain_cb)(pa_bluetooth_transport *t,
> uint16_t gain);
> +typedef void
> (*pa_bluetooth_transport_set_a2dp_source_volume_cb)(pa_bluetooth_transport
> *t, uint16_t volume);
>
>  struct pa_bluetooth_transport {
>      pa_bluetooth_device *device;
> @@ -77,6 +79,7 @@ struct pa_bluetooth_transport {
>
>      uint16_t microphone_gain;
>      uint16_t speaker_gain;
> +    uint16_t source_volume;
>
>      pa_bluetooth_transport_state_t state;
>
> @@ -85,6 +88,7 @@ struct pa_bluetooth_transport {
>      pa_bluetooth_transport_destroy_cb destroy;
>      pa_bluetooth_transport_set_speaker_gain_cb set_speaker_gain;
>      pa_bluetooth_transport_set_microphone_gain_cb set_microphone_gain;
> +    pa_bluetooth_transport_set_a2dp_source_volume_cb set_source_volume;
>      void *userdata;
>  };
>
> @@ -139,6 +143,7 @@ pa_bluetooth_transport
> *pa_bluetooth_transport_new(pa_bluetooth_device *d, const
>                                                     pa_bluetooth_profile_t
> p, const uint8_t *config, size_t size);
>
>  void pa_bluetooth_transport_set_state(pa_bluetooth_transport *t,
> pa_bluetooth_transport_state_t state);
> +void pa_bluetooth_transport_set_source_volume(pa_bluetooth_transport *t,
> uint16_t volume);
>  void pa_bluetooth_transport_put(pa_bluetooth_transport *t);
>  void pa_bluetooth_transport_unlink(pa_bluetooth_transport *t);
>  void pa_bluetooth_transport_free(pa_bluetooth_transport *t);
> diff --git a/src/modules/bluetooth/module-bluez5-device.c
> b/src/modules/bluetooth/module-bluez5-device.c
> index 84e6d55..2aa7277 100644
> --- a/src/modules/bluetooth/module-bluez5-device.c
> +++ b/src/modules/bluetooth/module-bluez5-device.c
> @@ -65,6 +65,7 @@ PA_MODULE_USAGE("path=<device object path>");
>  #define BITPOOL_DEC_LIMIT 32
>  #define BITPOOL_DEC_STEP 5
>  #define HSP_MAX_GAIN 15
> +#define A2DP_MAX_VOLUME 127
>
>  static const char* const valid_modargs[] = {
>      "path",
> @@ -104,6 +105,7 @@ struct userdata {
>      pa_hook_slot *transport_state_changed_slot;
>      pa_hook_slot *transport_speaker_gain_changed_slot;
>      pa_hook_slot *transport_microphone_gain_changed_slot;
> +    pa_hook_slot *transport_a2dp_source_volume_changed_slot;
>
>      pa_bluetooth_discovery *discovery;
>      pa_bluetooth_device *device;
> @@ -2102,6 +2104,29 @@ static pa_hook_result_t
> transport_microphone_gain_changed_cb(pa_bluetooth_discov
>      return PA_HOOK_OK;
>  }
>
> +static pa_hook_result_t
> transport_a2dp_source_volume_changed_cb(pa_bluetooth_discovery *y,
> pa_bluetooth_transport *t, struct userdata *u) {
> +    pa_volume_t volume;
> +    pa_cvolume v;
> +
> +    pa_assert(t);
> +    pa_assert(u);
> +
> +    if (t != u->transport)
> +      return PA_HOOK_OK;
> +
> +    volume = (pa_volume_t) (t->source_volume * PA_VOLUME_NORM /
> A2DP_MAX_VOLUME);
> +
> +    /* increment volume by one to correct rounding errors */
> +    if (volume < PA_VOLUME_NORM)
> +        volume++;
> +
> +    pa_cvolume_set(&v, u->sample_spec.channels, volume);
> +    pa_source_set_volume(u->source, &v, true, true);
> +    pa_source_volume_changed(u->source, &v);
> +
> +    return PA_HOOK_OK;
> +}
> +
>  /* Run from main thread context */
>  static int device_process_msg(pa_msgobject *obj, int code, void *data,
> int64_t offset, pa_memchunk *chunk) {
>      struct bluetooth_msg *m = BLUETOOTH_MSG(obj);
> @@ -2172,6 +2197,8 @@ int pa__init(pa_module* m) {
>      u->transport_microphone_gain_changed_slot =
>          pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery,
> PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED), PA_HOOK_NORMAL,
> (pa_hook_cb_t) transport_microphone_gain_changed_cb, u);
>
> +    u->transport_a2dp_source_volume_changed_slot =
> +        pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery,
> PA_BLUETOOTH_HOOK_TRANSPORT_A2DP_SOURCE_VOLUME_CHANGED), PA_HOOK_NORMAL,
> (pa_hook_cb_t) transport_a2dp_source_volume_changed_cb, u);
>
>      if (add_card(u) < 0)
>          goto fail;
> @@ -2231,6 +2258,9 @@ void pa__done(pa_module *m) {
>      if (u->transport_microphone_gain_changed_slot)
>          pa_hook_slot_free(u->transport_microphone_gain_changed_slot);
>
> +    if (u->transport_a2dp_source_volume_changed_slot)
> +        pa_hook_slot_free(u->transport_a2dp_source_volume_changed_slot);
> +
>      if (u->sbc_info.buffer)
>          pa_xfree(u->sbc_info.buffer);
>
> --
> 2.1.4
>
>
MediaTransport1 hierarchy
=========================

Service		org.bluez
Interface	org.bluez.MediaTransport1
Object path	[variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/fdX

Methods		fd, uint16, uint16 Acquire()

			Acquire transport file descriptor and the MTU for read
			and write respectively.

			Possible Errors: org.bluez.Error.NotAuthorized
					 org.bluez.Error.Failed

		fd, uint16, uint16 TryAcquire()

			Acquire transport file descriptor only if the transport
			is in "pending" state at the time the message is
			received by BlueZ. Otherwise no request will be sent
			to the remote device and the function will just fail
			with org.bluez.Error.NotAvailable.

			Possible Errors: org.bluez.Error.NotAuthorized
					 org.bluez.Error.Failed
					 org.bluez.Error.NotAvailable

		void Release()

			Releases file descriptor.

Properties	object Device [readonly]

			Device object which the transport is connected to.

		string UUID [readonly]

			UUID of the profile which the transport is for.

		byte Codec [readonly]

			Assigned number of codec that the transport support.
			The values should match the profile specification which
			is indicated by the UUID.

		array{byte} Configuration [readonly]

			Configuration blob, it is used as it is so the size and
			byte order must match.

		string State [readonly]

			Indicates the state of the transport. Possible
			values are:
				"idle": not streaming
				"pending": streaming but not acquired
				"active": streaming and acquired

		uint16 Delay [readwrite]

			Optional. Transport delay in 1/10 of millisecond, this
			property is only writeable when the transport was
			acquired by the sender.

		boolean NREC [readwrite]

			Optional and HFP specific (external to BlueZ).
			Indicates if echo cancelling and noise reduction
			functions are active in the transport, this
			property is only writeable when the transport
			was acquired by the sender.

		boolean InbandRingtone [readwrite]

			Optional and HFP specific (external to BlueZ).
			Indicates if the transport support sending
			ringtones, this property is only writeable when
			the transport was acquired by the sender.

		string Routing [readonly]

			Optional and HFP specific (external to BlueZ).
			Indicates where is the transport being routed.

			Possible Values: "HCI" or "PCM"

		uint16 Volume [readwrite]

			Optional. Indicates volume level of the transport,
			this property is only writeable when the transport was
			acquired by the sender.

			Note: the property will not be present for HSP/HFP
			transports and MicrophoneGain/SpeakerGain should be
			used instead.

			Possible Values: 0-127

		byte MicrophoneGain [readwrite]

			Optional. Indicates volume level of the transport's
			incoming audio stream for HSP/HFP transports. This
			property is only writeable when the transport was
			acquired by the sender.

			Possible Values: 0-15

		byte SpeakerGain [readwrite]

			Optional. Indicates volume level of the transport's
			outgoing audio stream for HSP/HFP transports. This
			property is only writeable when the transport was
			acquired by the sender.

			Possible Values: 0-15