[pulseaudio-discuss] [PATCH] WIP: Bluetooth A2DP aptX codec support
Luiz Augusto von Dentz
luiz.dentz at gmail.com
Sun Jul 8 20:06:12 UTC 2018
Hi Pali,
On Fri, Jul 6, 2018 at 12:16 PM, Pali Rohár <pali.rohar at gmail.com> 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:
>
> Only A2DP sink can use aptX codec. A2DP source is still restricted to SBC
> codec.
>
> Only standard aptX codec is supported for now. Support for other variants
> like aptX HD, aptX Low Latency, FastStream may come up later.
>
> FastStream and aptX Low Latency A2DP codecs are interested because they are
> bi-directional (voice recording support). For now user needs to switch between
> A2DP and HSP if he want voice recording or music playback. IIRC FastStream
> codec is just rebranded SBC codec with fixed parameters. File a2dp-codecs.h
> is now prepared for all of them.
>
> Known problems:
>
> For every A2DP codec it is needed to register endpoint to the bluez daemon.
> This is working fine, but I do not know how and why bluez daemon choose
> endpoint (and so codec) for new connection. And I have not figured out how
> to tell bluez daemon to switch A2DP codec from SBC to aptX and vice-versa.
> It looks like that bluez daemon chooses endpoint (and so codec) at
> connection time randomly :-(
>
> So for now, when support for aptX codec is enabled at compile time, SBC
> endpoint is not registered and therefore bluez daemon always choose only
> registered aptX endpoint.
>
> This needs to be investigated and properly implemented. Reason why this
> patch is marked just as WIP. See TODO: and FIXME: comments in source code.
> ---
> configure.ac | 19 ++
> src/Makefile.am | 4 +
> src/modules/bluetooth/a2dp-codecs.h | 121 +++++++-
> src/modules/bluetooth/bluez5-util.c | 448 ++++++++++++++++++---------
> src/modules/bluetooth/bluez5-util.h | 8 +-
> src/modules/bluetooth/module-bluez5-device.c | 319 ++++++++++++++-----
> 6 files changed, 697 insertions(+), 222 deletions(-)
I don't think it is a good idea to change device module directly, what
we shall probably do first is make the codec-specific code into a
module themselves with util.c providing helpers to register the
endpoint, etc and also notify the device module which codec module has
been selected, or perhaps not even that if we there is a way to make
the data follow directly to the codec module without the device module
sitting in between.
> diff --git a/configure.ac b/configure.ac
> index 8890aa15f..9077edb8e 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -1103,6 +1103,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])])
> +
> +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],
> @@ -1588,6 +1605,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)
> @@ -1646,6 +1664,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 f62e7d5c4..9d6a1b03a 100644
> --- a/src/Makefile.am
> +++ b/src/Makefile.am
> @@ -2141,6 +2141,10 @@ module_bluez5_device_la_LDFLAGS = $(MODULE_LDFLAGS)
> module_bluez5_device_la_LIBADD = $(MODULE_LIBADD) $(SBC_LIBS) libbluez5-util.la
> module_bluez5_device_la_CFLAGS = $(AM_CFLAGS) $(SBC_CFLAGS) -DPA_MODULE_NAME=module_bluez5_device
>
> +if HAVE_OPENAPTX
> +module_bluez5_device_la_LIBADD += -lopenaptx
> +endif
> +
> # Apple Airtunes/RAOP
> module_raop_sink_la_SOURCES = modules/raop/module-raop-sink.c
> module_raop_sink_la_LDFLAGS = $(MODULE_LDFLAGS)
> diff --git a/src/modules/bluetooth/a2dp-codecs.h b/src/modules/bluetooth/a2dp-codecs.h
> index 8afcfcb24..217f8056f 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
> @@ -25,6 +26,17 @@
> #define A2DP_CODEC_MPEG12 0x01
> #define A2DP_CODEC_MPEG24 0x02
> #define A2DP_CODEC_ATRAC 0x03
> +#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)
> @@ -47,6 +59,9 @@
> #define SBC_ALLOCATION_SNR (1 << 1)
> #define SBC_ALLOCATION_LOUDNESS 1
>
> +#define SBC_MIN_BITPOOL 2
> +#define SBC_MAX_BITPOOL 64
> +
> #define MPEG_CHANNEL_MODE_MONO (1 << 3)
> #define MPEG_CHANNEL_MODE_DUAL_CHANNEL (1 << 2)
> #define MPEG_CHANNEL_MODE_STEREO (1 << 1)
> @@ -63,8 +78,26 @@
> #define MPEG_SAMPLING_FREQ_44100 (1 << 1)
> #define MPEG_SAMPLING_FREQ_48000 1
>
> -#define MAX_BITPOOL 64
> -#define MIN_BITPOOL 2
> +#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
>
> @@ -88,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 {
> @@ -110,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 2d8337317..2db147736 100644
> --- a/src/modules/bluetooth/bluez5-util.c
> +++ b/src/modules/bluetooth/bluez5-util.c
> @@ -2,6 +2,7 @@
> This file is part of PulseAudio.
>
> Copyright 2008-2013 João Paulo Rechi Vita
> + Copyrigth 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
> @@ -48,7 +49,8 @@
>
> #define BLUEZ_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported"
>
> -#define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource"
> +#define A2DP_SOURCE_SBC_ENDPOINT "/MediaEndpoint/A2DPSourceSBC"
> +#define A2DP_SOURCE_APTX_ENDPOINT "/MediaEndpoint/A2DPSourceAPTX"
> #define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink"
>
> #define ENDPOINT_INTROSPECT_XML \
> @@ -170,8 +172,15 @@ static const char *transport_state_to_string(pa_bluetooth_transport_state_t stat
>
> static bool device_supports_profile(pa_bluetooth_device *device, pa_bluetooth_profile_t profile) {
> switch (profile) {
> - case PA_BLUETOOTH_PROFILE_A2DP_SINK:
> + 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: How to check if device supports aptX?? */
> + return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_SINK);
> +#else
> + return false;
> +#endif
> case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
> return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_SOURCE);
> case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
> @@ -885,10 +894,10 @@ finish:
> pa_xfree(endpoint);
> }
>
> -static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const char *endpoint, const char *uuid) {
> +static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const char *endpoint, const char *uuid, pa_bluetooth_a2dp_codec_t codec) {
> DBusMessage *m;
> DBusMessageIter i, d;
> - uint8_t codec = 0;
> + uint8_t codec_id;
>
> pa_log_debug("Registering %s on adapter %s", endpoint, path);
>
> @@ -898,25 +907,50 @@ static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const
> pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &endpoint));
> dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
> DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &d);
> - pa_dbus_append_basic_variant_dict_entry(&d, "UUID", DBUS_TYPE_STRING, &uuid);
> - pa_dbus_append_basic_variant_dict_entry(&d, "Codec", DBUS_TYPE_BYTE, &codec);
> -
> - if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE) || pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK)) {
> - a2dp_sbc_t capabilities;
> -
> - capabilities.channel_mode = SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL | SBC_CHANNEL_MODE_STEREO |
> - SBC_CHANNEL_MODE_JOINT_STEREO;
> - capabilities.frequency = SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 | SBC_SAMPLING_FREQ_44100 |
> - SBC_SAMPLING_FREQ_48000;
> - capabilities.allocation_method = SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS;
> - capabilities.subbands = SBC_SUBBANDS_4 | SBC_SUBBANDS_8;
> - capabilities.block_length = SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16;
> - capabilities.min_bitpool = MIN_BITPOOL;
> - capabilities.max_bitpool = MAX_BITPOOL;
> -
> - pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capabilities, sizeof(capabilities));
> +
> + switch (codec) {
> + case PA_BLUETOOTH_A2DP_CODEC_SBC:
> + {
> + a2dp_sbc_t capabilities;
> +
> + capabilities.channel_mode = SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL | SBC_CHANNEL_MODE_STEREO |
> + SBC_CHANNEL_MODE_JOINT_STEREO;
> + capabilities.frequency = SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 | SBC_SAMPLING_FREQ_44100 |
> + SBC_SAMPLING_FREQ_48000;
> + capabilities.allocation_method = SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS;
> + capabilities.subbands = SBC_SUBBANDS_4 | SBC_SUBBANDS_8;
> + capabilities.block_length = SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16;
> + capabilities.min_bitpool = SBC_MIN_BITPOOL;
> + capabilities.max_bitpool = SBC_MAX_BITPOOL;
> +
> + codec_id = A2DP_CODEC_SBC;
> + pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capabilities, sizeof(capabilities));
> + break;
> + }
> + case PA_BLUETOOTH_A2DP_CODEC_APTX:
> + {
> + a2dp_aptx_t 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;
> +
> + codec_id = A2DP_CODEC_VENDOR;
> + pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capabilities, sizeof(capabilities));
> + break;
> + }
> + default:
> + {
> + pa_assert_not_reached();
> + break;
> + }
> }
>
> + pa_dbus_append_basic_variant_dict_entry(&d, "Codec", DBUS_TYPE_BYTE, &codec_id);
> + pa_dbus_append_basic_variant_dict_entry(&d, "UUID", DBUS_TYPE_STRING, &uuid);
> +
> dbus_message_iter_close_container(&i, &d);
>
> send_and_add_to_pending(y, m, register_endpoint_reply, pa_xstrdup(endpoint));
> @@ -964,8 +998,13 @@ static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessa
> if (!a->valid)
> return;
>
> - register_endpoint(y, path, A2DP_SOURCE_ENDPOINT, PA_BLUETOOTH_UUID_A2DP_SOURCE);
> - register_endpoint(y, path, A2DP_SINK_ENDPOINT, PA_BLUETOOTH_UUID_A2DP_SINK);
> + /* FIXME: Disable SBC endpoint, until we find a way how to tell bluez which endpoint/codec to use; seems that bluez choose endpoint randomly */
> +#ifndef HAVE_OPENAPTX
> + register_endpoint(y, path, A2DP_SOURCE_SBC_ENDPOINT, PA_BLUETOOTH_UUID_A2DP_SOURCE, PA_BLUETOOTH_A2DP_CODEC_SBC);
> +#else
> + register_endpoint(y, path, A2DP_SOURCE_APTX_ENDPOINT, PA_BLUETOOTH_UUID_A2DP_SOURCE, PA_BLUETOOTH_A2DP_CODEC_APTX);
> +#endif
> + register_endpoint(y, path, A2DP_SINK_ENDPOINT, PA_BLUETOOTH_UUID_A2DP_SINK, PA_BLUETOOTH_A2DP_CODEC_SBC);
>
> } else if (pa_streq(interface, BLUEZ_DEVICE_INTERFACE)) {
>
> @@ -1301,8 +1340,10 @@ static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) {
>
> const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile) {
> switch(profile) {
> - case PA_BLUETOOTH_PROFILE_A2DP_SINK:
> + 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_SOURCE:
> return "a2dp_source";
> case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
> @@ -1345,6 +1386,8 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
> if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY)
> goto fail;
>
> + endpoint_path = dbus_message_get_path(m);
> +
> /* Read transport properties */
> while (dbus_message_iter_get_arg_type(&props) == DBUS_TYPE_DICT_ENTRY) {
> const char *key;
> @@ -1367,10 +1410,12 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
>
> dbus_message_iter_get_basic(&value, &uuid);
>
> - endpoint_path = dbus_message_get_path(m);
> - if (pa_streq(endpoint_path, A2DP_SOURCE_ENDPOINT)) {
> + if (pa_streq(endpoint_path, A2DP_SOURCE_SBC_ENDPOINT)) {
> if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE))
> - p = PA_BLUETOOTH_PROFILE_A2DP_SINK;
> + 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_ENDPOINT)) {
> if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK))
> p = PA_BLUETOOTH_PROFILE_A2DP_SOURCE;
> @@ -1389,7 +1434,6 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
> dbus_message_iter_get_basic(&value, &dev_path);
> } else if (pa_streq(key, "Configuration")) {
> DBusMessageIter array;
> - a2dp_sbc_t *c;
>
> if (var != DBUS_TYPE_ARRAY) {
> pa_log_error("Property %s of wrong type %c", key, (char)var);
> @@ -1404,39 +1448,71 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
> }
>
> dbus_message_iter_get_fixed_array(&array, &config, &size);
> - if (size != sizeof(a2dp_sbc_t)) {
> - pa_log_error("Configuration array of invalid size");
> - goto fail;
> - }
>
> - c = (a2dp_sbc_t *) config;
> + if (pa_streq(endpoint_path, A2DP_SOURCE_SBC_ENDPOINT) || pa_streq(endpoint_path, A2DP_SINK_ENDPOINT)) {
> + a2dp_sbc_t *c;
>
> - if (c->frequency != SBC_SAMPLING_FREQ_16000 && c->frequency != SBC_SAMPLING_FREQ_32000 &&
> - c->frequency != SBC_SAMPLING_FREQ_44100 && c->frequency != SBC_SAMPLING_FREQ_48000) {
> - pa_log_error("Invalid sampling frequency in configuration");
> - goto fail;
> - }
> + if (size != sizeof(a2dp_sbc_t)) {
> + pa_log_error("Configuration array of invalid size");
> + goto fail;
> + }
>
> - if (c->channel_mode != SBC_CHANNEL_MODE_MONO && c->channel_mode != SBC_CHANNEL_MODE_DUAL_CHANNEL &&
> - c->channel_mode != SBC_CHANNEL_MODE_STEREO && c->channel_mode != SBC_CHANNEL_MODE_JOINT_STEREO) {
> - pa_log_error("Invalid channel mode in configuration");
> - goto fail;
> - }
> + c = (a2dp_sbc_t *) config;
>
> - if (c->allocation_method != SBC_ALLOCATION_SNR && c->allocation_method != SBC_ALLOCATION_LOUDNESS) {
> - pa_log_error("Invalid allocation method in configuration");
> - goto fail;
> - }
> + if (c->frequency != SBC_SAMPLING_FREQ_16000 && c->frequency != SBC_SAMPLING_FREQ_32000 &&
> + c->frequency != SBC_SAMPLING_FREQ_44100 && c->frequency != SBC_SAMPLING_FREQ_48000) {
> + pa_log_error("Invalid sampling frequency in configuration");
> + goto fail;
> + }
>
> - if (c->subbands != SBC_SUBBANDS_4 && c->subbands != SBC_SUBBANDS_8) {
> - pa_log_error("Invalid SBC subbands in configuration");
> - goto fail;
> - }
> + if (c->channel_mode != SBC_CHANNEL_MODE_MONO && c->channel_mode != SBC_CHANNEL_MODE_DUAL_CHANNEL &&
> + c->channel_mode != SBC_CHANNEL_MODE_STEREO && c->channel_mode != SBC_CHANNEL_MODE_JOINT_STEREO) {
> + pa_log_error("Invalid channel mode in configuration");
> + goto fail;
> + }
>
> - if (c->block_length != SBC_BLOCK_LENGTH_4 && c->block_length != SBC_BLOCK_LENGTH_8 &&
> - c->block_length != SBC_BLOCK_LENGTH_12 && c->block_length != SBC_BLOCK_LENGTH_16) {
> - pa_log_error("Invalid block length in configuration");
> - goto fail;
> + if (c->allocation_method != SBC_ALLOCATION_SNR && c->allocation_method != SBC_ALLOCATION_LOUDNESS) {
> + pa_log_error("Invalid allocation method in configuration");
> + goto fail;
> + }
> +
> + if (c->subbands != SBC_SUBBANDS_4 && c->subbands != SBC_SUBBANDS_8) {
> + pa_log_error("Invalid SBC subbands in configuration");
> + goto fail;
> + }
> +
> + if (c->block_length != SBC_BLOCK_LENGTH_4 && c->block_length != SBC_BLOCK_LENGTH_8 &&
> + c->block_length != SBC_BLOCK_LENGTH_12 && c->block_length != SBC_BLOCK_LENGTH_16) {
> + pa_log_error("Invalid block length in configuration");
> + goto fail;
> + }
> + } else if (pa_streq(endpoint_path, A2DP_SOURCE_APTX_ENDPOINT)) {
> + a2dp_aptx_t *c;
> +
> + if (size != sizeof(a2dp_aptx_t)) {
> + pa_log_error("Configuration array of invalid size");
> + goto fail;
> + }
> +
> + c = (a2dp_aptx_t *) config;
> +
> + if (c->info.vendor_id != A2DP_VENDOR_APT_LIC_LTD || c->info.codec_id != APT_LIC_LTD_CODEC_APTX) {
> + pa_log_error("Invalid vendor codec information in configuration");
> + goto fail;
> + }
> +
> + if (c->frequency != APTX_SAMPLING_FREQ_16000 && c->frequency != APTX_SAMPLING_FREQ_32000 &&
> + c->frequency != APTX_SAMPLING_FREQ_44100 && c->frequency != APTX_SAMPLING_FREQ_48000) {
> + pa_log_error("Invalid sampling frequency in configuration");
> + goto fail;
> + }
> +
> + if (c->channel_mode != APTX_CHANNEL_MODE_STEREO) {
> + pa_log_error("Invalid channel mode in configuration");
> + goto fail;
> + }
> + } else {
> + pa_assert_not_reached();
> }
> }
>
> @@ -1484,125 +1560,199 @@ fail2:
>
> static DBusMessage *endpoint_select_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
> pa_bluetooth_discovery *y = userdata;
> - a2dp_sbc_t *cap, config;
> - uint8_t *pconf = (uint8_t *) &config;
> + const char *endpoint_path;
> + uint8_t *pcap;
> int i, size;
> DBusMessage *r;
> DBusError err;
>
> - static const struct {
> - uint32_t rate;
> - uint8_t cap;
> - } freq_table[] = {
> - { 16000U, SBC_SAMPLING_FREQ_16000 },
> - { 32000U, SBC_SAMPLING_FREQ_32000 },
> - { 44100U, SBC_SAMPLING_FREQ_44100 },
> - { 48000U, SBC_SAMPLING_FREQ_48000 }
> - };
> + endpoint_path = dbus_message_get_path(m);
>
> dbus_error_init(&err);
>
> - if (!dbus_message_get_args(m, &err, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &cap, &size, DBUS_TYPE_INVALID)) {
> + if (!dbus_message_get_args(m, &err, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pcap, &size, DBUS_TYPE_INVALID)) {
> pa_log_error("Endpoint SelectConfiguration(): %s", err.message);
> dbus_error_free(&err);
> goto fail;
> }
>
> - if (size != sizeof(config)) {
> - pa_log_error("Capabilities array has invalid size");
> - goto fail;
> - }
> + if (pa_streq(endpoint_path, A2DP_SOURCE_SBC_ENDPOINT) || pa_streq(endpoint_path, A2DP_SINK_ENDPOINT)) {
>
> - pa_zero(config);
> + a2dp_sbc_t config;
> + a2dp_sbc_t *cap = (a2dp_sbc_t *) pcap;
> + uint8_t *pconf = (uint8_t *) &config;
>
> - /* 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 >= y->core->default_sample_spec.rate && (cap->frequency & freq_table[i].cap)) {
> - config.frequency = freq_table[i].cap;
> - break;
> + static const struct {
> + uint32_t rate;
> + uint8_t cap;
> + } freq_table[] = {
> + { 16000U, SBC_SAMPLING_FREQ_16000 },
> + { 32000U, SBC_SAMPLING_FREQ_32000 },
> + { 44100U, SBC_SAMPLING_FREQ_44100 },
> + { 48000U, SBC_SAMPLING_FREQ_48000 }
> + };
> +
> + if (size != sizeof(config)) {
> + pa_log_error("Capabilities array has invalid size");
> + goto fail;
> }
>
> - if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {
> - for (--i; i >= 0; i--) {
> - if (cap->frequency & freq_table[i].cap) {
> + pa_zero(config);
> +
> + /* 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 >= y->core->default_sample_spec.rate && (cap->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 (cap->frequency & freq_table[i].cap) {
> + config.frequency = freq_table[i].cap;
> + break;
> + }
> + }
> +
> + if (i < 0) {
> + pa_log_error("Not suitable sample rate");
> + goto fail;
> + }
> }
>
> - if (i < 0) {
> - pa_log_error("Not suitable sample rate");
> - goto fail;
> + pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table));
> +
> + if (y->core->default_sample_spec.channels <= 1) {
> + if (cap->channel_mode & SBC_CHANNEL_MODE_MONO)
> + config.channel_mode = SBC_CHANNEL_MODE_MONO;
> + else if (cap->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
> + config.channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
> + else if (cap->channel_mode & SBC_CHANNEL_MODE_STEREO)
> + config.channel_mode = SBC_CHANNEL_MODE_STEREO;
> + else if (cap->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
> + config.channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
> + else {
> + pa_log_error("No supported channel modes");
> + goto fail;
> + }
> }
> - }
>
> - pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table));
> -
> - if (y->core->default_sample_spec.channels <= 1) {
> - if (cap->channel_mode & SBC_CHANNEL_MODE_MONO)
> - config.channel_mode = SBC_CHANNEL_MODE_MONO;
> - else if (cap->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
> - config.channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
> - else if (cap->channel_mode & SBC_CHANNEL_MODE_STEREO)
> - config.channel_mode = SBC_CHANNEL_MODE_STEREO;
> - else if (cap->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
> - config.channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
> + if (y->core->default_sample_spec.channels >= 2) {
> + if (cap->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
> + config.channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
> + else if (cap->channel_mode & SBC_CHANNEL_MODE_STEREO)
> + config.channel_mode = SBC_CHANNEL_MODE_STEREO;
> + else if (cap->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
> + config.channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
> + else if (cap->channel_mode & SBC_CHANNEL_MODE_MONO)
> + config.channel_mode = SBC_CHANNEL_MODE_MONO;
> + else {
> + pa_log_error("No supported channel modes");
> + goto fail;
> + }
> + }
> +
> + if (cap->block_length & SBC_BLOCK_LENGTH_16)
> + config.block_length = SBC_BLOCK_LENGTH_16;
> + else if (cap->block_length & SBC_BLOCK_LENGTH_12)
> + config.block_length = SBC_BLOCK_LENGTH_12;
> + else if (cap->block_length & SBC_BLOCK_LENGTH_8)
> + config.block_length = SBC_BLOCK_LENGTH_8;
> + else if (cap->block_length & SBC_BLOCK_LENGTH_4)
> + config.block_length = SBC_BLOCK_LENGTH_4;
> else {
> - pa_log_error("No supported channel modes");
> + pa_log_error("No supported block lengths");
> goto fail;
> }
> - }
>
> - if (y->core->default_sample_spec.channels >= 2) {
> - if (cap->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
> - config.channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
> - else if (cap->channel_mode & SBC_CHANNEL_MODE_STEREO)
> - config.channel_mode = SBC_CHANNEL_MODE_STEREO;
> - else if (cap->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
> - config.channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
> - else if (cap->channel_mode & SBC_CHANNEL_MODE_MONO)
> - config.channel_mode = SBC_CHANNEL_MODE_MONO;
> + if (cap->subbands & SBC_SUBBANDS_8)
> + config.subbands = SBC_SUBBANDS_8;
> + else if (cap->subbands & SBC_SUBBANDS_4)
> + config.subbands = SBC_SUBBANDS_4;
> else {
> + pa_log_error("No supported subbands");
> + goto fail;
> + }
> +
> + if (cap->allocation_method & SBC_ALLOCATION_LOUDNESS)
> + config.allocation_method = SBC_ALLOCATION_LOUDNESS;
> + else if (cap->allocation_method & SBC_ALLOCATION_SNR)
> + config.allocation_method = SBC_ALLOCATION_SNR;
> +
> + config.min_bitpool = (uint8_t) PA_MAX(SBC_MIN_BITPOOL, cap->min_bitpool);
> + config.max_bitpool = (uint8_t) PA_MIN(a2dp_default_bitpool(config.frequency, config.channel_mode), cap->max_bitpool);
> +
> + if (config.min_bitpool > config.max_bitpool)
> + goto fail;
> +
> + pa_assert_se(r = dbus_message_new_method_return(m));
> + pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pconf, size, DBUS_TYPE_INVALID));
> +
> + } else if (pa_streq(endpoint_path, A2DP_SOURCE_APTX_ENDPOINT)) {
> +
> + a2dp_aptx_t config;
> + a2dp_aptx_t *cap = (a2dp_aptx_t *) pcap;
> + uint8_t *pconf = (uint8_t *) &config;
> +
> + 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 (size != sizeof(config)) {
> + pa_log_error("Capabilities array has invalid size");
> + goto fail;
> + }
> +
> + pa_zero(config);
> +
> + if (cap->info.vendor_id != A2DP_VENDOR_APT_LIC_LTD || cap->info.codec_id != APT_LIC_LTD_CODEC_APTX) {
> + pa_log_error("No supported vendor codec information");
> + goto fail;
> + }
> + config.info.vendor_id = A2DP_VENDOR_APT_LIC_LTD;
> + config.info.codec_id = APT_LIC_LTD_CODEC_APTX;
> +
> + if (y->core->default_sample_spec.channels != 2 || !(cap->channel_mode & APTX_CHANNEL_MODE_STEREO)) {
> pa_log_error("No supported channel modes");
> goto fail;
> }
> - }
> + config.channel_mode = APTX_CHANNEL_MODE_STEREO;
>
> - if (cap->block_length & SBC_BLOCK_LENGTH_16)
> - config.block_length = SBC_BLOCK_LENGTH_16;
> - else if (cap->block_length & SBC_BLOCK_LENGTH_12)
> - config.block_length = SBC_BLOCK_LENGTH_12;
> - else if (cap->block_length & SBC_BLOCK_LENGTH_8)
> - config.block_length = SBC_BLOCK_LENGTH_8;
> - else if (cap->block_length & SBC_BLOCK_LENGTH_4)
> - config.block_length = SBC_BLOCK_LENGTH_4;
> - else {
> - pa_log_error("No supported block lengths");
> - goto fail;
> - }
> + /* 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 >= y->core->default_sample_spec.rate && (cap->frequency & freq_table[i].cap)) {
> + config.frequency = freq_table[i].cap;
> + break;
> + }
>
> - if (cap->subbands & SBC_SUBBANDS_8)
> - config.subbands = SBC_SUBBANDS_8;
> - else if (cap->subbands & SBC_SUBBANDS_4)
> - config.subbands = SBC_SUBBANDS_4;
> - else {
> - pa_log_error("No supported subbands");
> - goto fail;
> - }
> + if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {
> + for (--i; i >= 0; i--) {
> + if (cap->frequency & freq_table[i].cap) {
> + config.frequency = freq_table[i].cap;
> + break;
> + }
> + }
>
> - if (cap->allocation_method & SBC_ALLOCATION_LOUDNESS)
> - config.allocation_method = SBC_ALLOCATION_LOUDNESS;
> - else if (cap->allocation_method & SBC_ALLOCATION_SNR)
> - config.allocation_method = SBC_ALLOCATION_SNR;
> + if (i < 0) {
> + pa_log_error("Not suitable sample rate");
> + goto fail;
> + }
> + }
>
> - config.min_bitpool = (uint8_t) PA_MAX(MIN_BITPOOL, cap->min_bitpool);
> - config.max_bitpool = (uint8_t) PA_MIN(a2dp_default_bitpool(config.frequency, config.channel_mode), cap->max_bitpool);
> + pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table));
>
> - if (config.min_bitpool > config.max_bitpool)
> - goto fail;
> + pa_assert_se(r = dbus_message_new_method_return(m));
> + pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pconf, size, DBUS_TYPE_INVALID));
>
> - pa_assert_se(r = dbus_message_new_method_return(m));
> - pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pconf, size, DBUS_TYPE_INVALID));
> + } else {
> + pa_assert_not_reached();
> + }
>
> return r;
>
> @@ -1677,7 +1827,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_ENDPOINT) && !pa_streq(path, A2DP_SINK_ENDPOINT))
> + if (!pa_streq(path, A2DP_SOURCE_SBC_ENDPOINT) && !pa_streq(path, A2DP_SINK_ENDPOINT) &&
> + !pa_streq(path, A2DP_SOURCE_APTX_ENDPOINT))
> return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
>
> if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
> @@ -1713,8 +1864,12 @@ static void endpoint_init(pa_bluetooth_discovery *y, pa_bluetooth_profile_t prof
> pa_assert(y);
>
> switch(profile) {
> - case PA_BLUETOOTH_PROFILE_A2DP_SINK:
> - pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT,
> + case PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK:
> + pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_SBC_ENDPOINT,
> + &vtable_endpoint, y));
> + break;
> + case PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK:
> + pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_APTX_ENDPOINT,
> &vtable_endpoint, y));
> break;
> case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
> @@ -1731,8 +1886,11 @@ static void endpoint_done(pa_bluetooth_discovery *y, pa_bluetooth_profile_t prof
> pa_assert(y);
>
> switch(profile) {
> - case PA_BLUETOOTH_PROFILE_A2DP_SINK:
> - dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT);
> + case PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK:
> + dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_SBC_ENDPOINT);
> + break;
> + case PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK:
> + dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_APTX_ENDPOINT);
> break;
> case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
> dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT);
> @@ -1799,7 +1957,8 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backe
> }
> y->matches_added = true;
>
> - endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_SINK);
> + endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK);
> + endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK);
> endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_SOURCE);
>
> get_managed_objects(y);
> @@ -1868,7 +2027,8 @@ 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_SINK);
> + endpoint_done(y, PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK);
> + endpoint_done(y, PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK);
> endpoint_done(y, PA_BLUETOOTH_PROFILE_A2DP_SOURCE);
>
> pa_dbus_connection_unref(y->connection);
> diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
> index ad30708f0..927fbdf6f 100644
> --- a/src/modules/bluetooth/bluez5-util.h
> +++ b/src/modules/bluetooth/bluez5-util.h
> @@ -51,7 +51,8 @@ typedef enum pa_bluetooth_hook {
> } pa_bluetooth_hook_t;
>
> typedef enum profile {
> - PA_BLUETOOTH_PROFILE_A2DP_SINK,
> + PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK,
> + PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK,
> PA_BLUETOOTH_PROFILE_A2DP_SOURCE,
> PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT,
> PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY,
> @@ -65,6 +66,11 @@ typedef enum pa_bluetooth_transport_state {
> PA_BLUETOOTH_TRANSPORT_STATE_PLAYING
> } pa_bluetooth_transport_state_t;
>
> +typedef enum pa_bluetooth_a2dp_codec {
> + PA_BLUETOOTH_A2DP_CODEC_SBC,
> + PA_BLUETOOTH_A2DP_CODEC_APTX,
> +} pa_bluetooth_a2dp_codec_t;
> +
> typedef int (*pa_bluetooth_transport_acquire_cb)(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu);
> typedef void (*pa_bluetooth_transport_release_cb)(pa_bluetooth_transport *t);
> typedef void (*pa_bluetooth_transport_destroy_cb)(pa_bluetooth_transport *t);
> diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
> index 9dbdca316..16593da19 100644
> --- a/src/modules/bluetooth/module-bluez5-device.c
> +++ b/src/modules/bluetooth/module-bluez5-device.c
> @@ -3,6 +3,7 @@
>
> Copyright 2008-2013 João Paulo Rechi Vita
> Copyright 2011-2013 BMW Car IT GmbH.
> + 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
> @@ -27,6 +28,10 @@
> #include <arpa/inet.h>
> #include <sbc/sbc.h>
>
> +#ifdef HAVE_OPENAPTX
> +#include <openaptx.h>
> +#endif
> +
> #include <pulse/rtclock.h>
> #include <pulse/timeval.h>
> #include <pulse/utf8.h>
> @@ -101,9 +106,6 @@ typedef struct sbc_info {
> uint16_t seq_num; /* Cumulative packet sequence */
> uint8_t min_bitpool;
> uint8_t max_bitpool;
> -
> - void* buffer; /* Codec transfer buffer */
> - size_t buffer_size; /* Size of the buffer */
> } sbc_info_t;
>
> struct userdata {
> @@ -147,6 +149,12 @@ struct userdata {
> pa_memchunk write_memchunk;
> pa_sample_spec sample_spec;
> struct sbc_info sbc_info;
> +#ifdef HAVE_OPENAPTX
> + struct aptx_context *aptx_c;
> +#endif
> +
> + void* buffer; /* Codec transfer buffer */
> + size_t buffer_size; /* Size of the buffer */
> };
>
> typedef enum pa_bluetooth_form_factor {
> @@ -418,28 +426,76 @@ static void a2dp_prepare_buffer(struct userdata *u) {
>
> pa_assert(u);
>
> - if (u->sbc_info.buffer_size >= min_buffer_size)
> + if (u->buffer_size >= min_buffer_size)
> return;
>
> - u->sbc_info.buffer_size = 2 * min_buffer_size;
> - pa_xfree(u->sbc_info.buffer);
> - u->sbc_info.buffer = pa_xmalloc(u->sbc_info.buffer_size);
> + u->buffer_size = 2 * min_buffer_size;
> + pa_xfree(u->buffer);
> + u->buffer = pa_xmalloc(u->buffer_size);
> +}
> +
> +/* Run from IO thread */
> +static int a2dp_write_buffer(struct userdata *u, size_t nbytes) {
> + int ret = 0;
> +
> + for (;;) {
> + ssize_t l;
> +
> + l = pa_write(u->stream_fd, u->buffer, nbytes, &u->stream_write_type);
> +
> + pa_assert(l != 0);
> +
> + if (l < 0) {
> +
> + if (errno == EINTR)
> + /* Retry right away if we got interrupted */
> + continue;
> +
> + else if (errno == EAGAIN) {
> + /* Hmm, apparently the socket was not writable, give up for now */
> + pa_log_debug("Got EAGAIN on write() after POLLOUT, probably there is a temporary connection loss.");
> + break;
> + }
> +
> + pa_log_error("Failed to write data to socket: %s", pa_cstrerror(errno));
> + ret = -1;
> + break;
> + }
> +
> + pa_assert((size_t) l <= nbytes);
> +
> + if ((size_t) l != nbytes) {
> + pa_log_warn("Wrote memory block to socket only partially! %llu written, wanted to write %llu.",
> + (unsigned long long) l,
> + (unsigned long long) nbytes);
> + ret = -1;
> + break;
> + }
> +
> + u->write_index += (uint64_t) u->write_memchunk.length;
> + pa_memblock_unref(u->write_memchunk.memblock);
> + pa_memchunk_reset(&u->write_memchunk);
> +
> + ret = 1;
> +
> + break;
> + }
> +
> + return ret;
> }
>
> /* Run from IO thread */
> -static int a2dp_process_render(struct userdata *u) {
> +static int a2dp_sbc_process_render(struct userdata *u) {
> struct sbc_info *sbc_info;
> struct rtp_header *header;
> struct rtp_payload *payload;
> - size_t nbytes;
> void *d;
> const void *p;
> size_t to_write, to_encode;
> unsigned frame_count;
> - int ret = 0;
>
> pa_assert(u);
> - pa_assert(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK);
> + pa_assert(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK);
> pa_assert(u->sink);
>
> /* First, render some data */
> @@ -451,8 +507,8 @@ static int a2dp_process_render(struct userdata *u) {
> a2dp_prepare_buffer(u);
>
> sbc_info = &u->sbc_info;
> - header = sbc_info->buffer;
> - payload = (struct rtp_payload*) ((uint8_t*) sbc_info->buffer + sizeof(*header));
> + header = u->buffer;
> + payload = (struct rtp_payload*) ((uint8_t*) u->buffer + sizeof(*header));
>
> frame_count = 0;
>
> @@ -461,8 +517,8 @@ static int a2dp_process_render(struct userdata *u) {
> p = (const uint8_t *) pa_memblock_acquire_chunk(&u->write_memchunk);
> to_encode = u->write_memchunk.length;
>
> - d = (uint8_t*) sbc_info->buffer + sizeof(*header) + sizeof(*payload);
> - to_write = sbc_info->buffer_size - sizeof(*header) - sizeof(*payload);
> + d = (uint8_t*) u->buffer + sizeof(*header) + sizeof(*payload);
> + to_write = u->buffer_size - sizeof(*header) - sizeof(*payload);
>
> while (PA_LIKELY(to_encode > 0 && to_write > 0)) {
> ssize_t written;
> @@ -503,7 +559,7 @@ static int a2dp_process_render(struct userdata *u) {
> } PA_ONCE_END;
>
> /* write it to the fifo */
> - memset(sbc_info->buffer, 0, sizeof(*header) + sizeof(*payload));
> + memset(u->buffer, 0, sizeof(*header) + sizeof(*payload));
> header->v = 2;
> header->pt = 1;
> header->sequence_number = htons(sbc_info->seq_num++);
> @@ -511,53 +567,65 @@ static int a2dp_process_render(struct userdata *u) {
> header->ssrc = htonl(1);
> payload->frame_count = frame_count;
>
> - nbytes = (uint8_t*) d - (uint8_t*) sbc_info->buffer;
> -
> - for (;;) {
> - ssize_t l;
> + return a2dp_write_buffer(u, (uint8_t*) d - (uint8_t*) u->buffer);
> +}
>
> - l = pa_write(u->stream_fd, sbc_info->buffer, nbytes, &u->stream_write_type);
> +#ifdef HAVE_OPENAPTX
> +/* Run from IO thread */
> +static int a2dp_aptx_process_render(struct userdata *u) {
> + void *d;
> + const void *p;
> + size_t to_write, to_encode;
>
> - pa_assert(l != 0);
> + pa_assert(u);
> + pa_assert(u->profile == PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK);
> + pa_assert(u->sink);
>
> - if (l < 0) {
> + /* First, render some data */
> + if (!u->write_memchunk.memblock)
> + pa_sink_render_full(u->sink, u->write_block_size, &u->write_memchunk);
>
> - if (errno == EINTR)
> - /* Retry right away if we got interrupted */
> - continue;
> + pa_assert(u->write_memchunk.length == u->write_block_size);
>
> - else if (errno == EAGAIN) {
> - /* Hmm, apparently the socket was not writable, give up for now */
> - pa_log_debug("Got EAGAIN on write() after POLLOUT, probably there is a temporary connection loss.");
> - break;
> - }
> + a2dp_prepare_buffer(u);
>
> - pa_log_error("Failed to write data to socket: %s", pa_cstrerror(errno));
> - ret = -1;
> - break;
> - }
> + p = (const uint8_t *) pa_memblock_acquire_chunk(&u->write_memchunk);
> + to_encode = u->write_memchunk.length;
> + d = u->buffer;
> + to_write = u->buffer_size;
>
> - pa_assert((size_t) l <= nbytes);
> + while (PA_LIKELY(to_encode > 0 && to_write > 0)) {
> + size_t written;
> + size_t encoded;
> + encoded = aptx_encode(u->aptx_c, p, to_encode, d, to_write, &written);
>
> - if ((size_t) l != nbytes) {
> - pa_log_warn("Wrote memory block to socket only partially! %llu written, wanted to write %llu.",
> - (unsigned long long) l,
> - (unsigned long long) nbytes);
> - ret = -1;
> - break;
> + if (PA_UNLIKELY(encoded == 0)) {
> + pa_log_error("aptX encoding error");
> + pa_memblock_release(u->write_memchunk.memblock);
> + return -1;
> }
>
> - u->write_index += (uint64_t) u->write_memchunk.length;
> - pa_memblock_unref(u->write_memchunk.memblock);
> - pa_memchunk_reset(&u->write_memchunk);
> + pa_assert_fp((size_t) encoded <= to_encode);
> + pa_assert_fp((size_t) written <= to_write);
>
> - ret = 1;
> + p = (const uint8_t*) p + encoded;
> + to_encode -= encoded;
>
> - break;
> + d = (uint8_t*) d + written;
> + to_write -= written;
> }
>
> - return ret;
> + PA_ONCE_BEGIN {
> + pa_log_error("Using aptX encoder implementation: libopenaptx");
> + } PA_ONCE_END;
> +
> + pa_memblock_release(u->write_memchunk.memblock);
> +
> + pa_assert(to_encode == 0);
> +
> + return a2dp_write_buffer(u, (uint8_t*) d - (uint8_t*) u->buffer);
> }
> +#endif
>
> /* Run from IO thread */
> static int a2dp_process_push(struct userdata *u) {
> @@ -587,10 +655,10 @@ static int a2dp_process_push(struct userdata *u) {
> a2dp_prepare_buffer(u);
>
> sbc_info = &u->sbc_info;
> - header = sbc_info->buffer;
> - payload = (struct rtp_payload*) ((uint8_t*) sbc_info->buffer + sizeof(*header));
> + header = u->buffer;
> + payload = (struct rtp_payload*) ((uint8_t*) u->buffer + sizeof(*header));
>
> - l = pa_read(u->stream_fd, sbc_info->buffer, sbc_info->buffer_size, &u->stream_write_type);
> + l = pa_read(u->stream_fd, u->buffer, u->buffer_size, &u->stream_write_type);
>
> if (l <= 0) {
>
> @@ -607,7 +675,7 @@ static int a2dp_process_push(struct userdata *u) {
> break;
> }
>
> - pa_assert((size_t) l <= sbc_info->buffer_size);
> + pa_assert((size_t) l <= u->buffer_size);
>
> /* TODO: get timestamp from rtp */
> if (!found_tstamp) {
> @@ -615,7 +683,7 @@ static int a2dp_process_push(struct userdata *u) {
> tstamp = pa_rtclock_now();
> }
>
> - p = (uint8_t*) sbc_info->buffer + sizeof(*header) + sizeof(*payload);
> + p = (uint8_t*) u->buffer + sizeof(*header) + sizeof(*payload);
> to_decode = l - sizeof(*header) - sizeof(*payload);
>
> d = pa_memblock_acquire(memchunk.memblock);
> @@ -706,7 +774,7 @@ static void update_buffer_size(struct userdata *u) {
> }
>
> /* Run from I/O thread */
> -static void a2dp_set_bitpool(struct userdata *u, uint8_t bitpool) {
> +static void a2dp_set_sbc_bitpool(struct userdata *u, uint8_t bitpool) {
> struct sbc_info *sbc_info;
>
> pa_assert(u);
> @@ -751,7 +819,7 @@ static void a2dp_set_bitpool(struct userdata *u, uint8_t bitpool) {
> }
>
> /* Run from I/O thread */
> -static void a2dp_reduce_bitpool(struct userdata *u) {
> +static void a2dp_reduce_sbc_bitpool(struct userdata *u) {
> struct sbc_info *sbc_info;
> uint8_t bitpool;
>
> @@ -768,7 +836,7 @@ static void a2dp_reduce_bitpool(struct userdata *u) {
> if (bitpool < BITPOOL_DEC_LIMIT)
> bitpool = BITPOOL_DEC_LIMIT;
>
> - a2dp_set_bitpool(u, bitpool);
> + a2dp_set_sbc_bitpool(u, bitpool);
> }
>
> static void teardown_stream(struct userdata *u) {
> @@ -859,7 +927,7 @@ static void transport_config_mtu(struct userdata *u) {
> pa_log_debug("Got invalid write MTU: %lu, rounding down", u->write_block_size);
> u->write_block_size = pa_frame_align(u->write_block_size, &u->sink->sample_spec);
> }
> - } else {
> + } else if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK || u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE) {
> u->read_block_size =
> (u->read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
> / u->sbc_info.frame_length * u->sbc_info.codesize;
> @@ -867,12 +935,18 @@ static void transport_config_mtu(struct userdata *u) {
> u->write_block_size =
> (u->write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
> / u->sbc_info.frame_length * u->sbc_info.codesize;
> + } else if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK) {
> + u->write_block_size = (u->write_link_mtu/(8*4)) * 8*4*6;
> + u->read_block_size = (u->read_link_mtu/(8*4)) * 8*4*6;
> + } else {
> + pa_assert_not_reached();
> }
>
> 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_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));
> }
> @@ -906,8 +980,10 @@ static void setup_stream(struct userdata *u) {
>
> pa_log_debug("Stream properly set up, we're ready to roll!");
>
> - if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) {
> - a2dp_set_bitpool(u, u->sbc_info.max_bitpool);
> + if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK) {
> + a2dp_set_sbc_bitpool(u, u->sbc_info.max_bitpool);
> + update_buffer_size(u);
> + } else if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK) {
> update_buffer_size(u);
> }
>
> @@ -1090,8 +1166,10 @@ static int add_source(struct userdata *u) {
> else
> pa_assert_not_reached();
> break;
> - case PA_BLUETOOTH_PROFILE_A2DP_SINK:
> + case PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK:
> + case PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK:
> case PA_BLUETOOTH_PROFILE_OFF:
> + default:
> pa_assert_not_reached();
> break;
> }
> @@ -1263,10 +1341,12 @@ static int add_sink(struct userdata *u) {
> else
> pa_assert_not_reached();
> break;
> - case PA_BLUETOOTH_PROFILE_A2DP_SINK:
> + case PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK:
> + case PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK:
> /* Profile switch should have failed */
> case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
> case PA_BLUETOOTH_PROFILE_OFF:
> + default:
> pa_assert_not_reached();
> break;
> }
> @@ -1295,7 +1375,7 @@ static void transport_config(struct userdata *u) {
> u->sample_spec.format = PA_SAMPLE_S16LE;
> u->sample_spec.channels = 1;
> u->sample_spec.rate = 8000;
> - } else {
> + } else if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK || u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE) {
> sbc_info_t *sbc_info = &u->sbc_info;
> a2dp_sbc_t *config;
>
> @@ -1395,12 +1475,55 @@ static void transport_config(struct userdata *u) {
> sbc_info->max_bitpool = config->max_bitpool;
>
> /* Set minimum bitpool for source to get the maximum possible block_size */
> - sbc_info->sbc.bitpool = u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK ? sbc_info->max_bitpool : sbc_info->min_bitpool;
> + sbc_info->sbc.bitpool = u->profile == PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK ? sbc_info->max_bitpool : sbc_info->min_bitpool;
> sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc);
> sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
>
> pa_log_info("SBC parameters: allocation=%u, subbands=%u, blocks=%u, bitpool=%u",
> sbc_info->sbc.allocation, sbc_info->sbc.subbands ? 8 : 4, sbc_info->sbc.blocks, sbc_info->sbc.bitpool);
> + } else if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK) {
> +#ifdef HAVE_OPENAPTX
> + a2dp_aptx_t *config;
> +
> + pa_assert(u->transport);
> +
> + u->sample_spec.format = PA_SAMPLE_S24LE;
> + config = (a2dp_aptx_t *) u->transport->config;
> +
> + if (u->aptx_c)
> + aptx_reset(u->aptx_c);
> + else
> + u->aptx_c = aptx_init(0);
> +
> + switch (config->frequency) {
> + case APTX_SAMPLING_FREQ_16000:
> + u->sample_spec.rate = 16000U;
> + break;
> + case APTX_SAMPLING_FREQ_32000:
> + u->sample_spec.rate = 32000U;
> + break;
> + case APTX_SAMPLING_FREQ_44100:
> + u->sample_spec.rate = 44100U;
> + break;
> + case APTX_SAMPLING_FREQ_48000:
> + u->sample_spec.rate = 48000U;
> + break;
> + default:
> + pa_assert_not_reached();
> + }
> +
> + switch (config->channel_mode) {
> + case APTX_CHANNEL_MODE_STEREO:
> + u->sample_spec.channels = 2;
> + break;
> + default:
> + pa_assert_not_reached();
> + }
> +#else
> + pa_assert_not_reached();
> +#endif
> + } else {
> + pa_assert_not_reached();
> }
> }
>
> @@ -1439,7 +1562,8 @@ static int setup_transport(struct userdata *u) {
> /* Run from main thread */
> static pa_direction_t get_profile_direction(pa_bluetooth_profile_t p) {
> static const pa_direction_t profile_direction[] = {
> - [PA_BLUETOOTH_PROFILE_A2DP_SINK] = PA_DIRECTION_OUTPUT,
> + [PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK] = PA_DIRECTION_OUTPUT,
> + [PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK] = PA_DIRECTION_OUTPUT,
> [PA_BLUETOOTH_PROFILE_A2DP_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,
> @@ -1477,12 +1601,21 @@ static int write_block(struct userdata *u) {
> if (u->write_index <= 0)
> u->started_at = pa_rtclock_now();
>
> - if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) {
> - if ((n_written = a2dp_process_render(u)) < 0)
> + if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK) {
> + if ((n_written = a2dp_sbc_process_render(u)) < 0)
> return -1;
> - } else {
> + } else if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK) {
> +#ifdef HAVE_OPENAPTX
> + if ((n_written = a2dp_aptx_process_render(u)) < 0)
> + return -1;
> +#else
> + return -1;
> +#endif
> + } else if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
> if ((n_written = sco_process_render(u)) < 0)
> return -1;
> + } else {
> + pa_assert_not_reached();
> }
>
> return n_written;
> @@ -1651,8 +1784,8 @@ static void thread_func(void *userdata) {
> skip_bytes -= bytes_to_render;
> }
>
> - if (u->write_index > 0 && u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK)
> - a2dp_reduce_bitpool(u);
> + if (u->write_index > 0 && u->profile == PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK)
> + a2dp_reduce_sbc_bitpool(u);
> }
>
> blocks_to_write = 1;
> @@ -1980,8 +2113,8 @@ static pa_card_profile *create_card_profile(struct userdata *u, pa_bluetooth_pro
> name = pa_bluetooth_profile_to_string(profile);
>
> switch (profile) {
> - case PA_BLUETOOTH_PROFILE_A2DP_SINK:
> - cp = pa_card_profile_new(name, _("High Fidelity Playback (A2DP Sink)"), sizeof(pa_bluetooth_profile_t));
> + case PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK:
> + cp = pa_card_profile_new(name, _("High Fidelity SBC Playback (A2DP Sink)"), sizeof(pa_bluetooth_profile_t));
> cp->priority = 40;
> cp->n_sinks = 1;
> cp->n_sources = 0;
> @@ -1992,6 +2125,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_SOURCE:
> cp = pa_card_profile_new(name, _("High Fidelity Capture (A2DP Source)"), sizeof(pa_bluetooth_profile_t));
> cp->priority = 20;
> @@ -2087,8 +2232,9 @@ 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_SINK;
> + *_r = PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK;
> else if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE))
> *_r = PA_BLUETOOTH_PROFILE_A2DP_SOURCE;
> else if (pa_bluetooth_uuid_is_hsp_hs(uuid) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_HF))
> @@ -2154,6 +2300,24 @@ 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 */
> + if (uuid_to_profile(uuid, &profile) < 0)
> + continue;
> +
> + /* Handle APTX */
> + if (profile != PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK)
> + continue;
> + profile = PA_BLUETOOTH_PROFILE_A2DP_APTX_SINK;
> +
> + 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);
> + }
> + }
> +
> pa_assert(!pa_hashmap_isempty(data.profiles));
>
> cp = pa_card_profile_new("off", _("Off"), sizeof(pa_bluetooth_profile_t));
> @@ -2492,12 +2656,17 @@ void pa__done(pa_module *m) {
> if (u->transport_microphone_gain_changed_slot)
> pa_hook_slot_free(u->transport_microphone_gain_changed_slot);
>
> - if (u->sbc_info.buffer)
> - pa_xfree(u->sbc_info.buffer);
> + if (u->buffer)
> + pa_xfree(u->buffer);
>
> if (u->sbc_info.sbc_initialized)
> sbc_finish(&u->sbc_info.sbc);
>
> +#ifdef HAVE_OPENAPTX
> + if (u->aptx_c)
> + aptx_finish(u->aptx_c);
> +#endif
> +
> if (u->msg)
> pa_xfree(u->msg);
>
> --
> 2.11.0
>
> _______________________________________________
> pulseaudio-discuss mailing list
> pulseaudio-discuss at lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
--
Luiz Augusto von Dentz
More information about the pulseaudio-discuss
mailing list