From 081116906dafa98538ab3a45f42d44aebd70c080 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 20 Apr 2025 20:09:16 +0300 Subject: [PATCH] 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. --- doc/dox/config/pipewire-props.7.md | 20 +++++++ spa/plugins/bluez5/bap-codec-caps.h | 37 ++++++++++++ spa/plugins/bluez5/bap-codec-lc3.c | 47 ++-------------- spa/plugins/bluez5/bluez5-dbus.c | 87 +++++++++++++++++++++++++---- 4 files changed, 138 insertions(+), 53 deletions(-) diff --git a/doc/dox/config/pipewire-props.7.md b/doc/dox/config/pipewire-props.7.md index ffa094cfc..8d1cbd70c 100644 --- a/doc/dox/config/pipewire-props.7.md +++ b/doc/dox/config/pipewire-props.7.md @@ -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 diff --git a/spa/plugins/bluez5/bap-codec-caps.h b/spa/plugins/bluez5/bap-codec-caps.h index 9d1da42b4..6a862894c 100644 --- a/spa/plugins/bluez5/bap-codec-caps.h +++ b/spa/plugins/bluez5/bap-codec-caps.h @@ -5,6 +5,8 @@ #ifndef SPA_BLUEZ5_BAP_CODEC_CAPS_H_ #define SPA_BLUEZ5_BAP_CODEC_CAPS_H_ +#include + #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 diff --git a/spa/plugins/bluez5/bap-codec-lc3.c b/spa/plugins/bluez5/bap-codec-lc3.c index dd18585b3..2f1117ef8 100644 --- a/spa/plugins/bluez5/bap-codec-lc3.c +++ b/spa/plugins/bluez5/bap-codec-lc3.c @@ -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) diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 47fff8bc7..8b29086f7 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -33,6 +33,7 @@ #include #include #include +#include #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;