bluez5: fix shared A2DP endpoint caps including disabled companion codecs

Codecs that share an A2DP endpoint (e.g. aac + aac_eld) previously used
an asymmetric pattern: one codec (the "owner") had fill_caps and quietly
advertised the union of all siblings' capability bits; companions had
fill_caps = NULL.

This caused a bug: when aac_eld is absent from bluez5.codecs, the "aac"
endpoint still advertised the ELD object-type bit because a2dp_codec_aac's
fill_caps emitted it unconditionally (gated only by eld_supported(), never
by enabled_codecs). A remote could then select ELD; SetConfiguration
resolved the transport to a2dp_codec_aac (the only fill_caps holder for "aac")

Refactor to a symmetric, composable model:

- Add bool endpoint_companion to struct media_codec (default false = owner).
  Companion codec objects set this flag instead of fill_caps = NULL.
- Add combine_caps() function to struct media_codec. Owners that share
  an endpoint implement this to union two capability blobs of the same
  codec_id.
- Each codec's fill_caps now describes only its own bits. AAC LC advertises
  only LC object types; AAC ELD advertises only the ELD type (or returns
  -ENOTSUP when FDK-AAC lacks ELD support).
- Bump SPA_VERSION_BLUEZ5_CODEC_MEDIA 16 -> 17.

On the monitor side (bluez5-dbus.c):
- New media_codec_fill_endpoint_caps() helper fills the owner's caps then
  walks enabled companions on the same endpoint and merges each via
  combine_caps. All five endpoint-registration fill_caps call sites are
  routed through this helper.
- endpoint_should_be_registered() gates on !endpoint_companion (not on
  fill_caps != NULL), so companions correctly skip endpoint registration.
- media_endpoint_to_codec() now filters by is_media_codec_enabled and
  prefers owners as a tiebreaker.
- New media_endpoint_to_codec_for_config() resolves SetConfiguration by
  calling each enabled candidate's validate_config against the negotiated
  config bytes. This ensures that an "aac" endpoint carrying ELD bytes is
  mapped to a2dp_codec_aac_eld (not a2dp_codec_aac).
- AAC validate_config and codec_init are id-gated so a stale ELD config
  can never be decoded by the LC codec object.

Assisted-by: Claude Opus 4.7
This commit is contained in:
George Kiagiadakis 2026-06-30 17:39:53 +03:00
parent 0a413c866c
commit 4e46489964
6 changed files with 281 additions and 78 deletions

View file

@ -27,7 +27,7 @@
#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_interface iface;
@ -93,14 +93,37 @@ struct media_codec {
* After successful decode, start_decode() should be
* 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,
uint8_t *caps_size, struct spa_dict *settings,
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,
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,
const void *caps, size_t caps_size,
const struct media_codec_audio_info *info,