[pulseaudio-discuss] [PATCH v2 2/2] Bluetooth A2DP aptX codec support

Tanu Kaskinen tanuk at iki.fi
Wed Sep 5 10:57:08 UTC 2018


On Sat, 2018-07-28 at 17:34 +0200, Pali Rohár wrote:
> This patch provides support for aptX codec in bluetooth A2DP profile. In
> pulseaudio it is implemented as a new profile a2dp_aptx_sink. For aptX
> encoding it uses open source LGPLv2.1+ licensed libopenaptx library which
> can be found at https://github.com/pali/libopenaptx.
> 
> Limitations:
> 
> Codec selection (either SBC or aptX) is done by bluez itself and it does
> not provide API for switching codec. Therefore pulseaudio is not able to
> change codec and it is up to bluez if it decide to use aptX or not.
> 
> Only standard aptX codec is supported for now. Support for other variants
> like aptX HD, aptX Low Latency, FastStream may come up later.
> ---
>  configure.ac                                 |  19 ++
>  src/Makefile.am                              |   5 +
>  src/modules/bluetooth/a2dp-codecs.h          | 118 ++++++++++-
>  src/modules/bluetooth/bluez5-util.c          |  48 ++++-
>  src/modules/bluetooth/bluez5-util.h          |   2 +
>  src/modules/bluetooth/module-bluez5-device.c |  65 +++++-
>  src/modules/bluetooth/pa-a2dp-codec-aptx.c   | 297 +++++++++++++++++++++++++++
>  src/modules/bluetooth/pa-a2dp-codec.h        |   1 +
>  8 files changed, 548 insertions(+), 7 deletions(-)
>  create mode 100644 src/modules/bluetooth/pa-a2dp-codec-aptx.c
> 
> diff --git a/configure.ac b/configure.ac
> index d2bfab23b..c2d13fa53 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -1094,6 +1094,23 @@ AC_SUBST(HAVE_BLUEZ_5_NATIVE_HEADSET)
>  AM_CONDITIONAL([HAVE_BLUEZ_5_NATIVE_HEADSET], [test "x$HAVE_BLUEZ_5_NATIVE_HEADSET" = x1])
>  AS_IF([test "x$HAVE_BLUEZ_5_NATIVE_HEADSET" = "x1"], AC_DEFINE([HAVE_BLUEZ_5_NATIVE_HEADSET], 1, [Bluez 5 native headset backend enabled]))
>  
> +#### Bluetooth A2DP aptX codec (optional) ###
> +
> +AC_ARG_ENABLE([aptx],
> +    AS_HELP_STRING([--disable-aptx],[Disable optional bluetooth A2DP aptX codec support (via libopenaptx)]))
> +
> +AS_IF([test "x$HAVE_BLUEZ_5" = "x1" && test "x$enable_aptx" != "xno"],
> +    [AC_CHECK_HEADER([openaptx.h],
> +        [AC_CHECK_LIB([openaptx], [aptx_init], [HAVE_OPENAPTX=1], [HAVE_OPENAPTX=0])],
> +        [HAVE_OPENAPTX=0])])

Have you considered providing a .pc file? Now we have to hardcode the
openaptx specific CFLAGS and LIBADD for libbluez5-util. If you ever
need to add new flags, all openaptx users need to update their build
systems. Also, if the library is installed to a non-standard location,
the .pc file can set the -L and -I flags to point to the right place.

