From 4d15df6f24de272b920355b2c82909faef644f9b Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Sun, 10 Jan 2021 20:53:59 +0100 Subject: [PATCH] bluez5: improve device and node properties Set form factor, name routes based on form factor. Improve profile names Make card and node names like pulseaudio, with bt address in them. Fill in port type in the route info. Fixes #544 --- spa/include/spa/utils/keys.h | 1 + spa/plugins/bluez5/bluez5-dbus.c | 15 ++- spa/plugins/bluez5/bluez5-device.c | 105 +++++++++++++++++---- spa/plugins/bluez5/defs.h | 74 +++++++++++++++ src/examples/media-session/bluez-monitor.c | 20 +++- 5 files changed, 193 insertions(+), 22 deletions(-) diff --git a/spa/include/spa/utils/keys.h b/spa/include/spa/utils/keys.h index feaef45f4..c1e0a1af4 100644 --- a/spa/include/spa/utils/keys.h +++ b/spa/include/spa/utils/keys.h @@ -112,6 +112,7 @@ extern "C" { #define SPA_KEY_API_BLUEZ5_PROFILE "api.bluez5.profile" /**< a bluetooth profile */ #define SPA_KEY_API_BLUEZ5_ADDRESS "api.bluez5.address" /**< a bluetooth address */ #define SPA_KEY_API_BLUEZ5_CODEC "api.bluez5.codec" /**< a bluetooth codec */ +#define SPA_KEY_API_BLUEZ5_CLASS "api.bluez5.class" /**< a bluetooth class */ /** keys for jack api */ #define SPA_KEY_API_JACK "api.jack" /**< key for the JACK api */ diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 32a7076ca..67f4788ca 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -399,9 +399,9 @@ static void device_free(struct spa_bt_device *device) static int device_add(struct spa_bt_monitor *monitor, struct spa_bt_device *device) { struct spa_device_object_info info; - char dev[32]; + char dev[32], name[128], class[16]; struct spa_dict_item items[20]; - uint32_t n_items = 0; + uint32_t n_items = 0; if (device->added) return 0; @@ -415,18 +415,25 @@ static int device_add(struct spa_bt_monitor *monitor, struct spa_bt_device *devi items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "bluez5"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Audio/Device"); - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_NAME, device->name); + snprintf(name, sizeof(name), "bluez_card.%s", device->address); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_NAME, name); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_DESCRIPTION, device->name); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_ALIAS, device->alias); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_ICON_NAME, device->icon); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_FORM_FACTOR, + spa_bt_form_factor_name( + spa_bt_form_factor_from_class(device->bluetooth_class))); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_PATH, device->path); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_ADDRESS, device->address); snprintf(dev, sizeof(dev), "pointer:%p", device); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_DEVICE, dev); + snprintf(class, sizeof(class), "0x%06x", device->bluetooth_class); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_CLASS, class); info.props = &SPA_DICT_INIT(items, n_items); device->added = true; - spa_device_emit_object_info(&monitor->hooks, device->id, &info); + spa_device_emit_object_info(&monitor->hooks, device->id, &info); return 0; } diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index 03e12be36..ccdfd03ad 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -110,8 +110,9 @@ static void init_node(struct impl *this, struct node *node, uint32_t id) static void emit_node(struct impl *this, struct spa_bt_transport *t, uint32_t id, const char *factory_name) { + struct spa_bt_device *device = this->bt_dev; struct spa_device_object_info info; - struct spa_dict_item items[4]; + struct spa_dict_item items[5]; char transport[32], str_id[32]; snprintf(transport, sizeof(transport), "pointer:%p", t); @@ -121,6 +122,7 @@ static void emit_node(struct impl *this, struct spa_bt_transport *t, t->a2dp_codec ? t->a2dp_codec->name : "unknown"); snprintf(str_id, sizeof(str_id), "%d", id); items[3] = SPA_DICT_ITEM_INIT("card.profile.device", str_id); + items[4] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_ADDRESS, device->address); info = SPA_DEVICE_OBJECT_INFO_INIT(); info.type = SPA_TYPE_INTERFACE_Node; @@ -322,15 +324,18 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * { uint32_t profile = device->connected_profiles & (SPA_BT_PROFILE_A2DP_SINK | SPA_BT_PROFILE_A2DP_SOURCE); - name = "A2DP"; - if (profile == 0) + if (profile == 0) { return NULL; - else if (profile == SPA_BT_PROFILE_A2DP_SINK) + } else if (profile == SPA_BT_PROFILE_A2DP_SINK) { desc = "High Fidelity Playback (A2DP Sink)"; - else if (profile == SPA_BT_PROFILE_A2DP_SOURCE) + name = "a2dp-sink"; + } else if (profile == SPA_BT_PROFILE_A2DP_SOURCE) { desc = "High Fidelity Capture (A2DP Source)"; - else + name = "a2dp-source"; + } else { desc = "High Fidelity Duplex (A2DP Source/Sink)"; + name = "a2dp-duplex"; + } if (profile & SPA_BT_PROFILE_A2DP_SOURCE) n_source++; if (profile & SPA_BT_PROFILE_A2DP_SINK) @@ -341,15 +346,18 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * { uint32_t profile = device->connected_profiles & (SPA_BT_PROFILE_HEADSET_HEAD_UNIT | SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY); - name = "HSP/HFP"; - if (profile == 0) + if (profile == 0) { return NULL; - else if (profile == SPA_BT_PROFILE_HEADSET_HEAD_UNIT) + } else if (profile == SPA_BT_PROFILE_HEADSET_HEAD_UNIT) { desc = "Headset Head Unit (HSP/HFP)"; - else if (profile == SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY) + name = "headset-head-unit"; + } else if (profile == SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY) { desc = "Headset Audio Gateway (HSP/HFP)"; - else + name = "headset-audio-gateway"; + } else { desc = "Headset Audio (HSP/HFP)"; + name = "headset-audio"; + } n_source++; n_sink++; break; @@ -386,22 +394,79 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, uint32_t id, uint32_t port, uint32_t dev, uint32_t profile) { + struct spa_bt_device *device = this->bt_dev; struct spa_pod_frame f[2]; enum spa_direction direction; - const char *name, *description; + const char *name_prefix, *description, *port_type; enum spa_param_availability available; + enum spa_bt_form_factor ff; + char name[128]; uint32_t i; + ff = spa_bt_form_factor_from_class(device->bluetooth_class); + + switch (ff) { + case SPA_BT_FORM_FACTOR_HEADSET: + name_prefix = "headset"; + description = "Headset"; + port_type = "headset"; + break; + case SPA_BT_FORM_FACTOR_HANDSFREE: + name_prefix = "handsfree"; + description = "Handsfree"; + port_type = "handsfree"; + break; + case SPA_BT_FORM_FACTOR_MICROPHONE: + name_prefix = "microphone"; + description = "Microphone"; + port_type = "mic"; + break; + case SPA_BT_FORM_FACTOR_SPEAKER: + name_prefix = "speaker"; + description = "Speaker"; + port_type = "speaker"; + break; + case SPA_BT_FORM_FACTOR_HEADPHONE: + name_prefix = "headphone"; + description = "Headphone"; + port_type = "headphones"; + break; + case SPA_BT_FORM_FACTOR_PORTABLE: + name_prefix = "portable"; + description = "Portable"; + port_type = "portable"; + break; + case SPA_BT_FORM_FACTOR_CAR: + name_prefix = "car"; + description = "Car"; + port_type = "car"; + break; + case SPA_BT_FORM_FACTOR_HIFI: + name_prefix = "hifi"; + description = "HiFi"; + port_type = "hifi"; + break; + case SPA_BT_FORM_FACTOR_PHONE: + name_prefix = "phone"; + description = "Phone"; + port_type = "phone"; + break; + case SPA_BT_FORM_FACTOR_UNKNOWN: + default: + name_prefix = "bluetooth"; + description = "Bluetooth"; + port_type = "bluetooth"; + break; + } + switch (port) { case 0: direction = SPA_DIRECTION_INPUT; - name = "bluetooth-input"; - description = "Bluetooth input"; + snprintf(name, sizeof(name), "%s-input", name_prefix); break; case 1: direction = SPA_DIRECTION_OUTPUT; - name = "bluetooth-output"; - description = "Bluetooth Output"; + snprintf(name, sizeof(name), "%s-output", name_prefix); break; default: errno = -EINVAL; @@ -422,6 +487,14 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, SPA_PARAM_ROUTE_priority, SPA_POD_Int(0), SPA_PARAM_ROUTE_available, SPA_POD_Id(available), 0); + spa_pod_builder_prop(b, SPA_PARAM_ROUTE_info, 0); + spa_pod_builder_push_struct(b, &f[1]); + spa_pod_builder_int(b, 1); + spa_pod_builder_add(b, + SPA_POD_String("port.type"), + SPA_POD_String(port_type), + NULL); + spa_pod_builder_pop(b, &f[1]); spa_pod_builder_prop(b, SPA_PARAM_ROUTE_profiles, 0); spa_pod_builder_push_array(b, &f[1]); for (i = 0; i < 3; i++) { diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index 148965cfa..f5721715d 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -216,6 +216,80 @@ struct spa_bt_adapter { unsigned int application_registered:1; }; +enum spa_bt_form_factor { + SPA_BT_FORM_FACTOR_UNKNOWN, + SPA_BT_FORM_FACTOR_HEADSET, + SPA_BT_FORM_FACTOR_HANDSFREE, + SPA_BT_FORM_FACTOR_MICROPHONE, + SPA_BT_FORM_FACTOR_SPEAKER, + SPA_BT_FORM_FACTOR_HEADPHONE, + SPA_BT_FORM_FACTOR_PORTABLE, + SPA_BT_FORM_FACTOR_CAR, + SPA_BT_FORM_FACTOR_HIFI, + SPA_BT_FORM_FACTOR_PHONE, +}; + +static inline const char *spa_bt_form_factor_name(enum spa_bt_form_factor ff) +{ + switch (ff) { + case SPA_BT_FORM_FACTOR_HEADSET: + return "headset"; + case SPA_BT_FORM_FACTOR_HANDSFREE: + return "hands-free"; + case SPA_BT_FORM_FACTOR_MICROPHONE: + return "microphone"; + case SPA_BT_FORM_FACTOR_SPEAKER: + return "speaker"; + case SPA_BT_FORM_FACTOR_HEADPHONE: + return "headphone"; + case SPA_BT_FORM_FACTOR_PORTABLE: + return "portable"; + case SPA_BT_FORM_FACTOR_CAR: + return "car"; + case SPA_BT_FORM_FACTOR_HIFI: + return "hifi"; + case SPA_BT_FORM_FACTOR_PHONE: + return "phone"; + case SPA_BT_FORM_FACTOR_UNKNOWN: + default: + return "unknown"; + } +} + +static inline enum spa_bt_form_factor spa_bt_form_factor_from_class(uint32_t bluetooth_class) +{ + uint32_t major, minor; + /* See Bluetooth Assigned Numbers: + * https://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm */ + major = (bluetooth_class >> 8) & 0x1F; + minor = (bluetooth_class >> 2) & 0x3F; + + switch (major) { + case 2: + return SPA_BT_FORM_FACTOR_PHONE; + case 4: + switch (minor) { + case 1: + return SPA_BT_FORM_FACTOR_HEADSET; + case 2: + return SPA_BT_FORM_FACTOR_HANDSFREE; + case 4: + return SPA_BT_FORM_FACTOR_MICROPHONE; + case 5: + return SPA_BT_FORM_FACTOR_SPEAKER; + case 6: + return SPA_BT_FORM_FACTOR_HEADPHONE; + case 7: + return SPA_BT_FORM_FACTOR_PORTABLE; + case 8: + return SPA_BT_FORM_FACTOR_CAR; + case 10: + return SPA_BT_FORM_FACTOR_HIFI; + } + } + return SPA_BT_FORM_FACTOR_UNKNOWN; +} + struct spa_bt_device { struct spa_list link; struct spa_bt_monitor *monitor; diff --git a/src/examples/media-session/bluez-monitor.c b/src/examples/media-session/bluez-monitor.c index 3ba263fe0..46154b2e6 100644 --- a/src/examples/media-session/bluez-monitor.c +++ b/src/examples/media-session/bluez-monitor.c @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -126,7 +127,7 @@ static struct node *bluez5_create_node(struct device *device, uint32_t id, struct pw_context *context = impl->session->context; struct pw_impl_factory *factory; int res; - const char *str; + const char *prefix, *str, *profile; pw_log_debug("new node %u", id); @@ -153,8 +154,23 @@ static struct node *bluez5_create_node(struct device *device, uint32_t id, str = "bluetooth-device"; pw_properties_setf(node->props, PW_KEY_DEVICE_ID, "%d", device->device_id); - pw_properties_setf(node->props, PW_KEY_NODE_NAME, "%s.%s", info->factory_name, str); pw_properties_set(node->props, PW_KEY_NODE_DESCRIPTION, str); + + profile = pw_properties_get(node->props, SPA_KEY_API_BLUEZ5_PROFILE); + if (profile == NULL) + profile = "unknown"; + str = pw_properties_get(node->props, SPA_KEY_API_BLUEZ5_ADDRESS); + if (str == NULL) + str = pw_properties_get(device->props, SPA_KEY_DEVICE_NAME); + + if (strstr(info->factory_name, "sink") != NULL) + prefix = "bluez_sink"; + else if (strstr(info->factory_name, "source") != NULL) + prefix = "bluez_source"; + else + prefix = info->factory_name; + + pw_properties_setf(node->props, PW_KEY_NODE_NAME, "%s.%s.%s", prefix, str, profile); pw_properties_set(node->props, PW_KEY_FACTORY_NAME, info->factory_name); node->impl = impl;