bluez5: Add LE Audio BAP support to bluez5-device

This commit is contained in:
Frédéric Danis 2022-06-17 15:12:24 +02:00 committed by Wim Taymans
parent 201fd7755d
commit 96acc5a79a
2 changed files with 140 additions and 41 deletions

View file

@ -73,6 +73,7 @@ enum {
DEVICE_PROFILE_AG = 1,
DEVICE_PROFILE_A2DP = 2,
DEVICE_PROFILE_HSP_HFP = 3,
DEVICE_PROFILE_BAP = 4,
};
struct props {
@ -143,8 +144,8 @@ struct impl {
const struct media_codec **supported_codecs;
size_t supported_codec_count;
struct dynamic_node dyn_a2dp_source;
struct dynamic_node dyn_a2dp_sink;
struct dynamic_node dyn_media_source;
struct dynamic_node dyn_media_sink;
struct dynamic_node dyn_sco_source;
struct dynamic_node dyn_sco_sink;
@ -351,6 +352,7 @@ static bool node_update_volume_from_transport(struct node *node, bool reset)
/* PW is the controller for remote device. */
if (impl->profile != DEVICE_PROFILE_A2DP
&& impl->profile != DEVICE_PROFILE_BAP
&& impl->profile != DEVICE_PROFILE_HSP_HFP)
return false;
@ -676,15 +678,18 @@ static int emit_nodes(struct impl *this)
1, SPA_NAME_API_BLUEZ5_SCO_SINK, false);
}
}
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE) {
t = find_transport(this, SPA_BT_PROFILE_A2DP_SOURCE, 0);
if (this->bt_dev->connected_profiles & (SPA_BT_PROFILE_MEDIA_SOURCE)) {
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_BAP_SOURCE)
t = find_transport(this, SPA_BT_PROFILE_BAP_SOURCE, 0);
else
t = find_transport(this, SPA_BT_PROFILE_A2DP_SOURCE, 0);
if (t) {
this->props.codec = t->media_codec->id;
emit_dynamic_node(&this->dyn_a2dp_source, this, t,
emit_dynamic_node(&this->dyn_media_source, this, t,
2, SPA_NAME_API_BLUEZ5_MEDIA_SOURCE, false);
if (t->media_codec->duplex_codec) {
emit_dynamic_node(&this->dyn_a2dp_sink, this, t,
emit_dynamic_node(&this->dyn_media_sink, this, t,
3, SPA_NAME_API_BLUEZ5_MEDIA_SINK, true);
}
}
@ -695,7 +700,7 @@ static int emit_nodes(struct impl *this)
t = find_transport(this, SPA_BT_PROFILE_A2DP_SOURCE, 0);
if (t) {
this->props.codec = t->media_codec->id;
emit_dynamic_node(&this->dyn_a2dp_source, this, t,
emit_dynamic_node(&this->dyn_media_source, this, t,
DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_MEDIA_SOURCE, false);
if (t->media_codec->duplex_codec) {
@ -718,6 +723,27 @@ static int emit_nodes(struct impl *this)
}
}
if (get_supported_media_codec(this, this->props.codec, NULL) == NULL)
this->props.codec = 0;
break;
case DEVICE_PROFILE_BAP:
if (this->bt_dev->connected_profiles & (SPA_BT_PROFILE_BAP_SOURCE)) {
t = find_transport(this, SPA_BT_PROFILE_BAP_SOURCE, 0);
if (t) {
this->props.codec = t->media_codec->id;
emit_dynamic_node(&this->dyn_media_source, this, t,
DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_MEDIA_SOURCE, false);
}
}
if (this->bt_dev->connected_profiles & (SPA_BT_PROFILE_BAP_SINK)) {
t = find_transport(this, SPA_BT_PROFILE_BAP_SINK, this->props.codec);
if (t) {
this->props.codec = t->media_codec->id;
emit_node(this, t, DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_MEDIA_SINK, false);
}
}
if (get_supported_media_codec(this, this->props.codec, NULL) == NULL)
this->props.codec = 0;
break;
@ -766,8 +792,8 @@ static void emit_info(struct impl *this, bool full)
static void emit_remove_nodes(struct impl *this)
{
remove_dynamic_node (&this->dyn_a2dp_source);
remove_dynamic_node (&this->dyn_a2dp_sink);
remove_dynamic_node (&this->dyn_media_source);
remove_dynamic_node (&this->dyn_media_sink);
remove_dynamic_node (&this->dyn_sco_source);
remove_dynamic_node (&this->dyn_sco_sink);
@ -800,6 +826,7 @@ static int set_profile(struct impl *this, uint32_t profile, enum spa_bluetooth_a
if (this->profile == profile &&
(this->profile != DEVICE_PROFILE_A2DP || codec == this->props.codec) &&
(this->profile != DEVICE_PROFILE_BAP || codec == this->props.codec) &&
(this->profile != DEVICE_PROFILE_HSP_HFP || codec == this->props.codec))
return 0;
@ -812,11 +839,12 @@ static int set_profile(struct impl *this, uint32_t profile, enum spa_bluetooth_a
this->props.codec = codec;
/*
* A2DP: ensure there's a transport with the selected codec (0 means any).
* A2DP/BAP: ensure there's a transport with the selected codec (0 means any).
* Don't try to switch codecs when the device is in the A2DP source role, since
* devices do not appear to like that.
*/
if (profile == DEVICE_PROFILE_A2DP && !(this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE)) {
if ((profile == DEVICE_PROFILE_A2DP || profile == DEVICE_PROFILE_BAP)
&& !(this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE)) {
int ret;
const struct media_codec *codecs[64];
@ -873,6 +901,8 @@ static void codec_switched(void *userdata, int status)
spa_log_error(this->log, "failed to switch codec (%d), setting fallback profile", status);
if (this->profile == DEVICE_PROFILE_A2DP && this->props.codec != 0) {
this->props.codec = 0;
} else if (this->profile == DEVICE_PROFILE_BAP && this->props.codec != 0) {
this->props.codec = 0;
} else if (this->profile == DEVICE_PROFILE_HSP_HFP && this->props.codec != 0) {
this->props.codec = 0;
} else {
@ -925,16 +955,17 @@ static void profiles_changed(void *userdata, uint32_t prev_profiles, uint32_t pr
break;
case DEVICE_PROFILE_AG:
nodes_changed = (connected_change & (SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY |
SPA_BT_PROFILE_A2DP_SOURCE));
SPA_BT_PROFILE_MEDIA_SOURCE));
spa_log_debug(this->log, "profiles changed: AG nodes changed: %d",
nodes_changed);
break;
case DEVICE_PROFILE_A2DP:
case DEVICE_PROFILE_BAP:
if (get_supported_media_codec(this, this->props.codec, NULL) == NULL)
this->props.codec = 0;
nodes_changed = (connected_change & (SPA_BT_PROFILE_A2DP_SINK |
SPA_BT_PROFILE_A2DP_SOURCE));
spa_log_debug(this->log, "profiles changed: A2DP nodes changed: %d",
nodes_changed = (connected_change & (SPA_BT_PROFILE_MEDIA_SINK |
SPA_BT_PROFILE_MEDIA_SOURCE));
spa_log_debug(this->log, "profiles changed: media nodes changed: %d",
nodes_changed);
break;
case DEVICE_PROFILE_HSP_HFP:
@ -1023,7 +1054,8 @@ static uint32_t profile_direction_mask(struct impl *this, uint32_t index, enum s
switch (index) {
case DEVICE_PROFILE_A2DP:
if (device->connected_profiles & SPA_BT_PROFILE_A2DP_SINK)
case DEVICE_PROFILE_BAP:
if (device->connected_profiles & SPA_BT_PROFILE_MEDIA_SINK)
have_output = true;
media_codec = get_supported_media_codec(this, codec, NULL);
@ -1060,6 +1092,7 @@ static uint32_t get_profile_from_index(struct impl *this, uint32_t index, uint32
return index;
} else if (index != SPA_ID_INVALID) {
const struct spa_type_info *info;
uint32_t profile;
*codec = index - 3;
*next = SPA_ID_INVALID;
@ -1068,7 +1101,14 @@ static uint32_t get_profile_from_index(struct impl *this, uint32_t index, uint32
if (info->type > *codec)
*next = SPA_MIN(info->type + 3, *next);
return get_hfp_codec(*codec) ? DEVICE_PROFILE_HSP_HFP : DEVICE_PROFILE_A2DP;
if (get_hfp_codec(*codec))
profile = DEVICE_PROFILE_HSP_HFP;
else if (*codec == SPA_BLUETOOTH_AUDIO_CODEC_LC3)
profile = DEVICE_PROFILE_BAP;
else
profile = DEVICE_PROFILE_A2DP;
return profile;
}
*next = SPA_ID_INVALID;
@ -1080,8 +1120,8 @@ static uint32_t get_index_from_profile(struct impl *this, uint32_t profile, enum
if (profile == DEVICE_PROFILE_OFF || profile == DEVICE_PROFILE_AG)
return profile;
if (profile == DEVICE_PROFILE_A2DP) {
if (codec == 0 || (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE))
if (profile == DEVICE_PROFILE_A2DP || profile == DEVICE_PROFILE_BAP) {
if (codec == 0 || (this->bt_dev->connected_profiles & SPA_BT_PROFILE_MEDIA_SOURCE))
return profile;
return codec + 3;
@ -1132,8 +1172,8 @@ static void set_initial_profile(struct impl *this)
this->supported_codecs = spa_bt_device_get_supported_media_codecs(
this->bt_dev, &this->supported_codec_count, true);
/* Prefer A2DP, then HFP, then null, but select AG if the device
appears not to have A2DP_SINK or any HEAD_UNIT profile */
/* Prefer BAP, then A2DP, then HFP, then null, but select AG if the device
appears not to have BAP_SINK, A2DP_SINK or any HEAD_UNIT profile */
/* If default profile is set to HSP/HFP, first try those and exit if found. */
if (this->bt_dev->settings != NULL) {
@ -1144,16 +1184,20 @@ static void set_initial_profile(struct impl *this)
return;
}
for (i = SPA_BT_PROFILE_A2DP_SINK; i <= SPA_BT_PROFILE_A2DP_SOURCE; i <<= 1) {
for (i = SPA_BT_PROFILE_BAP_SINK; i <= SPA_BT_PROFILE_A2DP_SOURCE; i <<= 1) {
if (!(this->bt_dev->connected_profiles & i))
continue;
t = find_transport(this, i, 0);
if (t) {
this->profile = (i == SPA_BT_PROFILE_A2DP_SOURCE) ?
DEVICE_PROFILE_AG : DEVICE_PROFILE_A2DP;
if (i == SPA_BT_PROFILE_A2DP_SOURCE || i == SPA_BT_PROFILE_BAP_SOURCE)
this->profile = DEVICE_PROFILE_AG;
else if (i == SPA_BT_PROFILE_BAP_SINK)
this->profile = DEVICE_PROFILE_BAP;
else
this->profile = DEVICE_PROFILE_A2DP;
this->props.codec = t->media_codec->id;
spa_log_debug(this->log, "initial profile A2DP profile:%d codec:%d",
spa_log_debug(this->log, "initial profile media profile:%d codec:%d",
this->profile, this->props.codec);
return;
}
@ -1240,6 +1284,52 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder *
}
break;
}
case DEVICE_PROFILE_BAP:
{
uint32_t profile = device->connected_profiles &
(SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE);
size_t idx;
const struct media_codec *media_codec;
if (profile == 0)
return NULL;
if (!codec) {
errno = EINVAL;
return NULL;
}
if (profile & (SPA_BT_PROFILE_BAP_SINK))
n_sink++;
if (profile & (SPA_BT_PROFILE_BAP_SOURCE))
n_source++;
name = spa_bt_profile_name(profile);
media_codec = get_supported_media_codec(this, codec, &idx);
if (media_codec == NULL) {
errno = EINVAL;
return NULL;
}
name_and_codec = spa_aprintf("%s-%s", name, media_codec->name);
name = name_and_codec;
switch (profile) {
case SPA_BT_PROFILE_BAP_SINK:
desc_and_codec = spa_aprintf(_("High Fidelity Playback (BAP Sink, codec %s)"),
media_codec->description);
break;
case SPA_BT_PROFILE_BAP_SOURCE:
desc_and_codec = spa_aprintf(_("High Fidelity Input (BAP Source, codec %s)"),
media_codec->description);
break;
default:
desc_and_codec = spa_aprintf(_("High Fidelity Duplex (BAP Source/Sink, codec %s)"),
media_codec->description);
}
desc = desc_and_codec;
priority = 128 + this->supported_codec_count - idx; /* order as in codec list */
break;
}
case DEVICE_PROFILE_HSP_HFP:
{
/* make this device profile visible only if there is a head unit */
@ -1537,7 +1627,8 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b,
spa_pod_builder_array(b, sizeof(uint32_t), SPA_TYPE_Id,
node->n_channels, node->channels);
if (this->profile == DEVICE_PROFILE_A2DP && dev == DEVICE_ID_SINK) {
if ((this->profile == DEVICE_PROFILE_A2DP || this->profile == DEVICE_PROFILE_BAP) &&
dev == DEVICE_ID_SINK) {
spa_pod_builder_prop(b, SPA_PROP_latencyOffsetNsec, 0);
spa_pod_builder_long(b, node->latency_offset);
}
@ -1586,7 +1677,7 @@ static struct spa_pod *build_prop_info(struct impl *this, struct spa_pod_builder
size_t n;
int j;
#define FOR_EACH_A2DP_CODEC(j, codec) \
#define FOR_EACH_MEDIA_CODEC(j, codec) \
for (j = -1; iterate_supported_media_codecs(this, &j, &codec);)
#define FOR_EACH_HFP_CODEC(j) \
for (j = HFP_AUDIO_CODEC_MSBC; j >= HFP_AUDIO_CODEC_CVSD; --j) \
@ -1608,8 +1699,8 @@ static struct spa_pod *build_prop_info(struct impl *this, struct spa_pod_builder
spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0);
choice = (struct spa_pod_choice *)spa_pod_builder_frame(b, &f[1]);
n = 0;
if (this->profile == DEVICE_PROFILE_A2DP) {
FOR_EACH_A2DP_CODEC(j, codec) {
if (this->profile == DEVICE_PROFILE_A2DP || this->profile == DEVICE_PROFILE_BAP) {
FOR_EACH_MEDIA_CODEC(j, codec) {
if (n == 0)
spa_pod_builder_int(b, codec->id);
spa_pod_builder_int(b, codec->id);
@ -1628,8 +1719,8 @@ static struct spa_pod *build_prop_info(struct impl *this, struct spa_pod_builder
spa_pod_builder_pop(b, &f[1]);
spa_pod_builder_prop(b, SPA_PROP_INFO_labels, 0);
spa_pod_builder_push_struct(b, &f[1]);
if (this->profile == DEVICE_PROFILE_A2DP) {
FOR_EACH_A2DP_CODEC(j, codec) {
if (this->profile == DEVICE_PROFILE_A2DP || this->profile == DEVICE_PROFILE_BAP) {
FOR_EACH_MEDIA_CODEC(j, codec) {
spa_pod_builder_int(b, codec->id);
spa_pod_builder_string(b, codec->description);
}
@ -1642,7 +1733,7 @@ static struct spa_pod *build_prop_info(struct impl *this, struct spa_pod_builder
spa_pod_builder_pop(b, &f[1]);
return spa_pod_builder_pop(b, &f[0]);
#undef FOR_EACH_A2DP_CODEC
#undef FOR_EACH_MEDIA_CODEC
#undef FOR_EACH_HFP_CODEC
}
@ -1688,6 +1779,7 @@ static int impl_enum_params(void *object, int seq,
case DEVICE_PROFILE_OFF:
case DEVICE_PROFILE_AG:
case DEVICE_PROFILE_A2DP:
case DEVICE_PROFILE_BAP:
case DEVICE_PROFILE_HSP_HFP:
param = build_profile(this, &b, id, result.index, profile, codec, false);
if (param == NULL)
@ -2024,7 +2116,7 @@ static int impl_set_param(void *object,
if (codec_id == SPA_ID_INVALID)
return 0;
if (this->profile == DEVICE_PROFILE_A2DP) {
if (this->profile == DEVICE_PROFILE_A2DP || this->profile == DEVICE_PROFILE_BAP) {
size_t j;
for (j = 0; j < this->supported_codec_count; ++j) {
if (this->supported_codecs[j]->id == codec_id) {

View file

@ -184,16 +184,17 @@ enum spa_bt_media_direction {
enum spa_bt_profile {
SPA_BT_PROFILE_NULL = 0,
SPA_BT_PROFILE_A2DP_SINK = (1 << 0),
SPA_BT_PROFILE_A2DP_SOURCE = (1 << 1),
SPA_BT_PROFILE_HSP_HS = (1 << 2),
SPA_BT_PROFILE_HSP_AG = (1 << 3),
SPA_BT_PROFILE_HFP_HF = (1 << 4),
SPA_BT_PROFILE_HFP_AG = (1 << 5),
SPA_BT_PROFILE_BAP_SINK = (1 << 6),
SPA_BT_PROFILE_BAP_SOURCE = (1 << 7),
SPA_BT_PROFILE_BAP_SINK = (1 << 0),
SPA_BT_PROFILE_BAP_SOURCE = (1 << 1),
SPA_BT_PROFILE_A2DP_SINK = (1 << 2),
SPA_BT_PROFILE_A2DP_SOURCE = (1 << 3),
SPA_BT_PROFILE_HSP_HS = (1 << 4),
SPA_BT_PROFILE_HSP_AG = (1 << 5),
SPA_BT_PROFILE_HFP_HF = (1 << 6),
SPA_BT_PROFILE_HFP_AG = (1 << 7),
SPA_BT_PROFILE_A2DP_DUPLEX = (SPA_BT_PROFILE_A2DP_SINK | SPA_BT_PROFILE_A2DP_SOURCE),
SPA_BT_PROFILE_BAP_DUPLEX = (SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE),
SPA_BT_PROFILE_HEADSET_HEAD_UNIT = (SPA_BT_PROFILE_HSP_HS | SPA_BT_PROFILE_HFP_HF),
SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY = (SPA_BT_PROFILE_HSP_AG | SPA_BT_PROFILE_HFP_AG),
SPA_BT_PROFILE_HEADSET_AUDIO = (SPA_BT_PROFILE_HEADSET_HEAD_UNIT | SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY),
@ -322,6 +323,12 @@ static inline const char *spa_bt_profile_name (enum spa_bt_profile profile) {
return "headset-audio-gateway";
case SPA_BT_PROFILE_HEADSET_AUDIO:
return "headset-audio";
case SPA_BT_PROFILE_BAP_SOURCE:
return "bap-source";
case SPA_BT_PROFILE_BAP_SINK:
return "bap-sink";
case SPA_BT_PROFILE_BAP_DUPLEX:
return "bap-duplex";
default:
break;
}