> +
> +AS_IF([test "x$HAVE_BLUEZ_5" = "x1" && test "x$enable_aptx" = "xyes" && test "x$HAVE_OPENAPTX" = "x0"],
> +    [AC_MSG_ERROR([*** libopenaptx from https://github.com/pali/libopenaptx not found])])
> +
> +AC_SUBST(HAVE_OPENAPTX)
> +AM_CONDITIONAL([HAVE_OPENAPTX], [test "x$HAVE_OPENAPTX" = "x1"])
> +AS_IF([test "x$HAVE_OPENAPTX" = "x1"], AC_DEFINE([HAVE_OPENAPTX], 1, [Have openaptx codec library]))
> +
>  #### UDEV support (optional) ####
>  
>  AC_ARG_ENABLE([udev],
> @@ -1579,6 +1596,7 @@ AS_IF([test "x$HAVE_SYSTEMD_JOURNAL" = "x1"], ENABLE_SYSTEMD_JOURNAL=yes, ENABLE
>  AS_IF([test "x$HAVE_BLUEZ_5" = "x1"], ENABLE_BLUEZ_5=yes, ENABLE_BLUEZ_5=no)
>  AS_IF([test "x$HAVE_BLUEZ_5_OFONO_HEADSET" = "x1"], ENABLE_BLUEZ_5_OFONO_HEADSET=yes, ENABLE_BLUEZ_5_OFONO_HEADSET=no)
>  AS_IF([test "x$HAVE_BLUEZ_5_NATIVE_HEADSET" = "x1"], ENABLE_BLUEZ_5_NATIVE_HEADSET=yes, ENABLE_BLUEZ_5_NATIVE_HEADSET=no)
> +AS_IF([test "x$HAVE_OPENAPTX" = "x1"], ENABLE_APTX=yes, ENABLE_APTX=no)
>  AS_IF([test "x$HAVE_HAL_COMPAT" = "x1"], ENABLE_HAL_COMPAT=yes, ENABLE_HAL_COMPAT=no)
>  AS_IF([test "x$HAVE_TCPWRAP" = "x1"], ENABLE_TCPWRAP=yes, ENABLE_TCPWRAP=no)
>  AS_IF([test "x$HAVE_LIBSAMPLERATE" = "x1"], ENABLE_LIBSAMPLERATE="yes (DEPRECATED)", ENABLE_LIBSAMPLERATE=no)
> @@ -1637,6 +1655,7 @@ echo "
>        Enable BlueZ 5:              ${ENABLE_BLUEZ_5}
>          Enable ofono headsets:     ${ENABLE_BLUEZ_5_OFONO_HEADSET}
>          Enable native headsets:    ${ENABLE_BLUEZ_5_NATIVE_HEADSET}
> +        Enable aptX codec:         ${ENABLE_APTX}
>      Enable udev:                   ${ENABLE_UDEV}
>        Enable HAL->udev compat:     ${ENABLE_HAL_COMPAT}
>      Enable systemd
> diff --git a/src/Makefile.am b/src/Makefile.am
> index 411b9e5e5..bbd797589 100644
> --- a/src/Makefile.am
> +++ b/src/Makefile.am
> @@ -2136,6 +2136,11 @@ libbluez5_util_la_SOURCES += modules/bluetooth/pa-a2dp-codec-sbc.c
>  libbluez5_util_la_LIBADD += $(SBC_LIBS)
>  libbluez5_util_la_CFLAGS += $(SBC_CFLAGS)
>  
> +if HAVE_OPENAPTX
> +libbluez5_util_la_SOURCES += modules/bluetooth/pa-a2dp-codec-aptx.c
> +libbluez5_util_la_LIBADD += -lopenaptx
> +endif
> +
>  module_bluez5_discover_la_SOURCES = modules/bluetooth/module-bluez5-discover.c
>  module_bluez5_discover_la_LDFLAGS = $(MODULE_LDFLAGS)
>  module_bluez5_discover_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) libbluez5-util.la
> diff --git a/src/modules/bluetooth/a2dp-codecs.h b/src/modules/bluetooth/a2dp-codecs.h
> index 004975586..0c3583434 100644
> --- a/src/modules/bluetooth/a2dp-codecs.h
> +++ b/src/modules/bluetooth/a2dp-codecs.h
> @@ -4,6 +4,7 @@
>   *
>   *  Copyright (C) 2006-2010  Nokia Corporation
>   *  Copyright (C) 2004-2010  Marcel Holtmann <marcel at holtmann.org>
> + *  Copyright (C) 2018       Pali Rohár <pali.rohar at gmail.com>
>   *
>   *
>   *  This library is free software; you can redistribute it and/or
> @@ -24,7 +25,18 @@
>  #define A2DP_CODEC_SBC			0x00
>  #define A2DP_CODEC_MPEG12		0x01
>  #define A2DP_CODEC_MPEG24		0x02
> -#define A2DP_CODEC_ATRAC		0x03
> +#define A2DP_CODEC_ATRAC		0x04
> +#define A2DP_CODEC_VENDOR		0xFF
> +
> +#define A2DP_VENDOR_APT_LIC_LTD		0x0000004f
> +#define APT_LIC_LTD_CODEC_APTX		0x0001
> +
> +#define A2DP_VENDOR_QTIL		0x0000000a
> +#define QTIL_CODEC_FASTSTREAM		0x0001
> +#define QTIL_CODEC_APTX_LL		0x0002
> +
> +#define A2DP_VENDOR_QUALCOMM_TECH_INC	0x000000d7
> +#define QUALCOMM_TECH_INC_CODEC_APTX_HD	0x0024
>  
>  #define SBC_SAMPLING_FREQ_16000		(1 << 3)
>  #define SBC_SAMPLING_FREQ_32000		(1 << 2)
> @@ -66,6 +78,26 @@
>  #define MPEG_SAMPLING_FREQ_44100	(1 << 1)
>  #define MPEG_SAMPLING_FREQ_48000	1
>  
> +#define APTX_CHANNEL_MODE_MONO		0x1
> +#define APTX_CHANNEL_MODE_STEREO	0x2
> +
> +#define APTX_SAMPLING_FREQ_16000	0x8
> +#define APTX_SAMPLING_FREQ_32000	0x4
> +#define APTX_SAMPLING_FREQ_44100	0x2
> +#define APTX_SAMPLING_FREQ_48000	0x1
> +
> +#define FASTSTREAM_DIRECTION_SINK	0x1
> +#define FASTSTREAM_DIRECTION_SOURCE	0x2
> +
> +#define FASTSTREAM_SINK_SAMPLING_FREQ_44100	0x2
> +#define FASTSTREAM_SINK_SAMPLING_FREQ_48000	0x1
> +
> +#define FASTSTREAM_SOURCE_SAMPLING_FREQ_16000	0x2
> +
> +typedef struct {
> +	uint32_t vendor_id;
> +	uint16_t codec_id;
> +} __attribute__ ((packed)) a2dp_vendor_codec_t;
>  
>  #if __BYTE_ORDER == __LITTLE_ENDIAN
>  
> @@ -89,6 +121,48 @@ typedef struct {
>  	uint16_t bitrate;
>  } __attribute__ ((packed)) a2dp_mpeg_t;
>  
> +typedef struct {
> +	a2dp_vendor_codec_t info;
> +	uint8_t channel_mode:4;
> +	uint8_t frequency:4;
> +} __attribute__ ((packed)) a2dp_aptx_t;
> +
> +typedef struct {
> +	a2dp_vendor_codec_t info;
> +	uint8_t direction;
> +	uint8_t sink_frequency:4;
> +	uint8_t source_frequency:4;
> +} __attribute__ ((packed)) a2dp_faststream_t;
> +
> +typedef struct {
> +	uint8_t reserved;
> +	uint16_t target_codec_level;
> +	uint16_t initial_codec_level;
> +	uint8_t sra_max_rate;
> +	uint8_t sra_avg_time;
> +	uint16_t good_working_level;
> +} __attribute__ ((packed)) a2dp_aptx_ll_new_caps_t;
> +
> +typedef struct {
> +	a2dp_vendor_codec_t info;
> +	uint8_t channel_mode:4;
> +	uint8_t frequency:4;
> +	uint8_t bidirect_link:1;
> +	uint8_t has_new_caps:1;
> +	uint8_t reserved:6;
> +	a2dp_aptx_ll_new_caps_t new_caps[0];
> +} __attribute__ ((packed)) a2dp_aptx_ll_t;
> +
> +typedef struct {
> +	a2dp_vendor_codec_t info;
> +	uint8_t channel_mode:4;
> +	uint8_t frequency:4;
> +	uint8_t reserved0;
> +	uint8_t reserved1;
> +	uint8_t reserved2;
> +	uint8_t reserved3;
> +} __attribute__ ((packed)) a2dp_aptx_hd_t;
> +
>  #elif __BYTE_ORDER == __BIG_ENDIAN
>  
>  typedef struct {
> @@ -111,6 +185,48 @@ typedef struct {
>  	uint16_t bitrate;
>  } __attribute__ ((packed)) a2dp_mpeg_t;
>  
> +typedef struct {
> +	a2dp_vendor_codec_t info;
> +	uint8_t frequency:4;
> +	uint8_t channel_mode:4;
> +} __attribute__ ((packed)) a2dp_aptx_t;
> +
> +typedef struct {
> +	a2dp_vendor_codec_t info;
> +	uint8_t direction;
> +	uint8_t source_frequency:4;
> +	uint8_t sink_frequency:4;
> +} __attribute__ ((packed)) a2dp_faststream_t;
> +
> +typedef struct {
> +	uint8_t reserved;
> +	uint16_t target_codec_level;
> +	uint16_t initial_codec_level;
> +	uint8_t sra_max_rate;
> +	uint8_t sra_avg_time;
> +	uint16_t good_working_level;
> +} __attribute__ ((packed)) a2dp_aptx_ll_new_caps_t;
> +
> +typedef struct {
> +	a2dp_vendor_codec_t info;
> +	uint8_t frequency:4;
> +	uint8_t channel_mode:4;
> +	uint8_t reserved:6;
> +	uint8_t has_new_caps:1;
> +	uint8_t bidirect_link:1;
> +	a2dp_aptx_ll_new_caps_t new_caps[0];
> +} __attribute__ ((packed)) a2dp_aptx_ll_t;
> +
> +typedef struct {
> +	a2dp_vendor_codec_t info;
> +	uint8_t frequency:4;
> +	uint8_t channel_mode:4;
> +	uint8_t reserved0;
> +	uint8_t reserved1;
> +	uint8_t reserved2;
> +	uint8_t reserved3;
> +} __attribute__ ((packed)) a2dp_aptx_hd_t;
> +
>  #else
>  #error "Unknown byte order"
>  #endif
> diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
> index 9c4e3367b..c139f7fc3 100644
> --- a/src/modules/bluetooth/bluez5-util.c
> +++ b/src/modules/bluetooth/bluez5-util.c
> @@ -50,7 +50,9 @@
>  #define BLUEZ_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported"
>  
>  #define A2DP_SOURCE_SBC_ENDPOINT "/MediaEndpoint/A2DPSourceSBC"
> +#define A2DP_SOURCE_APTX_ENDPOINT "/MediaEndpoint/A2DPSourceAPTX"
>  #define A2DP_SINK_SBC_ENDPOINT "/MediaEndpoint/A2DPSinkSBC"
> +#define A2DP_SINK_APTX_ENDPOINT "/MediaEndpoint/A2DPSinkAPTX"
>  
>  #define ENDPOINT_INTROSPECT_XML                                         \
>      DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                           \
> @@ -173,8 +175,22 @@ static bool device_supports_profile(pa_bluetooth_device *device, pa_bluetooth_pr
>      switch (profile) {
>          case PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK:
>              return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_SINK);
> +        case PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK:
> +#ifdef HAVE_OPENAPTX
> +            /* TODO: Implement once bluez provides API to check if codec is supported */
> +            return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_SINK);

Is someone working on that API?

> +#else
> +            return false;
> +#endif
>          case PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE:
>              return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_SOURCE);
> +        case PA_BLUETOOTH_PROFILE_A2DP_APTX_SOURCE:
> +#ifdef HAVE_OPENAPTX
> +            /* TODO: Implement once bluez provides API to check if codec is supported */
> +            return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_SOURCE);
> +#else
> +            return false;
> +#endif
>          case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
>              return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_HS)
>                  || !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_HS_ALT)
> @@ -961,7 +977,9 @@ static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessa
>              if (!a->valid)
>                  return;
>  
> +            register_endpoint(y, path, A2DP_SOURCE_APTX_ENDPOINT, PA_BLUETOOTH_UUID_A2DP_SOURCE);
>              register_endpoint(y, path, A2DP_SOURCE_SBC_ENDPOINT, PA_BLUETOOTH_UUID_A2DP_SOURCE);
> +            register_endpoint(y, path, A2DP_SINK_APTX_ENDPOINT, PA_BLUETOOTH_UUID_A2DP_SINK);
>              register_endpoint(y, path, A2DP_SINK_SBC_ENDPOINT, PA_BLUETOOTH_UUID_A2DP_SINK);

We shouldn't register aptX endpoints if aptX support is not enabled.

Does the registration order matter? I have some vague recollection from
the earlier discussion that BlueZ chooses the codec based on the
endpoint registration order - is that true? If the order is important,
we should document that here.

>  
>          } else if (pa_streq(interface, BLUEZ_DEVICE_INTERFACE)) {
> @@ -1258,8 +1276,12 @@ const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile) {
>      switch(profile) {
>          case PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK:
>              return "a2dp_sink";
> +        case PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK:
> +            return "a2dp_aptx_sink";
>          case PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE:
>              return "a2dp_source";
> +        case PA_BLUETOOTH_PROFILE_A2DP_APTX_SOURCE:
> +            return "a2dp_aptx_source";
>          case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
>              return "headset_head_unit";
>          case PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY:
> @@ -1275,8 +1297,12 @@ const char *pa_bluetooth_profile_to_a2dp_endpoint(pa_bluetooth_profile_t profile
>      switch (profile) {
>          case PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK:
>              return A2DP_SOURCE_SBC_ENDPOINT;
> +        case PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK:
> +            return A2DP_SOURCE_APTX_ENDPOINT;
>          case PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE:
>              return A2DP_SINK_SBC_ENDPOINT;
> +        case PA_BLUETOOTH_PROFILE_A2DP_APTX_SOURCE:
> +            return A2DP_SINK_APTX_ENDPOINT;
>          default:
>              return NULL;
>      }
> @@ -1289,6 +1315,11 @@ const pa_a2dp_codec_t *pa_bluetooth_profile_to_a2dp_codec(pa_bluetooth_profile_t
>          case PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK:
>          case PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE:
>              return &pa_a2dp_codec_sbc;
> +#ifdef HAVE_OPENAPTX
> +        case PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK:
> +        case PA_BLUETOOTH_PROFILE_A2DP_APTX_SOURCE:
> +            return &pa_a2dp_codec_aptx;
> +#endif
>          default:
>              return NULL;
>      }
> @@ -1299,6 +1330,10 @@ const pa_a2dp_codec_t *pa_bluetooth_profile_to_a2dp_codec(pa_bluetooth_profile_t
>  const pa_a2dp_codec_t *pa_a2dp_endpoint_to_a2dp_codec(const char *endpoint) {
>      if (pa_streq(endpoint, A2DP_SOURCE_SBC_ENDPOINT) || pa_streq(endpoint, A2DP_SINK_SBC_ENDPOINT))
>          return &pa_a2dp_codec_sbc;
> +#ifdef HAVE_OPENAPTX
> +    else if (pa_streq(endpoint, A2DP_SOURCE_APTX_ENDPOINT) || pa_streq(endpoint, A2DP_SINK_APTX_ENDPOINT))
> +        return &pa_a2dp_codec_aptx;
> +#endif
>      else
>          return NULL;
>  }
> @@ -1359,9 +1394,15 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
>              if (pa_streq(endpoint_path, A2DP_SOURCE_SBC_ENDPOINT)) {
>                  if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE))
>                      p = PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK;
> +            } else if (pa_streq(endpoint_path, A2DP_SOURCE_APTX_ENDPOINT)) {
> +                if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE))
> +                    p = PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK;
>              } else if (pa_streq(endpoint_path, A2DP_SINK_SBC_ENDPOINT)) {
>                  if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK))
>                      p = PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE;
> +            } else if (pa_streq(endpoint_path, A2DP_SINK_APTX_ENDPOINT)) {
> +                if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK))
> +                    p = PA_BLUETOOTH_PROFILE_A2DP_APTX_SOURCE;
>              }
>  
>              if (p == PA_BLUETOOTH_PROFILE_OFF) {
> @@ -1546,7 +1587,8 @@ static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, voi
>  
>      pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member);
>  
> -    if (!pa_streq(path, A2DP_SOURCE_SBC_ENDPOINT) && !pa_streq(path, A2DP_SINK_SBC_ENDPOINT))
> +    if (!pa_streq(path, A2DP_SOURCE_SBC_ENDPOINT) && !pa_streq(path, A2DP_SINK_SBC_ENDPOINT) &&
> +        !pa_streq(path, A2DP_SOURCE_APTX_ENDPOINT) && !pa_streq(path, A2DP_SINK_APTX_ENDPOINT))
>          return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
>  
>      if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
> @@ -1652,7 +1694,9 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backe
>      }
>      y->matches_added = true;
>  
> +    endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK);
>      endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK);
> +    endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_APTX_SOURCE);
>      endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE);

