mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-07-11 11:04:10 -04:00
bluez5: complete Opus 05 companion caps like AAC ELD
After a6f529b5, opus_05_51/71 only set endpoint_companion. The owner
fill_caps still advertised up to MAX_CHANNELS, there was no combine_caps,
and validate_config was not id-gated. Disabled surround companions remained
visible in endpoint caps, and SetConfiguration could bind a 5.1/7.1 config
to the stereo opus_05 object (wrong bitrate limits / channel profile).
Mirror a2dp-codec-aac.c:
- split fill_caps per codec id (2ch / 5.1 / 7.1; duplex 2+2; pro wide)
- add combine_caps on endpoint owners
- id-gate validate_config so the config-aware resolver picks the companion
- give companions their own fill_caps for the monitor merge path
This commit is contained in:
parent
73c0c51af6
commit
a2c09923ff
1 changed files with 132 additions and 28 deletions
|
|
@ -551,43 +551,113 @@ static int get_mapping(const struct media_codec *codec, const a2dp_opus_05_direc
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const uint8_t opus_05_frame_durations =
|
||||||
|
OPUS_05_FRAME_DURATION_25 |
|
||||||
|
OPUS_05_FRAME_DURATION_50 |
|
||||||
|
OPUS_05_FRAME_DURATION_100 |
|
||||||
|
OPUS_05_FRAME_DURATION_200 |
|
||||||
|
OPUS_05_FRAME_DURATION_400;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Each codec object advertises only its own channel profile. Companions on the
|
||||||
|
* same endpoint (51/71 on "opus_05", pro on "opus_05_duplex") are merged by the
|
||||||
|
* monitor via combine_caps when enabled — same model as AAC LC + AAC-ELD.
|
||||||
|
*/
|
||||||
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])
|
||||||
{
|
{
|
||||||
a2dp_opus_05_t a2dp_opus_05 = {
|
uint8_t channels;
|
||||||
.info = codec->vendor,
|
uint32_t location;
|
||||||
.main = {
|
bool has_bidi = false;
|
||||||
.channels = SPA_MIN(255u, MAX_CHANNELS),
|
uint8_t bidi_channels = 0;
|
||||||
.frame_duration = (OPUS_05_FRAME_DURATION_25 |
|
uint32_t bidi_location = 0;
|
||||||
OPUS_05_FRAME_DURATION_50 |
|
a2dp_opus_05_t a2dp_opus_05;
|
||||||
OPUS_05_FRAME_DURATION_100 |
|
|
||||||
OPUS_05_FRAME_DURATION_200 |
|
|
||||||
OPUS_05_FRAME_DURATION_400),
|
|
||||||
OPUS_05_INIT_LOCATION(BT_AUDIO_LOCATION_ANY)
|
|
||||||
OPUS_05_INIT_BITRATE(0)
|
|
||||||
},
|
|
||||||
.bidi = {
|
|
||||||
.channels = SPA_MIN(255u, MAX_CHANNELS),
|
|
||||||
.frame_duration = (OPUS_05_FRAME_DURATION_25 |
|
|
||||||
OPUS_05_FRAME_DURATION_50 |
|
|
||||||
OPUS_05_FRAME_DURATION_100 |
|
|
||||||
OPUS_05_FRAME_DURATION_200 |
|
|
||||||
OPUS_05_FRAME_DURATION_400),
|
|
||||||
OPUS_05_INIT_LOCATION(BT_AUDIO_LOCATION_ANY)
|
|
||||||
OPUS_05_INIT_BITRATE(0)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Only duplex/pro codec has bidi, since bluez5-device has to know early
|
switch (codec->id) {
|
||||||
* whether to show nodes or not. */
|
case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05:
|
||||||
if (codec->id != SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_DUPLEX &&
|
channels = 2;
|
||||||
codec->id != SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO)
|
location = BT_AUDIO_LOCATION_FL | BT_AUDIO_LOCATION_FR;
|
||||||
spa_zero(a2dp_opus_05.bidi);
|
break;
|
||||||
|
case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_51:
|
||||||
|
channels = surround_encoders[5].channels;
|
||||||
|
location = surround_encoders[5].location;
|
||||||
|
break;
|
||||||
|
case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_71:
|
||||||
|
channels = surround_encoders[7].channels;
|
||||||
|
location = surround_encoders[7].location;
|
||||||
|
break;
|
||||||
|
case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_DUPLEX:
|
||||||
|
channels = 2;
|
||||||
|
location = BT_AUDIO_LOCATION_FL | BT_AUDIO_LOCATION_FR;
|
||||||
|
has_bidi = true;
|
||||||
|
bidi_channels = 2;
|
||||||
|
bidi_location = BT_AUDIO_LOCATION_FL | BT_AUDIO_LOCATION_FR;
|
||||||
|
break;
|
||||||
|
case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO:
|
||||||
|
/* Pro Audio allows arbitrary layouts; keep the wide advertisement. */
|
||||||
|
channels = SPA_MIN(255u, MAX_CHANNELS);
|
||||||
|
location = BT_AUDIO_LOCATION_ANY;
|
||||||
|
has_bidi = true;
|
||||||
|
bidi_channels = SPA_MIN(255u, MAX_CHANNELS);
|
||||||
|
bidi_location = BT_AUDIO_LOCATION_ANY;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -ENOTSUP;
|
||||||
|
}
|
||||||
|
|
||||||
|
spa_zero(a2dp_opus_05);
|
||||||
|
a2dp_opus_05.info = codec->vendor;
|
||||||
|
a2dp_opus_05.main.channels = channels;
|
||||||
|
a2dp_opus_05.main.frame_duration = opus_05_frame_durations;
|
||||||
|
OPUS_05_SET_LOCATION(a2dp_opus_05.main, location);
|
||||||
|
OPUS_05_SET_BITRATE(a2dp_opus_05.main, 0);
|
||||||
|
|
||||||
|
if (has_bidi) {
|
||||||
|
a2dp_opus_05.bidi.channels = bidi_channels;
|
||||||
|
a2dp_opus_05.bidi.frame_duration = opus_05_frame_durations;
|
||||||
|
OPUS_05_SET_LOCATION(a2dp_opus_05.bidi, bidi_location);
|
||||||
|
OPUS_05_SET_BITRATE(a2dp_opus_05.bidi, 0);
|
||||||
|
}
|
||||||
|
|
||||||
memcpy(caps, &a2dp_opus_05, sizeof(a2dp_opus_05));
|
memcpy(caps, &a2dp_opus_05, sizeof(a2dp_opus_05));
|
||||||
return sizeof(a2dp_opus_05);
|
return sizeof(a2dp_opus_05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void combine_direction_caps(a2dp_opus_05_direction_t *dst,
|
||||||
|
const a2dp_opus_05_direction_t *src)
|
||||||
|
{
|
||||||
|
uint32_t dst_bitrate, src_bitrate;
|
||||||
|
|
||||||
|
dst->channels = SPA_MAX(dst->channels, src->channels);
|
||||||
|
dst->frame_duration |= src->frame_duration;
|
||||||
|
OPUS_05_SET_LOCATION(*dst,
|
||||||
|
OPUS_05_GET_LOCATION(*dst) | OPUS_05_GET_LOCATION(*src));
|
||||||
|
|
||||||
|
/* bitrate 0 means unlimited */
|
||||||
|
dst_bitrate = OPUS_05_GET_BITRATE(*dst);
|
||||||
|
src_bitrate = OPUS_05_GET_BITRATE(*src);
|
||||||
|
if (dst_bitrate == 0 || src_bitrate == 0)
|
||||||
|
OPUS_05_SET_BITRATE(*dst, 0);
|
||||||
|
else
|
||||||
|
OPUS_05_SET_BITRATE(*dst, SPA_MAX(dst_bitrate, src_bitrate));
|
||||||
|
}
|
||||||
|
|
||||||
|
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_opus_05_t *dst = (a2dp_opus_05_t *)caps;
|
||||||
|
const a2dp_opus_05_t *src = (const a2dp_opus_05_t *)other;
|
||||||
|
|
||||||
|
if (caps_size != (int)sizeof(*dst) || other_size != (int)sizeof(*src))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
combine_direction_caps(&dst->main, &src->main);
|
||||||
|
combine_direction_caps(&dst->bidi, &src->bidi);
|
||||||
|
|
||||||
|
return caps_size;
|
||||||
|
}
|
||||||
|
|
||||||
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
|
static int codec_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,
|
||||||
|
|
@ -831,6 +901,35 @@ static int codec_validate_config(const struct media_codec *codec, uint32_t flags
|
||||||
dir2 = &conf->main;
|
dir2 = &conf->main;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Each codec object only accepts its own channel profile. This lets the
|
||||||
|
* monitor's config-aware resolver attribute the transport to the correct
|
||||||
|
* companion (opus_05 vs opus_05_51/71, duplex vs pro), matching AAC LC/ELD.
|
||||||
|
*/
|
||||||
|
switch (codec->id) {
|
||||||
|
case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05:
|
||||||
|
if (dir1->channels > 2 || dir2->channels != 0)
|
||||||
|
return -EINVAL;
|
||||||
|
break;
|
||||||
|
case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_51:
|
||||||
|
if (dir1->channels != surround_encoders[5].channels || dir2->channels != 0)
|
||||||
|
return -EINVAL;
|
||||||
|
break;
|
||||||
|
case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_71:
|
||||||
|
if (dir1->channels != surround_encoders[7].channels || dir2->channels != 0)
|
||||||
|
return -EINVAL;
|
||||||
|
break;
|
||||||
|
case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_DUPLEX:
|
||||||
|
if (dir1->channels > 2 || dir2->channels > 2)
|
||||||
|
return -EINVAL;
|
||||||
|
break;
|
||||||
|
case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO:
|
||||||
|
case 0: /* duplex bidi return channel */
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
info->info.raw.channels = dir1->channels;
|
info->info.raw.channels = dir1->channels;
|
||||||
if (get_mapping(codec, dir1, surround_encoder, NULL, NULL, NULL,
|
if (get_mapping(codec, dir1, surround_encoder, NULL, NULL, NULL,
|
||||||
info->info.raw.position) < 0)
|
info->info.raw.position) < 0)
|
||||||
|
|
@ -1382,6 +1481,7 @@ const struct media_codec a2dp_codec_opus_05 = {
|
||||||
.name = "opus_05",
|
.name = "opus_05",
|
||||||
.description = "Opus 05",
|
.description = "Opus 05",
|
||||||
.fill_caps = codec_fill_caps,
|
.fill_caps = codec_fill_caps,
|
||||||
|
.combine_caps = codec_combine_caps,
|
||||||
};
|
};
|
||||||
|
|
||||||
const struct media_codec a2dp_codec_opus_05_51 = {
|
const struct media_codec a2dp_codec_opus_05_51 = {
|
||||||
|
|
@ -1391,6 +1491,7 @@ const struct media_codec a2dp_codec_opus_05_51 = {
|
||||||
.description = "Opus 05 5.1 Surround",
|
.description = "Opus 05 5.1 Surround",
|
||||||
.endpoint_name = "opus_05",
|
.endpoint_name = "opus_05",
|
||||||
.endpoint_companion = true,
|
.endpoint_companion = true,
|
||||||
|
.fill_caps = codec_fill_caps,
|
||||||
};
|
};
|
||||||
|
|
||||||
const struct media_codec a2dp_codec_opus_05_71 = {
|
const struct media_codec a2dp_codec_opus_05_71 = {
|
||||||
|
|
@ -1400,6 +1501,7 @@ const struct media_codec a2dp_codec_opus_05_71 = {
|
||||||
.description = "Opus 05 7.1 Surround",
|
.description = "Opus 05 7.1 Surround",
|
||||||
.endpoint_name = "opus_05",
|
.endpoint_name = "opus_05",
|
||||||
.endpoint_companion = true,
|
.endpoint_companion = true,
|
||||||
|
.fill_caps = codec_fill_caps,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Bidi return channel codec: doesn't have endpoints */
|
/* Bidi return channel codec: doesn't have endpoints */
|
||||||
|
|
@ -1417,6 +1519,7 @@ const struct media_codec a2dp_codec_opus_05_duplex = {
|
||||||
.description = "Opus 05 Duplex",
|
.description = "Opus 05 Duplex",
|
||||||
.duplex_codec = &a2dp_codec_opus_05_return,
|
.duplex_codec = &a2dp_codec_opus_05_return,
|
||||||
.fill_caps = codec_fill_caps,
|
.fill_caps = codec_fill_caps,
|
||||||
|
.combine_caps = codec_combine_caps,
|
||||||
};
|
};
|
||||||
|
|
||||||
const struct media_codec a2dp_codec_opus_05_pro = {
|
const struct media_codec a2dp_codec_opus_05_pro = {
|
||||||
|
|
@ -1429,6 +1532,7 @@ const struct media_codec a2dp_codec_opus_05_pro = {
|
||||||
.duplex_codec = &a2dp_codec_opus_05_return,
|
.duplex_codec = &a2dp_codec_opus_05_return,
|
||||||
.endpoint_name = "opus_05_duplex",
|
.endpoint_name = "opus_05_duplex",
|
||||||
.endpoint_companion = true,
|
.endpoint_companion = true,
|
||||||
|
.fill_caps = codec_fill_caps,
|
||||||
};
|
};
|
||||||
|
|
||||||
MEDIA_CODEC_EXPORT_DEF(
|
MEDIA_CODEC_EXPORT_DEF(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue