summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSanchayan Maity <sanchayan@asymptotic.io>2020-12-15 16:51:11 +0530
committerSanchayan Maity <sanchayan@asymptotic.io>2021-01-19 13:43:42 +0530
commit5284b450e74d65d127dc248cea3aeec50554a170 (patch)
treecc16036f04b1434f1295e50d384b16ef4ca79a17
parentfca6a2fd68ade78a82ebd96b9bad2410086bb3e2 (diff)
bluez5-util: Add support for using RegisterApplication D-Bus API
A2DP codec switching needs new version of bluez as older version does not provide needed org.freedesktop.DBus.ObjectManager and RegisterApplication DBus APIs. As a preparation for the next step of adding codecs and then codec switching, add support for using the new API. Getting list of supported codecs by remote device is supported only by new version of bluez daemon. If the RegisterApplication API fails, we fallback to the older RegisterEndpoint API and only register the SBC codec. For more information, see https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/media-api.txt This changeset has been taken from Pali Rohár's A2DP codec patch series but separated out to only include the D-Bus specific changes. Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/440>
-rw-r--r--src/modules/bluetooth/bluez5-util.c271
-rw-r--r--src/modules/bluetooth/bluez5-util.h1
2 files changed, 256 insertions, 16 deletions
diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index a21896ede..4c2a77612 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -34,6 +34,7 @@
#include <pulsecore/refcnt.h>
#include <pulsecore/shared.h>
+#include "a2dp-codec-api.h"
#include "a2dp-codec-util.h"
#include "a2dp-codecs.h"
@@ -50,8 +51,34 @@
#define BLUEZ_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported"
-#define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource"
-#define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink"
+#define A2DP_OBJECT_MANAGER_PATH "/MediaEndpoint"
+#define A2DP_SOURCE_ENDPOINT A2DP_OBJECT_MANAGER_PATH "/A2DPSource"
+#define A2DP_SINK_ENDPOINT A2DP_OBJECT_MANAGER_PATH "/A2DPSink"
+
+#define OBJECT_MANAGER_INTROSPECT_XML \
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
+ "<node>\n" \
+ " <interface name=\"org.freedesktop.DBus.ObjectManager\">\n" \
+ " <method name=\"GetManagedObjects\">\n" \
+ " <arg name=\"objects\" direction=\"out\" type=\"a{oa{sa{sv}}}\"/>\n" \
+ " </method>\n" \
+ " <signal name=\"InterfacesAdded\">\n" \
+ " <arg name=\"object\" type=\"o\"/>\n" \
+ " <arg name=\"interfaces\" type=\"a{sa{sv}}\"/>\n" \
+ " </signal>\n" \
+ " <signal name=\"InterfacesRemoved\">\n" \
+ " <arg name=\"object\" type=\"o\"/>\n" \
+ " <arg name=\"interfaces\" type=\"as\"/>\n" \
+ " </signal>\n" \
+ " </interface>\n" \
+ " <interface name=\"org.freedesktop.DBus.Introspectable\">\n" \
+ " <method name=\"Introspect\">\n" \
+ " <arg name=\"data\" direction=\"out\" type=\"s\"/>\n" \
+ " </method>\n" \
+ " </interface>\n" \
+ " <node name=\"A2DPSink\"/>\n" \
+ " <node name=\"A2DPSource\"/>\n" \
+ "</node>\n"
#define ENDPOINT_INTROSPECT_XML \
DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
@@ -857,7 +884,7 @@ static void parse_adapter_properties(pa_bluetooth_adapter *a, DBusMessageIter *i
}
}
-static void register_endpoint_reply(DBusPendingCall *pending, void *userdata) {
+static void register_legacy_sbc_endpoint_reply(DBusPendingCall *pending, void *userdata) {
DBusMessage *r;
pa_dbus_pending *p;
pa_bluetooth_discovery *y;
@@ -889,7 +916,7 @@ finish:
pa_xfree(endpoint);
}
-static void register_endpoint(pa_bluetooth_discovery *y, const pa_a2dp_codec *a2dp_codec, const char *path, const char *endpoint, const char *uuid) {
+static void register_legacy_sbc_endpoint(pa_bluetooth_discovery *y, const pa_a2dp_codec *a2dp_codec, const char *path, const char *endpoint, const char *uuid) {
DBusMessage *m;
DBusMessageIter i, d;
uint8_t capabilities[MAX_A2DP_CAPS_SIZE];
@@ -914,7 +941,92 @@ static void register_endpoint(pa_bluetooth_discovery *y, const pa_a2dp_codec *a2
dbus_message_iter_close_container(&i, &d);
- send_and_add_to_pending(y, m, register_endpoint_reply, pa_xstrdup(endpoint));
+ send_and_add_to_pending(y, m, register_legacy_sbc_endpoint_reply, pa_xstrdup(endpoint));
+}
+
+static void register_application_reply(DBusPendingCall *pending, void *userdata) {
+ DBusMessage *r;
+ pa_dbus_pending *p;
+ pa_bluetooth_adapter *a;
+ pa_bluetooth_discovery *y;
+ char *path;
+ bool fallback = true;
+
+ pa_assert(pending);
+ pa_assert_se(p = userdata);
+ pa_assert_se(y = p->context_data);
+ pa_assert_se(path = p->call_data);
+ pa_assert_se(r = dbus_pending_call_steal_reply(pending));
+
+ if (dbus_message_is_error(r, BLUEZ_ERROR_NOT_SUPPORTED)) {
+ pa_log_info("Couldn't register media application for adapter %s because it is disabled in BlueZ", path);
+ goto finish;
+ }
+
+ if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
+ pa_log_warn(BLUEZ_MEDIA_INTERFACE ".RegisterApplication() failed: %s: %s",
+ dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
+ pa_log_warn("Couldn't register media application for adapter %s", path);
+ goto finish;
+ }
+
+ a = pa_hashmap_get(y->adapters, path);
+ if (!a) {
+ pa_log_error("Couldn't register media application for adapter %s because it does not exist anymore", path);
+ goto finish;
+ }
+
+ fallback = false;
+ a->application_registered = true;
+ pa_log_debug("Media application for adapter %s was successfully registered", path);
+
+finish:
+ dbus_message_unref(r);
+
+ PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
+ pa_dbus_pending_free(p);
+
+ if (fallback) {
+ /* If bluez does not support RegisterApplication, fallback to old legacy API with just one SBC codec */
+ const pa_a2dp_codec *a2dp_codec_sbc;
+ a2dp_codec_sbc = pa_bluetooth_get_a2dp_codec("sbc");
+ pa_assert(a2dp_codec_sbc);
+ register_legacy_sbc_endpoint(y, a2dp_codec_sbc, path, A2DP_SINK_ENDPOINT "/sbc",
+ PA_BLUETOOTH_UUID_A2DP_SINK);
+ register_legacy_sbc_endpoint(y, a2dp_codec_sbc, path, A2DP_SOURCE_ENDPOINT "/sbc",
+ PA_BLUETOOTH_UUID_A2DP_SOURCE);
+ pa_log_warn("Only SBC codec is available for A2DP profiles");
+ }
+
+ pa_xfree(path);
+}
+
+static void register_application(pa_bluetooth_adapter *a) {
+ DBusMessage *m;
+ DBusMessageIter i, d;
+ const char *object_manager_path = A2DP_OBJECT_MANAGER_PATH;
+
+ if (a->application_registered) {
+ pa_log_info("Media application is already registered for adapter %s", a->path);
+ return;
+ }
+
+ pa_log_debug("Registering media application for adapter %s", a->path);
+
+ pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, a->path,
+ BLUEZ_MEDIA_INTERFACE, "RegisterApplication"));
+
+ dbus_message_iter_init_append(m, &i);
+ pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &object_manager_path));
+ dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING
+ DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
+ &d);
+ dbus_message_iter_close_container(&i, &d);
+
+ send_and_add_to_pending(a->discovery, m, register_application_reply, pa_xstrdup(a->path));
}
static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessageIter *dict_i) {
@@ -944,8 +1056,6 @@ static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessa
pa_assert(dbus_message_iter_get_arg_type(&iface_i) == DBUS_TYPE_ARRAY);
if (pa_streq(interface, BLUEZ_ADAPTER_INTERFACE)) {
-
- const pa_a2dp_codec *a2dp_codec_sbc;
pa_bluetooth_adapter *a;
if ((a = pa_hashmap_get(y->adapters, path))) {
@@ -961,14 +1071,7 @@ static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessa
if (!a->valid)
return;
- /* Currently only one A2DP codec is supported, so register only SBC
- * Support for multiple codecs needs to use a new Bluez API which
- * pulseaudio does not implement yet, patches are waiting in queue */
- a2dp_codec_sbc = pa_bluetooth_get_a2dp_codec("sbc");
- pa_assert(a2dp_codec_sbc);
- register_endpoint(y, a2dp_codec_sbc, path, A2DP_SINK_ENDPOINT "/sbc", PA_BLUETOOTH_UUID_A2DP_SINK);
- register_endpoint(y, a2dp_codec_sbc, path, A2DP_SOURCE_ENDPOINT "/sbc", PA_BLUETOOTH_UUID_A2DP_SOURCE);
-
+ register_application(a);
} else if (pa_streq(interface, BLUEZ_DEVICE_INTERFACE)) {
if ((d = pa_hashmap_get(y->devices, path))) {
@@ -982,7 +1085,6 @@ static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessa
pa_log_debug("Device %s found", d->path);
parse_device_properties(d, &iface_i);
-
} else
pa_log_debug("Unknown interface %s found, skipping", interface);
@@ -1419,6 +1521,7 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
pa_bluetooth_transport_put(t);
pa_log_debug("Transport %s available for profile %s", t->path, pa_bluetooth_profile_to_string(t->profile));
+ pa_log_info("Selected codec: %s", a2dp_codec->name);
return NULL;
@@ -1582,6 +1685,134 @@ static void endpoint_done(pa_bluetooth_discovery *y, const char *endpoint) {
dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), endpoint);
}
+static void append_a2dp_object(DBusMessageIter *iter, const char *endpoint, const char *uuid, uint8_t codec_id, uint8_t *capabilities, uint8_t capabilities_size) {
+ const char *interface_name = BLUEZ_MEDIA_ENDPOINT_INTERFACE;
+ DBusMessageIter object, array, entry, dict;
+
+ dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &object);
+ pa_assert_se(dbus_message_iter_append_basic(&object, DBUS_TYPE_OBJECT_PATH, &endpoint));
+
+ dbus_message_iter_open_container(&object, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING
+ DBUS_TYPE_ARRAY_AS_STRING
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING
+ DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
+ &array);
+
+ dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, NULL, &entry);
+ pa_assert_se(dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface_name));
+
+ dbus_message_iter_open_container(&entry, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING
+ DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
+ &dict);
+
+ pa_dbus_append_basic_variant_dict_entry(&dict, "UUID", DBUS_TYPE_STRING, &uuid);
+ pa_dbus_append_basic_variant_dict_entry(&dict, "Codec", DBUS_TYPE_BYTE, &codec_id);
+ pa_dbus_append_basic_array_variant_dict_entry(&dict, "Capabilities", DBUS_TYPE_BYTE,
+ capabilities, capabilities_size);
+
+ dbus_message_iter_close_container(&entry, &dict);
+ dbus_message_iter_close_container(&array, &entry);
+ dbus_message_iter_close_container(&object, &array);
+ dbus_message_iter_close_container(iter, &object);
+}
+
+static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage *m, void *userdata) {
+ struct pa_bluetooth_discovery *y = userdata;
+ DBusMessage *r;
+ const char *path, *interface, *member;
+
+ pa_assert(y);
+
+ path = dbus_message_get_path(m);
+ interface = dbus_message_get_interface(m);
+ member = dbus_message_get_member(m);
+
+ pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member);
+
+ if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
+ const char *xml = OBJECT_MANAGER_INTROSPECT_XML;
+
+ pa_assert_se(r = dbus_message_new_method_return(m));
+ pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID));
+ } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.ObjectManager", "GetManagedObjects")) {
+ DBusMessageIter iter, array;
+ int i;
+
+ pa_assert_se(r = dbus_message_new_method_return(m));
+
+ dbus_message_iter_init_append(r, &iter);
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_OBJECT_PATH_AS_STRING
+ DBUS_TYPE_ARRAY_AS_STRING
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING
+ DBUS_TYPE_ARRAY_AS_STRING
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING
+ DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
+ &array);
+
+ for (i = 0; i < pa_bluetooth_a2dp_codec_count(); i++) {
+ const pa_a2dp_codec *a2dp_codec;
+ uint8_t capabilities[MAX_A2DP_CAPS_SIZE];
+ uint8_t capabilities_size;
+ uint8_t codec_id;
+ char *endpoint;
+
+ a2dp_codec = pa_bluetooth_a2dp_codec_iter(i);
+ codec_id = a2dp_codec->id.codec_id;
+ capabilities_size = a2dp_codec->fill_capabilities(capabilities);
+ pa_assert(capabilities_size != 0);
+
+ endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, a2dp_codec->name);
+ append_a2dp_object(&array, endpoint, PA_BLUETOOTH_UUID_A2DP_SINK, codec_id,
+ capabilities, capabilities_size);
+ pa_xfree(endpoint);
+
+ endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, a2dp_codec->name);
+ append_a2dp_object(&array, endpoint, PA_BLUETOOTH_UUID_A2DP_SOURCE, codec_id,
+ capabilities, capabilities_size);
+ pa_xfree(endpoint);
+ }
+
+ dbus_message_iter_close_container(&iter, &array);
+ } else
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+ pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL));
+ dbus_message_unref(r);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static void object_manager_init(pa_bluetooth_discovery *y) {
+ static const DBusObjectPathVTable vtable = {
+ .message_function = object_manager_handler,
+ };
+
+ pa_assert(y);
+ pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection),
+ A2DP_OBJECT_MANAGER_PATH, &vtable, y));
+}
+
+static void object_manager_done(pa_bluetooth_discovery *y) {
+ pa_assert(y);
+ dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection),
+ A2DP_OBJECT_MANAGER_PATH);
+}
+
pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backend) {
pa_bluetooth_discovery *y;
DBusError err;
@@ -1633,6 +1864,8 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backe
"type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
",arg0='" BLUEZ_DEVICE_INTERFACE "'",
"type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
+ ",arg0='" BLUEZ_MEDIA_ENDPOINT_INTERFACE "'",
+ "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
",arg0='" BLUEZ_MEDIA_TRANSPORT_INTERFACE "'",
NULL) < 0) {
pa_log_error("Failed to add D-Bus matches: %s", err.message);
@@ -1640,6 +1873,8 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backe
}
y->matches_added = true;
+ object_manager_init(y);
+
count = pa_bluetooth_a2dp_codec_count();
for (i = 0; i < count; i++) {
a2dp_codec = pa_bluetooth_a2dp_codec_iter(i);
@@ -1717,12 +1952,16 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
"type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',"
"member='PropertiesChanged',arg0='" BLUEZ_DEVICE_INTERFACE "'",
"type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',"
+ "member='PropertiesChanged',arg0='" BLUEZ_MEDIA_ENDPOINT_INTERFACE "'",
+ "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',"
"member='PropertiesChanged',arg0='" BLUEZ_MEDIA_TRANSPORT_INTERFACE "'",
NULL);
if (y->filter_added)
dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y);
+ object_manager_done(y);
+
count = pa_bluetooth_a2dp_codec_count();
for (i = 0; i < count; i++) {
a2dp_codec = pa_bluetooth_a2dp_codec_iter(i);
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index ff172e025..e1f0578a3 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -129,6 +129,7 @@ struct pa_bluetooth_adapter {
char *address;
bool valid;
+ bool application_registered;
};
#ifdef HAVE_BLUEZ_5_OFONO_HEADSET