Re: [PATCH BlueZ] audio: Add support for specific error codes for A2DP configuration

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Luiz, thanks for reviewing.

> From: Luiz Augusto von Dentz <luiz.dentz@xxxxxxxxx>
> Sent: Thursday, September 11, 2025 16:43
> To: Per Waago (pwaago); Pauli Virtanen
> Cc: linux-bluetooth@xxxxxxxxxxxxxxx
> Subject: Re: [PATCH BlueZ] audio: Add support for specific error codes for A2DP configuration
> 
> Hi Per,
> 
> On Thu, Sep 11, 2025 at 9:56 AM Per Waagø <pwaago@xxxxxxxxx> wrote:
> >
> > The A2DP specification defines error codes that shall be used if
> > the codec capabilities contain improper settings. This change allows
> > clients to trigger the sending of these specific error codes by
> > returning the corresponding error messages from
> > MediaEndpoint1.SetConfiguration.
> >
> > This update is fully backwards compatible: clients passing other error
> > messages will continue to receive the default error code as before. On
> > older BlueZ versions, these new errors will also result in the default
> > error code, enabling clients to implement support for the new errors
> > without breaking compatibility.
> 
> While I can see the value for debugging I doubt we could do any
> handling of these errors, so the result would be the same regardless
> of what error is sent back it is not recoverable.
> 

The main motivation for adding them is to be able to pass the
mandatory qualification tests, which now checks the errors codes
returned from SetConfiguration in detail. I don't think they are very
useful otherwise.

The errors are specified in table 5.5 in the A2DP spec:
https://www.bluetooth.com/specifications/specs/html/?src=a2dp_v1-4-1_1752513648/A2DP_v1.4.1/out/en/index-en.html#UUID-0ba19ee9-7277-1068-d2dc-b9e638cca568_Table_5.5

I included all of them for completeness. In that table, it is also stated
which codecs they apply to. Some are SBC-specific, some apply to all codecs or
other codecs.

