mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-07-05 00:06:16 -04:00
Merge branch 'btcodec-combine-caps' into 'master'
Draft: bluez5: fix shared A2DP endpoint caps including disabled companion codecs See merge request pipewire/pipewire!2887
This commit is contained in:
commit
45150ebe4c
6 changed files with 281 additions and 78 deletions
|
|
@ -73,13 +73,21 @@ done:
|
||||||
static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
|
static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
|
||||||
const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE])
|
const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE])
|
||||||
{
|
{
|
||||||
|
uint8_t object_type;
|
||||||
|
|
||||||
|
if (codec->id == SPA_BLUETOOTH_AUDIO_CODEC_AAC_ELD) {
|
||||||
|
if (!eld_supported())
|
||||||
|
return -ENOTSUP;
|
||||||
|
object_type = AAC_OBJECT_TYPE_MPEG4_AAC_ELD;
|
||||||
|
} else {
|
||||||
|
/* NOTE: AAC Long Term Prediction and AAC Scalable are
|
||||||
|
* not supported by the FDK-AAC library. */
|
||||||
|
object_type = AAC_OBJECT_TYPE_MPEG2_AAC_LC |
|
||||||
|
AAC_OBJECT_TYPE_MPEG4_AAC_LC;
|
||||||
|
}
|
||||||
|
|
||||||
const a2dp_aac_t a2dp_aac = {
|
const a2dp_aac_t a2dp_aac = {
|
||||||
.object_type =
|
.object_type = object_type,
|
||||||
/* NOTE: AAC Long Term Prediction and AAC Scalable are
|
|
||||||
* not supported by the FDK-AAC library. */
|
|
||||||
AAC_OBJECT_TYPE_MPEG2_AAC_LC |
|
|
||||||
AAC_OBJECT_TYPE_MPEG4_AAC_LC |
|
|
||||||
(eld_supported() ? AAC_OBJECT_TYPE_MPEG4_AAC_ELD : 0),
|
|
||||||
AAC_INIT_FREQUENCY(
|
AAC_INIT_FREQUENCY(
|
||||||
AAC_SAMPLING_FREQ_8000 |
|
AAC_SAMPLING_FREQ_8000 |
|
||||||
AAC_SAMPLING_FREQ_11025 |
|
AAC_SAMPLING_FREQ_11025 |
|
||||||
|
|
@ -104,6 +112,32 @@ static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
|
||||||
return sizeof(a2dp_aac);
|
return sizeof(a2dp_aac);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int codec_combine_caps(const struct media_codec *codec, uint32_t flags,
|
||||||
|
uint8_t caps[A2DP_MAX_CAPS_SIZE], int caps_size,
|
||||||
|
const uint8_t *other, int other_size)
|
||||||
|
{
|
||||||
|
a2dp_aac_t *dst = (a2dp_aac_t *)caps;
|
||||||
|
const a2dp_aac_t *src = (const a2dp_aac_t *)other;
|
||||||
|
uint32_t freq;
|
||||||
|
uint32_t dst_bitrate, src_bitrate;
|
||||||
|
|
||||||
|
if (caps_size != (int)sizeof(*dst) || other_size != (int)sizeof(*src))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
dst->object_type |= src->object_type;
|
||||||
|
dst->channels |= src->channels;
|
||||||
|
dst->vbr |= src->vbr;
|
||||||
|
|
||||||
|
freq = AAC_GET_FREQUENCY(*dst) | AAC_GET_FREQUENCY(*src);
|
||||||
|
AAC_SET_FREQUENCY(*dst, freq);
|
||||||
|
|
||||||
|
dst_bitrate = AAC_GET_BITRATE(*dst);
|
||||||
|
src_bitrate = AAC_GET_BITRATE(*src);
|
||||||
|
AAC_SET_BITRATE(*dst, SPA_MAX(dst_bitrate, src_bitrate));
|
||||||
|
|
||||||
|
return caps_size;
|
||||||
|
}
|
||||||
|
|
||||||
static const struct media_codec_config
|
static const struct media_codec_config
|
||||||
aac_frequencies[] = {
|
aac_frequencies[] = {
|
||||||
{ AAC_SAMPLING_FREQ_44100, 44100, 10 },
|
{ AAC_SAMPLING_FREQ_44100, 44100, 10 },
|
||||||
|
|
@ -218,11 +252,20 @@ static int codec_validate_config(const struct media_codec *codec, uint32_t flags
|
||||||
*
|
*
|
||||||
* Some devices also set multiple bits in frequencies & channels.
|
* Some devices also set multiple bits in frequencies & channels.
|
||||||
* For these, pick a "preferred" choice.
|
* For these, pick a "preferred" choice.
|
||||||
|
*
|
||||||
|
* Each codec object only accepts its own object types: a2dp_codec_aac
|
||||||
|
* accepts LC variants, a2dp_codec_aac_eld accepts ELD. This lets the
|
||||||
|
* monitor's config-aware resolver attribute the transport to the
|
||||||
|
* correct codec object.
|
||||||
*/
|
*/
|
||||||
if (!(conf.object_type & (AAC_OBJECT_TYPE_MPEG2_AAC_LC |
|
if (codec->id == SPA_BLUETOOTH_AUDIO_CODEC_AAC_ELD) {
|
||||||
AAC_OBJECT_TYPE_MPEG4_AAC_LC |
|
if (!(conf.object_type & AAC_OBJECT_TYPE_MPEG4_AAC_ELD))
|
||||||
AAC_OBJECT_TYPE_MPEG4_AAC_ELD)))
|
return -EINVAL;
|
||||||
return -EINVAL;
|
} else {
|
||||||
|
if (!(conf.object_type & (AAC_OBJECT_TYPE_MPEG2_AAC_LC |
|
||||||
|
AAC_OBJECT_TYPE_MPEG4_AAC_LC)))
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
j = 0;
|
j = 0;
|
||||||
SPA_FOR_EACH_ELEMENT_VAR(aac_frequencies, f) {
|
SPA_FOR_EACH_ELEMENT_VAR(aac_frequencies, f) {
|
||||||
if (AAC_GET_FREQUENCY(conf) & f->config) {
|
if (AAC_GET_FREQUENCY(conf) & f->config) {
|
||||||
|
|
@ -331,17 +374,15 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags,
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
/* If object type has multiple bits set (invalid per spec, see above),
|
/* If object type has multiple bits set (invalid per spec, see above),
|
||||||
* assume the device usually means MPEG2 AAC LC which is mandatory.
|
* the validate_config step is gated by codec->id so we trust the
|
||||||
|
* configuration matches this codec object. Within the LC family,
|
||||||
|
* prefer MPEG2 AAC LC which is mandatory.
|
||||||
*/
|
*/
|
||||||
if (conf->object_type & AAC_OBJECT_TYPE_MPEG2_AAC_LC) {
|
if (codec->id == SPA_BLUETOOTH_AUDIO_CODEC_AAC_ELD) {
|
||||||
res = aacEncoder_SetParam(this->aacenc, AACENC_AOT, AOT_MP2_AAC_LC);
|
if (!(conf->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_ELD)) {
|
||||||
if (res != AACENC_OK)
|
res = -EINVAL;
|
||||||
goto error;
|
goto error;
|
||||||
} else if (conf->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_LC) {
|
}
|
||||||
res = aacEncoder_SetParam(this->aacenc, AACENC_AOT, AOT_AAC_LC);
|
|
||||||
if (res != AACENC_OK)
|
|
||||||
goto error;
|
|
||||||
} else if (conf->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_ELD) {
|
|
||||||
res = aacEncoder_SetParam(this->aacenc, AACENC_AOT, AOT_ER_AAC_ELD);
|
res = aacEncoder_SetParam(this->aacenc, AACENC_AOT, AOT_ER_AAC_ELD);
|
||||||
if (res != AACENC_OK)
|
if (res != AACENC_OK)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
@ -349,6 +390,14 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags,
|
||||||
res = aacEncoder_SetParam(this->aacenc, AACENC_SBR_MODE, 1);
|
res = aacEncoder_SetParam(this->aacenc, AACENC_SBR_MODE, 1);
|
||||||
if (res != AACENC_OK)
|
if (res != AACENC_OK)
|
||||||
goto error;
|
goto error;
|
||||||
|
} else if (conf->object_type & AAC_OBJECT_TYPE_MPEG2_AAC_LC) {
|
||||||
|
res = aacEncoder_SetParam(this->aacenc, AACENC_AOT, AOT_MP2_AAC_LC);
|
||||||
|
if (res != AACENC_OK)
|
||||||
|
goto error;
|
||||||
|
} else if (conf->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_LC) {
|
||||||
|
res = aacEncoder_SetParam(this->aacenc, AACENC_AOT, AOT_AAC_LC);
|
||||||
|
if (res != AACENC_OK)
|
||||||
|
goto error;
|
||||||
} else {
|
} else {
|
||||||
res = -EINVAL;
|
res = -EINVAL;
|
||||||
goto error;
|
goto error;
|
||||||
|
|
@ -665,6 +714,7 @@ const struct media_codec a2dp_codec_aac = {
|
||||||
.name = "aac",
|
.name = "aac",
|
||||||
.description = "AAC",
|
.description = "AAC",
|
||||||
.fill_caps = codec_fill_caps,
|
.fill_caps = codec_fill_caps,
|
||||||
|
.combine_caps = codec_combine_caps,
|
||||||
.select_config = codec_select_config,
|
.select_config = codec_select_config,
|
||||||
.enum_config = codec_enum_config,
|
.enum_config = codec_enum_config,
|
||||||
.validate_config = codec_validate_config,
|
.validate_config = codec_validate_config,
|
||||||
|
|
@ -691,7 +741,8 @@ const struct media_codec a2dp_codec_aac_eld = {
|
||||||
.name = "aac_eld",
|
.name = "aac_eld",
|
||||||
.description = "AAC-ELD",
|
.description = "AAC-ELD",
|
||||||
.endpoint_name = "aac",
|
.endpoint_name = "aac",
|
||||||
.fill_caps = NULL,
|
.endpoint_companion = true,
|
||||||
|
.fill_caps = codec_fill_caps,
|
||||||
.select_config = codec_select_config,
|
.select_config = codec_select_config,
|
||||||
.enum_config = codec_enum_config,
|
.enum_config = codec_enum_config,
|
||||||
.validate_config = codec_validate_config,
|
.validate_config = codec_validate_config,
|
||||||
|
|
|
||||||
|
|
@ -1390,7 +1390,7 @@ const struct media_codec a2dp_codec_opus_05_51 = {
|
||||||
.name = "opus_05_51",
|
.name = "opus_05_51",
|
||||||
.description = "Opus 05 5.1 Surround",
|
.description = "Opus 05 5.1 Surround",
|
||||||
.endpoint_name = "opus_05",
|
.endpoint_name = "opus_05",
|
||||||
.fill_caps = NULL,
|
.endpoint_companion = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const struct media_codec a2dp_codec_opus_05_71 = {
|
const struct media_codec a2dp_codec_opus_05_71 = {
|
||||||
|
|
@ -1399,7 +1399,7 @@ const struct media_codec a2dp_codec_opus_05_71 = {
|
||||||
.name = "opus_05_71",
|
.name = "opus_05_71",
|
||||||
.description = "Opus 05 7.1 Surround",
|
.description = "Opus 05 7.1 Surround",
|
||||||
.endpoint_name = "opus_05",
|
.endpoint_name = "opus_05",
|
||||||
.fill_caps = NULL,
|
.endpoint_companion = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Bidi return channel codec: doesn't have endpoints */
|
/* Bidi return channel codec: doesn't have endpoints */
|
||||||
|
|
@ -1428,7 +1428,7 @@ const struct media_codec a2dp_codec_opus_05_pro = {
|
||||||
.clear_props = codec_clear_props,
|
.clear_props = codec_clear_props,
|
||||||
.duplex_codec = &a2dp_codec_opus_05_return,
|
.duplex_codec = &a2dp_codec_opus_05_return,
|
||||||
.endpoint_name = "opus_05_duplex",
|
.endpoint_name = "opus_05_duplex",
|
||||||
.fill_caps = NULL,
|
.endpoint_companion = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
MEDIA_CODEC_EXPORT_DEF(
|
MEDIA_CODEC_EXPORT_DEF(
|
||||||
|
|
|
||||||
|
|
@ -615,7 +615,7 @@ const struct media_codec a2dp_codec_sbc_xq = {
|
||||||
.name = "sbc_xq",
|
.name = "sbc_xq",
|
||||||
.description = "SBC-XQ",
|
.description = "SBC-XQ",
|
||||||
.endpoint_name = "sbc",
|
.endpoint_name = "sbc",
|
||||||
.fill_caps = NULL,
|
.endpoint_companion = true,
|
||||||
.select_config = codec_select_config,
|
.select_config = codec_select_config,
|
||||||
.enum_config = codec_enum_config,
|
.enum_config = codec_enum_config,
|
||||||
.validate_config = codec_validate_config,
|
.validate_config = codec_validate_config,
|
||||||
|
|
|
||||||
|
|
@ -507,6 +507,41 @@ static int media_codec_to_endpoint(const struct media_codec *codec,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool is_media_codec_enabled(struct spa_bt_monitor *monitor, const struct media_codec *codec)
|
||||||
|
{
|
||||||
|
/* Mandatory codecs are always enabled */
|
||||||
|
switch (codec->id) {
|
||||||
|
case SPA_BLUETOOTH_AUDIO_CODEC_SBC:
|
||||||
|
case SPA_BLUETOOTH_AUDIO_CODEC_CVSD:
|
||||||
|
case SPA_BLUETOOTH_AUDIO_CODEC_LC3:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return spa_dict_lookup(&monitor->enabled_codecs, codec->name) != NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *media_endpoint_to_ep_name(const char *endpoint, bool *sink)
|
||||||
|
{
|
||||||
|
static const struct { const char *prefix; bool sink; } prefixes[] = {
|
||||||
|
{ A2DP_SINK_ENDPOINT "/", true },
|
||||||
|
{ A2DP_SOURCE_ENDPOINT "/", false },
|
||||||
|
{ BAP_SOURCE_ENDPOINT "/", false },
|
||||||
|
{ BAP_SINK_ENDPOINT "/", true },
|
||||||
|
{ BAP_BROADCAST_SOURCE_ENDPOINT "/", false },
|
||||||
|
{ BAP_BROADCAST_SINK_ENDPOINT "/", true },
|
||||||
|
};
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < SPA_N_ELEMENTS(prefixes); i++) {
|
||||||
|
if (spa_strstartswith(endpoint, prefixes[i].prefix)) {
|
||||||
|
*sink = prefixes[i].sink;
|
||||||
|
return endpoint + strlen(prefixes[i].prefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*sink = true;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static const struct media_codec *media_endpoint_to_codec(struct spa_bt_monitor *monitor, const char *endpoint, bool *sink, const struct media_codec *preferred)
|
static const struct media_codec *media_endpoint_to_codec(struct spa_bt_monitor *monitor, const char *endpoint, bool *sink, const struct media_codec *preferred)
|
||||||
{
|
{
|
||||||
const char *ep_name;
|
const char *ep_name;
|
||||||
|
|
@ -514,51 +549,97 @@ static const struct media_codec *media_endpoint_to_codec(struct spa_bt_monitor *
|
||||||
const struct media_codec *found = NULL;
|
const struct media_codec *found = NULL;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
if (spa_strstartswith(endpoint, A2DP_SINK_ENDPOINT "/")) {
|
ep_name = media_endpoint_to_ep_name(endpoint, sink);
|
||||||
ep_name = endpoint + strlen(A2DP_SINK_ENDPOINT "/");
|
if (ep_name == NULL)
|
||||||
*sink = true;
|
|
||||||
} else if (spa_strstartswith(endpoint, A2DP_SOURCE_ENDPOINT "/")) {
|
|
||||||
ep_name = endpoint + strlen(A2DP_SOURCE_ENDPOINT "/");
|
|
||||||
*sink = false;
|
|
||||||
} else if (spa_strstartswith(endpoint, BAP_SOURCE_ENDPOINT "/")) {
|
|
||||||
ep_name = endpoint + strlen(BAP_SOURCE_ENDPOINT "/");
|
|
||||||
*sink = false;
|
|
||||||
} else if (spa_strstartswith(endpoint, BAP_SINK_ENDPOINT "/")) {
|
|
||||||
ep_name = endpoint + strlen(BAP_SINK_ENDPOINT "/");
|
|
||||||
*sink = true;
|
|
||||||
} else if (spa_strstartswith(endpoint, BAP_BROADCAST_SOURCE_ENDPOINT "/")) {
|
|
||||||
ep_name = endpoint + strlen(BAP_BROADCAST_SOURCE_ENDPOINT "/");
|
|
||||||
*sink = false;
|
|
||||||
} else if (spa_strstartswith(endpoint, BAP_BROADCAST_SINK_ENDPOINT "/")) {
|
|
||||||
ep_name = endpoint + strlen(BAP_BROADCAST_SINK_ENDPOINT "/");
|
|
||||||
*sink = true;
|
|
||||||
} else {
|
|
||||||
*sink = true;
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; media_codecs[i]; i++) {
|
for (i = 0; media_codecs[i]; i++) {
|
||||||
const struct media_codec *codec = media_codecs[i];
|
const struct media_codec *codec = media_codecs[i];
|
||||||
const char *codec_ep_name =
|
const char *codec_ep_name =
|
||||||
codec->endpoint_name ? codec->endpoint_name : codec->name;
|
codec->endpoint_name ? codec->endpoint_name : codec->name;
|
||||||
|
|
||||||
if (!preferred && !codec->fill_caps)
|
if (!is_media_codec_enabled(monitor, codec))
|
||||||
continue;
|
continue;
|
||||||
if (!spa_streq(ep_name, codec_ep_name))
|
if (!spa_streq(ep_name, codec_ep_name))
|
||||||
continue;
|
continue;
|
||||||
if ((*sink && !codec->decode) || (!*sink && !codec->encode))
|
if ((*sink && !codec->decode) || (!*sink && !codec->encode))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* Same endpoint may be shared with multiple codec objects,
|
/* Same endpoint may be shared with multiple codec objects.
|
||||||
* which may e.g. correspond to different encoder settings.
|
* Prefer the requested one, then the endpoint owner, else
|
||||||
* Look up which one we selected.
|
* fall back to the first match.
|
||||||
*/
|
*/
|
||||||
if ((preferred && codec == preferred) || found == NULL)
|
if (preferred && codec == preferred)
|
||||||
|
return codec;
|
||||||
|
if (found == NULL || (found->endpoint_companion && !codec->endpoint_companion))
|
||||||
found = codec;
|
found = codec;
|
||||||
}
|
}
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Like media_endpoint_to_codec, but when multiple codecs share the endpoint,
|
||||||
|
* pick the one whose validate_config accepts the given on-the-wire config.
|
||||||
|
* Falls back to the preferred/owner heuristic if none accepts (or if no codec
|
||||||
|
* implements validate_config).
|
||||||
|
*/
|
||||||
|
static const struct media_codec *media_endpoint_to_codec_for_config(
|
||||||
|
struct spa_bt_monitor *monitor, const char *endpoint, bool *sink,
|
||||||
|
const struct media_codec *preferred,
|
||||||
|
const void *config, size_t config_size)
|
||||||
|
{
|
||||||
|
const char *ep_name;
|
||||||
|
const struct media_codec * const * const media_codecs = monitor->media_codecs;
|
||||||
|
const struct media_codec *fallback;
|
||||||
|
const struct media_codec *accepted_preferred = NULL;
|
||||||
|
const struct media_codec *accepted_owner = NULL;
|
||||||
|
const struct media_codec *accepted_first = NULL;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
fallback = media_endpoint_to_codec(monitor, endpoint, sink, preferred);
|
||||||
|
if (fallback == NULL || config == NULL || config_size == 0)
|
||||||
|
return fallback;
|
||||||
|
|
||||||
|
ep_name = media_endpoint_to_ep_name(endpoint, sink);
|
||||||
|
if (ep_name == NULL)
|
||||||
|
return fallback;
|
||||||
|
|
||||||
|
for (i = 0; media_codecs[i]; i++) {
|
||||||
|
const struct media_codec *codec = media_codecs[i];
|
||||||
|
const char *codec_ep_name =
|
||||||
|
codec->endpoint_name ? codec->endpoint_name : codec->name;
|
||||||
|
struct spa_audio_info info;
|
||||||
|
|
||||||
|
if (!is_media_codec_enabled(monitor, codec))
|
||||||
|
continue;
|
||||||
|
if (!spa_streq(ep_name, codec_ep_name))
|
||||||
|
continue;
|
||||||
|
if ((*sink && !codec->decode) || (!*sink && !codec->encode))
|
||||||
|
continue;
|
||||||
|
if (!codec->validate_config)
|
||||||
|
continue;
|
||||||
|
if (codec->validate_config(codec, *sink ? MEDIA_CODEC_FLAG_SINK : 0,
|
||||||
|
config, config_size, &info) < 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (preferred && codec == preferred) {
|
||||||
|
accepted_preferred = codec;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (accepted_owner == NULL && !codec->endpoint_companion)
|
||||||
|
accepted_owner = codec;
|
||||||
|
if (accepted_first == NULL)
|
||||||
|
accepted_first = codec;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accepted_preferred)
|
||||||
|
return accepted_preferred;
|
||||||
|
if (accepted_owner)
|
||||||
|
return accepted_owner;
|
||||||
|
if (accepted_first)
|
||||||
|
return accepted_first;
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
static int media_endpoint_to_profile(const char *endpoint)
|
static int media_endpoint_to_profile(const char *endpoint)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
@ -578,19 +659,6 @@ static int media_endpoint_to_profile(const char *endpoint)
|
||||||
return SPA_BT_PROFILE_NULL;
|
return SPA_BT_PROFILE_NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool is_media_codec_enabled(struct spa_bt_monitor *monitor, const struct media_codec *codec)
|
|
||||||
{
|
|
||||||
/* Mandatory codecs are always enabled */
|
|
||||||
switch (codec->id) {
|
|
||||||
case SPA_BLUETOOTH_AUDIO_CODEC_SBC:
|
|
||||||
case SPA_BLUETOOTH_AUDIO_CODEC_CVSD:
|
|
||||||
case SPA_BLUETOOTH_AUDIO_CODEC_LC3:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return spa_dict_lookup(&monitor->enabled_codecs, codec->name) != NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static enum spa_bt_profile get_codec_profile(const struct media_codec *codec,
|
static enum spa_bt_profile get_codec_profile(const struct media_codec *codec,
|
||||||
enum spa_bt_media_direction direction)
|
enum spa_bt_media_direction direction)
|
||||||
{
|
{
|
||||||
|
|
@ -686,10 +754,11 @@ static bool endpoint_should_be_registered(struct spa_bt_monitor *monitor,
|
||||||
const struct media_codec *codec,
|
const struct media_codec *codec,
|
||||||
enum spa_bt_media_direction direction)
|
enum spa_bt_media_direction direction)
|
||||||
{
|
{
|
||||||
/* Codecs with fill_caps == NULL share endpoint with another codec,
|
/* Companion codecs share another codec's endpoint and don't get
|
||||||
* and don't have their own endpoint
|
* registered themselves; the owner's registration covers them.
|
||||||
*/
|
*/
|
||||||
return codec_has_direction(monitor, codec, direction) &&
|
return codec_has_direction(monitor, codec, direction) &&
|
||||||
|
!codec->endpoint_companion &&
|
||||||
codec->fill_caps;
|
codec->fill_caps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -767,6 +836,60 @@ const struct spa_dict *get_device_codec_settings(struct spa_bt_device *device, b
|
||||||
return bap ? device->settings : &device->monitor->global_settings;
|
return bap ? device->settings : &device->monitor->global_settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Build the endpoint's advertised caps by filling the owner's own caps and
|
||||||
|
* merging in those of every enabled companion sharing the same endpoint.
|
||||||
|
* \a owner must satisfy endpoint_should_be_registered() for \a direction.
|
||||||
|
*/
|
||||||
|
static int media_codec_fill_endpoint_caps(struct spa_bt_monitor *monitor,
|
||||||
|
const struct media_codec *owner,
|
||||||
|
enum spa_bt_media_direction direction,
|
||||||
|
uint8_t caps[A2DP_MAX_CAPS_SIZE])
|
||||||
|
{
|
||||||
|
const struct media_codec * const * const media_codecs = monitor->media_codecs;
|
||||||
|
const char *owner_ep = owner->endpoint_name ? owner->endpoint_name : owner->name;
|
||||||
|
uint32_t flags = (direction == SPA_BT_MEDIA_SINK ||
|
||||||
|
direction == SPA_BT_MEDIA_SINK_BROADCAST) ? MEDIA_CODEC_FLAG_SINK : 0;
|
||||||
|
int caps_size;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
caps_size = owner->fill_caps(owner, flags, &monitor->global_settings, caps);
|
||||||
|
if (caps_size < 0)
|
||||||
|
return caps_size;
|
||||||
|
|
||||||
|
if (!owner->combine_caps)
|
||||||
|
return caps_size;
|
||||||
|
|
||||||
|
for (i = 0; media_codecs[i]; i++) {
|
||||||
|
const struct media_codec *c = media_codecs[i];
|
||||||
|
const char *c_ep = c->endpoint_name ? c->endpoint_name : c->name;
|
||||||
|
uint8_t other[A2DP_MAX_CAPS_SIZE];
|
||||||
|
int other_size;
|
||||||
|
|
||||||
|
if (c == owner || !c->endpoint_companion)
|
||||||
|
continue;
|
||||||
|
if (!spa_streq(c_ep, owner_ep))
|
||||||
|
continue;
|
||||||
|
if (c->codec_id != owner->codec_id || c->kind != owner->kind)
|
||||||
|
continue;
|
||||||
|
if (!codec_has_direction(monitor, c, direction))
|
||||||
|
continue;
|
||||||
|
if (!is_media_codec_enabled(monitor, c))
|
||||||
|
continue;
|
||||||
|
if (!c->fill_caps)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
other_size = c->fill_caps(c, flags, &monitor->global_settings, other);
|
||||||
|
if (other_size < 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
caps_size = owner->combine_caps(owner, flags, caps, caps_size, other, other_size);
|
||||||
|
if (caps_size < 0)
|
||||||
|
return caps_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return caps_size;
|
||||||
|
}
|
||||||
|
|
||||||
static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBusMessage *m, void *userdata)
|
static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBusMessage *m, void *userdata)
|
||||||
{
|
{
|
||||||
struct spa_bt_monitor *monitor = userdata;
|
struct spa_bt_monitor *monitor = userdata;
|
||||||
|
|
@ -5351,9 +5474,13 @@ static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn,
|
||||||
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If multiple codecs share the endpoint, pick the one we wanted */
|
/* If multiple codecs share the endpoint, pick the one whose
|
||||||
transport->media_codec = codec = media_endpoint_to_codec(monitor, endpoint, &sink,
|
* validate_config accepts the configuration BlueZ sent us. This is
|
||||||
transport->device->preferred_codec);
|
* how we tell e.g. AAC LC from AAC ELD on the shared "aac" endpoint.
|
||||||
|
*/
|
||||||
|
transport->media_codec = codec = media_endpoint_to_codec_for_config(monitor, endpoint, &sink,
|
||||||
|
transport->device->preferred_codec,
|
||||||
|
transport->configuration, transport->configuration_len);
|
||||||
spa_assert(codec != NULL);
|
spa_assert(codec != NULL);
|
||||||
spa_log_debug(monitor->log, "%p: %s codec:%s", monitor, path, codec ? codec->name : "<null>");
|
spa_log_debug(monitor->log, "%p: %s codec:%s", monitor, path, codec ? codec->name : "<null>");
|
||||||
|
|
||||||
|
|
@ -5700,7 +5827,7 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage *
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SINK)) {
|
if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SINK)) {
|
||||||
caps_size = codec->fill_caps(codec, MEDIA_CODEC_FLAG_SINK, &monitor->global_settings, caps);
|
caps_size = media_codec_fill_endpoint_caps(monitor, codec, SPA_BT_MEDIA_SINK, caps);
|
||||||
if (caps_size < 0)
|
if (caps_size < 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
@ -5715,7 +5842,7 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage *
|
||||||
}
|
}
|
||||||
|
|
||||||
if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SOURCE)) {
|
if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SOURCE)) {
|
||||||
caps_size = codec->fill_caps(codec, 0, &monitor->global_settings, caps);
|
caps_size = media_codec_fill_endpoint_caps(monitor, codec, SPA_BT_MEDIA_SOURCE, caps);
|
||||||
if (caps_size < 0)
|
if (caps_size < 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
@ -5731,7 +5858,7 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage *
|
||||||
|
|
||||||
if (is_bap && register_bcast) {
|
if (is_bap && register_bcast) {
|
||||||
if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SOURCE_BROADCAST)) {
|
if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SOURCE_BROADCAST)) {
|
||||||
caps_size = codec->fill_caps(codec, 0, &monitor->global_settings, caps);
|
caps_size = media_codec_fill_endpoint_caps(monitor, codec, SPA_BT_MEDIA_SOURCE_BROADCAST, caps);
|
||||||
if (caps_size < 0)
|
if (caps_size < 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
@ -5746,7 +5873,7 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage *
|
||||||
}
|
}
|
||||||
|
|
||||||
if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SINK_BROADCAST)) {
|
if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SINK_BROADCAST)) {
|
||||||
caps_size = codec->fill_caps(codec, MEDIA_CODEC_FLAG_SINK, &monitor->global_settings, caps);
|
caps_size = media_codec_fill_endpoint_caps(monitor, codec, SPA_BT_MEDIA_SINK_BROADCAST, caps);
|
||||||
if (caps_size < 0)
|
if (caps_size < 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -136,11 +136,13 @@ static int load_media_codecs_from(struct impl *impl, const char *factory_name, c
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Don't load duplicate endpoints */
|
/* Don't load duplicate endpoints. Two owners on the same
|
||||||
|
* endpoint is a conflict; companions sharing an owner are not.
|
||||||
|
*/
|
||||||
for (j = 0; j < impl->n_codecs; ++j) {
|
for (j = 0; j < impl->n_codecs; ++j) {
|
||||||
const struct media_codec *c2 = impl->codecs[j];
|
const struct media_codec *c2 = impl->codecs[j];
|
||||||
const char *ep2 = c2->endpoint_name ? c2->endpoint_name : c2->name;
|
const char *ep2 = c2->endpoint_name ? c2->endpoint_name : c2->name;
|
||||||
if (spa_streq(ep, ep2) && c->fill_caps && c2->fill_caps) {
|
if (spa_streq(ep, ep2) && !c->endpoint_companion && !c2->endpoint_companion) {
|
||||||
spa_log_debug(impl->log, "media codec %s from %s duplicate endpoint %s",
|
spa_log_debug(impl->log, "media codec %s from %s duplicate endpoint %s",
|
||||||
c->name, factory_name, ep);
|
c->name, factory_name, ep);
|
||||||
goto next_codec;
|
goto next_codec;
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
|
|
||||||
#define SPA_TYPE_INTERFACE_Bluez5CodecMedia SPA_TYPE_INFO_INTERFACE_BASE "Bluez5:Codec:Media:Private"
|
#define SPA_TYPE_INTERFACE_Bluez5CodecMedia SPA_TYPE_INFO_INTERFACE_BASE "Bluez5:Codec:Media:Private"
|
||||||
|
|
||||||
#define SPA_VERSION_BLUEZ5_CODEC_MEDIA 16
|
#define SPA_VERSION_BLUEZ5_CODEC_MEDIA 17
|
||||||
|
|
||||||
struct spa_bluez5_codec_a2dp {
|
struct spa_bluez5_codec_a2dp {
|
||||||
struct spa_interface iface;
|
struct spa_interface iface;
|
||||||
|
|
@ -93,14 +93,37 @@ struct media_codec {
|
||||||
* After successful decode, start_decode() should be
|
* After successful decode, start_decode() should be
|
||||||
* called again to parse the remaining data. */
|
* called again to parse the remaining data. */
|
||||||
|
|
||||||
|
/** True for codecs that share an endpoint with another codec and let
|
||||||
|
* that other codec own the endpoint registration. The owner's caps
|
||||||
|
* are advertised, with enabled companions merged in via combine_caps.
|
||||||
|
* The default (false) means this codec owns its endpoint; set this
|
||||||
|
* to true only on companion codec objects (e.g. aac_eld, sbc_xq).
|
||||||
|
*/
|
||||||
|
bool endpoint_companion;
|
||||||
|
|
||||||
int (*get_bis_config)(const struct media_codec *codec, uint8_t *caps,
|
int (*get_bis_config)(const struct media_codec *codec, uint8_t *caps,
|
||||||
uint8_t *caps_size, struct spa_dict *settings,
|
uint8_t *caps_size, struct spa_dict *settings,
|
||||||
struct bap_codec_qos *qos);
|
struct bap_codec_qos *qos);
|
||||||
|
|
||||||
/** If fill_caps is NULL, no endpoint is registered (for sharing with another codec). */
|
/** Fill in this codec's own capability bits.
|
||||||
|
* For codecs that share an endpoint, only the bits belonging to this
|
||||||
|
* codec object are advertised; the monitor merges siblings using
|
||||||
|
* combine_caps on the endpoint owner. May be NULL on a companion
|
||||||
|
* codec whose advertised caps are identical to the owner's.
|
||||||
|
*/
|
||||||
int (*fill_caps) (const struct media_codec *codec, uint32_t flags,
|
int (*fill_caps) (const struct media_codec *codec, uint32_t flags,
|
||||||
const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE]);
|
const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE]);
|
||||||
|
|
||||||
|
/** Merge another sibling codec's caps blob (same codec_id) into caps.
|
||||||
|
* Only required on the endpoint owner when an enabled companion's
|
||||||
|
* caps differ from the owner's. If NULL, only the owner's caps are
|
||||||
|
* advertised and companions' fill_caps is not called.
|
||||||
|
* Returns the new caps_size, or < 0 on error.
|
||||||
|
*/
|
||||||
|
int (*combine_caps) (const struct media_codec *codec, uint32_t flags,
|
||||||
|
uint8_t caps[A2DP_MAX_CAPS_SIZE], int caps_size,
|
||||||
|
const uint8_t *other, int other_size);
|
||||||
|
|
||||||
int (*select_config) (const struct media_codec *codec, uint32_t flags,
|
int (*select_config) (const struct media_codec *codec, uint32_t flags,
|
||||||
const void *caps, size_t caps_size,
|
const void *caps, size_t caps_size,
|
||||||
const struct media_codec_audio_info *info,
|
const struct media_codec_audio_info *info,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue