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;