diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c index c442ba6c2..f24e12e89 100644 --- a/src/modules/bluetooth/bluez5-util.c +++ b/src/modules/bluetooth/bluez5-util.c @@ -606,6 +606,16 @@ static void pa_bluetooth_transport_remote_volume_changed(pa_bluetooth_transport return; t->sink_volume = volume; hook = PA_BLUETOOTH_HOOK_TRANSPORT_SINK_VOLUME_CHANGED; + + /* 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 { pa_assert_not_reached(); } @@ -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(pending); + 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", + dbus_message_get_path(p->message), + dbus_message_get_error_name(r), + pa_dbus_get_error_message(r)); + 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); + +finish: + dbus_message_unref(r); + + PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p); + pa_dbus_pending_free(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); + pa_assert(t->device); + pa_assert(t->device->discovery); + + 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")); + pa_assert_se(dbus_message_append_args(m, + DBUS_TYPE_STRING, &mediatransport_str, + DBUS_TYPE_STRING, &volume_str, + DBUS_TYPE_INVALID)); + + 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) { + pa_assert(t); + + if (t->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) + /* A2DP Absolute Volume control (AVRCP 1.4) is optional */ + bluez5_transport_get_volume(t); +} + 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 diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h index c08a7a337..762ec50d0 100644 --- a/src/modules/bluetooth/bluez5-util.h +++ b/src/modules/bluetooth/bluez5-util.h @@ -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); diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c index fa29ef447..5dfe57652 100644 --- a/src/modules/bluetooth/module-bluez5-device.c +++ b/src/modules/bluetooth/module-bluez5-device.c @@ -1688,6 +1688,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) + pa_bluetooth_transport_load_a2dp_sink_volume(u->transport); + return 0; }