We shouldn't set up endpoints for aptX if the aptX support is not
enabled.

>  
>      get_managed_objects(y);
> @@ -1721,7 +1765,9 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
>          if (y->filter_added)
>              dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y);
>  
> +        endpoint_done(y, PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK);
>          endpoint_done(y, PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK);
> +        endpoint_done(y, PA_BLUETOOTH_PROFILE_A2DP_APTX_SOURCE);
>          endpoint_done(y, PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE);

If endpoint_init() is made conditional, these need to be made
conditional too (according to the dbus docs, unregistering an object
path that hasn't been registered first is considered an application
bug).

>  
>          pa_dbus_connection_unref(y->connection);
> diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
> index 365b9ef6f..29d862fe1 100644
> --- a/src/modules/bluetooth/bluez5-util.h
> +++ b/src/modules/bluetooth/bluez5-util.h
> @@ -55,7 +55,9 @@ typedef enum pa_bluetooth_hook {
>  
>  typedef enum profile {
>      PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK,
> +    PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK,
>      PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE,
> +    PA_BLUETOOTH_PROFILE_A2DP_APTX_SOURCE,
>      PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT,
>      PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY,
>      PA_BLUETOOTH_PROFILE_OFF
> diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
> index e626e80e9..e8a07d067 100644
> --- a/src/modules/bluetooth/module-bluez5-device.c
> +++ b/src/modules/bluetooth/module-bluez5-device.c
> @@ -706,7 +706,8 @@ static void transport_config_mtu(struct userdata *u) {
>      if (u->sink) {
>          pa_sink_set_max_request_within_thread(u->sink, u->write_block_size);
>          pa_sink_set_fixed_latency_within_thread(u->sink,
> -                                                (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK ?
> +                                                ((u->profile == PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK ||
> +                                                  u->profile == PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK) ?
>                                                   FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_SCO) +
>                                                  pa_bytes_to_usec(u->write_block_size, &u->sample_spec));
>  
> @@ -752,7 +753,7 @@ static void setup_stream(struct userdata *u) {
>      if (setsockopt(u->stream_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)) < 0)
>          pa_log_warn("Failed to enable SO_TIMESTAMP: %s", pa_cstrerror(errno));
>  
> -    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK)
> +    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK || u->profile == PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK)
>          update_buffer_size(u);
>  
>      pa_log_debug("Stream properly set up, we're ready to roll!");
> @@ -925,6 +926,7 @@ static int add_source(struct userdata *u) {
>      if (!u->transport_acquired)
>          switch (u->profile) {
>              case PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE:
> +            case PA_BLUETOOTH_PROFILE_A2DP_APTX_SOURCE:
>              case PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY:
>                  data.suspend_cause = PA_SUSPEND_USER;
>                  break;
> @@ -937,6 +939,7 @@ static int add_source(struct userdata *u) {
>                      pa_assert_not_reached();
>                  break;
>              case PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK:
> +            case PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK:
>              case PA_BLUETOOTH_PROFILE_OFF:
>              default:
>                  pa_assert_not_reached();
> @@ -1111,8 +1114,10 @@ static int add_sink(struct userdata *u) {
>                      pa_assert_not_reached();
>                  break;
>              case PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK:
> +            case PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK:
>                  /* Profile switch should have failed */
>              case PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE:
> +            case PA_BLUETOOTH_PROFILE_A2DP_APTX_SOURCE:
>              case PA_BLUETOOTH_PROFILE_OFF:
>              default:
>                  pa_assert_not_reached();
> @@ -1145,7 +1150,7 @@ static void transport_config(struct userdata *u) {
>          u->sample_spec.rate = 8000;
>      } else {
>          const pa_a2dp_codec_t *codec = pa_bluetooth_profile_to_a2dp_codec(u->profile);
> -        bool is_sink = (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK);
> +        bool is_sink = (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK || u->profile == PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK);
>          pa_assert(codec);
>          pa_assert(u->transport);
>          codec->init(is_sink ? &u->encoder_info : &u->decoder_info, &u->sample_spec, is_sink, u->transport->config, u->transport->config_size);
> @@ -1169,7 +1174,7 @@ static int setup_transport(struct userdata *u) {
>  
>      u->transport = t;
>  
> -    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY)
> +    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE || u->profile == PA_BLUETOOTH_PROFILE_A2DP_APTX_SOURCE || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY)
>          transport_acquire(u, true); /* In case of error, the sink/sources will be created suspended */
>      else {
>          int transport_error;
> @@ -1188,7 +1193,9 @@ static int setup_transport(struct userdata *u) {
>  static pa_direction_t get_profile_direction(pa_bluetooth_profile_t p) {
>      static const pa_direction_t profile_direction[] = {
>          [PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK] = PA_DIRECTION_OUTPUT,
> +        [PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK] = PA_DIRECTION_OUTPUT,
>          [PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE] = PA_DIRECTION_INPUT,
> +        [PA_BLUETOOTH_PROFILE_A2DP_APTX_SOURCE] = PA_DIRECTION_INPUT,
>          [PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
>          [PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
>          [PA_BLUETOOTH_PROFILE_OFF] = 0
> @@ -1520,7 +1527,7 @@ static int start_thread(struct userdata *u) {
>          /* If we are in the headset role or the device is an a2dp source,
>           * the source should not become default unless there is no other
>           * sound device available. */
> -        if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY || u->profile == PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE)
> +        if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY || u->profile == PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE || u->profile == PA_BLUETOOTH_PROFILE_A2DP_APTX_SOURCE)
>              u->source->priority = 1500;
>  
>          pa_source_put(u->source);
> @@ -1745,6 +1752,18 @@ static pa_card_profile *create_card_profile(struct userdata *u, pa_bluetooth_pro
>          p = PA_CARD_PROFILE_DATA(cp);
>          break;
>  
> +    case PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK:
> +        cp = pa_card_profile_new(name, _("High Fidelity aptX Playback (A2DP Sink)"), sizeof(pa_bluetooth_profile_t));
> +        cp->priority = 50;
> +        cp->n_sinks = 1;
> +        cp->n_sources = 0;
> +        cp->max_sink_channels = 2;
> +        cp->max_source_channels = 0;
> +        pa_hashmap_put(output_port->profiles, cp->name, cp);
> +
> +        p = PA_CARD_PROFILE_DATA(cp);
> +        break;
> +
>      case PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE:
>          cp = pa_card_profile_new(name, _("High Fidelity SBC Capture (A2DP Source)"), sizeof(pa_bluetooth_profile_t));
>          cp->priority = 20;
> @@ -1757,6 +1776,18 @@ static pa_card_profile *create_card_profile(struct userdata *u, pa_bluetooth_pro
>          p = PA_CARD_PROFILE_DATA(cp);
>          break;
>  
> +    case PA_BLUETOOTH_PROFILE_A2DP_APTX_SOURCE:
> +        cp = pa_card_profile_new(name, _("High Fidelity aptX Capture (A2DP Source)"), sizeof(pa_bluetooth_profile_t));
> +        cp->priority = 25;
> +        cp->n_sinks = 0;
> +        cp->n_sources = 1;
> +        cp->max_sink_channels = 0;
> +        cp->max_source_channels = 2;
> +        pa_hashmap_put(input_port->profiles, cp->name, cp);
> +
> +        p = PA_CARD_PROFILE_DATA(cp);
> +        break;
> +

My compiler started to be unsure whether p is always initialized:

  CC       modules/bluetooth/module_bluez5_device_la-module-bluez5-device.lo
modules/bluetooth/module-bluez5-device.c: In function ‘create_card_profile’:
modules/bluetooth/module-bluez5-device.c:1821:8: warning: ‘p’ may be used uninitialized in this function [-Wmaybe-uninitialized]
     *p = profile;
     ~~~^~~~~~~~~

This is a false positive, but we should do something to make the
compiler shut up.

>      case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
>          cp = pa_card_profile_new(name, _("Headset Head Unit (HSP/HFP)"), sizeof(pa_bluetooth_profile_t));
>          cp->priority = 30;
> @@ -1840,8 +1871,10 @@ off:
>  }
>  
>  static int uuid_to_profile(const char *uuid, pa_bluetooth_profile_t *_r) {
> +    /* FIXME: PA_BLUETOOTH_UUID_A2DP_SINK maps to both SBC and APTX */
>      if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK))
>          *_r = PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK;
> +    /* FIXME: PA_BLUETOOTH_UUID_A2DP_SOURCE maps to both SBC and APTX */
>      else if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE))
>          *_r = PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE;
>      else if (pa_bluetooth_uuid_is_hsp_hs(uuid) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_HF))
> @@ -1907,6 +1940,28 @@ static int add_card(struct userdata *u) {
>          pa_hashmap_put(data.profiles, cp->name, cp);
>      }
>  
> +    PA_HASHMAP_FOREACH(uuid, d->uuids, state) {
> +        pa_bluetooth_profile_t profile;
> +
> +        /* FIXME: PA_BLUETOOTH_UUID_A2DP_SINK maps to both SBC and APTX */
> +        /* FIXME: PA_BLUETOOTH_UUID_A2DP_SOURCE maps to both SBC and APTX */
> +        if (uuid_to_profile(uuid, &profile) < 0)
> +            continue;
> +
> +        /* Handle APTX */
> +        if (profile == PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK)
> +            profile = PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK;
> +        else if (profile == PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE)
> +            profile = PA_BLUETOOTH_PROFILE_A2DP_APTX_SOURCE;
> +        else
> +            continue;
> +
> +        if (!pa_hashmap_get(data.profiles, pa_bluetooth_profile_to_string(profile))) {
> +            cp = create_card_profile(u, profile, data.ports);
> +            pa_hashmap_put(data.profiles, cp->name, cp);
> +        }
> +    }
> +

We shouldn't create the card profile if aptX support is disabled.

>      pa_assert(!pa_hashmap_isempty(data.profiles));
>  
>      cp = pa_card_profile_new("off", _("Off"), sizeof(pa_bluetooth_profile_t));
> diff --git a/src/modules/bluetooth/pa-a2dp-codec-aptx.c b/src/modules/bluetooth/pa-a2dp-codec-aptx.c
> new file mode 100644
> index 000000000..8ce1fc67c
> --- /dev/null
> +++ b/src/modules/bluetooth/pa-a2dp-codec-aptx.c
> @@ -0,0 +1,297 @@
> +/***
> +  This file is part of PulseAudio.
> +
> +  Copyright 2018 Pali Rohár <pali.rohar at gmail.com>
> +
> +  PulseAudio is free software; you can redistribute it and/or modify
> +  it under the terms of the GNU Lesser General Public License as
> +  published by the Free Software Foundation; either version 2.1 of the
> +  License, or (at your option) any later version.
> +
> +  PulseAudio is distributed in the hope that it will be useful, but
> +  WITHOUT ANY WARRANTY; without even the implied warranty of
> +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> +  General Public License for more details.
> +
> +  You should have received a copy of the GNU Lesser General Public
> +  License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
> +***/
> +
> +#ifdef HAVE_CONFIG_H
> +#include <config.h>
> +#endif
> +
> +#include <pulsecore/log.h>
> +#include <pulsecore/macro.h>
> +#include <pulsecore/once.h>
> +#include <pulse/sample.h>
> +
> +#include <openaptx.h>
> +
> +#include "a2dp-codecs.h"
> +#include "pa-a2dp-codec.h"
> +
> +static size_t pa_aptx_fill_capabilities(uint8_t *capabilities_buffer, size_t capabilities_size) {
> +    a2dp_aptx_t *capabilities = (a2dp_aptx_t *) capabilities_buffer;
> +
> +    if (capabilities_size < sizeof(*capabilities)) {
> +        pa_log_error("Invalid size of capabilities buffer");
> +        return 0;
> +    }
> +
> +    pa_zero(*capabilities);
> +
> +    capabilities->info.vendor_id = A2DP_VENDOR_APT_LIC_LTD;
> +    capabilities->info.codec_id = APT_LIC_LTD_CODEC_APTX;
> +    capabilities->channel_mode = APTX_CHANNEL_MODE_STEREO;
> +    capabilities->frequency = APTX_SAMPLING_FREQ_16000 | APTX_SAMPLING_FREQ_32000 | APTX_SAMPLING_FREQ_44100 |
> +                             APTX_SAMPLING_FREQ_48000;
> +
> +    return sizeof(*capabilities);
> +}
> +
> +static bool pa_aptx_validate_configuration(const uint8_t *config_buffer, size_t config_size) {
> +    a2dp_aptx_t *config = (a2dp_aptx_t *) config_buffer;
> +
> +    if (config_size != sizeof(*config)) {
> +        pa_log_error("Invalid size of config buffer");
> +        return false;
> +    }
> +
> +    if (config->info.vendor_id != A2DP_VENDOR_APT_LIC_LTD || config->info.codec_id != APT_LIC_LTD_CODEC_APTX) {
> +        pa_log_error("Invalid vendor codec information in configuration");
> +        return false;
> +    }
> +
> +    if (config->frequency != APTX_SAMPLING_FREQ_16000 && config->frequency != APTX_SAMPLING_FREQ_32000 &&
> +        config->frequency != APTX_SAMPLING_FREQ_44100 && config->frequency != APTX_SAMPLING_FREQ_48000) {
> +        pa_log_error("Invalid sampling frequency in configuration");
> +        return false;
> +    }
> +
> +    if (config->channel_mode != APTX_CHANNEL_MODE_STEREO) {
> +        pa_log_error("Invalid channel mode in configuration");
> +        return false;
> +    }
> +
> +    return true;
> +}
> +
> +static size_t pa_aptx_select_configuration(const pa_sample_spec *sample_spec, const uint8_t *capabilities_buffer, size_t capabilities_size, uint8_t *config_buffer, size_t config_size) {
> +    a2dp_aptx_t *config = (a2dp_aptx_t *) config_buffer;
> +    a2dp_aptx_t *capabilities = (a2dp_aptx_t *) capabilities_buffer;
> +    int i;
> +
> +    static const struct {
> +        uint32_t rate;
> +        uint8_t cap;
> +    } freq_table[] = {
> +        { 16000U, APTX_SAMPLING_FREQ_16000 },
> +        { 32000U, APTX_SAMPLING_FREQ_32000 },
> +        { 44100U, APTX_SAMPLING_FREQ_44100 },
> +        { 48000U, APTX_SAMPLING_FREQ_48000 }
> +    };
> +
> +    if (capabilities_size != sizeof(*capabilities)) {
> +        pa_log_error("Invalid size of capabilities buffer");
> +        return 0;
> +    }
> +
> +    if (config_size < sizeof(*config)) {
> +        pa_log_error("Invalid size of config buffer");
> +        return 0;
> +    }
> +
> +    pa_zero(*config);
> +
> +    if (capabilities->info.vendor_id != A2DP_VENDOR_APT_LIC_LTD || capabilities->info.codec_id != APT_LIC_LTD_CODEC_APTX) {
> +        pa_log_error("No supported vendor codec information");
> +        return 0;
> +    }
> +
> +    config->info.vendor_id = A2DP_VENDOR_APT_LIC_LTD;
> +    config->info.codec_id = APT_LIC_LTD_CODEC_APTX;
> +
> +    if (sample_spec->channels != 2 || !(capabilities->channel_mode & APTX_CHANNEL_MODE_STEREO)) {
> +        pa_log_error("No supported channel modes");
> +        return 0;
> +    }

sample_spec->channels is the global default channel count, and it's
used only as a hint for choosing the preferable channel mode. Since we
support only one channel mode, we can ignore sample_spec->channels
altogether.

> +
> +    config->channel_mode = APTX_CHANNEL_MODE_STEREO;
> +
> +    /* Find the lowest freq that is at least as high as the requested sampling rate */
> +    for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++) {
> +        if (freq_table[i].rate >= sample_spec->rate && (capabilities->frequency & freq_table[i].cap)) {
> +            config->frequency = freq_table[i].cap;
> +            break;
> +        }
> +    }
> +
> +    if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {
> +        for (--i; i >= 0; i--) {
> +            if (capabilities->frequency & freq_table[i].cap) {
> +                config->frequency = freq_table[i].cap;
> +                break;
> +            }
> +        }
> +
> +        if (i < 0) {
> +            pa_log_error("Not suitable sample rate");
> +            return 0;
> +        }
> +    }
> +
> +    return sizeof(*config);
> +}
> +
> +static void pa_aptx_init(void **info_ptr, pa_sample_spec *sample_spec, bool is_a2dp_sink, const uint8_t *config_buffer, size_t config_size) {
> +    a2dp_aptx_t *config = (a2dp_aptx_t *) config_buffer;
> +
> +    pa_assert(config_size == sizeof(*config));
> +
> +    sample_spec->format = PA_SAMPLE_S24LE;
> +
> +    if (*info_ptr)
> +        aptx_reset((struct aptx_context *) *info_ptr);
> +    else
> +        *info_ptr = (void *)aptx_init(0);
> +
> +    switch (config->frequency) {
> +        case APTX_SAMPLING_FREQ_16000:
> +            sample_spec->rate = 16000U;
> +            break;
> +        case APTX_SAMPLING_FREQ_32000:
> +            sample_spec->rate = 32000U;
> +            break;
> +        case APTX_SAMPLING_FREQ_44100:
> +            sample_spec->rate = 44100U;
> +            break;
> +        case APTX_SAMPLING_FREQ_48000:
> +            sample_spec->rate = 48000U;
> +            break;
> +        default:
> +            pa_assert_not_reached();
> +    }
> +
> +    switch (config->channel_mode) {
> +        case APTX_CHANNEL_MODE_STEREO:
> +            sample_spec->channels = 2;
> +            break;
> +        default:
> +            pa_assert_not_reached();
> +    }
> +}
> +
> +static void pa_aptx_finish(void **info_ptr) {
> +    struct aptx_context *aptx_c = (struct aptx_context *) *info_ptr;
> +
> +    if (aptx_c) {
> +        aptx_finish(aptx_c);
> +        *info_ptr = NULL;
> +    }
> +}
> +
> +static void pa_aptx_setup(void **info_ptr) {
> +}
> +
> +static void pa_aptx_fill_blocksize(void **info_ptr, size_t read_link_mtu, size_t write_link_mtu, size_t *read_block_size, size_t *write_block_size) {
> +    *write_block_size = (write_link_mtu/(8*4)) * 8*4*6;
> +    *read_block_size = (read_link_mtu/(8*4)) * 8*4*6;

Please add comments that explain the math here.

> +}
> +
> +static bool pa_aptx_fix_latency(void **info_ptr) {
> +    return false;
> +}
> +
> +static size_t pa_aptx_encode(void **info_ptr, uint32_t timestamp, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size) {
> +    struct aptx_context *aptx_c = (struct aptx_context *) *info_ptr;
> +    uint8_t *d;
> +    const uint8_t *p;
> +    size_t to_write, to_encode;
> +
> +    p = input_buffer;
> +    to_encode = input_size;
> +
> +    d = output_buffer;
> +    to_write = output_size;
> +
> +    while (PA_LIKELY(to_encode > 0 && to_write > 0)) {
> +        size_t written;
> +        size_t encoded;
> +        encoded = aptx_encode(aptx_c, p, to_encode, d, to_write, &written);
> +
> +        if (PA_UNLIKELY(encoded == 0)) {
> +            pa_log_error("aptX encoding error");
> +            return 0;
> +        }
> +
> +        pa_assert_fp((size_t) encoded <= to_encode);
> +        pa_assert_fp((size_t) written <= to_write);
> +
> +        p += encoded;
> +        to_encode -= encoded;
> +
> +        d += written;
> +        to_write -= written;
> +    }
> +
> +    pa_assert(to_encode == 0);
> +
> +    PA_ONCE_BEGIN {
> +        pa_log_debug("Using aptX encoder implementation: libopenaptx from https://github.com/pali/libopenaptx");
> +    } PA_ONCE_END;
> +
> +    return d - output_buffer;
> +}
> +
> +static size_t pa_aptx_decode(void **info_ptr, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) {
> +    struct aptx_context *aptx_c = (struct aptx_context *) *info_ptr;
> +
> +    const uint8_t *p;
> +    uint8_t *d;
> +    size_t to_write, to_decode;
> +
> +    p = input_buffer;
> +    to_decode = input_size;
> +
> +    d = output_buffer;
> +    to_write = output_size;
> +
> +    while (PA_LIKELY(to_decode > 0)) {
> +        size_t written;
> +        size_t decoded;
> +
> +        decoded = aptx_decode(aptx_c, p, to_decode, d, to_write, &written);
> +
> +        if (PA_UNLIKELY(decoded == 0)) {
> +            pa_log_error("aptX decoding error");
> +            *processed = p - input_buffer;
> +            return 0;
> +        }
> +
> +        pa_assert_fp((size_t) decoded <= to_decode);
> +
> +        p += decoded;
> +        to_decode -= decoded;
> +
> +        d += written;
> +        to_write -= written;
> +    }
> +
> +    *processed = p - input_buffer;
> +    return d - output_buffer;
> +}
> +
> +const pa_a2dp_codec_t pa_a2dp_codec_aptx = {
> +    .codec_id = A2DP_CODEC_VENDOR,
> +    .fill_capabilities = pa_aptx_fill_capabilities,
> +    .validate_configuration = pa_aptx_validate_configuration,
> +    .select_configuration = pa_aptx_select_configuration,
> +    .init = pa_aptx_init,
> +    .finish = pa_aptx_finish,
> +    .setup = pa_aptx_setup,
> +    .fill_blocksize = pa_aptx_fill_blocksize,
> +    .fix_latency = pa_aptx_fix_latency,
> +    .encode = pa_aptx_encode,
> +    .decode = pa_aptx_decode,
> +};
> diff --git a/src/modules/bluetooth/pa-a2dp-codec.h b/src/modules/bluetooth/pa-a2dp-codec.h
> index 68b1619c2..26e7653a6 100644
> --- a/src/modules/bluetooth/pa-a2dp-codec.h
> +++ b/src/modules/bluetooth/pa-a2dp-codec.h
> @@ -36,5 +36,6 @@ typedef struct pa_a2dp_codec {
>  } pa_a2dp_codec_t;
>  
>  extern const pa_a2dp_codec_t pa_a2dp_codec_sbc;
> +extern const pa_a2dp_codec_t pa_a2dp_codec_aptx;
>  
>  #endif

-- 
Tanu

https://www.patreon.com/tanuk
https://liberapay.com/tanuk


More information about the pulseaudio-discuss mailing list