bluez5: bap: allow configuring server locations/context

Add configuration options for the BAP/PACS sink and source endpoint
location (= channel positions) and context settings.

Although BlueZ associates these with individual endpoints, in the PACS
spec they are actually device-global, so configure directly in monitor
settings.
This commit is contained in:
Pauli Virtanen 2025-04-20 20:09:16 +03:00
parent a4148c80b4
commit 081116906d
4 changed files with 138 additions and 53 deletions

View file

@ -963,6 +963,26 @@ Maximum number of octets supported per codec frame for the LC3 codec (default: 4
@PAR@ monitor-prop bluez5.bap-server-capabilities.max_frames # integer
Maximum number of codec frames supported per SDU for the LC3 codec (default: 2).
@PAR@ monitor-prop bluez5.bap-server-capabilities.sink.locations # JSON or integer
Sink audio locations of the server, as channel positions or PACS bitmask.
Example: `FL,FR`
@PAR@ monitor-prop bluez5.bap-server-capabilities.sink.contexts # integer
Available sink contexts PACS bitmask of the the server.
@PAR@ monitor-prop bluez5.bap-server-capabilities.sink.supported-contexts # integer
Supported sink contexts PACS bitmask of the the server.
@PAR@ monitor-prop bluez5.bap-server-capabilities.source.locations # JSON or integer
Source audio locations of the server, as channel positions or PACS bitmask.
Example: `FL,FR`
@PAR@ monitor-prop bluez5.bap-server-capabilities.source.contexts # integer
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.
## Device properties
@PAR@ device-prop bluez5.auto-connect # boolean

View file

@ -5,6 +5,8 @@
#ifndef SPA_BLUEZ5_BAP_CODEC_CAPS_H_
#define SPA_BLUEZ5_BAP_CODEC_CAPS_H_
#include <spa/param/audio/format.h>
#define BAP_CODEC_LC3 0x06
#define LC3_TYPE_FREQ 0x01
@ -175,4 +177,39 @@ struct bap_codec_qos_full {
struct bap_codec_qos qos;
};
static const struct {
uint32_t bit;
enum spa_audio_channel channel;
} bap_channel_bits[] = {
{ BAP_CHANNEL_MONO, SPA_AUDIO_CHANNEL_MONO },
{ BAP_CHANNEL_FL, SPA_AUDIO_CHANNEL_FL },
{ BAP_CHANNEL_FR, SPA_AUDIO_CHANNEL_FR },
{ BAP_CHANNEL_FC, SPA_AUDIO_CHANNEL_FC },
{ BAP_CHANNEL_LFE, SPA_AUDIO_CHANNEL_LFE },
{ BAP_CHANNEL_BL, SPA_AUDIO_CHANNEL_RL },
{ BAP_CHANNEL_BR, SPA_AUDIO_CHANNEL_RR },
{ BAP_CHANNEL_FLC, SPA_AUDIO_CHANNEL_FLC },
{ BAP_CHANNEL_FRC, SPA_AUDIO_CHANNEL_FRC },
{ BAP_CHANNEL_BC, SPA_AUDIO_CHANNEL_BC },
{ BAP_CHANNEL_LFE2, SPA_AUDIO_CHANNEL_LFE2 },
{ BAP_CHANNEL_SL, SPA_AUDIO_CHANNEL_SL },
{ BAP_CHANNEL_SR, SPA_AUDIO_CHANNEL_SR },
{ BAP_CHANNEL_TFL, SPA_AUDIO_CHANNEL_TFL },
{ BAP_CHANNEL_TFR, SPA_AUDIO_CHANNEL_TFR },
{ BAP_CHANNEL_TFC, SPA_AUDIO_CHANNEL_TFC },
{ BAP_CHANNEL_TC, SPA_AUDIO_CHANNEL_TC },
{ BAP_CHANNEL_TBL, SPA_AUDIO_CHANNEL_TRL },
{ BAP_CHANNEL_TBR, SPA_AUDIO_CHANNEL_TRR },
{ BAP_CHANNEL_TSL, SPA_AUDIO_CHANNEL_TSL },
{ BAP_CHANNEL_TSR, SPA_AUDIO_CHANNEL_TSR },
{ BAP_CHANNEL_TBC, SPA_AUDIO_CHANNEL_TRC },
{ BAP_CHANNEL_BFC, SPA_AUDIO_CHANNEL_BC },
{ BAP_CHANNEL_BFL, SPA_AUDIO_CHANNEL_BLC },
{ BAP_CHANNEL_BFR, SPA_AUDIO_CHANNEL_BRC },
{ BAP_CHANNEL_FLW, SPA_AUDIO_CHANNEL_FLW },
{ BAP_CHANNEL_FRW, SPA_AUDIO_CHANNEL_FRW },
{ BAP_CHANNEL_LS, SPA_AUDIO_CHANNEL_SL }, /* is it the right mapping? */
{ BAP_CHANNEL_RS, SPA_AUDIO_CHANNEL_SR }, /* is it the right mapping? */
};
#endif

View file

@ -81,41 +81,6 @@ typedef struct {
unsigned int priority;
} bap_lc3_t;
static const struct {
uint32_t bit;
enum spa_audio_channel channel;
} channel_bits[] = {
{ BAP_CHANNEL_MONO, SPA_AUDIO_CHANNEL_MONO },
{ BAP_CHANNEL_FL, SPA_AUDIO_CHANNEL_FL },
{ BAP_CHANNEL_FR, SPA_AUDIO_CHANNEL_FR },
{ BAP_CHANNEL_FC, SPA_AUDIO_CHANNEL_FC },
{ BAP_CHANNEL_LFE, SPA_AUDIO_CHANNEL_LFE },
{ BAP_CHANNEL_BL, SPA_AUDIO_CHANNEL_RL },
{ BAP_CHANNEL_BR, SPA_AUDIO_CHANNEL_RR },
{ BAP_CHANNEL_FLC, SPA_AUDIO_CHANNEL_FLC },
{ BAP_CHANNEL_FRC, SPA_AUDIO_CHANNEL_FRC },
{ BAP_CHANNEL_BC, SPA_AUDIO_CHANNEL_BC },
{ BAP_CHANNEL_LFE2, SPA_AUDIO_CHANNEL_LFE2 },
{ BAP_CHANNEL_SL, SPA_AUDIO_CHANNEL_SL },
{ BAP_CHANNEL_SR, SPA_AUDIO_CHANNEL_SR },
{ BAP_CHANNEL_TFL, SPA_AUDIO_CHANNEL_TFL },
{ BAP_CHANNEL_TFR, SPA_AUDIO_CHANNEL_TFR },
{ BAP_CHANNEL_TFC, SPA_AUDIO_CHANNEL_TFC },
{ BAP_CHANNEL_TC, SPA_AUDIO_CHANNEL_TC },
{ BAP_CHANNEL_TBL, SPA_AUDIO_CHANNEL_TRL },
{ BAP_CHANNEL_TBR, SPA_AUDIO_CHANNEL_TRR },
{ BAP_CHANNEL_TSL, SPA_AUDIO_CHANNEL_TSL },
{ BAP_CHANNEL_TSR, SPA_AUDIO_CHANNEL_TSR },
{ BAP_CHANNEL_TBC, SPA_AUDIO_CHANNEL_TRC },
{ BAP_CHANNEL_BFC, SPA_AUDIO_CHANNEL_BC },
{ BAP_CHANNEL_BFL, SPA_AUDIO_CHANNEL_BLC },
{ BAP_CHANNEL_BFR, SPA_AUDIO_CHANNEL_BRC },
{ BAP_CHANNEL_FLW, SPA_AUDIO_CHANNEL_FLW },
{ BAP_CHANNEL_FRW, SPA_AUDIO_CHANNEL_FRW },
{ BAP_CHANNEL_LS, SPA_AUDIO_CHANNEL_SL }, /* is it the right mapping? */
{ BAP_CHANNEL_RS, SPA_AUDIO_CHANNEL_SR }, /* is it the right mapping? */
};
#define BAP_QOS(name_, rate_, duration_, framing_, framelen_, rtn_, latency_, delay_, priority_) \
((struct bap_qos){ .name = (name_), .rate = (rate_), .frame_duration = (duration_), .framing = (framing_), \
.framelen = (framelen_), .retransmission = (rtn_), .latency = (latency_), \
@ -569,9 +534,9 @@ static int select_channels(uint8_t channel_counts, uint32_t locations, uint32_t
return -1;
*allocation = 0;
for (i = 0; i < SPA_N_ELEMENTS(channel_bits); ++i) {
if (locations & channel_bits[i].bit) {
*allocation |= channel_bits[i].bit;
for (i = 0; i < SPA_N_ELEMENTS(bap_channel_bits); ++i) {
if (locations & bap_channel_bits[i].bit) {
*allocation |= bap_channel_bits[i].bit;
--num;
if (num == 0)
break;
@ -944,9 +909,9 @@ static uint8_t channels_to_positions(uint32_t channels, uint32_t *position)
} else {
unsigned int i;
for (i = 0; i < SPA_N_ELEMENTS(channel_bits); ++i)
if (channels & channel_bits[i].bit)
position[n_positions++] = channel_bits[i].channel;
for (i = 0; i < SPA_N_ELEMENTS(bap_channel_bits); ++i)
if (channels & bap_channel_bits[i].bit)
position[n_positions++] = bap_channel_bits[i].channel;
}
if (n_positions != n_channels)

View file

@ -33,6 +33,7 @@
#include <spa/utils/string.h>
#include <spa/utils/json.h>
#include <spa-private/dbus-helpers.h>
#include <spa/param/audio/raw-json.h>
#include "config.h"
#include "codec-loader.h"
@ -122,6 +123,13 @@ struct spa_bt_monitor {
struct spa_list bcast_source_config_list;
uint32_t bap_sink_locations;
uint32_t bap_sink_contexts;
uint32_t bap_sink_supported_contexts;
uint32_t bap_source_locations;
uint32_t bap_source_contexts;
uint32_t bap_source_supported_contexts;
struct spa_bt_quirks *quirks;
#define MAX_SETTINGS 128
@ -5006,7 +5014,7 @@ out:
return err;
}
static void append_media_object(DBusMessageIter *iter, const char *endpoint,
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)
{
const char *interface_name = BLUEZ_MEDIA_ENDPOINT_INTERFACE;
@ -5033,19 +5041,24 @@ static void append_media_object(DBusMessageIter *iter, const char *endpoint,
}
if (spa_bt_profile_from_uuid(uuid) & (SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE)) {
dbus_uint32_t locations;
dbus_uint16_t supported_context, context;
dbus_uint16_t supported_contexts, contexts;
locations = BAP_CHANNEL_ALL;
if (spa_bt_profile_from_uuid(uuid) & SPA_BT_PROFILE_BAP_SINK) {
supported_context = context = BAP_CONTEXT_ALL;
locations = monitor->bap_sink_locations;
contexts = monitor->bap_sink_contexts;
supported_contexts = monitor->bap_sink_supported_contexts;
} else {
supported_context = context = (BAP_CONTEXT_UNSPECIFIED | BAP_CONTEXT_CONVERSATIONAL |
BAP_CONTEXT_MEDIA | BAP_CONTEXT_GAME);
locations = monitor->bap_source_locations;
contexts = monitor->bap_source_contexts;
supported_contexts = monitor->bap_source_supported_contexts;
}
spa_log_debug(monitor->log, "BAP endpoint %s locations:0x%x contexts:0x%x supported-contexs:0x%x",
endpoint, locations, contexts, supported_contexts);
append_basic_variant_dict_entry(&dict, "Locations", DBUS_TYPE_UINT32, "u", &locations);
append_basic_variant_dict_entry(&dict, "Context", DBUS_TYPE_UINT16, "q", &context);
append_basic_variant_dict_entry(&dict, "SupportedContext", DBUS_TYPE_UINT16, "q", &supported_context);
append_basic_variant_dict_entry(&dict, "Context", DBUS_TYPE_UINT16, "q", &contexts);
append_basic_variant_dict_entry(&dict, "SupportedContext", DBUS_TYPE_UINT16, "q", &supported_contexts);
}
dbus_message_iter_close_container(&entry, &dict);
@ -5127,7 +5140,7 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage *
ret = media_codec_to_endpoint(codec, SPA_BT_MEDIA_SINK, &endpoint);
if (ret == 0) {
spa_log_info(monitor->log, "register media sink codec %s: %s", media_codecs[i]->name, endpoint);
append_media_object(&array, endpoint,
append_media_object(monitor, &array, endpoint,
codec->bap ? SPA_BT_UUID_BAP_SINK : SPA_BT_UUID_A2DP_SINK,
codec_id, caps, caps_size);
}
@ -5142,7 +5155,7 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage *
ret = media_codec_to_endpoint(codec, SPA_BT_MEDIA_SOURCE, &endpoint);
if (ret == 0) {
spa_log_info(monitor->log, "register media source codec %s: %s", media_codecs[i]->name, endpoint);
append_media_object(&array, endpoint,
append_media_object(monitor, &array, endpoint,
codec->bap ? SPA_BT_UUID_BAP_SOURCE : SPA_BT_UUID_A2DP_SOURCE,
codec_id, caps, caps_size);
}
@ -5158,7 +5171,7 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage *
ret = media_codec_to_endpoint(codec, SPA_BT_MEDIA_SOURCE_BROADCAST, &endpoint);
if (ret == 0) {
spa_log_info(monitor->log, "register media source codec %s: %s", media_codecs[i]->name, endpoint);
append_media_object(&array, endpoint,
append_media_object(monitor, &array, endpoint,
SPA_BT_UUID_BAP_BROADCAST_SOURCE,
codec_id, caps, caps_size);
}
@ -5173,7 +5186,7 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage *
ret = media_codec_to_endpoint(codec, SPA_BT_MEDIA_SINK_BROADCAST, &endpoint);
if (ret == 0) {
spa_log_info(monitor->log, "register broadcast media sink codec %s: %s", media_codecs[i]->name, endpoint);
append_media_object(&array, endpoint,
append_media_object(monitor, &array, endpoint,
SPA_BT_UUID_BAP_BROADCAST_SINK,
codec_id, caps, caps_size);
}
@ -6549,6 +6562,55 @@ fallback:
return 0;
}
static void parse_bap_locations(struct spa_bt_monitor *this, const struct spa_dict *info,
const char *key, uint32_t *value)
{
const char *str;
uint32_t position[SPA_AUDIO_MAX_CHANNELS];
uint32_t n_channels;
uint32_t locations;
unsigned int i, j;
if (!info || !(str = spa_dict_lookup(info, key)))
return;
if (spa_atou32(str, value, 0))
return;
if (!spa_audio_parse_position(str, strlen(str), position, &n_channels)) {
spa_log_error(this->log, "property %s '%s' is not valid position array", key, str);
return;
}
locations = 0;
for (i = 0; i < n_channels; ++i)
for (j = 0; j < SPA_N_ELEMENTS(bap_channel_bits); ++j)
if (bap_channel_bits[j].channel == position[i])
locations |= bap_channel_bits[j].bit;
*value = locations;
}
static void parse_bap_server(struct spa_bt_monitor *this, const struct spa_dict *info)
{
this->bap_sink_locations = BAP_CHANNEL_ALL;
this->bap_source_locations = BAP_CHANNEL_ALL;
this->bap_sink_contexts = this->bap_sink_supported_contexts = BAP_CONTEXT_ALL;
this->bap_source_contexts = this->bap_source_supported_contexts = (BAP_CONTEXT_UNSPECIFIED | BAP_CONTEXT_CONVERSATIONAL |
BAP_CONTEXT_MEDIA | BAP_CONTEXT_GAME);
if (!info)
return;
parse_bap_locations(this, info, "bluez5.bap-server-capabilities.sink.locations", &this->bap_sink_locations);
spa_atou32(spa_dict_lookup(info, "bluez5.bap-server-capabilities.sink.contexts"), &this->bap_sink_contexts, 0);
spa_atou32(spa_dict_lookup(info, "bluez5.bap-server-capabilities.sink.supported-contexts"), &this->bap_sink_supported_contexts, 0);
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);
}
static void get_global_settings(struct spa_bt_monitor *this, const struct spa_dict *dict)
{
uint32_t n_items = 0;
@ -6662,6 +6724,7 @@ impl_init(const struct spa_handle_factory *factory,
parse_roles(this, info);
parse_broadcast_source_config(this, info);
parse_bap_server(this, info);
this->default_audio_info.rate = A2DP_CODEC_DEFAULT_RATE;
this->default_audio_info.channels = A2DP_CODEC_DEFAULT_CHANNELS;