Commit 9a6ca4d4 authored by Marijn Suijten's avatar Marijn Suijten 🦀
Browse files

bluetooth: Delay A2DP Absolute Volume setup until property is available

The Volume property on org.bluez.MediaTransport1 is required to utilize
Absolute Volume, but it will only become availabe if the peer device
supports the feature.  This happens asynchronously somewhere after the
transport itself has been acquired, after which the callbacks are
attached and software volume is reset.

To prevent race conditions availability of the property is also checked
on startup through a "Get" call.
parent 9297bde1
Pipeline #306697 passed with stages
in 4 minutes and 10 seconds
......@@ -606,6 +606,16 @@ static void pa_bluetooth_transport_remote_volume_changed(pa_bluetooth_transport
t->sink_volume = volume;
/* A2DP Absolute Volume is optional. This callback is only
* attached when the peer supports it, and the hook handler
* further attaches the necessary hardware callback to the
* pa_sink and disables software attenuation.
if (!t->set_sink_volume) {
pa_log_debug("A2DP sink supports volume control");
t->set_sink_volume = pa_bluetooth_transport_set_sink_volume;
} else {
......@@ -724,6 +734,78 @@ static void bluez5_transport_release_cb(pa_bluetooth_transport *t) {
pa_log_info("Transport %s released", t->path);
static void get_volume_reply(DBusPendingCall *pending, void *userdata) {
DBusMessage *r;
DBusMessageIter iter, variant;
pa_dbus_pending *p;
pa_bluetooth_discovery *y;
pa_bluetooth_transport *t;
uint16_t gain;
pa_volume_t volume;
pa_assert_se(p = userdata);
pa_assert_se(y = p->context_data);
pa_assert_se(t = p->call_data);
pa_assert_se(r = dbus_pending_call_steal_reply(pending));
if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
pa_log_error(DBUS_INTERFACE_PROPERTIES ".Get %s Volume failed: %s: %s",
goto finish;
dbus_message_iter_init(r, &iter);
pa_assert(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_VARIANT);
dbus_message_iter_recurse(&iter, &variant);
pa_assert(dbus_message_iter_get_arg_type(&variant) == DBUS_TYPE_UINT16);
dbus_message_iter_get_basic(&variant, &gain);
if (gain > A2DP_MAX_GAIN)
gain = A2DP_MAX_GAIN;
pa_log_debug("Received A2DP Absolute Volume %d", gain);
volume = a2dp_gain_to_volume(gain);
pa_bluetooth_transport_remote_volume_changed(t, volume);
PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
static void bluez5_transport_get_volume(pa_bluetooth_transport *t) {
static const char *volume_str = "Volume";
static const char *mediatransport_str = BLUEZ_MEDIA_TRANSPORT_INTERFACE;
DBusMessage *m;
pa_assert(t->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK || t->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE);
pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, t->path, DBUS_INTERFACE_PROPERTIES, "Get"));
DBUS_TYPE_STRING, &mediatransport_str,
DBUS_TYPE_STRING, &volume_str,
send_and_add_to_pending(t->device->discovery, m, get_volume_reply, t);
void pa_bluetooth_transport_load_a2dp_sink_volume(pa_bluetooth_transport *t) {
/* A2DP Absolute Volume control (AVRCP 1.4) is optional */
static ssize_t a2dp_transport_write(pa_bluetooth_transport *t, int fd, const void* buffer, size_t size, size_t write_mtu) {
ssize_t l = 0;
size_t written = 0;
......@@ -2114,7 +2196,6 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
t = pa_bluetooth_transport_new(d, sender, path, p, config, size);
t->acquire = bluez5_transport_acquire_cb;
t->release = bluez5_transport_release_cb;
t->set_sink_volume = pa_bluetooth_transport_set_sink_volume;
/* A2DP Absolute Volume is optional but BlueZ unconditionally reports
* feature category 2, meaning supporting it is mandatory.
* PulseAudio can and should perform the attenuation anyway in
......@@ -192,6 +192,7 @@ void pa_bluetooth_transport_set_state(pa_bluetooth_transport *t, pa_bluetooth_tr
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);
void pa_bluetooth_transport_load_a2dp_sink_volume(pa_bluetooth_transport *t);
bool pa_bluetooth_device_any_transport_connected(const pa_bluetooth_device *d);
bool pa_bluetooth_device_switch_codec(pa_bluetooth_device *device, pa_bluetooth_profile_t profile, pa_hashmap *capabilities_hashmap, const pa_a2dp_endpoint_conf *endpoint_conf, void (*codec_switch_cb)(bool, pa_bluetooth_profile_t profile, void *), void *userdata);
......@@ -1685,6 +1685,22 @@ static int start_thread(struct userdata *u) {
if (u->bt_codec)
pa_proplist_sets(u->card->proplist, PA_PROP_BLUETOOTH_CODEC, u->bt_codec->name);
/* Now that everything is set up we are ready to check for the Volume property.
* Sometimes its initial "change" notification arrives too early when the sink
* is not available or still in UNLINKED state; check it again here to know if
* our sink peer supports Absolute Volume; in that case we should not perform
* any attenuation but delegate all set_volume calls to the peer through this
* Volume property.
* Note that this works the other way around if the peer is in source profile:
* we are rendering audio and hence responsible for applying attenuation. The
* set_volume callback is always registered, and Volume is always passed to
* BlueZ unconditionally. BlueZ only sends a notification to the peer if it
* registered a notification request for absolute volume previously.
if (u->transport && u->sink)
return 0;
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment