mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-11-23 06:59:58 -05:00
bluez5: set some BAP Context metadata value on streams
Some devices refuse to enable microphone if Streaming Context metadata is just Unspecified. Set some reasonable values for the stream context we create along TMAP, and try follow CAP rules for selecting the PAC.
This commit is contained in:
parent
2010a525d3
commit
8d59ad2713
4 changed files with 182 additions and 21 deletions
|
|
@ -46,6 +46,8 @@ struct impl {
|
|||
struct settings {
|
||||
uint32_t locations;
|
||||
uint32_t channel_allocation;
|
||||
uint16_t supported_context;
|
||||
uint16_t available_context;
|
||||
bool sink;
|
||||
bool duplex;
|
||||
char *qos_name;
|
||||
|
|
@ -55,13 +57,11 @@ struct settings {
|
|||
int framing;
|
||||
};
|
||||
|
||||
struct config_data {
|
||||
struct settings settings;
|
||||
};
|
||||
|
||||
struct pac_data {
|
||||
const void *data;
|
||||
size_t size;
|
||||
const void *metadata;
|
||||
size_t metadata_size;
|
||||
int index;
|
||||
const struct settings *settings;
|
||||
};
|
||||
|
|
@ -86,9 +86,16 @@ typedef struct {
|
|||
uint8_t n_blks;
|
||||
bool sink;
|
||||
bool duplex;
|
||||
uint16_t preferred_context;
|
||||
unsigned int priority;
|
||||
} bap_lc3_t;
|
||||
|
||||
struct config_data {
|
||||
bap_lc3_t conf;
|
||||
int pac_index;
|
||||
struct settings settings;
|
||||
};
|
||||
|
||||
#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_), \
|
||||
|
|
@ -370,7 +377,7 @@ static void debugc_ltv(struct spa_debug_context *debug_ctx, int pac, struct ltv
|
|||
}
|
||||
}
|
||||
|
||||
static int parse_bluez_pacs(const uint8_t *data, size_t data_size, struct pac_data pacs[MAX_PACS],
|
||||
static int parse_bluez_pacs_data(const uint8_t *data, size_t data_size, struct pac_data pacs[MAX_PACS],
|
||||
struct spa_debug_context *debug_ctx)
|
||||
{
|
||||
/*
|
||||
|
|
@ -379,7 +386,7 @@ static int parse_bluez_pacs(const uint8_t *data, size_t data_size, struct pac_da
|
|||
*/
|
||||
int pac = 0;
|
||||
|
||||
pacs[pac] = (struct pac_data){ data, 0 };
|
||||
pacs[pac] = (struct pac_data){ .data = data };
|
||||
|
||||
while (data_size > 0) {
|
||||
struct ltv *ltv = (struct ltv *)data;
|
||||
|
|
@ -390,7 +397,7 @@ static int parse_bluez_pacs(const uint8_t *data, size_t data_size, struct pac_da
|
|||
break;
|
||||
|
||||
++pac;
|
||||
pacs[pac] = (struct pac_data){ data + 1, 0, pac };
|
||||
pacs[pac] = (struct pac_data){ .data = data + 1, .index = pac };
|
||||
} else if (ltv->len >= data_size) {
|
||||
return -EINVAL;
|
||||
} else {
|
||||
|
|
@ -404,6 +411,28 @@ static int parse_bluez_pacs(const uint8_t *data, size_t data_size, struct pac_da
|
|||
return pac + 1;
|
||||
}
|
||||
|
||||
static int parse_bluez_pacs(const uint8_t *data, size_t data_size,
|
||||
const uint8_t *metadata, size_t metadata_size, struct pac_data pacs[MAX_PACS],
|
||||
struct spa_debug_context *debug_ctx)
|
||||
{
|
||||
struct pac_data meta[MAX_PACS];
|
||||
int pac_count, meta_count;
|
||||
|
||||
pac_count = parse_bluez_pacs_data(data, data_size, pacs, debug_ctx);
|
||||
if (pac_count < 0)
|
||||
return pac_count;
|
||||
|
||||
meta_count = parse_bluez_pacs_data(metadata, metadata_size, meta, debug_ctx);
|
||||
if (meta_count == pac_count) {
|
||||
for (int i = 0; i < pac_count; ++i) {
|
||||
pacs[i].metadata = meta[i].data;
|
||||
pacs[i].metadata_size = meta[i].size;
|
||||
}
|
||||
}
|
||||
|
||||
return pac_count;
|
||||
}
|
||||
|
||||
static uint8_t get_channel_count(uint32_t channels)
|
||||
{
|
||||
uint8_t num;
|
||||
|
|
@ -536,6 +565,7 @@ static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct sp
|
|||
uint8_t max_channels = 0;
|
||||
uint8_t duration_mask = 0;
|
||||
uint16_t rate_mask = 0;
|
||||
uint16_t preferred_context = 0;
|
||||
struct bap_qos bap_qos;
|
||||
unsigned int i;
|
||||
bool found = false;
|
||||
|
|
@ -584,6 +614,20 @@ static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct sp
|
|||
return false;
|
||||
}
|
||||
|
||||
data = pac->metadata;
|
||||
data_size = pac->metadata_size;
|
||||
while ((ltv = ltv_next(&data, &data_size))) {
|
||||
switch (ltv->type) {
|
||||
case BAP_META_TYPE_PREFERRED_CONTEXT:
|
||||
if (ltv->len != 3)
|
||||
break;
|
||||
preferred_context = ltv->value[0] + (ltv->value[1] << 8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (data)
|
||||
spa_debugc(debug_ctx, "malformed metadata");
|
||||
|
||||
for (i = 0; i < 8; ++i)
|
||||
if (channel_counts & (1u << i))
|
||||
max_channels = i + 1;
|
||||
|
|
@ -621,8 +665,8 @@ static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct sp
|
|||
* and reassemble SDUs as needed.
|
||||
*/
|
||||
if (pac->settings->duplex) {
|
||||
/* 16KHz input is mandatory in BAP v1.0.1 Table 3.5, so prefer
|
||||
* it or 32kHz for now for input rate in duplex configuration.
|
||||
/* 16KHz input is mandatory in BAP v1.0.1 Table 3.5, and 32KHz in TMAP,
|
||||
* so prefer those for now for input rate in duplex configuration.
|
||||
*
|
||||
* It appears few devices support 48kHz out + input, so in duplex mode
|
||||
* try 32 kHz or 16 kHz also for output direction.
|
||||
|
|
@ -645,6 +689,7 @@ static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct sp
|
|||
conf->frame_duration = bap_qos.frame_duration;
|
||||
conf->framelen = bap_qos.framelen;
|
||||
conf->priority = bap_qos.priority;
|
||||
conf->preferred_context = preferred_context;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -700,7 +745,32 @@ static bool parse_conf(bap_lc3_t *conf, const void *data, size_t data_size)
|
|||
return true;
|
||||
}
|
||||
|
||||
static int conf_cmp(const bap_lc3_t *conf1, int res1, const bap_lc3_t *conf2, int res2)
|
||||
static uint16_t get_wanted_context(const struct settings *settings)
|
||||
{
|
||||
uint16_t context;
|
||||
|
||||
/* Stick with contexts specified in TMAP. Anything else is probably crapshoot due
|
||||
* to interesting device firmware behavior.
|
||||
*
|
||||
* Eg. some Earfun firmwares fail if we set MEDIA | CONVERSATIONAL, other versions
|
||||
* disconnect if LIVE is set, Samsung Galaxy Buds fail on microphone Enable if
|
||||
* CONVERSATIONAL is not set.
|
||||
*/
|
||||
if (settings->sink || settings->duplex)
|
||||
context = BAP_CONTEXT_CONVERSATIONAL;
|
||||
else
|
||||
context = BAP_CONTEXT_MEDIA;
|
||||
|
||||
/* CAP v1.0.1 Sec 7.3.1.2.1: drop contexts if not available, otherwise unspecified */
|
||||
context &= settings->available_context;
|
||||
if (!context)
|
||||
context = BAP_CONTEXT_UNSPECIFIED & settings->available_context;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static int conf_cmp(const bap_lc3_t *conf1, int res1, const bap_lc3_t *conf2, int res2,
|
||||
const struct settings *settings)
|
||||
{
|
||||
const bap_lc3_t *conf;
|
||||
int a, b;
|
||||
|
|
@ -725,6 +795,9 @@ static int conf_cmp(const bap_lc3_t *conf1, int res1, const bap_lc3_t *conf2, in
|
|||
|
||||
PREFER_EXPR(conf->priority == UINT_MAX);
|
||||
|
||||
/* CAP v1.0.1 Sec 7.3.1.2.4: should use PAC preferred context if possible */
|
||||
PREFER_BOOL(conf->preferred_context & get_wanted_context(settings));
|
||||
|
||||
PREFER_BOOL(conf->channels & LC3_CHAN_2);
|
||||
PREFER_BOOL(conf->channels & LC3_CHAN_1);
|
||||
|
||||
|
|
@ -750,7 +823,7 @@ static int pac_cmp(const void *p1, const void *p2)
|
|||
res1 = select_config(&conf1, pac1, &debug_ctx.ctx) ? (int)sizeof(bap_lc3_t) : -EINVAL;
|
||||
res2 = select_config(&conf2, pac2, &debug_ctx.ctx) ? (int)sizeof(bap_lc3_t) : -EINVAL;
|
||||
|
||||
return conf_cmp(&conf1, res1, &conf2, res2);
|
||||
return conf_cmp(&conf1, res1, &conf2, res2, pac1->settings);
|
||||
}
|
||||
|
||||
static void parse_settings(struct settings *s, const struct spa_dict *settings,
|
||||
|
|
@ -789,6 +862,12 @@ static void parse_settings(struct settings *s, const struct spa_dict *settings,
|
|||
if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.channel-allocation"), &value, 0))
|
||||
s->channel_allocation = value;
|
||||
|
||||
if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.supported-context"), &value, 0))
|
||||
s->supported_context = value;
|
||||
|
||||
if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.available-context"), &value, 0))
|
||||
s->available_context = value;
|
||||
|
||||
if (spa_atob(spa_dict_lookup(settings, "bluez5.bap.debug")))
|
||||
*debug_ctx = SPA_LOG_DEBUG_INIT(log_, SPA_LOG_LEVEL_DEBUG);
|
||||
else
|
||||
|
|
@ -832,6 +911,8 @@ static int codec_select_config(const struct media_codec *codec, uint32_t flags,
|
|||
spa_autoptr(config_data) d = NULL;
|
||||
int i, ret;
|
||||
struct ltv_writer writer = LTV_WRITER(config, A2DP_MAX_CAPS_SIZE);
|
||||
const void *metadata = NULL;
|
||||
uint32_t metadata_len = 0;
|
||||
|
||||
if (caps == NULL)
|
||||
return -EINVAL;
|
||||
|
|
@ -842,8 +923,13 @@ static int codec_select_config(const struct media_codec *codec, uint32_t flags,
|
|||
|
||||
parse_settings(&d->settings, settings, &debug_ctx);
|
||||
|
||||
if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.metadata"), &metadata_len, 0))
|
||||
metadata = spa_dict_lookup(settings, "bluez5.bap.metadata");
|
||||
if (!metadata)
|
||||
metadata_len = 0;
|
||||
|
||||
/* Select best conf from those possible */
|
||||
npacs = parse_bluez_pacs(caps, caps_size, pacs, &debug_ctx.ctx);
|
||||
npacs = parse_bluez_pacs(caps, caps_size, metadata, metadata_len, pacs, &debug_ctx.ctx);
|
||||
if (npacs < 0) {
|
||||
spa_debugc(&debug_ctx.ctx, "malformed PACS");
|
||||
return npacs;
|
||||
|
|
@ -862,6 +948,9 @@ static int codec_select_config(const struct media_codec *codec, uint32_t flags,
|
|||
if (!select_config(&conf, &pacs[0], &debug_ctx.ctx))
|
||||
return -ENOTSUP;
|
||||
|
||||
d->conf = conf;
|
||||
d->pac_index = pacs[0].index;
|
||||
|
||||
ltv_writer_uint8(&writer, LC3_TYPE_FREQ, conf.rate);
|
||||
ltv_writer_uint8(&writer, LC3_TYPE_DUR, conf.frame_duration);
|
||||
|
||||
|
|
@ -884,13 +973,22 @@ static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t f
|
|||
const void *caps2, size_t caps2_size, const struct media_codec_audio_info *info, const struct spa_dict *global_settings)
|
||||
{
|
||||
bap_lc3_t conf1, conf2;
|
||||
int res1, res2;
|
||||
int res1, res2, res;
|
||||
void *data1 = NULL;
|
||||
void *data2 = NULL;
|
||||
const struct config_data *d;
|
||||
|
||||
/* Order selected configurations by preference */
|
||||
res1 = codec->select_config(codec, 0, caps1, caps1_size, info, global_settings, (uint8_t *)&conf1, NULL);
|
||||
res2 = codec->select_config(codec, 0, caps2, caps2_size, info, global_settings, (uint8_t *)&conf2, NULL);
|
||||
res1 = codec->select_config(codec, 0, caps1, caps1_size, info, global_settings, (uint8_t *)&conf1, &data1);
|
||||
res2 = codec->select_config(codec, 0, caps2, caps2_size, info, global_settings, (uint8_t *)&conf2, &data2);
|
||||
|
||||
return conf_cmp(&conf1, res1, &conf2, res2);
|
||||
d = data1 ? data1 : data2;
|
||||
res = conf_cmp(&conf1, res1, &conf2, res2, d ? &d->settings : NULL);
|
||||
|
||||
codec->free_config_data(codec, data1);
|
||||
codec->free_config_data(codec, data2);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static uint8_t channels_to_positions(uint32_t channels, uint32_t *position, uint32_t max_position)
|
||||
|
|
@ -1054,7 +1152,6 @@ static int codec_validate_config(const struct media_codec *codec, uint32_t flags
|
|||
}
|
||||
|
||||
static int codec_get_qos(const struct media_codec *codec,
|
||||
const void *config, size_t config_size,
|
||||
const struct bap_endpoint_qos *endpoint_qos,
|
||||
const void *config_data,
|
||||
struct bap_codec_qos *qos)
|
||||
|
|
@ -1068,8 +1165,8 @@ static int codec_get_qos(const struct media_codec *codec,
|
|||
|
||||
if (!d)
|
||||
return -EINVAL;
|
||||
if (!parse_conf(&conf, config, config_size))
|
||||
return -EINVAL;
|
||||
|
||||
conf = d->conf;
|
||||
|
||||
found = select_bap_qos(&bap_qos, &d->settings, get_rate_mask(conf.rate), get_duration_mask(conf.frame_duration),
|
||||
conf.framelen, conf.framelen);
|
||||
|
|
@ -1114,6 +1211,22 @@ static int codec_get_qos(const struct media_codec *codec,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int codec_get_metadata(const struct media_codec *codec, const void *config_data,
|
||||
uint8_t *meta, size_t meta_max_size)
|
||||
{
|
||||
const struct config_data *d = config_data;
|
||||
struct ltv_writer writer = LTV_WRITER(meta, meta_max_size);
|
||||
uint16_t ctx;
|
||||
|
||||
ctx = get_wanted_context(&d->settings);
|
||||
if (!ctx)
|
||||
ctx = BAP_CONTEXT_UNSPECIFIED;
|
||||
|
||||
ltv_writer_uint16(&writer, BAP_META_TYPE_STREAMING_CONTEXT, ctx);
|
||||
|
||||
return ltv_writer_end(&writer);
|
||||
}
|
||||
|
||||
static void codec_free_config_data(const struct media_codec *codec, void *config_data)
|
||||
{
|
||||
free_config_data(config_data);
|
||||
|
|
@ -1442,6 +1555,7 @@ const struct media_codec bap_codec_lc3 = {
|
|||
.enum_config = codec_enum_config,
|
||||
.validate_config = codec_validate_config,
|
||||
.get_qos = codec_get_qos,
|
||||
.get_metadata = codec_get_metadata,
|
||||
.free_config_data = codec_free_config_data,
|
||||
.caps_preference_cmp = codec_caps_preference_cmp,
|
||||
.init = codec_init,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue