From 27cdac3cd6b65cd6ee68d461d3227d24db828d5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Mon, 4 May 2026 13:02:36 +0200 Subject: [PATCH] bluez5: bap: Advertise ASCS properties BAP/USR/ADV/BV-01-C test requires to advertise Audio Stream Control Service properties with General Announcement type and supported contexts values for sink and source which should be in sync with the PACS values. --- spa/plugins/bluez5/bap-codec-caps.h | 13 ++ spa/plugins/bluez5/bluez5-dbus.c | 215 +++++++++++++++++++++++++++- spa/plugins/bluez5/defs.h | 5 + 3 files changed, 232 insertions(+), 1 deletion(-) diff --git a/spa/plugins/bluez5/bap-codec-caps.h b/spa/plugins/bluez5/bap-codec-caps.h index 8e0f6d9c5..a199cee55 100644 --- a/spa/plugins/bluez5/bap-codec-caps.h +++ b/spa/plugins/bluez5/bap-codec-caps.h @@ -146,6 +146,9 @@ #define BAP_CONTEXT_ALL 0x0fff +#define BAP_ANNOUNCEMENT_GENERAL 0x00 +#define BAP_ANNOUNCEMENT_TARGETED 0x01 + #define BT_ISO_QOS_CIG_UNSET 0xff #define BT_ISO_QOS_CIS_UNSET 0xff @@ -154,6 +157,8 @@ #define BT_ISO_QOS_TARGET_LATENCY_RELIABILITY 0x03 +#define BT_ASCS_UUID "0000184e-0000-1000-8000-00805f9b34fb" + #define BT_TMAP_UUID "00001855-0000-1000-8000-00805f9b34fb" #define BT_TMAP_ROLE_CG_STR "cg" @@ -285,4 +290,12 @@ static const struct { { BAP_CHANNEL_RS, SPA_AUDIO_CHANNEL_SR }, /* is it the right mapping? */ }; +struct bap_ascs_adv { + uint8_t announcement_type; + uint16_t available_sink_contexts; + uint16_t available_source_contexts; + uint8_t metadata_length; + uint8_t metadata[]; +} __attribute__((packed)); + #endif diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index aac9cb951..9e5db3e8f 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -1697,8 +1697,10 @@ static void adapter_free(struct spa_bt_adapter *adapter) } } - SPA_FOR_EACH_ELEMENT_VAR(adapter->apps, app) + SPA_FOR_EACH_ELEMENT_VAR(adapter->apps, app) { cancel_and_unref(&app->register_call); + cancel_and_unref(&app->register_adv_call); + } spa_bt_player_destroy(adapter->dummy_player); @@ -5779,6 +5781,160 @@ static DBusHandlerResult object_manager_handler_bap(DBusConnection *c, DBusMessa return object_manager_handler(c, m, user_data, true); } +static int append_le_service_data(struct spa_bt_monitor *monitor, DBusMessageIter *iter) +{ + struct bap_ascs_adv *adv; + int adv_len; + struct ltv_writer writer; + DBusMessageIter array_it; + + adv_len = sizeof(struct bap_ascs_adv) + 4; + adv = malloc(adv_len); + if (!adv) + return -1; + + adv->announcement_type = BAP_ANNOUNCEMENT_GENERAL; + adv->available_sink_contexts = htobs(monitor->bap_sink_qos.supported_context); + adv->available_source_contexts = htobs(monitor->bap_source_qos.supported_context); + adv->metadata_length = 4; + writer = LTV_WRITER(adv->metadata, 4); + ltv_writer_uint16(&writer, BAP_META_TYPE_PREFERRED_CONTEXT, htobs(BAP_CONTEXT_UNSPECIFIED)); + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &array_it); + append_basic_array_variant_dict_entry(&array_it, BT_ASCS_UUID, "ay", "y", DBUS_TYPE_BYTE, adv, adv_len); + dbus_message_iter_close_container(iter, &array_it); + + return 0; +} + +static DBusHandlerResult object_manager_handler_bap_adv(DBusConnection *c, DBusMessage *m, void *user_data) +{ + struct spa_bt_monitor *monitor = user_data; + const char *path, *interface, *member; + DBusHandlerResult res = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + path = dbus_message_get_path(m); + interface = dbus_message_get_interface(m); + member = dbus_message_get_member(m); + + spa_log_debug(monitor->log, "dbus: path=%s, interface=%s, member=%s", path, interface, member); + + if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Get")) { + const char *iface, *name; + DBusMessage *r; + DBusMessageIter i, v; + + if (!dbus_message_get_args(m, NULL, + DBUS_TYPE_STRING, &iface, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + if (!spa_streq(iface, BLUEZ_LE_ADVERTISEMENT_INTERFACE)) { + r = dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, + "No such interface"); + if (!dbus_connection_send(c, r, NULL)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + return DBUS_HANDLER_RESULT_HANDLED; + } + + r = dbus_message_new_method_return(m); + if (r == NULL) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + if (spa_streq(name, "Type")) { + const char *str = "peripheral"; + + dbus_message_iter_init_append(r, &i); + dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &str); + } else if (spa_streq(name, "ServiceUUIDs")) { + const char *str = BT_ASCS_UUID; + + dbus_message_iter_init_append(r, &i); + dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, "s", &v); + dbus_message_iter_append_basic(&v, DBUS_TYPE_STRING, &str); + dbus_message_iter_close_container(&i, &v); + } else if (spa_streq(name, "ServiceData")) { + dbus_message_iter_init_append(r, &i); + if (append_le_service_data(monitor, &i)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + } else if (spa_streq(name, "SecondaryChannel")) { + const char *str = "2M"; + + dbus_message_iter_init_append(r, &i); + dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &str); + } else { + return res; + } + + if (!dbus_connection_send(monitor->conn, r, NULL)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + res = DBUS_HANDLER_RESULT_HANDLED; + } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "GetAll")) { + const char *iface; + DBusMessage *r; + DBusMessageIter i, v; + DBusMessageIter dict_entry_it, variant_it, array_it; + char *str; + + if (!dbus_message_get_args(m, NULL, DBUS_TYPE_STRING, &iface, DBUS_TYPE_INVALID)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + if (!spa_streq(iface, BLUEZ_LE_ADVERTISEMENT_INTERFACE)) { + r = dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, + "No such interface"); + if (!dbus_connection_send(c, r, NULL)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + return DBUS_HANDLER_RESULT_HANDLED; + } + + r = dbus_message_new_method_return(m); + if (r == NULL) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + dbus_message_iter_init_append(r, &i); + dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, "{sv}", &v); + str = "peripheral"; + append_basic_variant_dict_entry(&v, "Type", DBUS_TYPE_STRING, "s", &str); + str = "2M"; + append_basic_variant_dict_entry(&v, "SecondaryChannel", DBUS_TYPE_STRING, "s", &str); + + dbus_message_iter_open_container(&v, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_it); + str = "ServiceUUIDs"; + dbus_message_iter_append_basic(&dict_entry_it, DBUS_TYPE_STRING, &str); + dbus_message_iter_open_container(&dict_entry_it, DBUS_TYPE_VARIANT, "as", &variant_it); + dbus_message_iter_open_container(&variant_it, DBUS_TYPE_ARRAY, "s", &array_it); + str = BT_ASCS_UUID; + dbus_message_iter_append_basic(&array_it, DBUS_TYPE_STRING, &str); + dbus_message_iter_close_container(&variant_it, &array_it); + dbus_message_iter_close_container(&dict_entry_it, &variant_it); + dbus_message_iter_close_container(&v, &dict_entry_it); + + dbus_message_iter_open_container(&v, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_it); + str = "ServiceData"; + dbus_message_iter_append_basic(&dict_entry_it, DBUS_TYPE_STRING, &str); + dbus_message_iter_open_container(&dict_entry_it, DBUS_TYPE_VARIANT, "a{sv}", &variant_it); + if (append_le_service_data(monitor, &variant_it)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + dbus_message_iter_close_container(&dict_entry_it, &variant_it); + dbus_message_iter_close_container(&v, &dict_entry_it); + + dbus_message_iter_close_container(&i, &v); + + if (!dbus_connection_send(monitor->conn, r, NULL)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + res = DBUS_HANDLER_RESULT_HANDLED; + } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Set")) { + spa_log_info(monitor->log, "Set property is not supported"); + res = DBUS_HANDLER_RESULT_HANDLED; + } else if (dbus_message_is_method_call(m, BLUEZ_LE_ADVERTISEMENT_INTERFACE, "Release")) { + spa_log_info(monitor->log, "Release property is not supported"); + res = DBUS_HANDLER_RESULT_HANDLED; + } + + return res; +} + static void bluez_register_application_a2dp_reply(DBusPendingCall *pending, void *user_data) { struct spa_bt_adapter *adapter = user_data; @@ -5804,11 +5960,35 @@ static void bluez_register_application_a2dp_reply(DBusPendingCall *pending, void app->registered = true; } +static void bluez_register_le_advertisement_reply(DBusPendingCall *pending, void *user_data) +{ + struct spa_bt_adapter *adapter = user_data; + struct spa_bt_monitor *monitor = adapter->monitor; + struct bluez_app *app = &adapter->apps[BLUEZ_APP_BAP]; + + spa_assert(app->register_adv_call == pending); + spa_autoptr(DBusMessage) r = steal_reply_and_unref(&app->register_adv_call); + if (r == NULL) + return; + + if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { + spa_log_warn(monitor->log, "BlueZ D-Bus ObjectManager not available"); + return; + } + if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { + spa_log_error(monitor->log, "RegisterAdvertisement() failed: %s", + dbus_message_get_error_name(r)); + return; + } +} + static void bluez_register_application_bap_reply(DBusPendingCall *pending, void *user_data) { struct spa_bt_adapter *adapter = user_data; struct spa_bt_monitor *monitor = adapter->monitor; struct bluez_app *app = &adapter->apps[BLUEZ_APP_BAP]; + DBusMessageIter object_it, dict_it; + const char *object_path = BAP_ADVERTISEMENT_PATH; spa_assert(app->register_call == pending); spa_autoptr(DBusMessage) r = steal_reply_and_unref(&app->register_call); @@ -5822,6 +6002,22 @@ static void bluez_register_application_bap_reply(DBusPendingCall *pending, void } app->registered = true; + + spa_autoptr(DBusMessage) m = dbus_message_new_method_call(BLUEZ_SERVICE, + adapter->path, + BLUEZ_LE_ADVERTISING_MANAGER_INTERFACE, + "RegisterAdvertisement"); + if (m == NULL) + return; + + dbus_message_iter_init_append(m, &object_it); + dbus_message_iter_append_basic(&object_it, DBUS_TYPE_OBJECT_PATH, &object_path); + dbus_message_iter_open_container(&object_it, DBUS_TYPE_ARRAY, "{sv}", &dict_it); + dbus_message_iter_close_container(&object_it, &dict_it); + + app->register_adv_call = send_with_reply(monitor->conn, m, bluez_register_le_advertisement_reply, adapter); + if (!app->register_adv_call) + return; } static int register_media_endpoint(struct spa_bt_monitor *monitor, @@ -5859,6 +6055,10 @@ static int register_media_application(struct spa_bt_monitor * monitor) static const DBusObjectPathVTable vtable_object_manager_bap = { .message_function = object_manager_handler_bap, }; + static const DBusObjectPathVTable vtable_object_manager_bap_adv = { + .message_function = object_manager_handler_bap_adv, + }; + bool le_audio; spa_log_info(monitor->log, "Registering DBus media object manager: %s", A2DP_OBJECT_MANAGER_PATH); @@ -5884,9 +6084,21 @@ static int register_media_application(struct spa_bt_monitor * monitor) if (codec->kind == MEDIA_CODEC_BAP) { register_media_endpoint(monitor, codec, SPA_BT_MEDIA_SOURCE_BROADCAST); register_media_endpoint(monitor, codec, SPA_BT_MEDIA_SINK_BROADCAST); + le_audio = true; } } + if (!le_audio) + return 0; + + spa_log_info(monitor->log, "Registering DBus BAP advertisement : %s", + BAP_ADVERTISEMENT_PATH); + + if (!dbus_connection_register_object_path(monitor->conn, + BAP_ADVERTISEMENT_PATH, + &vtable_object_manager_bap_adv, monitor)) + return -EIO; + return 0; } @@ -5923,6 +6135,7 @@ static void unregister_media_application(struct spa_bt_monitor * monitor) } } + dbus_connection_unregister_object_path(monitor->conn, BAP_ADVERTISEMENT_PATH); dbus_connection_unregister_object_path(monitor->conn, BAP_OBJECT_MANAGER_PATH); dbus_connection_unregister_object_path(monitor->conn, A2DP_OBJECT_MANAGER_PATH); } diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index 801b388b1..cb06551e7 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -34,6 +34,8 @@ extern "C" { #define BLUEZ_MEDIA_TRANSPORT_INTERFACE BLUEZ_SERVICE ".MediaTransport1" #define BLUEZ_INTERFACE_BATTERY_PROVIDER BLUEZ_SERVICE ".BatteryProvider1" #define BLUEZ_INTERFACE_BATTERY_PROVIDER_MANAGER BLUEZ_SERVICE ".BatteryProviderManager1" +#define BLUEZ_LE_ADVERTISING_MANAGER_INTERFACE BLUEZ_SERVICE ".LEAdvertisingManager1" +#define BLUEZ_LE_ADVERTISEMENT_INTERFACE BLUEZ_SERVICE ".LEAdvertisement1" #define DBUS_INTERFACE_OBJECT_MANAGER "org.freedesktop.DBus.ObjectManager" #define DBUS_SIGNAL_INTERFACES_ADDED "InterfacesAdded" @@ -154,6 +156,8 @@ extern "C" { #define BAP_BROADCAST_SOURCE_ENDPOINT BAP_OBJECT_MANAGER_PATH "/BAPBroadcastSource" #define BAP_BROADCAST_SINK_ENDPOINT BAP_OBJECT_MANAGER_PATH "/BAPBroadcastSink" +#define BAP_ADVERTISEMENT_PATH "/MediaLEAdvertisement" + #define SPA_BT_UNKNOWN_DELAY 0 #define SPA_BT_NO_BATTERY ((uint8_t)255) @@ -376,6 +380,7 @@ struct spa_bt_adapter { struct bluez_app { DBusPendingCall *register_call; bool registered; + DBusPendingCall *register_adv_call; } apps[BLUEZ_APP_LAST]; unsigned int has_msbc:1;