mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-01-26 14:13:19 +00:00
bluez5: parse and enable configuration of TMAP / GMAP features
Parse TMAP / GMAP features from MediaEndpoint:SupportedFeatures and pass them onto the codec in SelectProperties, so it can determine which mandatory features the device supports. Add configuration option for specifying which TMAP / GMAP feature bits we advertise to remote side. Although some of these could be determined automatically, for production systems it's better to have explicit option to specify which ones should be advertised as this may depend on HW capabilities.
This commit is contained in:
parent
c623886625
commit
2942bae034
@ -1257,6 +1257,18 @@ Available source contexts PACS bitmask of the the server.
|
||||
@PAR@ monitor-prop bluez5.bap-server-capabilities.source.supported-contexts # integer
|
||||
Supported source contexts PACS bitmask of the the server.
|
||||
|
||||
@PAR@ monitor-prop bluez5.bap-server-tmap-features = null # array of string
|
||||
Override advertised TMAP service features. See TMAP specification for their meaning.
|
||||
Possible values: "cg", "ct", "ums", "umr", "bms", "bmr".
|
||||
Default: none.
|
||||
|
||||
@PAR@ monitor-prop bluez5.bap-server-gmap-features = null # array of string
|
||||
Override advertised GMAP service features. See GMAP specification for their meaning.
|
||||
Possible values: "ugg", "ugt", "bgs", "bgr", "ugg-multiplex", "ugg-96kbps-source", "ugg-multisink",
|
||||
"ugt-source", "ugt-80kbps-source", "ugt-sink", "ugt-64kbps-sink", "ugt-multiplex", "ugt-multisink",
|
||||
"ugt-multisource", "bgs-96kbps", "bgr-multisink", "bgr-multiplex".
|
||||
Default: none.
|
||||
|
||||
## Device properties
|
||||
|
||||
@PAR@ device-prop bluez5.auto-connect # boolean
|
||||
|
||||
@ -153,6 +153,69 @@
|
||||
#define BT_ISO_QOS_TARGET_LATENCY_BALANCED 0x02
|
||||
#define BT_ISO_QOS_TARGET_LATENCY_RELIABILITY 0x03
|
||||
|
||||
|
||||
#define BT_TMAP_UUID "00001855-0000-1000-8000-00805f9b34fb"
|
||||
|
||||
#define BT_TMAP_ROLE_CG_STR "cg"
|
||||
#define BT_TMAP_ROLE_CT_STR "ct"
|
||||
#define BT_TMAP_ROLE_UMS_STR "ums"
|
||||
#define BT_TMAP_ROLE_UMR_STR "umr"
|
||||
#define BT_TMAP_ROLE_BMS_STR "bms"
|
||||
#define BT_TMAP_ROLE_BMR_STR "bmr"
|
||||
|
||||
#define BT_GMAP_ROLE_UGG_STR "ugg"
|
||||
#define BT_GMAP_ROLE_UGT_STR "ugt"
|
||||
#define BT_GMAP_ROLE_BGS_STR "bgs"
|
||||
#define BT_GMAP_ROLE_BGR_STR "bgr"
|
||||
|
||||
#define BT_TMAP_ROLE_LIST(role) \
|
||||
role(BT_TMAP_ROLE_CG) \
|
||||
role(BT_TMAP_ROLE_CT) \
|
||||
role(BT_TMAP_ROLE_UMS) \
|
||||
role(BT_TMAP_ROLE_UMR) \
|
||||
role(BT_TMAP_ROLE_BMS) \
|
||||
role(BT_TMAP_ROLE_BMR)
|
||||
|
||||
#define BT_GMAP_UUID "00001858-0000-1000-8000-00805f9b34fb"
|
||||
|
||||
#define BT_GMAP_UGG_MULTIPLEX_STR "ugg-multiplex"
|
||||
#define BT_GMAP_UGG_96KBPS_SOURCE_STR "ugg-96kbps-source"
|
||||
#define BT_GMAP_UGG_MULTISINK_STR "ugg-multisink"
|
||||
|
||||
#define BT_GMAP_UGT_SOURCE_STR "ugt-source"
|
||||
#define BT_GMAP_UGT_80KBPS_SOURCE_STR "ugt-80kbps-source"
|
||||
#define BT_GMAP_UGT_SINK_STR "ugt-sink"
|
||||
#define BT_GMAP_UGT_64KBPS_SINK_STR "ugt-64kbps-sink"
|
||||
#define BT_GMAP_UGT_MULTIPLEX_STR "ugt-multiplex"
|
||||
#define BT_GMAP_UGT_MULTISINK_STR "ugt-multisink"
|
||||
#define BT_GMAP_UGT_MULTISOURCE_STR "ugt-multisource"
|
||||
|
||||
#define BT_GMAP_BGS_96KBPS_STR "bgs-96kbps"
|
||||
|
||||
#define BT_GMAP_BGR_MULTISINK_STR "bgr-multisink"
|
||||
#define BT_GMAP_BGR_MULTIPLEX_STR "bgr-multiplex"
|
||||
|
||||
#define BT_GMAP_ROLE_LIST(role) \
|
||||
role(BT_GMAP_ROLE_UGG) \
|
||||
role(BT_GMAP_ROLE_UGT) \
|
||||
role(BT_GMAP_ROLE_BGS) \
|
||||
role(BT_GMAP_ROLE_BGR)
|
||||
|
||||
#define BT_GMAP_FEATURE_LIST(feature) \
|
||||
feature(BT_GMAP_UGG_MULTIPLEX) \
|
||||
feature(BT_GMAP_UGG_96KBPS_SOURCE) \
|
||||
feature(BT_GMAP_UGG_MULTISINK) \
|
||||
feature(BT_GMAP_UGT_SOURCE) \
|
||||
feature(BT_GMAP_UGT_80KBPS_SOURCE) \
|
||||
feature(BT_GMAP_UGT_SINK) \
|
||||
feature(BT_GMAP_UGT_64KBPS_SINK) \
|
||||
feature(BT_GMAP_UGT_MULTIPLEX) \
|
||||
feature(BT_GMAP_UGT_MULTISINK) \
|
||||
feature(BT_GMAP_UGT_MULTISOURCE) \
|
||||
feature(BT_GMAP_BGS_96KBPS) \
|
||||
feature(BT_GMAP_BGR_MULTISINK) \
|
||||
feature(BT_GMAP_BGR_MULTIPLEX)
|
||||
|
||||
struct bap_endpoint_qos {
|
||||
uint8_t framing;
|
||||
uint8_t phy;
|
||||
|
||||
@ -77,6 +77,11 @@ enum backend_selection {
|
||||
#define TRANSPORT_ERROR_TIMEOUT (2*BLUEZ_ACTION_RATE_MSEC*SPA_NSEC_PER_MSEC)
|
||||
|
||||
|
||||
struct bap_features {
|
||||
struct spa_dict dict;
|
||||
struct spa_dict_item items[32];
|
||||
};
|
||||
|
||||
struct spa_bt_monitor {
|
||||
struct spa_handle handle;
|
||||
struct spa_device device;
|
||||
@ -131,6 +136,8 @@ struct spa_bt_monitor {
|
||||
uint32_t bap_source_contexts;
|
||||
uint32_t bap_source_supported_contexts;
|
||||
|
||||
struct bap_features bap_features;
|
||||
|
||||
struct spa_bt_quirks *quirks;
|
||||
|
||||
#define MAX_SETTINGS 128
|
||||
@ -161,6 +168,8 @@ struct spa_bt_remote_endpoint {
|
||||
|
||||
struct bap_endpoint_qos qos;
|
||||
|
||||
struct bap_features bap_features;
|
||||
|
||||
bool asha_right_side;
|
||||
uint64_t hisyncid;
|
||||
};
|
||||
@ -658,6 +667,77 @@ static bool endpoint_should_be_registered(struct spa_bt_monitor *monitor,
|
||||
codec->fill_caps;
|
||||
}
|
||||
|
||||
static bool bap_features_add(struct bap_features *feat, const char *uuid, const char *name)
|
||||
{
|
||||
#define TMAP_ITEM(item) { BT_TMAP_UUID, item ##_STR, BT_TMAP_UUID ":" item ##_STR },
|
||||
#define GMAP_ITEM(item) { BT_GMAP_UUID, item ##_STR, BT_GMAP_UUID ":" item ##_STR },
|
||||
static const struct {
|
||||
const char *const uuid;
|
||||
const char *const name;
|
||||
const char *const key;
|
||||
} values[] = {
|
||||
BT_TMAP_ROLE_LIST(TMAP_ITEM)
|
||||
BT_GMAP_ROLE_LIST(GMAP_ITEM)
|
||||
BT_GMAP_FEATURE_LIST(GMAP_ITEM)
|
||||
{ NULL, NULL, NULL }
|
||||
};
|
||||
SPA_STATIC_ASSERT(SPA_N_ELEMENTS(feat->items) >= SPA_N_ELEMENTS(values));
|
||||
size_t n_items = feat->dict.n_items;
|
||||
size_t i;
|
||||
|
||||
/* Accept only listed features */
|
||||
for (i = 0; values[i].uuid; ++i)
|
||||
if (spa_streq(values[i].uuid, uuid) && spa_streq(values[i].name, name))
|
||||
break;
|
||||
if (!values[i].uuid)
|
||||
return false;
|
||||
|
||||
if (spa_dict_lookup(&feat->dict, values[i].key))
|
||||
return false;
|
||||
|
||||
spa_assert(n_items < SPA_N_ELEMENTS(feat->items));
|
||||
|
||||
/* Add */
|
||||
feat->items[n_items].key = values[i].key;
|
||||
feat->items[n_items].value = values[i].uuid;
|
||||
n_items++;
|
||||
|
||||
feat->dict = SPA_DICT(feat->items, n_items);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Get feature uuid at \a i */
|
||||
static const char *bap_features_get_uuid(struct bap_features *feat, size_t i)
|
||||
{
|
||||
if (!SPA_FLAG_IS_SET(feat->dict.flags, SPA_DICT_FLAG_SORTED))
|
||||
spa_dict_qsort(&feat->dict);
|
||||
|
||||
if (i >= feat->dict.n_items)
|
||||
return NULL;
|
||||
return feat->dict.items[i].value;
|
||||
}
|
||||
|
||||
/** Get feature name at \a i, or NULL if uuid doesn't match */
|
||||
static const char *bap_features_get_name(struct bap_features *feat, size_t i, const char *uuid)
|
||||
{
|
||||
char *pos;
|
||||
|
||||
if (i >= feat->dict.n_items)
|
||||
return NULL;
|
||||
if (!spa_streq(feat->dict.items[i].value, uuid))
|
||||
return NULL;
|
||||
|
||||
pos = strchr(feat->dict.items[i].key, ':');
|
||||
if (!pos)
|
||||
return NULL;
|
||||
return pos + 1;
|
||||
}
|
||||
|
||||
static void bap_features_clear(struct bap_features *feat)
|
||||
{
|
||||
spa_zero(*feat);
|
||||
}
|
||||
|
||||
static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBusMessage *m, void *userdata)
|
||||
{
|
||||
struct spa_bt_monitor *monitor = userdata;
|
||||
@ -1114,6 +1194,8 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
|
||||
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.debug", "true");
|
||||
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.metadata", (void *)ep->metadata);
|
||||
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.metadata-len", metadata_len);
|
||||
for (j = 0; j < ep->bap_features.dict.n_items && i < SPA_N_ELEMENTS(setting_items); ++i, ++j)
|
||||
setting_items[i] = ep->bap_features.dict.items[j];
|
||||
if (ep->device->settings)
|
||||
for (j = 0; j < ep->device->settings->n_items && i < SPA_N_ELEMENTS(setting_items); ++i, ++j)
|
||||
setting_items[i] = ep->device->settings->items[j];
|
||||
@ -2856,6 +2938,38 @@ static struct spa_bt_device *create_bcast_device(struct spa_bt_monitor *monitor,
|
||||
|
||||
static int setup_asha_transport(struct spa_bt_remote_endpoint *remote_endpoint, struct spa_bt_monitor *monitor);
|
||||
|
||||
static void parse_supported_features(struct spa_bt_monitor *monitor,
|
||||
DBusMessageIter *dict, struct bap_features *features)
|
||||
{
|
||||
while (dbus_message_iter_get_arg_type(dict) == DBUS_TYPE_DICT_ENTRY) {
|
||||
DBusMessageIter entry, variant, array;
|
||||
const char *key;
|
||||
|
||||
dbus_message_iter_recurse(dict, &entry);
|
||||
dbus_message_iter_get_basic(&entry, &key);
|
||||
dbus_message_iter_next(&entry);
|
||||
dbus_message_iter_recurse(&entry, &variant);
|
||||
|
||||
if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_ARRAY)
|
||||
goto next;
|
||||
|
||||
dbus_message_iter_recurse(&variant, &array);
|
||||
|
||||
while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) {
|
||||
const char *name;
|
||||
|
||||
dbus_message_iter_get_basic(&array, &name);
|
||||
if (bap_features_add(features, key, name))
|
||||
spa_log_debug(monitor->log, "remote_endpoint: BAP feature %s %s", key, name);
|
||||
dbus_message_iter_next(&array);
|
||||
}
|
||||
|
||||
next:
|
||||
dbus_message_iter_next(dict);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_endpoint,
|
||||
DBusMessageIter *props_iter,
|
||||
DBusMessageIter *invalidated_iter)
|
||||
@ -2991,8 +3105,13 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en
|
||||
remote_endpoint->hisyncid = *(uint64_t *)value;
|
||||
|
||||
spa_log_debug(monitor->log, "remote_endpoint %p: %s=%"PRIu64, remote_endpoint, key, remote_endpoint->hisyncid);
|
||||
}
|
||||
else {
|
||||
} else if (spa_streq(key, "SupportedFeatures")) {
|
||||
if (!check_iter_signature(&it[1], "a{sv}"))
|
||||
goto next;
|
||||
|
||||
dbus_message_iter_recurse(&it[1], &it[2]);
|
||||
parse_supported_features(monitor, &it[2], &remote_endpoint->bap_features);
|
||||
} else {
|
||||
unhandled:
|
||||
spa_log_debug(monitor->log, "remote_endpoint %p: unhandled key %s", remote_endpoint, key);
|
||||
}
|
||||
@ -3047,6 +3166,8 @@ static void remote_endpoint_free(struct spa_bt_remote_endpoint *remote_endpoint)
|
||||
if (remote_endpoint->device)
|
||||
spa_list_remove(&remote_endpoint->device_link);
|
||||
|
||||
bap_features_clear(&remote_endpoint->bap_features);
|
||||
|
||||
spa_list_remove(&remote_endpoint->link);
|
||||
free(remote_endpoint->path);
|
||||
free(remote_endpoint->transport_path);
|
||||
@ -5422,6 +5543,42 @@ out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void append_supported_features(DBusMessageIter *dict, struct bap_features *features)
|
||||
{
|
||||
const char *key = "SupportedFeatures";
|
||||
DBusMessageIter dict_entry, dict_variant, value_dict;
|
||||
DBusMessageIter entry, variant, array;
|
||||
const char *uuid, *name;
|
||||
size_t i;
|
||||
|
||||
dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry);
|
||||
dbus_message_iter_append_basic(&dict_entry, DBUS_TYPE_STRING, &key);
|
||||
dbus_message_iter_open_container(&dict_entry, DBUS_TYPE_VARIANT, "a{sv}", &dict_variant);
|
||||
|
||||
dbus_message_iter_open_container(&dict_variant, DBUS_TYPE_ARRAY, "{sv}", &value_dict);
|
||||
|
||||
i = 0;
|
||||
while ((uuid = bap_features_get_uuid(features, i))) {
|
||||
dbus_message_iter_open_container(&value_dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry);
|
||||
dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &uuid);
|
||||
dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, "as", &variant);
|
||||
dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, "s", &array);
|
||||
|
||||
while ((name = bap_features_get_name(features, i, uuid))) {
|
||||
dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &name);
|
||||
++i;
|
||||
}
|
||||
|
||||
dbus_message_iter_close_container(&variant, &array);
|
||||
dbus_message_iter_close_container(&entry, &variant);
|
||||
dbus_message_iter_close_container(&value_dict, &entry);
|
||||
}
|
||||
|
||||
dbus_message_iter_close_container(&dict_variant, &value_dict);
|
||||
dbus_message_iter_close_container(&dict_entry, &dict_variant);
|
||||
dbus_message_iter_close_container(dict, &dict_entry);
|
||||
}
|
||||
|
||||
static void append_media_object(struct spa_bt_monitor *monitor, DBusMessageIter *iter, const char *endpoint,
|
||||
const char *uuid, uint8_t codec_id, uint8_t *caps, size_t caps_size)
|
||||
{
|
||||
@ -5469,6 +5626,9 @@ static void append_media_object(struct spa_bt_monitor *monitor, DBusMessageIter
|
||||
append_basic_variant_dict_entry(&dict, "SupportedContext", DBUS_TYPE_UINT16, "q", &supported_contexts);
|
||||
}
|
||||
|
||||
if (spa_bt_profile_from_uuid(uuid) & SPA_BT_PROFILE_BAP_AUDIO)
|
||||
append_supported_features(&dict, &monitor->bap_features);
|
||||
|
||||
dbus_message_iter_close_container(&entry, &dict);
|
||||
dbus_message_iter_close_container(&array, &entry);
|
||||
dbus_message_iter_close_container(&object, &array);
|
||||
@ -6690,6 +6850,8 @@ static int impl_clear(struct spa_handle *handle)
|
||||
monitor->backend = NULL;
|
||||
monitor->backend_selection = BACKEND_NATIVE;
|
||||
|
||||
bap_features_clear(&monitor->bap_features);
|
||||
|
||||
spa_bt_quirks_destroy(monitor->quirks);
|
||||
|
||||
free_media_codecs(monitor->media_codecs);
|
||||
@ -7003,6 +7165,32 @@ static void parse_bap_locations(struct spa_bt_monitor *this, const struct spa_di
|
||||
*value = locations;
|
||||
}
|
||||
|
||||
static void bap_feature_parse(struct spa_bt_monitor *this, const char *uuid, const char *str)
|
||||
{
|
||||
struct spa_json it;
|
||||
char name[64];
|
||||
|
||||
if (!str)
|
||||
return;
|
||||
|
||||
if (spa_json_begin_array_relax(&it, str, strlen(str)) < 0)
|
||||
return;
|
||||
|
||||
while (spa_json_get_string(&it, name, sizeof(name)) > 0) {
|
||||
if (bap_features_add(&this->bap_features, uuid, name))
|
||||
spa_log_debug(this->log, "advertise BAP feature %s %s", uuid, name);
|
||||
}
|
||||
}
|
||||
|
||||
static void parse_bap_features(struct spa_bt_monitor *this, const struct spa_dict *info)
|
||||
{
|
||||
static const char *const tmap_uuid = "00001855-0000-1000-8000-00805f9b34fb";
|
||||
static const char *const gmap_uuid = "00001858-0000-1000-8000-00805f9b34fb";
|
||||
|
||||
bap_feature_parse(this, tmap_uuid, spa_dict_lookup(info, "bluez5.bap-server-tmap-features"));
|
||||
bap_feature_parse(this, gmap_uuid, spa_dict_lookup(info, "bluez5.bap-server-gmap-features"));
|
||||
}
|
||||
|
||||
static void parse_bap_server(struct spa_bt_monitor *this, const struct spa_dict *info)
|
||||
{
|
||||
this->bap_sink_locations = BAP_CHANNEL_FL | BAP_CHANNEL_FR;
|
||||
@ -7021,6 +7209,8 @@ static void parse_bap_server(struct spa_bt_monitor *this, const struct spa_dict
|
||||
parse_bap_locations(this, info, "bluez5.bap-server-capabilities.source.locations", &this->bap_source_locations);
|
||||
spa_atou32(spa_dict_lookup(info, "bluez5.bap-server-capabilities.source.contexts"), &this->bap_source_contexts, 0);
|
||||
spa_atou32(spa_dict_lookup(info, "bluez5.bap-server-capabilities.source.supported-contexts"), &this->bap_source_supported_contexts, 0);
|
||||
|
||||
parse_bap_features(this, info);
|
||||
}
|
||||
|
||||
static void get_global_settings(struct spa_bt_monitor *this, const struct spa_dict *dict)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user