mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
bluez5: lc3: handle multiple PACs in capabilities
BlueZ may send multiple PACs in the capabilities delimited by zero LTV. Handle this case by selecting the "best" one. The configuration size may also for BAP generally be different from PAC size.
This commit is contained in:
parent
37a5dca31e
commit
a372c89544
3 changed files with 124 additions and 48 deletions
|
|
@ -39,6 +39,8 @@
|
|||
#include "media-codecs.h"
|
||||
#include "bap-codec-caps.h"
|
||||
|
||||
#define MAX_PACS 64
|
||||
|
||||
struct impl {
|
||||
lc3_encoder_t enc[LC3_MAX_CHANNELS];
|
||||
lc3_decoder_t dec[LC3_MAX_CHANNELS];
|
||||
|
|
@ -52,11 +54,16 @@ struct impl {
|
|||
unsigned int codesize;
|
||||
};
|
||||
|
||||
struct ltv {
|
||||
struct __attribute__((packed)) ltv {
|
||||
uint8_t len;
|
||||
uint8_t type;
|
||||
uint8_t value[0];
|
||||
} __packed;
|
||||
uint8_t value[];
|
||||
};
|
||||
|
||||
struct pac_data {
|
||||
const uint8_t *data;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
static int write_ltv(uint8_t *dest, uint8_t type, void* value, size_t len)
|
||||
{
|
||||
|
|
@ -100,6 +107,38 @@ static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
|
|||
return data - caps;
|
||||
}
|
||||
|
||||
static int parse_bluez_pacs(const uint8_t *data, size_t data_size, struct pac_data pacs[MAX_PACS])
|
||||
{
|
||||
/*
|
||||
* BlueZ capabilites for the same codec may contain multiple
|
||||
* PACs separated by zero-length LTV (see BlueZ b907befc2d80)
|
||||
*/
|
||||
int pac = 0;
|
||||
|
||||
pacs[pac] = (struct pac_data){ data, 0 };
|
||||
|
||||
while (data_size > 0) {
|
||||
struct ltv *ltv = (struct ltv *)data;
|
||||
|
||||
if (ltv->len == 0) {
|
||||
/* delimiter */
|
||||
if (pac + 1 >= MAX_PACS)
|
||||
break;
|
||||
|
||||
++pac;
|
||||
pacs[pac] = (struct pac_data){ data + 1, 0 };
|
||||
} else if (ltv->len >= data_size) {
|
||||
return -EINVAL;
|
||||
} else {
|
||||
pacs[pac].size += ltv->len + 1;
|
||||
}
|
||||
data_size -= ltv->len + 1;
|
||||
data += ltv->len + 1;
|
||||
}
|
||||
|
||||
return pac + 1;
|
||||
}
|
||||
|
||||
static bool parse_capabilities(bap_lc3_t *conf, const uint8_t *data, size_t data_size)
|
||||
{
|
||||
uint16_t framelen_min = 0, framelen_max = 0;
|
||||
|
|
@ -113,7 +152,7 @@ static bool parse_capabilities(bap_lc3_t *conf, const uint8_t *data, size_t data
|
|||
while (data_size > 0) {
|
||||
struct ltv *ltv = (struct ltv *)data;
|
||||
|
||||
if (ltv->len > data_size)
|
||||
if (ltv->len < sizeof(struct ltv) || ltv->len >= data_size)
|
||||
return false;
|
||||
|
||||
switch (ltv->type) {
|
||||
|
|
@ -227,7 +266,7 @@ static bool parse_conf(bap_lc3_t *conf, const uint8_t *data, size_t data_size)
|
|||
while (data_size > 0) {
|
||||
struct ltv *ltv = (struct ltv *)data;
|
||||
|
||||
if (ltv->len > data_size)
|
||||
if (ltv->len < sizeof(struct ltv) || ltv->len >= data_size)
|
||||
return false;
|
||||
|
||||
switch (ltv->type) {
|
||||
|
|
@ -266,46 +305,16 @@ static bool parse_conf(bap_lc3_t *conf, const uint8_t *data, size_t data_size)
|
|||
return true;
|
||||
}
|
||||
|
||||
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
|
||||
const void *caps, size_t caps_size,
|
||||
const struct media_codec_audio_info *info,
|
||||
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
|
||||
static int conf_cmp(const bap_lc3_t *conf1, int res1, const bap_lc3_t *conf2, int res2)
|
||||
{
|
||||
bap_lc3_t conf;
|
||||
uint8_t *data = config;
|
||||
|
||||
if (caps == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
if (!parse_capabilities(&conf, caps, caps_size))
|
||||
return -ENOTSUP;
|
||||
|
||||
data += write_ltv_uint8(data, LC3_TYPE_FREQ, conf.rate);
|
||||
data += write_ltv_uint8(data, LC3_TYPE_DUR, conf.frame_duration);
|
||||
data += write_ltv_uint32(data, LC3_TYPE_CHAN, htobl(conf.channels));
|
||||
data += write_ltv_uint16(data, LC3_TYPE_FRAMELEN, htobs(conf.framelen));
|
||||
data += write_ltv_uint8(data, LC3_TYPE_BLKS, conf.n_blks);
|
||||
|
||||
return data - config;
|
||||
}
|
||||
|
||||
static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size,
|
||||
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;
|
||||
bap_lc3_t *conf;
|
||||
int res1, res2;
|
||||
const bap_lc3_t *conf;
|
||||
int a, b;
|
||||
|
||||
/* Order selected configurations by preference */
|
||||
res1 = codec->select_config(codec, 0, caps1, caps1_size, info, NULL, (uint8_t *)&conf1);
|
||||
res2 = codec->select_config(codec, 0, caps2, caps2_size, info , NULL, (uint8_t *)&conf2);
|
||||
|
||||
#define PREFER_EXPR(expr) \
|
||||
do { \
|
||||
conf = &conf1; \
|
||||
conf = conf1; \
|
||||
a = (expr); \
|
||||
conf = &conf2; \
|
||||
conf = conf2; \
|
||||
b = (expr); \
|
||||
if (a != b) \
|
||||
return b - a; \
|
||||
|
|
@ -329,6 +338,66 @@ static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t f
|
|||
#undef PREFER_BOOL
|
||||
}
|
||||
|
||||
static int pac_cmp(const void *p1, const void *p2)
|
||||
{
|
||||
const struct pac_data *pac1 = p1;
|
||||
const struct pac_data *pac2 = p2;
|
||||
bap_lc3_t conf1, conf2;
|
||||
int res1, res2;
|
||||
|
||||
res1 = parse_capabilities(&conf1, pac1->data, pac1->size) ? (int)sizeof(bap_lc3_t) : -EINVAL;
|
||||
res2 = parse_capabilities(&conf2, pac2->data, pac2->size) ? (int)sizeof(bap_lc3_t) : -EINVAL;
|
||||
|
||||
return conf_cmp(&conf1, res1, &conf2, res2);
|
||||
}
|
||||
|
||||
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
|
||||
const void *caps, size_t caps_size,
|
||||
const struct media_codec_audio_info *info,
|
||||
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
|
||||
{
|
||||
struct pac_data pacs[MAX_PACS];
|
||||
int npacs;
|
||||
bap_lc3_t conf;
|
||||
uint8_t *data = config;
|
||||
|
||||
if (caps == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
/* Select best conf from those possible */
|
||||
npacs = parse_bluez_pacs(caps, caps_size, pacs);
|
||||
if (npacs < 0)
|
||||
return npacs;
|
||||
else if (npacs == 0)
|
||||
return -EINVAL;
|
||||
|
||||
qsort(pacs, npacs, sizeof(struct pac_data), pac_cmp);
|
||||
|
||||
if (!parse_capabilities(&conf, pacs[0].data, pacs[0].size))
|
||||
return -ENOTSUP;
|
||||
|
||||
data += write_ltv_uint8(data, LC3_TYPE_FREQ, conf.rate);
|
||||
data += write_ltv_uint8(data, LC3_TYPE_DUR, conf.frame_duration);
|
||||
data += write_ltv_uint32(data, LC3_TYPE_CHAN, htobl(conf.channels));
|
||||
data += write_ltv_uint16(data, LC3_TYPE_FRAMELEN, htobs(conf.framelen));
|
||||
data += write_ltv_uint8(data, LC3_TYPE_BLKS, conf.n_blks);
|
||||
|
||||
return data - config;
|
||||
}
|
||||
|
||||
static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size,
|
||||
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;
|
||||
|
||||
/* Order selected configurations by preference */
|
||||
res1 = codec->select_config(codec, 0, caps1, caps1_size, info, NULL, (uint8_t *)&conf1);
|
||||
res2 = codec->select_config(codec, 0, caps2, caps2_size, info , NULL, (uint8_t *)&conf2);
|
||||
|
||||
return conf_cmp(&conf1, res1, &conf2, res2);
|
||||
}
|
||||
|
||||
static uint8_t channels_to_positions(uint32_t channels, uint8_t n_channels, uint32_t *position)
|
||||
{
|
||||
uint8_t n_positions = 0;
|
||||
|
|
|
|||
|
|
@ -628,6 +628,7 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
|
|||
uint8_t caps[A2DP_MAX_CAPS_SIZE];
|
||||
uint8_t config[A2DP_MAX_CAPS_SIZE];
|
||||
int caps_size = 0;
|
||||
int conf_size;
|
||||
DBusMessageIter dict;
|
||||
struct bap_endpoint_qos endpoint_qos;
|
||||
|
||||
|
|
@ -688,6 +689,10 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
|
|||
}
|
||||
|
||||
dbus_message_iter_get_fixed_array(&array, &buf, &caps_size);
|
||||
if (caps_size > (int)sizeof(caps)) {
|
||||
spa_log_error(monitor->log, "%s size:%d too large", key, (int)caps_size);
|
||||
goto error_invalid;
|
||||
}
|
||||
memcpy(caps, buf, caps_size);
|
||||
|
||||
spa_log_info(monitor->log, "%p: %s %s size:%d", monitor, path, key, caps_size);
|
||||
|
|
@ -764,15 +769,14 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
|
|||
/* TODO: determine which device the SelectConfiguration() call is associated
|
||||
* with; it's known here based on the remote endpoint.
|
||||
*/
|
||||
res = codec->select_config(codec, 0, caps, caps_size, &monitor->default_audio_info, NULL, config);
|
||||
|
||||
if (res < 0 || res != caps_size) {
|
||||
conf_size = codec->select_config(codec, 0, caps, caps_size, &monitor->default_audio_info, NULL, config);
|
||||
if (conf_size < 0) {
|
||||
spa_log_error(monitor->log, "can't select config: %d (%s)",
|
||||
res, spa_strerror(res));
|
||||
conf_size, spa_strerror(conf_size));
|
||||
goto error_invalid;
|
||||
}
|
||||
spa_log_info(monitor->log, "%p: selected conf %d", monitor, caps_size);
|
||||
spa_log_hexdump(monitor->log, SPA_LOG_LEVEL_DEBUG, ' ', (uint8_t *)config, (size_t)caps_size);
|
||||
spa_log_info(monitor->log, "%p: selected conf %d", monitor, conf_size);
|
||||
spa_log_hexdump(monitor->log, SPA_LOG_LEVEL_DEBUG, ' ', (uint8_t *)config, (size_t)conf_size);
|
||||
|
||||
if ((r = dbus_message_new_method_return(m)) == NULL)
|
||||
return DBUS_HANDLER_RESULT_NEED_MEMORY;
|
||||
|
|
@ -784,7 +788,7 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
|
|||
DBUS_TYPE_VARIANT_AS_STRING
|
||||
DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
|
||||
&dict);
|
||||
append_basic_array_variant_dict_entry(&dict, "Capabilities", "ay", "y", DBUS_TYPE_BYTE, &config, caps_size);
|
||||
append_basic_array_variant_dict_entry(&dict, "Capabilities", "ay", "y", DBUS_TYPE_BYTE, &config, conf_size);
|
||||
|
||||
if (codec->get_qos) {
|
||||
struct bap_codec_qos qos;
|
||||
|
|
@ -793,7 +797,7 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
|
|||
|
||||
spa_zero(qos);
|
||||
|
||||
res = codec->get_qos(codec, config, caps_size, &endpoint_qos, &qos);
|
||||
res = codec->get_qos(codec, config, conf_size, &endpoint_qos, &qos);
|
||||
if (res < 0) {
|
||||
spa_log_error(monitor->log, "can't select QOS config: %d (%s)",
|
||||
res, spa_strerror(res));
|
||||
|
|
|
|||
|
|
@ -78,7 +78,10 @@ bool media_codec_check_caps(const struct media_codec *codec, unsigned int codec_
|
|||
if (res < 0)
|
||||
return false;
|
||||
|
||||
return ((size_t)res == caps_size);
|
||||
if (codec->bap)
|
||||
return true;
|
||||
else
|
||||
return ((size_t)res == caps_size);
|
||||
}
|
||||
|
||||
#ifdef CODEC_PLUGIN
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue