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
This commit is contained in:
Wim Taymans 2021-01-10 20:53:59 +01:00
parent 7aabd50d1e
commit 4d15df6f24
5 changed files with 193 additions and 22 deletions

View file

@ -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 */

View file

@ -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;
}

View file

@ -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++) {

View file

@ -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;

View file

@ -37,6 +37,7 @@
#include <spa/utils/result.h>
#include <spa/utils/names.h>
#include <spa/utils/result.h>
#include <spa/utils/keys.h>
#include <spa/pod/builder.h>
#include <spa/pod/parser.h>
#include <spa/param/props.h>
@ -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;