> @Pauli Virtanen Perhaps you can give some feedback regarding these
> codes, would pipewire be interested in generating specific error
> codes? Some of them seems to be SBC specific like bitpool.
> 
> > This change enables passing A2DP/SNK/AVP/* and A2DP/SRC/AVP/*
> > qualification tests.
> > ---
> >  doc/org.bluez.MediaEndpoint.rst | 37 ++++++++++++++++
> >  profiles/audio/a2dp.c           | 78 ++++++++++++++++++++++++++++++---
> >  profiles/audio/a2dp.h           |  5 ++-
> >  profiles/audio/avdtp.c          |  4 +-
> >  profiles/audio/media.c          | 20 +++++----
> >  src/error.h                     | 38 ++++++++++++++++
> >  6 files changed, 165 insertions(+), 17 deletions(-)
> >
> > diff --git a/doc/org.bluez.MediaEndpoint.rst b/doc/org.bluez.MediaEndpoint.rst
> > index 474cc1160..2721d473e 100644
> > --- a/doc/org.bluez.MediaEndpoint.rst
> > +++ b/doc/org.bluez.MediaEndpoint.rst
> > @@ -54,6 +54,43 @@ be configured and the properties must contain the following properties:
> >
> >         See **org.bluez.MediaTransport(5)** QoS property.
> >
> > +       Possible errors:
> > +               :org.bluez.Error.A2DP.InvalidCodecType:
> > +               :org.bluez.Error.A2DP.NotSupportedCodecType:
> > +               :org.bluez.Error.A2DP.InvalidSamplingFrequency:
> > +               :org.bluez.Error.A2DP.NotSupportedSamplingFrequency:
> > +               :org.bluez.Error.A2DP.InvalidChannelMode:
> > +               :org.bluez.Error.A2DP.NotSupportedChannelMode:
> > +               :org.bluez.Error.A2DP.InvalidSubbands:
> > +               :org.bluez.Error.A2DP.NotSupportedSubbands:
> > +               :org.bluez.Error.A2DP.InvalidAllocationMethod:
> > +               :org.bluez.Error.A2DP.NotSupportedAllocationMethod:
> > +               :org.bluez.Error.A2DP.InvalidMinimumBitpoolValue:
> > +               :org.bluez.Error.A2DP.NotSupportedMinimumBitpoolValue:
> > +               :org.bluez.Error.A2DP.InvalidMaximumBitpoolValue:
> > +               :org.bluez.Error.A2DP.NotSupportedMaximumBitpoolValue:
> > +               :org.bluez.Error.A2DP.InvalidInvalidLayer:
> > +               :org.bluez.Error.A2DP.NotSupportedLayer:
> > +               :org.bluez.Error.A2DP.NotSupporterdCRC:
> > +               :org.bluez.Error.A2DP.NotSupporterdMPF:
> > +               :org.bluez.Error.A2DP.NotSupporterdVBR:
> > +               :org.bluez.Error.A2DP.InvalidBitRate:
> > +               :org.bluez.Error.A2DP.NotSupportedBitRate:
> > +               :org.bluez.Error.A2DP.InvalidObjectType:
> > +               :org.bluez.Error.A2DP.NotSupportedObjectType:
> > +               :org.bluez.Error.A2DP.InvalidChannels:
> > +               :org.bluez.Error.A2DP.NotSupportedChannels:
> > +               :org.bluez.Error.A2DP.InvalidVersion:
> > +               :org.bluez.Error.A2DP.NotSupportedVersion:
> > +               :org.bluez.Error.A2DP.NotSupportedMaximumSUL:
> > +               :org.bluez.Error.A2DP.InvalidBlockLength:
> > +               :org.bluez.Error.A2DP.InvalidCPType:
> > +               :org.bluez.Error.A2DP.InvalidCPFormat:
> > +               :org.bluez.Error.A2DP.InvalidCodecParameter:
> > +               :org.bluez.Error.A2DP.NotSupportedCodecParameter:
> > +               :org.bluez.Error.A2DP.InvalidDRC:
> > +               :org.bluez.Error.A2DP.NotSupportedDRC:
> 
> Introducing a error domain for A2DP is probably a good idea, but this
> only applies to endpoints that are A2DP specific, so this need to be
> adjusted to e.g.: possible for A2DP or something like that, also I
> don't know how the client would be able to tell it can return these
> errors? Or the expectation is that the client can start sending them
> immediately since the old behavior will convert them to
> AVDTP_UNSUPPORTED_CONFIGURATION anyway?
>

That was what I thought. The clients can just start sending these, and
they will be converted to the correct error code if bluez supports it
or to AVDTP_UNSUPPORTED_CONFIGURATION otherwise.

> While at it split the commit to have the documentation changes as a
> separate change.

Will adjust text and split into a separate commit.

> 
> >  array{byte} SelectConfiguration(array{byte} capabilities)
> >  `````````````````````````````````````````````````````````
> >
> > diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c
> > index c0a53eae9..661843a89 100644
> > --- a/profiles/audio/a2dp.c
> > +++ b/profiles/audio/a2dp.c
> > @@ -157,6 +157,73 @@ static GSList *servers = NULL;
> >  static GSList *setups = NULL;
> >  static unsigned int cb_id = 0;
> >
> > +struct a2dp_error {
> > +       const char *error_name;
> > +       uint8_t error_code;
> > +};
> > +
> > +#define A2DP_ERROR_PREFIX ERROR_INTERFACE ".A2DP."
> > +
> > +static struct a2dp_error config_errors[] = {
> > +       {"InvalidCodecType", A2DP_INVALID_CODEC_TYPE},
> > +       {"NotSupportedCodecType", A2DP_NOT_SUPPORTED_CODEC_TYPE},
> > +       {"InvalidSamplingFrequency", A2DP_INVALID_SAMPLING_FREQUENCY},
> > +       {"NotSupportedSamplingFrequency",
> > +                               A2DP_NOT_SUPPORTED_SAMPLING_FREQUENCY},
> > +       {"InvalidChannelMode", A2DP_INVALID_CHANNEL_MODE},
> > +       {"NotSupportedChannelMode", A2DP_NOT_SUPPORTED_CHANNEL_MODE},
> > +       {"InvalidSubbands", A2DP_INVALID_SUBBANDS},
> > +       {"NotSupportedSubbands", A2DP_NOT_SUPPORTED_SUBBANDS},
> > +       {"InvalidAllocationMethod", A2DP_INVALID_ALLOCATION_METHOD},
> > +       {"NotSupportedAllocationMethod", A2DP_NOT_SUPPORTED_ALLOCATION_METHOD},
> > +       {"InvalidMinimumBitpoolValue",
> > +                               A2DP_INVALID_MINIMUM_BITPOOL_VALUE},
> > +       {"NotSupportedMinimumBitpoolValue",
> > +                               A2DP_NOT_SUPPORTED_MINIMUM_BITPOOL_VALUE},
> > +       {"InvalidMaximumBitpoolValue", A2DP_INVALID_MAXIMUM_BITPOOL_VALUE},
> > +       {"NotSupportedMaximumBitpoolValue",
> > +                               A2DP_NOT_SUPPORTED_MAXIMUM_BITPOOL_VALUE},
> > +       {"InvalidInvalidLayer", A2DP_INVALID_INVALID_LAYER},
> > +       {"NotSupportedLayer", A2DP_NOT_SUPPORTED_LAYER},
> > +       {"NotSupporterdCRC", A2DP_NOT_SUPPORTERD_CRC},
> > +       {"NotSupporterdMPF", A2DP_NOT_SUPPORTERD_MPF},
> > +       {"NotSupporterdVBR", A2DP_NOT_SUPPORTERD_VBR},
> > +       {"InvalidBitRate", A2DP_INVALID_BIT_RATE},
> > +       {"NotSupportedBitRate", A2DP_NOT_SUPPORTED_BIT_RATE},
> > +       {"InvalidObjectType", A2DP_INVALID_OBJECT_TYPE},
> > +       {"NotSupportedObjectType", A2DP_NOT_SUPPORTED_OBJECT_TYPE},
> > +       {"InvalidChannels", A2DP_INVALID_CHANNELS},
> > +       {"NotSupportedChannels", A2DP_NOT_SUPPORTED_CHANNELS},
> > +       {"InvalidVersion", A2DP_INVALID_VERSION},
> > +       {"NotSupportedVersion", A2DP_NOT_SUPPORTED_VERSION},
> > +       {"NotSupportedMaximumSUL", A2DP_NOT_SUPPORTED_MAXIMUM_SUL},
> > +       {"InvalidBlockLength", A2DP_INVALID_BLOCK_LENGTH},
> > +       {"InvalidCPType", A2DP_INVALID_CP_TYPE},
> > +       {"InvalidCPFormat", A2DP_INVALID_CP_FORMAT},
> > +       {"InvalidCodecParameter", A2DP_INVALID_CODEC_PARAMETER},
> > +       {"NotSupportedCodecParameter", A2DP_NOT_SUPPORTED_CODEC_PARAMETER},
> > +       {"InvalidDRC", A2DP_INVALID_DRC},
> > +       {"NotSupportedDRC", A2DP_NOT_SUPPORTED_DRC}
> > +};
> > +
> > +uint8_t a2dp_parse_config_error(const char *error_name)
> > +{
> > +       size_t prefix_length;
> > +       size_t i;
> > +
> > +       prefix_length = strlen(A2DP_ERROR_PREFIX);
> > +       if (strncmp(A2DP_ERROR_PREFIX, error_name, prefix_length))
> > +               return AVDTP_UNSUPPORTED_CONFIGURATION;
> > +
> > +       error_name += prefix_length;
> > +       for (i = 0; i < ARRAY_SIZE(config_errors); i++) {
> > +               if (strcmp(config_errors[i].error_name, error_name) == 0)
> > +                       return config_errors[i].error_code;
> > +       }
> > +
> > +       return AVDTP_UNSUPPORTED_CONFIGURATION;
> > +}
> > +
> >  static struct a2dp_setup *setup_ref(struct a2dp_setup *setup)
> >  {
> >         setup->ref++;
> > @@ -688,11 +755,10 @@ done:
> >         return FALSE;
> >  }
> >
> > -static void endpoint_setconf_cb(struct a2dp_setup *setup, gboolean ret)
> > +static void endpoint_setconf_cb(struct a2dp_setup *setup, uint8_t error_code)
> >  {
> > -       if (ret == FALSE)
> > -               setup_error_init(setup, AVDTP_MEDIA_CODEC,
> > -                                       AVDTP_UNSUPPORTED_CONFIGURATION);
> > +       if (error_code != 0)
> > +               setup_error_init(setup, AVDTP_MEDIA_CODEC, error_code);
> >
> >         auto_config(setup);
> >         setup_unref(setup);
> > @@ -865,11 +931,11 @@ static gboolean endpoint_getcap_ind(struct avdtp *session,
> >         return TRUE;
> >  }
> >
> > -static void endpoint_open_cb(struct a2dp_setup *setup, gboolean ret)
> > +static void endpoint_open_cb(struct a2dp_setup *setup, uint8_t error_code)
> >  {
> >         int err = error_to_errno(setup->err);
> >
> > -       if (ret == FALSE) {
> > +       if (error_code != 0) {
> >                 setup->stream = NULL;
> >                 finalize_setup_errno(setup, -EPERM, finalize_config, NULL);
> >                 goto done;
> > diff --git a/profiles/audio/a2dp.h b/profiles/audio/a2dp.h
> > index c698bc983..afa02c12d 100644
> > --- a/profiles/audio/a2dp.h
> > +++ b/profiles/audio/a2dp.h
> > @@ -15,7 +15,8 @@ struct a2dp_setup;
> >
> >  typedef void (*a2dp_endpoint_select_t) (struct a2dp_setup *setup, void *ret,
> >                                         int size);
> > -typedef void (*a2dp_endpoint_config_t) (struct a2dp_setup *setup, gboolean ret);
> > +typedef void (*a2dp_endpoint_config_t) (struct a2dp_setup *setup,
> > +                                       uint8_t error_code);
> >
> >  struct a2dp_endpoint {
> >         const char *(*get_name) (struct a2dp_sep *sep, void *user_data);
> > @@ -70,6 +71,8 @@ unsigned int a2dp_select_capabilities(struct avdtp *session,
> >  unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep,
> >                                 a2dp_config_cb_t cb, GSList *caps,
> >                                 void *user_data);
> > +uint8_t a2dp_parse_config_error(const char *error_name);
> > +
> >  unsigned int a2dp_resume(struct avdtp *session, struct a2dp_sep *sep,
> >                                 a2dp_stream_cb_t cb, void *user_data);
> >  unsigned int a2dp_suspend(struct avdtp *session, struct a2dp_sep *sep,
> > diff --git a/profiles/audio/avdtp.c b/profiles/audio/avdtp.c
> > index 30648251f..ed4e22b26 100644
> > --- a/profiles/audio/avdtp.c
> > +++ b/profiles/audio/avdtp.c
> > @@ -1494,8 +1494,8 @@ static void setconf_cb(struct avdtp *session, struct avdtp_stream *stream,
> >         struct conf_rej rej;
> >
> >         if (err != NULL) {
> > -               rej.error = AVDTP_UNSUPPORTED_CONFIGURATION;
> > -               rej.category = err->err.error_code;
> > +               rej.error = err->err.error_code;
> > +               rej.category = AVDTP_UNSUPPORTED_CONFIGURATION;
> >                 avdtp_send(session, session->in_cmd.transaction,
> >                            AVDTP_MSG_TYPE_REJECT, AVDTP_SET_CONFIGURATION,
> >                            &rej, sizeof(rej));
> > diff --git a/profiles/audio/media.c b/profiles/audio/media.c
> > index 9b3042c18..332f643bb 100644
> > --- a/profiles/audio/media.c
> > +++ b/profiles/audio/media.c
> > @@ -333,7 +333,7 @@ static void endpoint_reply(DBusPendingCall *call, void *user_data)
> >         DBusMessage *reply;
> >         DBusMessageIter args, props;
> >         DBusError err;
> > -       gboolean value;
> > +       uint8_t error_code;
> >         void *ret = NULL;
> >         int size = -1;
> >
> > @@ -356,8 +356,12 @@ static void endpoint_reply(DBusPendingCall *call, void *user_data)
> >
> >                 if (dbus_message_is_method_call(request->msg,
> >                                         MEDIA_ENDPOINT_INTERFACE,
> > -                                       "SetConfiguration"))
> > +                                       "SetConfiguration")) {
> >                         endpoint_remove_transport(endpoint, request->transport);
> > +                       error_code = a2dp_parse_config_error(err.name);
> > +                       ret = &error_code;
> > +                       size = 1;
> > +               }
> >
> >                 dbus_error_free(&err);
> >                 goto done;
> > @@ -390,8 +394,8 @@ static void endpoint_reply(DBusPendingCall *call, void *user_data)
> >         }
> >
> >         size = 1;
> > -       value = TRUE;
> > -       ret = &value;
> > +       error_code = 0;
> > +       ret = &error_code;
> >
> >  done:
> >         dbus_message_unref(reply);
> > @@ -634,9 +638,9 @@ static void config_cb(struct media_endpoint *endpoint, void *ret, int size,
> >                                                         void *user_data)
> >  {
> >         struct a2dp_config_data *data = user_data;
> > -       gboolean *ret_value = ret;
> > +       uint8_t *ret_value = ret;
> >
> > -       data->cb(data->setup, ret_value ? *ret_value : FALSE);
> > +       data->cb(data->setup, ret_value ? *ret_value : 1);
> >  }
> >
> >  static int set_config(struct a2dp_sep *sep, uint8_t *configuration,
> > @@ -1098,7 +1102,7 @@ static void pac_config_cb(struct media_endpoint *endpoint, void *ret, int size,
> >                                                         void *user_data)
> >  {
> >         struct pac_config_data *data = user_data;
> > -       gboolean *ret_value = ret;
> > +       uint8_t *error_code = ret;
> >         struct media_transport *transport;
> >
> >         /* If transport was cleared, configuration was cancelled */
> > @@ -1106,7 +1110,7 @@ static void pac_config_cb(struct media_endpoint *endpoint, void *ret, int size,
> >         if (!transport)
> >                 return;
> >
> > -       data->cb(data->stream, ret_value ? 0 : -EINVAL);
> > +       data->cb(data->stream, error_code == 0 ? 0 : -EINVAL);
> >  }
> >
> >  static struct media_transport *pac_ucast_config(struct bt_bap_stream *stream,
> > diff --git a/src/error.h b/src/error.h
> > index 47602d39b..8157795c2 100644
> > --- a/src/error.h
> > +++ b/src/error.h
> > @@ -88,3 +88,41 @@ DBusMessage *btd_error_profile_unavailable(DBusMessage *msg);
> >  DBusMessage *btd_error_failed(DBusMessage *msg, const char *str);
> >  DBusMessage *btd_error_bredr_errno(DBusMessage *msg, int err);
> >  DBusMessage *btd_error_le_errno(DBusMessage *msg, int err);
> > +
> > +enum a2dp_error_codes : uint8_t {
> > +       A2DP_INVALID_CODEC_TYPE = 0xc1,
> > +       A2DP_NOT_SUPPORTED_CODEC_TYPE = 0xc2,
> > +       A2DP_INVALID_SAMPLING_FREQUENCY = 0xc3,
> > +       A2DP_NOT_SUPPORTED_SAMPLING_FREQUENCY = 0xc4,
> > +       A2DP_INVALID_CHANNEL_MODE = 0xc5,
> > +       A2DP_NOT_SUPPORTED_CHANNEL_MODE = 0xc6,
> > +       A2DP_INVALID_SUBBANDS = 0xc7,
> > +       A2DP_NOT_SUPPORTED_SUBBANDS = 0xc8,
> > +       A2DP_INVALID_ALLOCATION_METHOD = 0xc9,
> > +       A2DP_NOT_SUPPORTED_ALLOCATION_METHOD = 0xca,
> > +       A2DP_INVALID_MINIMUM_BITPOOL_VALUE = 0xcb,
> > +       A2DP_NOT_SUPPORTED_MINIMUM_BITPOOL_VALUE = 0xcc,
> > +       A2DP_INVALID_MAXIMUM_BITPOOL_VALUE = 0xcd,
> > +       A2DP_NOT_SUPPORTED_MAXIMUM_BITPOOL_VALUE = 0xce,
> > +       A2DP_INVALID_INVALID_LAYER = 0xcf,
> > +       A2DP_NOT_SUPPORTED_LAYER = 0xd0,
> > +       A2DP_NOT_SUPPORTERD_CRC = 0xd1,
> > +       A2DP_NOT_SUPPORTERD_MPF = 0xd2,
> > +       A2DP_NOT_SUPPORTERD_VBR = 0xd3,
> > +       A2DP_INVALID_BIT_RATE = 0xd4,
> > +       A2DP_NOT_SUPPORTED_BIT_RATE = 0xd5,
> > +       A2DP_INVALID_OBJECT_TYPE = 0xd6,
> > +       A2DP_NOT_SUPPORTED_OBJECT_TYPE = 0xd7,
> > +       A2DP_INVALID_CHANNELS = 0xd8,
> > +       A2DP_NOT_SUPPORTED_CHANNELS = 0xd9,
> > +       A2DP_INVALID_VERSION = 0xda,
> > +       A2DP_NOT_SUPPORTED_VERSION = 0xdb,
> > +       A2DP_NOT_SUPPORTED_MAXIMUM_SUL = 0xdc,
> > +       A2DP_INVALID_BLOCK_LENGTH = 0xdd,
> > +       A2DP_INVALID_CP_TYPE = 0xe0,
> > +       A2DP_INVALID_CP_FORMAT = 0xe1,
> > +       A2DP_INVALID_CODEC_PARAMETER = 0xe2,
> > +       A2DP_NOT_SUPPORTED_CODEC_PARAMETER = 0xe3,
> > +       A2DP_INVALID_DRC = 0xe4,
> > +       A2DP_NOT_SUPPORTED_DRC = 0xe5,
> > +};
>
> Hmm, I guess there should be part of a2dp.h since this error header is
> about D-Bus not A2DP codes.
>

Ok, will move them to a2dp.h





[Index of Archives]     [Bluez Devel]     [Linux Wireless Networking]     [Linux Wireless Personal Area Networking]     [Linux ATH6KL]     [Linux USB Devel]     [Linux Media Drivers]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Big List of Linux Books]

  Powered by Linux