Discussion:
[pulseaudio-discuss] [PATCH] WIP: Bluetooth A2DP aptX codec support
Pali Rohár
2018-07-06 09:16:09 UTC
Permalink
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(-)

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 <***@holtmann.org>
+ * Copyright (C) 2018 Pali Rohár <***@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 <***@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 <***@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
Pali Rohár
2018-07-07 11:08:49 UTC
Permalink
Post by Pali Rohár
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 :-(
Any idea how to tell bluez which endpoint should use? Or how to change
one active bluez endpoint to another for switching bluez codec?
--
Pali Rohár
***@gmail.com
Luiz Augusto von Dentz
2018-07-08 19:55:28 UTC
Permalink
Hi Pali,
Post by Pali Rohár
Post by Pali Rohár
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 :-(
Any idea how to tell bluez which endpoint should use? Or how to change
one active bluez endpoint to another for switching bluez codec?
It is the order PA register the endpoints, if you want apt-x take
priority register it first.
--
Luiz Augusto von Dentz
Pali Rohár
2018-07-08 20:02:51 UTC
Permalink
Post by Luiz Augusto von Dentz
Hi Pali,
Post by Pali Rohár
Post by Pali Rohár
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 :-(
Any idea how to tell bluez which endpoint should use? Or how to change
one active bluez endpoint to another for switching bluez codec?
It is the order PA register the endpoints, if you want apt-x take
priority register it first.
Do you mean to call functions in this order?

register_endpoint(y, path, A2DP_SOURCE_APTX_ENDPOINT, ...);
register_endpoint(y, path, A2DP_SOURCE_SBC_ENDPOINT, ...);

No, it does not work. Still codec is somehow selected randomly.
Sometimes aptX, sometimes SBC. I tested it with one same device.
--
Pali Rohár
***@gmail.com
Luiz Augusto von Dentz
2018-07-08 20:11:56 UTC
Permalink
Hi Pali,
Post by Pali Rohár
Post by Luiz Augusto von Dentz
Hi Pali,
Post by Pali Rohár
Post by Pali Rohár
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 :-(
Any idea how to tell bluez which endpoint should use? Or how to change
one active bluez endpoint to another for switching bluez codec?
It is the order PA register the endpoints, if you want apt-x take
priority register it first.
Do you mean to call functions in this order?
register_endpoint(y, path, A2DP_SOURCE_APTX_ENDPOINT, ...);
register_endpoint(y, path, A2DP_SOURCE_SBC_ENDPOINT, ...);
No, it does not work. Still codec is somehow selected randomly.
Sometimes aptX, sometimes SBC. I tested it with one same device.
https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/profiles/audio/a2dp.c#n1866

It is definitely not random in our end, note that this only works if
you initiate the connection, if the remote initiates it then it is up
to them to select which may explain why it is not always the same
priority.
Post by Pali Rohár
--
Pali Rohár
_______________________________________________
pulseaudio-discuss mailing list
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
--
Luiz Augusto von Dentz
Pali Rohár
2018-07-08 20:34:16 UTC
Permalink
This post might be inappropriate. Click to display it.
Luiz Augusto von Dentz
2018-07-08 20:06:12 UTC
Permalink
Hi Pali,
Post by Pali Rohár
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.
---
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.
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.
Post by Pali Rohár
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
*
*
* 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
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) {
return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_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
return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_SOURCE);
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) {
+ {
+ 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;
+ }
+ {
+ 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;
+ }
+ {
+ 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) {
return "a2dp_sink";
+ return "a2dp_aptx_sink";
return "a2dp_source";
@@ -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();
}
}
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) {
- pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT,
+ pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_SBC_ENDPOINT,
+ &vtable_endpoint, y));
+ break;
+ pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_APTX_ENDPOINT,
&vtable_endpoint, y));
break;
@@ -1731,8 +1886,11 @@ static void endpoint_done(pa_bluetooth_discovery *y, pa_bluetooth_profile_t prof
pa_assert(y);
switch(profile) {
- dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT);
+ dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_SBC_ENDPOINT);
+ break;
+ dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_APTX_ENDPOINT);
break;
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.
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;
pa_assert_not_reached();
break;
}
@@ -1263,10 +1341,12 @@ static int add_sink(struct userdata *u) {
else
pa_assert_not_reached();
break;
/* Profile switch should have failed */
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) {
+ u->sample_spec.rate = 16000U;
+ break;
+ u->sample_spec.rate = 32000U;
+ break;
+ u->sample_spec.rate = 44100U;
+ break;
+ u->sample_spec.rate = 48000U;
+ break;
+ pa_assert_not_reached();
+ }
+
+ switch (config->channel_mode) {
+ u->sample_spec.channels = 2;
+ break;
+ 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) {
- cp = pa_card_profile_new(name, _("High Fidelity Playback (A2DP Sink)"), sizeof(pa_bluetooth_profile_t));
+ 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;
+ 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;
+
cp = pa_card_profile_new(name, _("High Fidelity Capture (A2DP Source)"), sizeof(pa_bluetooth_profile_t));
cp->priority = 20;
}
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
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
--
Luiz Augusto von Dentz
Pali Rohár
2018-07-28 15:34:51 UTC
Permalink
This patch series moves A2DP codec code into new modules and add
support for Bluetooth A2DP aptX codec.

Pali Rohár (2):
Modular API for Bluetooth A2DP codec
Bluetooth A2DP aptX codec support

configure.ac | 19 +
src/Makefile.am | 14 +-
src/modules/bluetooth/a2dp-codecs.h | 123 +++++-
src/modules/bluetooth/bluez5-util.c | 377 +++++++----------
src/modules/bluetooth/bluez5-util.h | 12 +-
src/modules/bluetooth/module-bluez5-device.c | 542 ++++++++-----------------
src/modules/bluetooth/pa-a2dp-codec-aptx.c | 297 ++++++++++++++
src/modules/bluetooth/pa-a2dp-codec-sbc.c | 579 +++++++++++++++++++++++++++
src/modules/bluetooth/pa-a2dp-codec.h | 41 ++
9 files changed, 1393 insertions(+), 611 deletions(-)
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-aptx.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-sbc.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec.h
--
2.11.0
Pali Rohár
2018-07-28 15:34:52 UTC
Permalink
Move current SBC codec implementation into separate source file and use
this new API for providing all needed functionality for Bluetooth A2DP.

Both bluez5-util and module-bluez5-device are changed to use this new
modular codec API.
---
src/Makefile.am | 9 +-
src/modules/bluetooth/a2dp-codecs.h | 5 +-
src/modules/bluetooth/bluez5-util.c | 331 +++++----------
src/modules/bluetooth/bluez5-util.h | 10 +-
src/modules/bluetooth/module-bluez5-device.c | 487 ++++++----------------
src/modules/bluetooth/pa-a2dp-codec-sbc.c | 579 +++++++++++++++++++++++++++
src/modules/bluetooth/pa-a2dp-codec.h | 40 ++
7 files changed, 851 insertions(+), 610 deletions(-)
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-sbc.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec.h

diff --git a/src/Makefile.am b/src/Makefile.am
index f62e7d5c4..411b9e5e5 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2117,6 +2117,7 @@ module_bluetooth_discover_la_CFLAGS = $(AM_CFLAGS) -DPA_MODULE_NAME=module_bluet
libbluez5_util_la_SOURCES = \
modules/bluetooth/bluez5-util.c \
modules/bluetooth/bluez5-util.h \
+ modules/bluetooth/pa-a2dp-codec.h \
modules/bluetooth/a2dp-codecs.h
if HAVE_BLUEZ_5_OFONO_HEADSET
libbluez5_util_la_SOURCES += \
@@ -2131,6 +2132,10 @@ libbluez5_util_la_LDFLAGS = -avoid-version
libbluez5_util_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS)
libbluez5_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)

+libbluez5_util_la_SOURCES += modules/bluetooth/pa-a2dp-codec-sbc.c
+libbluez5_util_la_LIBADD += $(SBC_LIBS)
+libbluez5_util_la_CFLAGS += $(SBC_CFLAGS)
+
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
@@ -2138,8 +2143,8 @@ module_bluez5_discover_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) -DPA_MODULE_NAME=

module_bluez5_device_la_SOURCES = modules/bluetooth/module-bluez5-device.c
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
+module_bluez5_device_la_LIBADD = $(MODULE_LIBADD) libbluez5-util.la
+module_bluez5_device_la_CFLAGS = $(AM_CFLAGS) -DPA_MODULE_NAME=module_bluez5_device

# Apple Airtunes/RAOP
module_raop_sink_la_SOURCES = modules/raop/module-raop-sink.c
diff --git a/src/modules/bluetooth/a2dp-codecs.h b/src/modules/bluetooth/a2dp-codecs.h
index 8afcfcb24..004975586 100644
--- a/src/modules/bluetooth/a2dp-codecs.h
+++ b/src/modules/bluetooth/a2dp-codecs.h
@@ -47,6 +47,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 +66,6 @@
#define MPEG_SAMPLING_FREQ_44100 (1 << 1)
#define MPEG_SAMPLING_FREQ_48000 1

-#define MAX_BITPOOL 64
-#define MIN_BITPOOL 2

#if __BYTE_ORDER == __LITTLE_ENDIAN

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 2d8337317..9c4e3367b 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 <***@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
@@ -33,7 +34,7 @@
#include <pulsecore/refcnt.h>
#include <pulsecore/shared.h>

-#include "a2dp-codecs.h"
+#include "pa-a2dp-codec.h"

#include "bluez5-util.h"

@@ -48,8 +49,8 @@

#define BLUEZ_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported"

-#define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource"
-#define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink"
+#define A2DP_SOURCE_SBC_ENDPOINT "/MediaEndpoint/A2DPSourceSBC"
+#define A2DP_SINK_SBC_ENDPOINT "/MediaEndpoint/A2DPSinkSBC"

#define ENDPOINT_INTROSPECT_XML \
DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
@@ -170,9 +171,9 @@ 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_SOURCE:
+ case PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE:
return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_SOURCE);
case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_HS)
@@ -888,10 +889,21 @@ finish:
static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const char *endpoint, const char *uuid) {
DBusMessage *m;
DBusMessageIter i, d;
- uint8_t codec = 0;
+ uint8_t capabilities[1024];
+ size_t capabilities_size;
+ uint8_t codec_id;
+ const pa_a2dp_codec_t *codec;
+
+ codec = pa_a2dp_endpoint_to_a2dp_codec(endpoint);
+ if (!codec)
+ return;

pa_log_debug("Registering %s on adapter %s", endpoint, path);

+ codec_id = codec->codec_id;
+ capabilities_size = codec->fill_capabilities(capabilities, sizeof(capabilities));
+ pa_assert(capabilities_size != 0);
+
pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, path, BLUEZ_MEDIA_INTERFACE, "RegisterEndpoint"));

dbus_message_iter_init_append(m, &i);
@@ -899,23 +911,8 @@ static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const
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));
- }
+ pa_dbus_append_basic_variant_dict_entry(&d, "Codec", DBUS_TYPE_BYTE, &codec_id);
+ pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capabilities, capabilities_size);

dbus_message_iter_close_container(&i, &d);

@@ -964,8 +961,8 @@ 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);
+ register_endpoint(y, path, A2DP_SOURCE_SBC_ENDPOINT, PA_BLUETOOTH_UUID_A2DP_SOURCE);
+ register_endpoint(y, path, A2DP_SINK_SBC_ENDPOINT, PA_BLUETOOTH_UUID_A2DP_SINK);

} else if (pa_streq(interface, BLUEZ_DEVICE_INTERFACE)) {

@@ -1257,53 +1254,11 @@ fail:
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

-static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) {
- /* These bitpool values were chosen based on the A2DP spec recommendation */
- switch (freq) {
- case SBC_SAMPLING_FREQ_16000:
- case SBC_SAMPLING_FREQ_32000:
- return 53;
-
- case SBC_SAMPLING_FREQ_44100:
-
- switch (mode) {
- case SBC_CHANNEL_MODE_MONO:
- case SBC_CHANNEL_MODE_DUAL_CHANNEL:
- return 31;
-
- case SBC_CHANNEL_MODE_STEREO:
- case SBC_CHANNEL_MODE_JOINT_STEREO:
- return 53;
- }
-
- pa_log_warn("Invalid channel mode %u", mode);
- return 53;
-
- case SBC_SAMPLING_FREQ_48000:
-
- switch (mode) {
- case SBC_CHANNEL_MODE_MONO:
- case SBC_CHANNEL_MODE_DUAL_CHANNEL:
- return 29;
-
- case SBC_CHANNEL_MODE_STEREO:
- case SBC_CHANNEL_MODE_JOINT_STEREO:
- return 51;
- }
-
- pa_log_warn("Invalid channel mode %u", mode);
- return 51;
- }
-
- pa_log_warn("Invalid sampling freq %u", freq);
- return 53;
-}
-
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_SOURCE:
+ case PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE:
return "a2dp_source";
case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
return "headset_head_unit";
@@ -1316,6 +1271,38 @@ const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile) {
return NULL;
}

+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_SBC_SOURCE:
+ return A2DP_SINK_SBC_ENDPOINT;
+ default:
+ return NULL;
+ }
+
+ return NULL;
+}
+
+const pa_a2dp_codec_t *pa_bluetooth_profile_to_a2dp_codec(pa_bluetooth_profile_t profile) {
+ switch (profile) {
+ case PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK:
+ case PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE:
+ return &pa_a2dp_codec_sbc;
+ default:
+ return NULL;
+ }
+
+ return NULL;
+}
+
+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;
+ else
+ return NULL;
+}
+
static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
pa_bluetooth_discovery *y = userdata;
pa_bluetooth_device *d;
@@ -1345,6 +1332,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,13 +1356,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;
- } else if (pa_streq(endpoint_path, A2DP_SINK_ENDPOINT)) {
+ p = PA_BLUETOOTH_PROFILE_A2DP_SBC_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_SOURCE;
+ p = PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE;
}

if (p == PA_BLUETOOTH_PROFILE_OFF) {
@@ -1389,7 +1377,7 @@ 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;
+ const pa_a2dp_codec_t *codec;

if (var != DBUS_TYPE_ARRAY) {
pa_log_error("Property %s of wrong type %c", key, (char)var);
@@ -1404,40 +1392,12 @@ 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 (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->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->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;
- }
+ codec = pa_a2dp_endpoint_to_a2dp_codec(endpoint_path);
+ pa_assert(codec);

- 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");
+ if (!codec->validate_configuration(config, size))
goto fail;
- }
}

dbus_message_iter_next(&props);
@@ -1484,21 +1444,17 @@ 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;
- int i, size;
+ const char *endpoint_path;
+ uint8_t *cap;
+ int size;
+ const pa_a2dp_codec_t *codec;
+ uint8_t config[1024];
+ uint8_t *config_ptr = config;
+ size_t config_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);

@@ -1508,101 +1464,14 @@ static DBusMessage *endpoint_select_configuration(DBusConnection *conn, DBusMess
goto fail;
}

- if (size != sizeof(config)) {
- pa_log_error("Capabilities array has invalid size");
- goto fail;
- }
-
- 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;
- }
- }
-
- 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;
- }
- }
-
- 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 block lengths");
- goto fail;
- }
-
- 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;
- }
+ codec = pa_a2dp_endpoint_to_a2dp_codec(endpoint_path);
+ pa_assert(codec);

- 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(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;
+ config_size = codec->select_configuration(&y->core->default_sample_spec, cap, size, config, sizeof(config));
+ pa_assert(config_size != 0);

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(dbus_message_append_args(r, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &config_ptr, config_size, DBUS_TYPE_INVALID));

return r;

@@ -1677,7 +1546,7 @@ 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_SBC_ENDPOINT))
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;

if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
@@ -1709,38 +1578,22 @@ static void endpoint_init(pa_bluetooth_discovery *y, pa_bluetooth_profile_t prof
static const DBusObjectPathVTable vtable_endpoint = {
.message_function = endpoint_handler,
};
+ const char *endpoint = pa_bluetooth_profile_to_a2dp_endpoint(profile);

pa_assert(y);
+ pa_assert(endpoint);

- 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,
- &vtable_endpoint, y));
- break;
- case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
- pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT,
- &vtable_endpoint, y));
- break;
- default:
- pa_assert_not_reached();
- break;
- }
+ pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), endpoint,
+ &vtable_endpoint, y));
}

static void endpoint_done(pa_bluetooth_discovery *y, pa_bluetooth_profile_t profile) {
+ const char *endpoint = pa_bluetooth_profile_to_a2dp_endpoint(profile);
+
pa_assert(y);
+ pa_assert(endpoint);

- switch(profile) {
- case PA_BLUETOOTH_PROFILE_A2DP_SINK:
- dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT);
- break;
- case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
- dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT);
- break;
- default:
- pa_assert_not_reached();
- break;
- }
+ dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), endpoint);
}

pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backend) {
@@ -1799,8 +1652,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_SOURCE);
+ endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK);
+ endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE);

get_managed_objects(y);

@@ -1868,8 +1721,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_SOURCE);
+ endpoint_done(y, PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK);
+ endpoint_done(y, PA_BLUETOOTH_PROFILE_A2DP_SBC_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..365b9ef6f 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -5,6 +5,7 @@
This file is part of PulseAudio.

Copyright 2008-2013 João Paulo Rechi Vita
+ Copyrigth 2018 Pali Rohár <***@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
@@ -22,6 +23,8 @@

#include <pulsecore/core.h>

+#include "pa-a2dp-codec.h"
+
#define PA_BLUETOOTH_UUID_A2DP_SOURCE "0000110a-0000-1000-8000-00805f9b34fb"
#define PA_BLUETOOTH_UUID_A2DP_SINK "0000110b-0000-1000-8000-00805f9b34fb"

@@ -51,8 +54,8 @@ typedef enum pa_bluetooth_hook {
} pa_bluetooth_hook_t;

typedef enum profile {
- PA_BLUETOOTH_PROFILE_A2DP_SINK,
- PA_BLUETOOTH_PROFILE_A2DP_SOURCE,
+ PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK,
+ PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE,
PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT,
PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY,
PA_BLUETOOTH_PROFILE_OFF
@@ -163,6 +166,9 @@ pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_address(pa_bluetooth_d
pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook);

const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile);
+const char *pa_bluetooth_profile_to_a2dp_endpoint(pa_bluetooth_profile_t profile);
+const pa_a2dp_codec_t *pa_bluetooth_profile_to_a2dp_codec(pa_bluetooth_profile_t profile);
+const pa_a2dp_codec_t *pa_a2dp_endpoint_to_a2dp_codec(const char *endpoint);

static inline bool pa_bluetooth_uuid_is_hsp_hs(const char *uuid) {
return pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_HS) || pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_HS_ALT);
diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 9dbdca316..e626e80e9 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 <***@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
@@ -25,7 +26,6 @@
#include <errno.h>

#include <arpa/inet.h>
-#include <sbc/sbc.h>

#include <pulse/rtclock.h>
#include <pulse/timeval.h>
@@ -47,8 +47,8 @@
#include <pulsecore/time-smoother.h>

#include "a2dp-codecs.h"
+#include "pa-a2dp-codec.h"
#include "bluez5-util.h"
-#include "rtp.h"

PA_MODULE_AUTHOR("João Paulo Rechi Vita");
PA_MODULE_DESCRIPTION("BlueZ 5 Bluetooth audio sink and source");
@@ -62,8 +62,6 @@ PA_MODULE_USAGE("path=<device object path>"
#define FIXED_LATENCY_RECORD_A2DP (25 * PA_USEC_PER_MSEC)
#define FIXED_LATENCY_RECORD_SCO (25 * PA_USEC_PER_MSEC)

-#define BITPOOL_DEC_LIMIT 32
-#define BITPOOL_DEC_STEP 5
#define HSP_MAX_GAIN 15

static const char* const valid_modargs[] = {
@@ -94,18 +92,6 @@ typedef struct bluetooth_msg {
PA_DEFINE_PRIVATE_CLASS(bluetooth_msg, pa_msgobject);
#define BLUETOOTH_MSG(o) (bluetooth_msg_cast(o))

-typedef struct sbc_info {
- sbc_t sbc; /* Codec data */
- bool sbc_initialized; /* Keep track if the encoder is initialized */
- size_t codesize, frame_length; /* SBC Codesize, frame_length. We simply cache those values here */
- 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 {
pa_module *module;
pa_core *core;
@@ -146,7 +132,11 @@ struct userdata {
pa_smoother *read_smoother;
pa_memchunk write_memchunk;
pa_sample_spec sample_spec;
- struct sbc_info sbc_info;
+ void *encoder_info;
+ void *decoder_info;
+
+ void* buffer; /* Codec transfer buffer */
+ size_t buffer_size; /* Size of the buffer */
};

typedef enum pa_bluetooth_form_factor {
@@ -418,105 +408,22 @@ 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_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;
+static int a2dp_write_buffer(struct userdata *u, size_t nbytes) {
int ret = 0;

- pa_assert(u);
- pa_assert(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK);
- pa_assert(u->sink);
-
- /* First, render some data */
- if (!u->write_memchunk.memblock)
- pa_sink_render_full(u->sink, u->write_block_size, &u->write_memchunk);
-
- pa_assert(u->write_memchunk.length == u->write_block_size);
-
- a2dp_prepare_buffer(u);
-
- sbc_info = &u->sbc_info;
- header = sbc_info->buffer;
- payload = (struct rtp_payload*) ((uint8_t*) sbc_info->buffer + sizeof(*header));
-
- frame_count = 0;
-
- /* Try to create a packet of the full MTU */
-
- 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);
-
- while (PA_LIKELY(to_encode > 0 && to_write > 0)) {
- ssize_t written;
- ssize_t encoded;
-
- encoded = sbc_encode(&sbc_info->sbc,
- p, to_encode,
- d, to_write,
- &written);
-
- if (PA_UNLIKELY(encoded <= 0)) {
- pa_log_error("SBC encoding error (%li)", (long) encoded);
- pa_memblock_release(u->write_memchunk.memblock);
- return -1;
- }
-
- pa_assert_fp((size_t) encoded <= to_encode);
- pa_assert_fp((size_t) encoded == sbc_info->codesize);
-
- pa_assert_fp((size_t) written <= to_write);
- pa_assert_fp((size_t) written == sbc_info->frame_length);
-
- p = (const uint8_t*) p + encoded;
- to_encode -= encoded;
-
- d = (uint8_t*) d + written;
- to_write -= written;
-
- frame_count++;
- }
-
- pa_memblock_release(u->write_memchunk.memblock);
-
- pa_assert(to_encode == 0);
-
- PA_ONCE_BEGIN {
- pa_log_debug("Using SBC encoder implementation: %s", pa_strnull(sbc_get_implementation_info(&sbc_info->sbc)));
- } PA_ONCE_END;
-
- /* write it to the fifo */
- memset(sbc_info->buffer, 0, sizeof(*header) + sizeof(*payload));
- header->v = 2;
- header->pt = 1;
- header->sequence_number = htons(sbc_info->seq_num++);
- header->timestamp = htonl(u->write_index / pa_frame_size(&u->sample_spec));
- header->ssrc = htonl(1);
- payload->frame_count = frame_count;
-
- nbytes = (uint8_t*) d - (uint8_t*) sbc_info->buffer;
-
for (;;) {
ssize_t l;

- l = pa_write(u->stream_fd, sbc_info->buffer, nbytes, &u->stream_write_type);
+ l = pa_write(u->stream_fd, u->buffer, nbytes, &u->stream_write_type);

pa_assert(l != 0);

@@ -560,37 +467,65 @@ static int a2dp_process_render(struct userdata *u) {
}

/* Run from IO thread */
+static int a2dp_process_render(struct userdata *u) {
+ const pa_a2dp_codec_t *codec;
+ const uint8_t *ptr;
+ size_t length;
+
+ pa_assert(u);
+ pa_assert(u->sink);
+
+ codec = pa_bluetooth_profile_to_a2dp_codec(u->profile);
+ pa_assert(codec);
+
+ /* First, render some data */
+ if (!u->write_memchunk.memblock)
+ pa_sink_render_full(u->sink, u->write_block_size, &u->write_memchunk);
+
+ pa_assert(u->write_memchunk.length == u->write_block_size);
+
+ a2dp_prepare_buffer(u);
+
+ /* Try to create a packet of the full MTU */
+ ptr = (const uint8_t *) pa_memblock_acquire_chunk(&u->write_memchunk);
+
+ length = codec->encode(&u->encoder_info, u->write_index / pa_frame_size(&u->sample_spec), ptr, u->write_memchunk.length, u->buffer, u->buffer_size);
+
+ pa_memblock_release(u->write_memchunk.memblock);
+
+ if (length == 0)
+ return -1;
+
+ return a2dp_write_buffer(u, length);
+}
+
+/* Run from IO thread */
static int a2dp_process_push(struct userdata *u) {
+ const pa_a2dp_codec_t *codec;
int ret = 0;
pa_memchunk memchunk;

pa_assert(u);
- pa_assert(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE);
pa_assert(u->source);
pa_assert(u->read_smoother);

+ codec = pa_bluetooth_profile_to_a2dp_codec(u->profile);
+ pa_assert(codec);
+
memchunk.memblock = pa_memblock_new(u->core->mempool, u->read_block_size);
memchunk.index = memchunk.length = 0;

for (;;) {
bool found_tstamp = false;
pa_usec_t tstamp;
- struct sbc_info *sbc_info;
- struct rtp_header *header;
- struct rtp_payload *payload;
- const void *p;
- void *d;
+ uint8_t *ptr;
ssize_t l;
- size_t to_write, to_decode;
+ size_t processed;
size_t total_written = 0;

a2dp_prepare_buffer(u);

- sbc_info = &u->sbc_info;
- header = sbc_info->buffer;
- payload = (struct rtp_payload*) ((uint8_t*) sbc_info->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 +542,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,50 +550,18 @@ static int a2dp_process_push(struct userdata *u) {
tstamp = pa_rtclock_now();
}

- p = (uint8_t*) sbc_info->buffer + sizeof(*header) + sizeof(*payload);
- to_decode = l - sizeof(*header) - sizeof(*payload);
-
- d = pa_memblock_acquire(memchunk.memblock);
- to_write = memchunk.length = pa_memblock_get_length(memchunk.memblock);
-
- while (PA_LIKELY(to_decode > 0)) {
- size_t written;
- ssize_t decoded;
-
- decoded = sbc_decode(&sbc_info->sbc,
- p, to_decode,
- d, to_write,
- &written);
-
- if (PA_UNLIKELY(decoded <= 0)) {
- pa_log_error("SBC decoding error (%li)", (long) decoded);
- pa_memblock_release(memchunk.memblock);
- pa_memblock_unref(memchunk.memblock);
- return 0;
- }
-
- total_written += written;
-
- /* Reset frame length, it can be changed due to bitpool change */
- sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
-
- pa_assert_fp((size_t) decoded <= to_decode);
- pa_assert_fp((size_t) decoded == sbc_info->frame_length);
+ ptr = pa_memblock_acquire(memchunk.memblock);
+ memchunk.length = pa_memblock_get_length(memchunk.memblock);

- pa_assert_fp((size_t) written == sbc_info->codesize);
-
- p = (const uint8_t*) p + decoded;
- to_decode -= decoded;
-
- d = (uint8_t*) d + written;
- to_write -= written;
- }
+ total_written = codec->decode(&u->decoder_info, u->buffer, l, ptr, memchunk.length, &processed);
+ if (total_written == 0)
+ return 0;

u->read_index += (uint64_t) total_written;
pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec));
pa_smoother_resume(u->read_smoother, tstamp, true);

- memchunk.length -= to_write;
+ memchunk.length -= l - processed;

pa_memblock_release(memchunk.memblock);

@@ -705,72 +608,6 @@ static void update_buffer_size(struct userdata *u) {
}
}

-/* Run from I/O thread */
-static void a2dp_set_bitpool(struct userdata *u, uint8_t bitpool) {
- struct sbc_info *sbc_info;
-
- pa_assert(u);
-
- sbc_info = &u->sbc_info;
-
- if (sbc_info->sbc.bitpool == bitpool)
- return;
-
- if (bitpool > sbc_info->max_bitpool)
- bitpool = sbc_info->max_bitpool;
- else if (bitpool < sbc_info->min_bitpool)
- bitpool = sbc_info->min_bitpool;
-
- sbc_info->sbc.bitpool = bitpool;
-
- sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc);
- sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
-
- pa_log_debug("Bitpool has changed to %u", sbc_info->sbc.bitpool);
-
- u->read_block_size =
- (u->read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
- / sbc_info->frame_length * sbc_info->codesize;
-
- u->write_block_size =
- (u->write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
- / sbc_info->frame_length * sbc_info->codesize;
-
- pa_sink_set_max_request_within_thread(u->sink, u->write_block_size);
- pa_sink_set_fixed_latency_within_thread(u->sink,
- FIXED_LATENCY_PLAYBACK_A2DP + pa_bytes_to_usec(u->write_block_size, &u->sample_spec));
-
- /* If there is still data in the memchunk, we have to discard it
- * because the write_block_size may have changed. */
- if (u->write_memchunk.memblock) {
- pa_memblock_unref(u->write_memchunk.memblock);
- pa_memchunk_reset(&u->write_memchunk);
- }
-
- update_buffer_size(u);
-}
-
-/* Run from I/O thread */
-static void a2dp_reduce_bitpool(struct userdata *u) {
- struct sbc_info *sbc_info;
- uint8_t bitpool;
-
- pa_assert(u);
-
- sbc_info = &u->sbc_info;
-
- /* Check if bitpool is already at its limit */
- if (sbc_info->sbc.bitpool <= BITPOOL_DEC_LIMIT)
- return;
-
- bitpool = sbc_info->sbc.bitpool - BITPOOL_DEC_STEP;
-
- if (bitpool < BITPOOL_DEC_LIMIT)
- bitpool = BITPOOL_DEC_LIMIT;
-
- a2dp_set_bitpool(u, bitpool);
-}
-
static void teardown_stream(struct userdata *u) {
if (u->rtpoll_item) {
pa_rtpoll_item_free(u->rtpoll_item);
@@ -860,32 +697,37 @@ static void transport_config_mtu(struct userdata *u) {
u->write_block_size = pa_frame_align(u->write_block_size, &u->sink->sample_spec);
}
} else {
- 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;
-
- 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;
+ const pa_a2dp_codec_t *codec = pa_bluetooth_profile_to_a2dp_codec(u->profile);
+ pa_assert(codec);
+ pa_assert(u->sink || u->source);
+ codec->fill_blocksize(u->sink ? &u->encoder_info : &u->decoder_info, u->read_link_mtu, u->write_link_mtu, &u->read_block_size, &u->write_block_size);
}

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 ?
FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_SCO) +
pa_bytes_to_usec(u->write_block_size, &u->sample_spec));
+
+ /* If there is still data in the memchunk, we have to discard it
+ * because the write_block_size may have changed. */
+ if (u->write_memchunk.memblock) {
+ pa_memblock_unref(u->write_memchunk.memblock);
+ pa_memchunk_reset(&u->write_memchunk);
+ }
}

if (u->source)
pa_source_set_fixed_latency_within_thread(u->source,
- (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE ?
+ (pa_bluetooth_profile_to_a2dp_codec(u->profile) ?
FIXED_LATENCY_RECORD_A2DP : FIXED_LATENCY_RECORD_SCO) +
pa_bytes_to_usec(u->read_block_size, &u->sample_spec));
}

/* Run from I/O thread */
static void setup_stream(struct userdata *u) {
+ const pa_a2dp_codec_t *codec;
struct pollfd *pollfd;
int one;

@@ -895,6 +737,12 @@ static void setup_stream(struct userdata *u) {

pa_log_info("Transport %s resuming", u->transport->path);

+ codec = pa_bluetooth_profile_to_a2dp_codec(u->profile);
+ if (codec) {
+ pa_assert(u->sink || u->source);
+ codec->setup(u->sink ? &u->encoder_info : &u->decoder_info);
+ }
+
transport_config_mtu(u);

pa_make_fd_nonblock(u->stream_fd);
@@ -904,12 +752,10 @@ 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));

- 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)
update_buffer_size(u);
- }
+
+ pa_log_debug("Stream properly set up, we're ready to roll!");

u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
@@ -1078,7 +924,7 @@ static int add_source(struct userdata *u) {

if (!u->transport_acquired)
switch (u->profile) {
- case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
+ case PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE:
case PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY:
data.suspend_cause = PA_SUSPEND_USER;
break;
@@ -1090,8 +936,9 @@ 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_OFF:
+ default:
pa_assert_not_reached();
break;
}
@@ -1263,10 +1110,11 @@ 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:
/* Profile switch should have failed */
- case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
+ case PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE:
case PA_BLUETOOTH_PROFILE_OFF:
+ default:
pa_assert_not_reached();
break;
}
@@ -1296,111 +1144,11 @@ static void transport_config(struct userdata *u) {
u->sample_spec.channels = 1;
u->sample_spec.rate = 8000;
} else {
- sbc_info_t *sbc_info = &u->sbc_info;
- a2dp_sbc_t *config;
-
+ 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);
+ pa_assert(codec);
pa_assert(u->transport);
-
- u->sample_spec.format = PA_SAMPLE_S16LE;
- config = (a2dp_sbc_t *) u->transport->config;
-
- if (sbc_info->sbc_initialized)
- sbc_reinit(&sbc_info->sbc, 0);
- else
- sbc_init(&sbc_info->sbc, 0);
- sbc_info->sbc_initialized = true;
-
- switch (config->frequency) {
- case SBC_SAMPLING_FREQ_16000:
- sbc_info->sbc.frequency = SBC_FREQ_16000;
- u->sample_spec.rate = 16000U;
- break;
- case SBC_SAMPLING_FREQ_32000:
- sbc_info->sbc.frequency = SBC_FREQ_32000;
- u->sample_spec.rate = 32000U;
- break;
- case SBC_SAMPLING_FREQ_44100:
- sbc_info->sbc.frequency = SBC_FREQ_44100;
- u->sample_spec.rate = 44100U;
- break;
- case SBC_SAMPLING_FREQ_48000:
- sbc_info->sbc.frequency = SBC_FREQ_48000;
- u->sample_spec.rate = 48000U;
- break;
- default:
- pa_assert_not_reached();
- }
-
- switch (config->channel_mode) {
- case SBC_CHANNEL_MODE_MONO:
- sbc_info->sbc.mode = SBC_MODE_MONO;
- u->sample_spec.channels = 1;
- break;
- case SBC_CHANNEL_MODE_DUAL_CHANNEL:
- sbc_info->sbc.mode = SBC_MODE_DUAL_CHANNEL;
- u->sample_spec.channels = 2;
- break;
- case SBC_CHANNEL_MODE_STEREO:
- sbc_info->sbc.mode = SBC_MODE_STEREO;
- u->sample_spec.channels = 2;
- break;
- case SBC_CHANNEL_MODE_JOINT_STEREO:
- sbc_info->sbc.mode = SBC_MODE_JOINT_STEREO;
- u->sample_spec.channels = 2;
- break;
- default:
- pa_assert_not_reached();
- }
-
- switch (config->allocation_method) {
- case SBC_ALLOCATION_SNR:
- sbc_info->sbc.allocation = SBC_AM_SNR;
- break;
- case SBC_ALLOCATION_LOUDNESS:
- sbc_info->sbc.allocation = SBC_AM_LOUDNESS;
- break;
- default:
- pa_assert_not_reached();
- }
-
- switch (config->subbands) {
- case SBC_SUBBANDS_4:
- sbc_info->sbc.subbands = SBC_SB_4;
- break;
- case SBC_SUBBANDS_8:
- sbc_info->sbc.subbands = SBC_SB_8;
- break;
- default:
- pa_assert_not_reached();
- }
-
- switch (config->block_length) {
- case SBC_BLOCK_LENGTH_4:
- sbc_info->sbc.blocks = SBC_BLK_4;
- break;
- case SBC_BLOCK_LENGTH_8:
- sbc_info->sbc.blocks = SBC_BLK_8;
- break;
- case SBC_BLOCK_LENGTH_12:
- sbc_info->sbc.blocks = SBC_BLK_12;
- break;
- case SBC_BLOCK_LENGTH_16:
- sbc_info->sbc.blocks = SBC_BLK_16;
- break;
- default:
- pa_assert_not_reached();
- }
-
- sbc_info->min_bitpool = config->min_bitpool;
- 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->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);
+ codec->init(is_sink ? &u->encoder_info : &u->decoder_info, &u->sample_spec, is_sink, u->transport->config, u->transport->config_size);
}
}

@@ -1421,7 +1169,7 @@ static int setup_transport(struct userdata *u) {

u->transport = t;

- if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY)
+ if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SBC_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;
@@ -1439,8 +1187,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_SOURCE] = PA_DIRECTION_INPUT,
+ [PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK] = PA_DIRECTION_OUTPUT,
+ [PA_BLUETOOTH_PROFILE_A2DP_SBC_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
@@ -1477,11 +1225,11 @@ 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_HEADSET_HEAD_UNIT || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
+ if ((n_written = sco_process_render(u)) < 0)
return -1;
} else {
- if ((n_written = sco_process_render(u)) < 0)
+ if ((n_written = a2dp_process_render(u)) < 0)
return -1;
}

@@ -1551,7 +1299,7 @@ static void thread_func(void *userdata) {
if (pollfd->revents & POLLIN) {
int n_read;

- if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE)
+ if (pa_bluetooth_profile_to_a2dp_codec(u->profile))
n_read = a2dp_process_push(u);
else
n_read = sco_process_push(u);
@@ -1651,8 +1399,13 @@ 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->sink) {
+ const pa_a2dp_codec_t *codec = pa_bluetooth_profile_to_a2dp_codec(u->profile);
+ if (codec && codec->fix_latency(&u->encoder_info)) {
+ transport_config_mtu(u);
+ update_buffer_size(u);
+ }
+ }
}

blocks_to_write = 1;
@@ -1767,7 +1520,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_SOURCE)
+ if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY || u->profile == PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE)
u->source->priority = 1500;

pa_source_put(u->source);
@@ -1980,8 +1733,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,8 +1745,8 @@ 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_SOURCE:
- cp = pa_card_profile_new(name, _("High Fidelity Capture (A2DP Source)"), sizeof(pa_bluetooth_profile_t));
+ 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;
cp->n_sinks = 0;
cp->n_sources = 1;
@@ -2088,9 +1841,9 @@ off:

static int uuid_to_profile(const char *uuid, pa_bluetooth_profile_t *_r) {
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;
+ *_r = PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE;
else if (pa_bluetooth_uuid_is_hsp_hs(uuid) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_HF))
*_r = PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT;
else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_AG) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_AG))
@@ -2472,6 +2225,7 @@ fail:

void pa__done(pa_module *m) {
struct userdata *u;
+ const pa_a2dp_codec_t *codec;

pa_assert(m);

@@ -2492,11 +2246,14 @@ 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);
+ codec = pa_bluetooth_profile_to_a2dp_codec(u->profile);
+ if (codec) {
+ codec->finish(&u->encoder_info);
+ codec->finish(&u->decoder_info);
+ }

if (u->msg)
pa_xfree(u->msg);
diff --git a/src/modules/bluetooth/pa-a2dp-codec-sbc.c b/src/modules/bluetooth/pa-a2dp-codec-sbc.c
new file mode 100644
index 000000000..2a61114a6
--- /dev/null
+++ b/src/modules/bluetooth/pa-a2dp-codec-sbc.c
@@ -0,0 +1,579 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2018 Pali Rohár <***@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/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/once.h>
+#include <pulse/sample.h>
+#include <pulse/xmalloc.h>
+
+#include <arpa/inet.h>
+
+#include <sbc/sbc.h>
+
+#include "a2dp-codecs.h"
+#include "pa-a2dp-codec.h"
+#include "rtp.h"
+
+#define SBC_BITPOOL_DEC_LIMIT 32
+#define SBC_BITPOOL_DEC_STEP 5
+
+typedef struct sbc_info {
+ sbc_t sbc; /* Codec data */
+ bool sbc_initialized; /* Keep track if the encoder is initialized */
+ size_t codesize, frame_length; /* SBC Codesize, frame_length. We simply cache those values here */
+ uint16_t seq_num; /* Cumulative packet sequence */
+ uint8_t min_bitpool;
+ uint8_t max_bitpool;
+} sbc_info_t;
+
+static size_t pa_sbc_fill_capabilities(uint8_t *capabilities_buffer, size_t capabilities_size) {
+ a2dp_sbc_t *capabilities = (a2dp_sbc_t *) capabilities_buffer;
+
+ if (capabilities_size < sizeof(*capabilities)) {
+ pa_log_error("Invalid size of capabilities buffer");
+ return 0;
+ }
+
+ pa_zero(*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;
+
+ return sizeof(*capabilities);
+}
+
+static bool pa_sbc_validate_configuration(const uint8_t *config_buffer, size_t config_size) {
+ a2dp_sbc_t *config = (a2dp_sbc_t *) config_buffer;
+
+ if (config_size != sizeof(*config)) {
+ pa_log_error("Invalid size of config buffer");
+ return false;
+ }
+
+ if (config->frequency != SBC_SAMPLING_FREQ_16000 && config->frequency != SBC_SAMPLING_FREQ_32000 &&
+ config->frequency != SBC_SAMPLING_FREQ_44100 && config->frequency != SBC_SAMPLING_FREQ_48000) {
+ pa_log_error("Invalid sampling frequency in configuration");
+ return false;
+ }
+
+ if (config->channel_mode != SBC_CHANNEL_MODE_MONO && config->channel_mode != SBC_CHANNEL_MODE_DUAL_CHANNEL &&
+ config->channel_mode != SBC_CHANNEL_MODE_STEREO && config->channel_mode != SBC_CHANNEL_MODE_JOINT_STEREO) {
+ pa_log_error("Invalid channel mode in configuration");
+ return false;
+ }
+
+ if (config->allocation_method != SBC_ALLOCATION_SNR && config->allocation_method != SBC_ALLOCATION_LOUDNESS) {
+ pa_log_error("Invalid allocation method in configuration");
+ return false;
+ }
+
+ if (config->subbands != SBC_SUBBANDS_4 && config->subbands != SBC_SUBBANDS_8) {
+ pa_log_error("Invalid SBC subbands in configuration");
+ return false;
+ }
+
+ if (config->block_length != SBC_BLOCK_LENGTH_4 && config->block_length != SBC_BLOCK_LENGTH_8 &&
+ config->block_length != SBC_BLOCK_LENGTH_12 && config->block_length != SBC_BLOCK_LENGTH_16) {
+ pa_log_error("Invalid block length in configuration");
+ return false;
+ }
+
+ return true;
+}
+
+static uint8_t pa_sbc_default_bitpool(uint8_t freq, uint8_t mode) {
+ /* These bitpool values were chosen based on the A2DP spec recommendation */
+ switch (freq) {
+ case SBC_SAMPLING_FREQ_16000:
+ case SBC_SAMPLING_FREQ_32000:
+ return 53;
+
+ case SBC_SAMPLING_FREQ_44100:
+
+ switch (mode) {
+ case SBC_CHANNEL_MODE_MONO:
+ case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+ return 31;
+
+ case SBC_CHANNEL_MODE_STEREO:
+ case SBC_CHANNEL_MODE_JOINT_STEREO:
+ return 53;
+ }
+
+ pa_log_warn("Invalid channel mode %u", mode);
+ return 53;
+
+ case SBC_SAMPLING_FREQ_48000:
+
+ switch (mode) {
+ case SBC_CHANNEL_MODE_MONO:
+ case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+ return 29;
+
+ case SBC_CHANNEL_MODE_STEREO:
+ case SBC_CHANNEL_MODE_JOINT_STEREO:
+ return 51;
+ }
+
+ pa_log_warn("Invalid channel mode %u", mode);
+ return 51;
+ }
+
+ pa_log_warn("Invalid sampling freq %u", freq);
+ return 53;
+}
+
+static size_t pa_sbc_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_sbc_t *config = (a2dp_sbc_t *) config_buffer;
+ a2dp_sbc_t *capabilities = (a2dp_sbc_t *) capabilities_buffer;
+ int i;
+
+ 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 (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);
+
+ /* 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;
+ }
+ }
+
+ pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table));
+
+ if (sample_spec->channels <= 1) {
+ if (capabilities->channel_mode & SBC_CHANNEL_MODE_MONO)
+ config->channel_mode = SBC_CHANNEL_MODE_MONO;
+ else if (capabilities->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
+ config->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
+ else if (capabilities->channel_mode & SBC_CHANNEL_MODE_STEREO)
+ config->channel_mode = SBC_CHANNEL_MODE_STEREO;
+ else if (capabilities->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
+ config->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
+ else {
+ pa_log_error("No supported channel modes");
+ return 0;
+ }
+ }
+
+ if (sample_spec->channels >= 2) {
+ if (capabilities->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
+ config->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
+ else if (capabilities->channel_mode & SBC_CHANNEL_MODE_STEREO)
+ config->channel_mode = SBC_CHANNEL_MODE_STEREO;
+ else if (capabilities->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
+ config->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
+ else if (capabilities->channel_mode & SBC_CHANNEL_MODE_MONO)
+ config->channel_mode = SBC_CHANNEL_MODE_MONO;
+ else {
+ pa_log_error("No supported channel modes");
+ return 0;
+ }
+ }
+
+ if (capabilities->block_length & SBC_BLOCK_LENGTH_16)
+ config->block_length = SBC_BLOCK_LENGTH_16;
+ else if (capabilities->block_length & SBC_BLOCK_LENGTH_12)
+ config->block_length = SBC_BLOCK_LENGTH_12;
+ else if (capabilities->block_length & SBC_BLOCK_LENGTH_8)
+ config->block_length = SBC_BLOCK_LENGTH_8;
+ else if (capabilities->block_length & SBC_BLOCK_LENGTH_4)
+ config->block_length = SBC_BLOCK_LENGTH_4;
+ else {
+ pa_log_error("No supported block lengths");
+ return 0;
+ }
+
+ if (capabilities->subbands & SBC_SUBBANDS_8)
+ config->subbands = SBC_SUBBANDS_8;
+ else if (capabilities->subbands & SBC_SUBBANDS_4)
+ config->subbands = SBC_SUBBANDS_4;
+ else {
+ pa_log_error("No supported subbands");
+ return 0;
+ }
+
+ if (capabilities->allocation_method & SBC_ALLOCATION_LOUDNESS)
+ config->allocation_method = SBC_ALLOCATION_LOUDNESS;
+ else if (capabilities->allocation_method & SBC_ALLOCATION_SNR)
+ config->allocation_method = SBC_ALLOCATION_SNR;
+
+ config->min_bitpool = (uint8_t) PA_MAX(SBC_MIN_BITPOOL, capabilities->min_bitpool);
+ config->max_bitpool = (uint8_t) PA_MIN(pa_sbc_default_bitpool(config->frequency, config->channel_mode), capabilities->max_bitpool);
+
+ if (config->min_bitpool > config->max_bitpool)
+ return 0;
+
+ return sizeof(*config);
+}
+
+static void pa_sbc_init(void **info_ptr, pa_sample_spec *sample_spec, bool is_a2dp_sink, const uint8_t *config_buffer, size_t config_size) {
+ sbc_info_t *sbc_info;
+ a2dp_sbc_t *config = (a2dp_sbc_t *) config_buffer;
+
+ pa_assert(config_size == sizeof(*config));
+
+ if (!*info_ptr)
+ *info_ptr = pa_xmalloc0(sizeof(*sbc_info));
+
+ sbc_info = (sbc_info_t *) *info_ptr;
+
+ sample_spec->format = PA_SAMPLE_S16LE;
+
+ if (sbc_info->sbc_initialized)
+ sbc_reinit(&sbc_info->sbc, 0);
+ else
+ sbc_init(&sbc_info->sbc, 0);
+ sbc_info->sbc_initialized = true;
+
+ switch (config->frequency) {
+ case SBC_SAMPLING_FREQ_16000:
+ sbc_info->sbc.frequency = SBC_FREQ_16000;
+ sample_spec->rate = 16000U;
+ break;
+ case SBC_SAMPLING_FREQ_32000:
+ sbc_info->sbc.frequency = SBC_FREQ_32000;
+ sample_spec->rate = 32000U;
+ break;
+ case SBC_SAMPLING_FREQ_44100:
+ sbc_info->sbc.frequency = SBC_FREQ_44100;
+ sample_spec->rate = 44100U;
+ break;
+ case SBC_SAMPLING_FREQ_48000:
+ sbc_info->sbc.frequency = SBC_FREQ_48000;
+ sample_spec->rate = 48000U;
+ break;
+ default:
+ pa_assert_not_reached();
+ }
+
+ switch (config->channel_mode) {
+ case SBC_CHANNEL_MODE_MONO:
+ sbc_info->sbc.mode = SBC_MODE_MONO;
+ sample_spec->channels = 1;
+ break;
+ case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+ sbc_info->sbc.mode = SBC_MODE_DUAL_CHANNEL;
+ sample_spec->channels = 2;
+ break;
+ case SBC_CHANNEL_MODE_STEREO:
+ sbc_info->sbc.mode = SBC_MODE_STEREO;
+ sample_spec->channels = 2;
+ break;
+ case SBC_CHANNEL_MODE_JOINT_STEREO:
+ sbc_info->sbc.mode = SBC_MODE_JOINT_STEREO;
+ sample_spec->channels = 2;
+ break;
+ default:
+ pa_assert_not_reached();
+ }
+
+ switch (config->allocation_method) {
+ case SBC_ALLOCATION_SNR:
+ sbc_info->sbc.allocation = SBC_AM_SNR;
+ break;
+ case SBC_ALLOCATION_LOUDNESS:
+ sbc_info->sbc.allocation = SBC_AM_LOUDNESS;
+ break;
+ default:
+ pa_assert_not_reached();
+ }
+
+ switch (config->subbands) {
+ case SBC_SUBBANDS_4:
+ sbc_info->sbc.subbands = SBC_SB_4;
+ break;
+ case SBC_SUBBANDS_8:
+ sbc_info->sbc.subbands = SBC_SB_8;
+ break;
+ default:
+ pa_assert_not_reached();
+ }
+
+ switch (config->block_length) {
+ case SBC_BLOCK_LENGTH_4:
+ sbc_info->sbc.blocks = SBC_BLK_4;
+ break;
+ case SBC_BLOCK_LENGTH_8:
+ sbc_info->sbc.blocks = SBC_BLK_8;
+ break;
+ case SBC_BLOCK_LENGTH_12:
+ sbc_info->sbc.blocks = SBC_BLK_12;
+ break;
+ case SBC_BLOCK_LENGTH_16:
+ sbc_info->sbc.blocks = SBC_BLK_16;
+ break;
+ default:
+ pa_assert_not_reached();
+ }
+
+ sbc_info->min_bitpool = config->min_bitpool;
+ sbc_info->max_bitpool = config->max_bitpool;
+
+ /* Set minimum bitpool for source to get the maximum possible block_size */
+ sbc_info->sbc.bitpool = is_a2dp_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);
+}
+
+static void pa_sbc_finish(void **info_ptr) {
+ sbc_info_t *sbc_info = (sbc_info_t *) *info_ptr;
+
+ if (!sbc_info)
+ return;
+
+ if (sbc_info->sbc_initialized)
+ sbc_finish(&sbc_info->sbc);
+
+ pa_xfree(sbc_info);
+ *info_ptr = NULL;
+}
+
+static void pa_sbc_set_bitpool(sbc_info_t *sbc_info, uint8_t bitpool) {
+ if (bitpool > sbc_info->max_bitpool)
+ bitpool = sbc_info->max_bitpool;
+ else if (bitpool < sbc_info->min_bitpool)
+ bitpool = sbc_info->min_bitpool;
+
+ sbc_info->sbc.bitpool = bitpool;
+
+ sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc);
+ sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
+
+ pa_log_debug("Bitpool has changed to %u", sbc_info->sbc.bitpool);
+}
+
+static void pa_sbc_setup(void **info_ptr) {
+ sbc_info_t *sbc_info = (sbc_info_t *) *info_ptr;
+
+ pa_sbc_set_bitpool(sbc_info, sbc_info->max_bitpool);
+}
+
+static void pa_sbc_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) {
+ sbc_info_t *sbc_info = (sbc_info_t *) *info_ptr;
+
+ *read_block_size =
+ (read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+ / sbc_info->frame_length * sbc_info->codesize;
+
+ *write_block_size =
+ (write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+ / sbc_info->frame_length * sbc_info->codesize;
+}
+
+static bool pa_sbc_fix_latency(void **info_ptr) {
+ sbc_info_t *sbc_info = (sbc_info_t *) *info_ptr;
+ uint8_t bitpool;
+
+ /* Check if bitpool is already at its limit */
+ if (sbc_info->sbc.bitpool <= SBC_BITPOOL_DEC_LIMIT)
+ return false;
+
+ bitpool = sbc_info->sbc.bitpool - SBC_BITPOOL_DEC_STEP;
+
+ if (bitpool < SBC_BITPOOL_DEC_LIMIT)
+ bitpool = SBC_BITPOOL_DEC_LIMIT;
+
+ if (sbc_info->sbc.bitpool == bitpool)
+ return false;
+
+ pa_sbc_set_bitpool(sbc_info, bitpool);
+ return true;
+}
+
+static size_t pa_sbc_encode(void **info_ptr, uint32_t timestamp, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size) {
+ sbc_info_t *sbc_info = (sbc_info_t *) *info_ptr;
+ struct rtp_header *header;
+ struct rtp_payload *payload;
+ uint8_t *d;
+ const uint8_t *p;
+ size_t to_write, to_encode;
+ unsigned frame_count;
+
+ header = (struct rtp_header*) output_buffer;
+ payload = (struct rtp_payload*) (output_buffer + sizeof(*header));
+
+ frame_count = 0;
+
+ p = input_buffer;
+ to_encode = input_size;
+
+ d = output_buffer + sizeof(*header) + sizeof(*payload);
+ to_write = output_size - sizeof(*header) - sizeof(*payload);
+
+ while (PA_LIKELY(to_encode > 0 && to_write > 0)) {
+ ssize_t written;
+ ssize_t encoded;
+
+ encoded = sbc_encode(&sbc_info->sbc,
+ p, to_encode,
+ d, to_write,
+ &written);
+
+ if (PA_UNLIKELY(encoded <= 0)) {
+ pa_log_error("SBC encoding error (%li)", (long) encoded);
+ return 0;
+ }
+
+ pa_assert_fp((size_t) encoded <= to_encode);
+ pa_assert_fp((size_t) encoded == sbc_info->codesize);
+
+ pa_assert_fp((size_t) written <= to_write);
+ pa_assert_fp((size_t) written == sbc_info->frame_length);
+
+ p += encoded;
+ to_encode -= encoded;
+
+ d += written;
+ to_write -= written;
+
+ frame_count++;
+ }
+
+ pa_assert(to_encode == 0);
+
+ PA_ONCE_BEGIN {
+ pa_log_debug("Using SBC encoder implementation: %s", pa_strnull(sbc_get_implementation_info(&sbc_info->sbc)));
+ } PA_ONCE_END;
+
+ /* write it to the fifo */
+ memset(output_buffer, 0, sizeof(*header) + sizeof(*payload));
+ header->v = 2;
+ header->pt = 1;
+ header->sequence_number = htons(sbc_info->seq_num++);
+ header->timestamp = htonl(timestamp);
+ header->ssrc = htonl(1);
+ payload->frame_count = frame_count;
+
+ return d - output_buffer;
+}
+
+static size_t pa_sbc_decode(void **info_ptr, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) {
+ sbc_info_t *sbc_info = (sbc_info_t *) *info_ptr;
+
+ struct rtp_header *header;
+ struct rtp_payload *payload;
+ const uint8_t *p;
+ uint8_t *d;
+ size_t to_write, to_decode;
+
+ header = (struct rtp_header *) input_buffer;
+ payload = (struct rtp_payload*) (input_buffer + sizeof(*header));
+
+ p = input_buffer + sizeof(*header) + sizeof(*payload);
+ to_decode = input_size - sizeof(*header) - sizeof(*payload);
+
+ d = output_buffer;
+ to_write = output_size;
+
+ while (PA_LIKELY(to_decode > 0)) {
+ size_t written;
+ ssize_t decoded;
+
+ decoded = sbc_decode(&sbc_info->sbc,
+ p, to_decode,
+ d, to_write,
+ &written);
+
+ if (PA_UNLIKELY(decoded <= 0)) {
+ pa_log_error("SBC decoding error (%li)", (long) decoded);
+ *processed = p - input_buffer;
+ return 0;
+ }
+
+ /* Reset frame length, it can be changed due to bitpool change */
+ sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
+
+ pa_assert_fp((size_t) decoded <= to_decode);
+ pa_assert_fp((size_t) decoded == sbc_info->frame_length);
+
+ pa_assert_fp((size_t) written == sbc_info->codesize);
+
+ 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_sbc = {
+ .codec_id = A2DP_CODEC_SBC,
+ .fill_capabilities = pa_sbc_fill_capabilities,
+ .validate_configuration = pa_sbc_validate_configuration,
+ .select_configuration = pa_sbc_select_configuration,
+ .init = pa_sbc_init,
+ .finish = pa_sbc_finish,
+ .setup = pa_sbc_setup,
+ .fill_blocksize = pa_sbc_fill_blocksize,
+ .fix_latency = pa_sbc_fix_latency,
+ .encode = pa_sbc_encode,
+ .decode = pa_sbc_decode,
+};
diff --git a/src/modules/bluetooth/pa-a2dp-codec.h b/src/modules/bluetooth/pa-a2dp-codec.h
new file mode 100644
index 000000000..68b1619c2
--- /dev/null
+++ b/src/modules/bluetooth/pa-a2dp-codec.h
@@ -0,0 +1,40 @@
+#ifndef foopaa2dpcodechfoo
+#define foopaa2dpcodechfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2018 Pali Rohár <***@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/>.
+***/
+
+typedef struct pa_a2dp_codec {
+ const char *codec_name;
+ uint8_t codec_id;
+ size_t (*fill_capabilities)(uint8_t *, size_t);
+ bool (*validate_configuration)(const uint8_t *, size_t);
+ size_t (*select_configuration)(const pa_sample_spec *, const uint8_t *, size_t, uint8_t *, size_t);
+ void (*init)(void **, pa_sample_spec *, bool, const uint8_t *, size_t);
+ void (*finish)(void **);
+ void (*setup)(void **);
+ void (*fill_blocksize)(void **, size_t, size_t, size_t *, size_t *);
+ bool (*fix_latency)(void **);
+ size_t (*encode)(void **, uint32_t, const uint8_t *, size_t, uint8_t *, size_t);
+ size_t (*decode)(void **, const uint8_t *, size_t, uint8_t *, size_t, size_t *);
+} pa_a2dp_codec_t;
+
+extern const pa_a2dp_codec_t pa_a2dp_codec_sbc;
+
+#endif
--
2.11.0
Arun Raghavan
2018-08-05 05:37:23 UTC
Permalink
Post by Pali Rohár
Move current SBC codec implementation into separate source file and use
this new API for providing all needed functionality for Bluetooth A2DP.
Both bluez5-util and module-bluez5-device are changed to use this new
modular codec API.
---
src/Makefile.am | 9 +-
src/modules/bluetooth/a2dp-codecs.h | 5 +-
src/modules/bluetooth/bluez5-util.c | 331 +++++----------
src/modules/bluetooth/bluez5-util.h | 10 +-
src/modules/bluetooth/module-bluez5-device.c | 487 ++++++----------------
src/modules/bluetooth/pa-a2dp-codec-sbc.c | 579 +++++++++++++++++++++++++++
src/modules/bluetooth/pa-a2dp-codec.h | 40 ++
7 files changed, 851 insertions(+), 610 deletions(-)
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-sbc.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec.h
diff --git a/src/Makefile.am b/src/Makefile.am
index f62e7d5c4..411b9e5e5 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2117,6 +2117,7 @@ module_bluetooth_discover_la_CFLAGS = $(AM_CFLAGS)
-DPA_MODULE_NAME=module_bluet
libbluez5_util_la_SOURCES = \
modules/bluetooth/bluez5-util.c \
modules/bluetooth/bluez5-util.h \
+ modules/bluetooth/pa-a2dp-codec.h \
modules/bluetooth/a2dp-codecs.h
if HAVE_BLUEZ_5_OFONO_HEADSET
libbluez5_util_la_SOURCES += \
@@ -2131,6 +2132,10 @@ libbluez5_util_la_LDFLAGS = -avoid-version
libbluez5_util_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS)
libbluez5_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
+libbluez5_util_la_SOURCES += modules/bluetooth/pa-a2dp-codec-sbc.c
+libbluez5_util_la_LIBADD += $(SBC_LIBS)
+libbluez5_util_la_CFLAGS += $(SBC_CFLAGS)
+
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
@@ -2138,8 +2143,8 @@ module_bluez5_discover_la_CFLAGS = $(AM_CFLAGS) $
(DBUS_CFLAGS) -DPA_MODULE_NAME=
module_bluez5_device_la_SOURCES = modules/bluetooth/module-bluez5-
device.c
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
+module_bluez5_device_la_LIBADD = $(MODULE_LIBADD) libbluez5-util.la
+module_bluez5_device_la_CFLAGS = $(AM_CFLAGS) -
DPA_MODULE_NAME=module_bluez5_device
# Apple Airtunes/RAOP
module_raop_sink_la_SOURCES = modules/raop/module-raop-sink.c
diff --git a/src/modules/bluetooth/a2dp-codecs.h b/src/modules/
bluetooth/a2dp-codecs.h
index 8afcfcb24..004975586 100644
--- a/src/modules/bluetooth/a2dp-codecs.h
+++ b/src/modules/bluetooth/a2dp-codecs.h
@@ -47,6 +47,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 +66,6 @@
#define MPEG_SAMPLING_FREQ_44100 (1 << 1)
#define MPEG_SAMPLING_FREQ_48000 1
-#define MAX_BITPOOL 64
-#define MIN_BITPOOL 2
#if __BYTE_ORDER == __LITTLE_ENDIAN
diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/
bluetooth/bluez5-util.c
index 2d8337317..9c4e3367b 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
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@@ -33,7 +34,7 @@
#include <pulsecore/refcnt.h>
#include <pulsecore/shared.h>
-#include "a2dp-codecs.h"
+#include "pa-a2dp-codec.h"
#include "bluez5-util.h"
@@ -48,8 +49,8 @@
#define BLUEZ_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported"
-#define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource"
-#define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink"
+#define A2DP_SOURCE_SBC_ENDPOINT "/MediaEndpoint/A2DPSourceSBC"
+#define A2DP_SINK_SBC_ENDPOINT "/MediaEndpoint/A2DPSinkSBC"
#define ENDPOINT_INTROSPECT_XML
\
DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
\
@@ -170,9 +171,9 @@ 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) {
return !!pa_hashmap_get(device->uuids,
PA_BLUETOOTH_UUID_A2DP_SINK);
return !!pa_hashmap_get(device->uuids,
I haven't done a thorough review yet, but this one caught my eye. Tying the codec and the profile together here does not seem to be a good choice. Seems cleaner to separate out the codec choice into a separate field.

There's a bunch of other stuff, but probably makes sense to discuss this up-front.

Also, we now have GitLab for patch submission, so feel free to use that for easier code reviews in the next iteration.

-- Arun
Pali Rohár
2018-08-06 08:23:35 UTC
Permalink
Post by Arun Raghavan
Post by Pali Rohár
Move current SBC codec implementation into separate source file and use
this new API for providing all needed functionality for Bluetooth A2DP.
Both bluez5-util and module-bluez5-device are changed to use this new
modular codec API.
---
src/Makefile.am | 9 +-
src/modules/bluetooth/a2dp-codecs.h | 5 +-
src/modules/bluetooth/bluez5-util.c | 331 +++++----------
src/modules/bluetooth/bluez5-util.h | 10 +-
src/modules/bluetooth/module-bluez5-device.c | 487 ++++++----------------
src/modules/bluetooth/pa-a2dp-codec-sbc.c | 579 +++++++++++++++++++++++++++
src/modules/bluetooth/pa-a2dp-codec.h | 40 ++
7 files changed, 851 insertions(+), 610 deletions(-)
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-sbc.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec.h
diff --git a/src/Makefile.am b/src/Makefile.am
index f62e7d5c4..411b9e5e5 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2117,6 +2117,7 @@ module_bluetooth_discover_la_CFLAGS = $(AM_CFLAGS)
-DPA_MODULE_NAME=module_bluet
libbluez5_util_la_SOURCES = \
modules/bluetooth/bluez5-util.c \
modules/bluetooth/bluez5-util.h \
+ modules/bluetooth/pa-a2dp-codec.h \
modules/bluetooth/a2dp-codecs.h
if HAVE_BLUEZ_5_OFONO_HEADSET
libbluez5_util_la_SOURCES += \
@@ -2131,6 +2132,10 @@ libbluez5_util_la_LDFLAGS = -avoid-version
libbluez5_util_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS)
libbluez5_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
+libbluez5_util_la_SOURCES += modules/bluetooth/pa-a2dp-codec-sbc.c
+libbluez5_util_la_LIBADD += $(SBC_LIBS)
+libbluez5_util_la_CFLAGS += $(SBC_CFLAGS)
+
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
@@ -2138,8 +2143,8 @@ module_bluez5_discover_la_CFLAGS = $(AM_CFLAGS) $
(DBUS_CFLAGS) -DPA_MODULE_NAME=
module_bluez5_device_la_SOURCES = modules/bluetooth/module-bluez5-
device.c
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
+module_bluez5_device_la_LIBADD = $(MODULE_LIBADD) libbluez5-util.la
+module_bluez5_device_la_CFLAGS = $(AM_CFLAGS) -
DPA_MODULE_NAME=module_bluez5_device
# Apple Airtunes/RAOP
module_raop_sink_la_SOURCES = modules/raop/module-raop-sink.c
diff --git a/src/modules/bluetooth/a2dp-codecs.h b/src/modules/
bluetooth/a2dp-codecs.h
index 8afcfcb24..004975586 100644
--- a/src/modules/bluetooth/a2dp-codecs.h
+++ b/src/modules/bluetooth/a2dp-codecs.h
@@ -47,6 +47,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 +66,6 @@
#define MPEG_SAMPLING_FREQ_44100 (1 << 1)
#define MPEG_SAMPLING_FREQ_48000 1
-#define MAX_BITPOOL 64
-#define MIN_BITPOOL 2
#if __BYTE_ORDER == __LITTLE_ENDIAN
diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/
bluetooth/bluez5-util.c
index 2d8337317..9c4e3367b 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
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@@ -33,7 +34,7 @@
#include <pulsecore/refcnt.h>
#include <pulsecore/shared.h>
-#include "a2dp-codecs.h"
+#include "pa-a2dp-codec.h"
#include "bluez5-util.h"
@@ -48,8 +49,8 @@
#define BLUEZ_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported"
-#define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource"
-#define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink"
+#define A2DP_SOURCE_SBC_ENDPOINT "/MediaEndpoint/A2DPSourceSBC"
+#define A2DP_SINK_SBC_ENDPOINT "/MediaEndpoint/A2DPSinkSBC"
#define ENDPOINT_INTROSPECT_XML
\
DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
\
@@ -170,9 +171,9 @@ 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) {
return !!pa_hashmap_get(device->uuids,
PA_BLUETOOTH_UUID_A2DP_SINK);
return !!pa_hashmap_get(device->uuids,
I haven't done a thorough review yet, but this one caught my eye. Tying the codec and the profile together here does not seem to be a good choice. Seems cleaner to separate out the codec choice into a separate field.
For each codec you need bluez endpoint, bound to bluez code which is in
bluez5-util.c

If codec should not be part of profile? How should be codec selected,
negotiated or switched by user?

And how to handle situation for bi-rectional A2DP codecs? Because now
PA_BLUETOOTH_PROFILE_A2DP_SINK and PA_BLUETOOTH_PROFILE_A2DP_SOURCE
support only one direction.
Post by Arun Raghavan
There's a bunch of other stuff, but probably makes sense to discuss this up-front.
Also, we now have GitLab for patch submission, so feel free to use that for easier code reviews in the next iteration.
-- Arun
--
Pali Rohár
***@gmail.com
Tanu Kaskinen
2018-09-04 08:44:10 UTC
Permalink
Hi Pali!

Thanks a lot for working on this! Arun has been strongly advocating
using GStreamer, and I don't know if his position is that "AptX must be
implemented with GStreamer or not implemented at all". I hope not. To
me the library choice is not important. Using libopenaptx directly
doesn't prevent us from switching to GStreamer later if someone writes
the code.

Another point that was raised that the codec choice shouldn't be bound
to the card profile. I tend to agree with that. There are situations
where module-bluetooth-policy or the user only wants to switch from HSP
to A2DP and not care about the codec. You asked how the codec should be
negotiated or switched by the user if it's not bound to the profile.
Regarding negotiation, we can add a hook that module-bluetooth-policy
can use to make the codec decision (if module-bluetooth-policy isn't
loaded, SBC seems like a sensible default).

Is there a need for the user to be able to choose the codec? Shouldn't
we always automatically pick the highest-quality one? I'm not against
adding the functionality, it would be useful for testing if nothing
else. It just doesn't seem very important.

We could have a "preferred-a2dp-codec" option in bluetooth.conf (that
file doesn't exist, but can be added). A per-card option would be
possible too, as would be having both a global option that could be
overridden with a per-card option.

For runtime configuration, we can add a command to set the codec
preference. This should be done with the message API that Georg Chini
has been working on (not yet finished). There's then need for saving
the choice too. We can either introduce a new database for bluetooth
stuff, or we can add some API to module-card-restore so that other
modules (module-bluetooth-discover in this case) can save arbitrary
parameters for cards.

I recall there was some lack of relevant APIs in bluetoothd for
choosing the codec from PulseAudio. Can you remind me, what are the
limitations, and how did you plan to deal with them?

Comments on the code below. This review was done over several days,
sorry for any inconsistencies or strange repetition (I noticed I
explain some things multiple times).
Post by Pali Rohár
Move current SBC codec implementation into separate source file and use
this new API for providing all needed functionality for Bluetooth A2DP.
Both bluez5-util and module-bluez5-device are changed to use this new
modular codec API.
---
src/Makefile.am | 9 +-
src/modules/bluetooth/a2dp-codecs.h | 5 +-
src/modules/bluetooth/bluez5-util.c | 331 +++++----------
src/modules/bluetooth/bluez5-util.h | 10 +-
src/modules/bluetooth/module-bluez5-device.c | 487 ++++++----------------
src/modules/bluetooth/pa-a2dp-codec-sbc.c | 579 +++++++++++++++++++++++++++
src/modules/bluetooth/pa-a2dp-codec.h | 40 ++
The "pa-" prefix doesn't look very good to me. Maybe you didn't want to
add a2dp-codec.h, because it looks so similar to the existing a2dp-
codecs.h header? I think we can get rid of a2dp-codecs.h: the SBC stuff
can be moved to a2dp-codec-sbc.c, and the MPEG stuff can be dropped
since it's not used anywhere.
Post by Pali Rohár
7 files changed, 851 insertions(+), 610 deletions(-)
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-sbc.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec.h
diff --git a/src/Makefile.am b/src/Makefile.am
index f62e7d5c4..411b9e5e5 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2117,6 +2117,7 @@ module_bluetooth_discover_la_CFLAGS = $(AM_CFLAGS) -DPA_MODULE_NAME=module_bluet
libbluez5_util_la_SOURCES = \
modules/bluetooth/bluez5-util.c \
modules/bluetooth/bluez5-util.h \
+ modules/bluetooth/pa-a2dp-codec.h \
modules/bluetooth/a2dp-codecs.h
if HAVE_BLUEZ_5_OFONO_HEADSET
libbluez5_util_la_SOURCES += \
@@ -2131,6 +2132,10 @@ libbluez5_util_la_LDFLAGS = -avoid-version
libbluez5_util_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS)
libbluez5_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
+libbluez5_util_la_SOURCES += modules/bluetooth/pa-a2dp-codec-sbc.c
+libbluez5_util_la_LIBADD += $(SBC_LIBS)
+libbluez5_util_la_CFLAGS += $(SBC_CFLAGS)
Cosmetic nitpicking: these can be merged with the earlier variable
assignemnts. Maybe you're aiming for some kind of "keep the SBC stuff
separate" modularization, but that's not really in line of how the rest
of the file is written.
Post by Pali Rohár
+
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
@@ -2138,8 +2143,8 @@ module_bluez5_discover_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) -DPA_MODULE_NAME=
module_bluez5_device_la_SOURCES = modules/bluetooth/module-bluez5-device.c
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
+module_bluez5_device_la_LIBADD = $(MODULE_LIBADD) libbluez5-util.la
+module_bluez5_device_la_CFLAGS = $(AM_CFLAGS) -DPA_MODULE_NAME=module_bluez5_device
# Apple Airtunes/RAOP
module_raop_sink_la_SOURCES = modules/raop/module-raop-sink.c
diff --git a/src/modules/bluetooth/a2dp-codecs.h b/src/modules/bluetooth/a2dp-codecs.h
index 8afcfcb24..004975586 100644
--- a/src/modules/bluetooth/a2dp-codecs.h
+++ b/src/modules/bluetooth/a2dp-codecs.h
@@ -47,6 +47,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 +66,6 @@
#define MPEG_SAMPLING_FREQ_44100 (1 << 1)
#define MPEG_SAMPLING_FREQ_48000 1
-#define MAX_BITPOOL 64
-#define MIN_BITPOOL 2
#if __BYTE_ORDER == __LITTLE_ENDIAN
diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 2d8337317..9c4e3367b 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
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@@ -33,7 +34,7 @@
#include <pulsecore/refcnt.h>
#include <pulsecore/shared.h>
-#include "a2dp-codecs.h"
+#include "pa-a2dp-codec.h"
#include "bluez5-util.h"
@@ -48,8 +49,8 @@
#define BLUEZ_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported"
-#define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource"
-#define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink"
+#define A2DP_SOURCE_SBC_ENDPOINT "/MediaEndpoint/A2DPSourceSBC"
+#define A2DP_SINK_SBC_ENDPOINT "/MediaEndpoint/A2DPSinkSBC"
#define ENDPOINT_INTROSPECT_XML \
DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
@@ -170,9 +171,9 @@ 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) {
return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_SINK);
return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_SOURCE);
return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_HS)
static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const char *endpoint, const char *uuid) {
DBusMessage *m;
DBusMessageIter i, d;
- uint8_t codec = 0;
+ uint8_t capabilities[1024];
+ size_t capabilities_size;
+ uint8_t codec_id;
+ const pa_a2dp_codec_t *codec;
+
+ codec = pa_a2dp_endpoint_to_a2dp_codec(endpoint);
I think it would be better to change the function parameters so that
instead of an endpoint path the function would take a codec id. There
would be a function called get_a2dp_codec() that would take the codec
id as a parameter and return the codec struct. The endpoint path could
be added to the codec struct.

Shuffling things around like this wouldn't have huge benefits, I just
find looking up the codec based on the id neater than based on the
endpoint path.

I hope you don't have a problem with that suggestion, but if you do and
you want to keep using pa_a2dp_endpoint_to_a2dp_codec(), then the
function should be made static (it's not used outside bluez5-utils.c)
and the pa_ prefix should be dropped, since functions that aren't
exported through headers should not use the pa_ prefix according to our
coding style.
Post by Pali Rohár
+ if (!codec)
+ return;
As far as I can tell, this should never happen, so an assertion would
be better (and it could be in the lookup function so that every caller
doesn't need to add a check).
Post by Pali Rohár
pa_log_debug("Registering %s on adapter %s", endpoint, path);
+ codec_id = codec->codec_id;
+ capabilities_size = codec->fill_capabilities(capabilities, sizeof(capabilities));
+ pa_assert(capabilities_size != 0);
+
pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, path, BLUEZ_MEDIA_INTERFACE, "RegisterEndpoint"));
dbus_message_iter_init_append(m, &i);
@@ -899,23 +911,8 @@ static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const
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));
- }
+ pa_dbus_append_basic_variant_dict_entry(&d, "Codec", DBUS_TYPE_BYTE, &codec_id);
+ pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capabilities, capabilities_size);
dbus_message_iter_close_container(&i, &d);
@@ -964,8 +961,8 @@ 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);
+ register_endpoint(y, path, A2DP_SOURCE_SBC_ENDPOINT, PA_BLUETOOTH_UUID_A2DP_SOURCE);
+ register_endpoint(y, path, A2DP_SINK_SBC_ENDPOINT, PA_BLUETOOTH_UUID_A2DP_SINK);
} else if (pa_streq(interface, BLUEZ_DEVICE_INTERFACE)) {
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
-static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) {
- /* These bitpool values were chosen based on the A2DP spec recommendation */
- switch (freq) {
- return 53;
-
-
- switch (mode) {
- return 31;
-
- return 53;
- }
-
- pa_log_warn("Invalid channel mode %u", mode);
- return 53;
-
-
- switch (mode) {
- return 29;
-
- return 51;
- }
-
- pa_log_warn("Invalid channel mode %u", mode);
- return 51;
- }
-
- pa_log_warn("Invalid sampling freq %u", freq);
- return 53;
-}
-
const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile) {
switch(profile) {
return "a2dp_sink";
return "a2dp_source";
return "headset_head_unit";
@@ -1316,6 +1271,38 @@ const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile) {
return NULL;
}
+const char *pa_bluetooth_profile_to_a2dp_endpoint(pa_bluetooth_profile_t profile) {
This function isn't used outside bluez5-util.c, so it can be made
static and removed from bluez5-util.h. Then the pa_bluetooth_ prefix
should be dropped.
Post by Pali Rohár
+ switch (profile) {
+ return A2DP_SOURCE_SBC_ENDPOINT;
+ return A2DP_SINK_SBC_ENDPOINT;
+ return NULL;
I think it would be good to use pa_assert_not_reached() here. I assume
this won't be used in a context where a non-a2dp profile would be
passed to the function.
Post by Pali Rohár
+ }
+
+ return NULL;
This is redundant.
Post by Pali Rohár
+}
+
+const pa_a2dp_codec_t *pa_bluetooth_profile_to_a2dp_codec(pa_bluetooth_profile_t profile) {
+ switch (profile) {
+ return &pa_a2dp_codec_sbc;
+ return NULL;
+ }
+
+ return NULL;
+}
This function seems to be used also for checking whether a profile is
an a2dp profile. I think it would be clearer to have a separate
pa_bluetooth_profile_is_a2dp() function. Then this function could also
have an assertion rather than returning NULL for non-a2dp profiles.

If we don't tie the codec and the profile together, however, then
there's no need for an is_a2dp() function.
Post by Pali Rohár
+
+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;
+ else
+ return NULL;
pa_assert_not_reached() could be used here.
Post by Pali Rohár
+}
+
static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
pa_bluetooth_discovery *y = userdata;
pa_bluetooth_device *d;
@@ -1345,6 +1332,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,13 +1356,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;
- } else if (pa_streq(endpoint_path, A2DP_SINK_ENDPOINT)) {
+ p = PA_BLUETOOTH_PROFILE_A2DP_SBC_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_SOURCE;
+ p = PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE;
}
if (p == PA_BLUETOOTH_PROFILE_OFF) {
@@ -1389,7 +1377,7 @@ 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;
+ const pa_a2dp_codec_t *codec;
if (var != DBUS_TYPE_ARRAY) {
pa_log_error("Property %s of wrong type %c", key, (char)var);
@@ -1404,40 +1392,12 @@ 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 (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->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->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;
- }
+ codec = pa_a2dp_endpoint_to_a2dp_codec(endpoint_path);
+ pa_assert(codec);
- 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");
+ if (!codec->validate_configuration(config, size))
goto fail;
- }
}
dbus_message_iter_next(&props);
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;
- int i, size;
+ const char *endpoint_path;
+ uint8_t *cap;
+ int size;
+ const pa_a2dp_codec_t *codec;
+ uint8_t config[1024];
+ uint8_t *config_ptr = config;
+ size_t config_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);
@@ -1508,101 +1464,14 @@ static DBusMessage *endpoint_select_configuration(DBusConnection *conn, DBusMess
goto fail;
}
- if (size != sizeof(config)) {
- pa_log_error("Capabilities array has invalid size");
- goto fail;
- }
-
- 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;
- }
- }
-
- 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;
- }
- }
-
- 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 block lengths");
- goto fail;
- }
-
- 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;
- }
+ codec = pa_a2dp_endpoint_to_a2dp_codec(endpoint_path);
+ pa_assert(codec);
- 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(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;
+ config_size = codec->select_configuration(&y->core->default_sample_spec, cap, size, config, sizeof(config));
+ pa_assert(config_size != 0);
We don't trust input from bluetoothd to be always valid, so
select_configuration() can fail. Proper error handling is needed here.
Post by Pali Rohár
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(dbus_message_append_args(r, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &config_ptr, config_size, DBUS_TYPE_INVALID));
return r;
@@ -1677,7 +1546,7 @@ 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_SBC_ENDPOINT))
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
@@ -1709,38 +1578,22 @@ static void endpoint_init(pa_bluetooth_discovery *y, pa_bluetooth_profile_t prof
static const DBusObjectPathVTable vtable_endpoint = {
.message_function = endpoint_handler,
};
+ const char *endpoint = pa_bluetooth_profile_to_a2dp_endpoint(profile);
The function could just take the endpoint path as an argument.
pa_bluetooth_profile_t is not needed by this function.
Post by Pali Rohár
pa_assert(y);
+ pa_assert(endpoint);
- switch(profile) {
- pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT,
- &vtable_endpoint, y));
- break;
- pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT,
- &vtable_endpoint, y));
- break;
- pa_assert_not_reached();
- break;
- }
+ pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), endpoint,
+ &vtable_endpoint, y));
}
static void endpoint_done(pa_bluetooth_discovery *y, pa_bluetooth_profile_t profile) {
+ const char *endpoint = pa_bluetooth_profile_to_a2dp_endpoint(profile);
Same comment as above.
Post by Pali Rohár
+
pa_assert(y);
+ pa_assert(endpoint);
- switch(profile) {
- dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT);
- break;
- dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT);
- break;
- pa_assert_not_reached();
- break;
- }
+ dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), endpoint);
}
pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backend) {
@@ -1799,8 +1652,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_SOURCE);
+ endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK);
+ endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE);
get_managed_objects(y);
@@ -1868,8 +1721,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_SOURCE);
+ endpoint_done(y, PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK);
+ endpoint_done(y, PA_BLUETOOTH_PROFILE_A2DP_SBC_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..365b9ef6f 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -5,6 +5,7 @@
This file is part of PulseAudio.
Copyright 2008-2013 João Paulo Rechi Vita
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@@ -22,6 +23,8 @@
#include <pulsecore/core.h>
+#include "pa-a2dp-codec.h"
+
#define PA_BLUETOOTH_UUID_A2DP_SOURCE "0000110a-0000-1000-8000-00805f9b34fb"
#define PA_BLUETOOTH_UUID_A2DP_SINK "0000110b-0000-1000-8000-00805f9b34fb"
@@ -51,8 +54,8 @@ typedef enum pa_bluetooth_hook {
} pa_bluetooth_hook_t;
typedef enum profile {
- PA_BLUETOOTH_PROFILE_A2DP_SINK,
- PA_BLUETOOTH_PROFILE_A2DP_SOURCE,
+ PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK,
+ PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE,
PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT,
PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY,
PA_BLUETOOTH_PROFILE_OFF
@@ -163,6 +166,9 @@ pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_address(pa_bluetooth_d
pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook);
const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile);
+const char *pa_bluetooth_profile_to_a2dp_endpoint(pa_bluetooth_profile_t profile);
+const pa_a2dp_codec_t *pa_bluetooth_profile_to_a2dp_codec(pa_bluetooth_profile_t profile);
+const pa_a2dp_codec_t *pa_a2dp_endpoint_to_a2dp_codec(const char *endpoint);
static inline bool pa_bluetooth_uuid_is_hsp_hs(const char *uuid) {
return pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_HS) || pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_HS_ALT);
diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 9dbdca316..e626e80e9 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.
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@@ -25,7 +26,6 @@
#include <errno.h>
#include <arpa/inet.h>
-#include <sbc/sbc.h>
#include <pulse/rtclock.h>
#include <pulse/timeval.h>
@@ -47,8 +47,8 @@
#include <pulsecore/time-smoother.h>
#include "a2dp-codecs.h"
+#include "pa-a2dp-codec.h"
#include "bluez5-util.h"
-#include "rtp.h"
PA_MODULE_AUTHOR("João Paulo Rechi Vita");
PA_MODULE_DESCRIPTION("BlueZ 5 Bluetooth audio sink and source");
@@ -62,8 +62,6 @@ PA_MODULE_USAGE("path=<device object path>"
#define FIXED_LATENCY_RECORD_A2DP (25 * PA_USEC_PER_MSEC)
#define FIXED_LATENCY_RECORD_SCO (25 * PA_USEC_PER_MSEC)
-#define BITPOOL_DEC_LIMIT 32
-#define BITPOOL_DEC_STEP 5
#define HSP_MAX_GAIN 15
static const char* const valid_modargs[] = {
@@ -94,18 +92,6 @@ typedef struct bluetooth_msg {
PA_DEFINE_PRIVATE_CLASS(bluetooth_msg, pa_msgobject);
#define BLUETOOTH_MSG(o) (bluetooth_msg_cast(o))
-typedef struct sbc_info {
- sbc_t sbc; /* Codec data */
- bool sbc_initialized; /* Keep track if the encoder is initialized */
- size_t codesize, frame_length; /* SBC Codesize, frame_length. We simply cache those values here */
- 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 {
pa_module *module;
pa_core *core;
@@ -146,7 +132,11 @@ struct userdata {
pa_smoother *read_smoother;
pa_memchunk write_memchunk;
pa_sample_spec sample_spec;
- struct sbc_info sbc_info;
+ void *encoder_info;
+ void *decoder_info;
I think it would be better to put these inside the pa_a2dp_codec
struct, preferably behind a single void pointer named "userdata" (as
that would follow the usual PA code conventions). The various callbacks
in pa_a2dp_codec that now take a void** parameter as their first
argument would take a pa_a2dp_codec pointer instead.
Post by Pali Rohár
+
+ void* buffer; /* Codec transfer buffer */
+ size_t buffer_size; /* Size of the buffer */
};
typedef enum pa_bluetooth_form_factor {
@@ -418,105 +408,22 @@ 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_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;
+static int a2dp_write_buffer(struct userdata *u, size_t nbytes) {
int ret = 0;
- pa_assert(u);
- pa_assert(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK);
- pa_assert(u->sink);
-
- /* First, render some data */
- if (!u->write_memchunk.memblock)
- pa_sink_render_full(u->sink, u->write_block_size, &u->write_memchunk);
-
- pa_assert(u->write_memchunk.length == u->write_block_size);
-
- a2dp_prepare_buffer(u);
-
- sbc_info = &u->sbc_info;
- header = sbc_info->buffer;
- payload = (struct rtp_payload*) ((uint8_t*) sbc_info->buffer + sizeof(*header));
-
- frame_count = 0;
-
- /* Try to create a packet of the full MTU */
-
- 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);
-
- while (PA_LIKELY(to_encode > 0 && to_write > 0)) {
- ssize_t written;
- ssize_t encoded;
-
- encoded = sbc_encode(&sbc_info->sbc,
- p, to_encode,
- d, to_write,
- &written);
-
- if (PA_UNLIKELY(encoded <= 0)) {
- pa_log_error("SBC encoding error (%li)", (long) encoded);
- pa_memblock_release(u->write_memchunk.memblock);
- return -1;
- }
-
- pa_assert_fp((size_t) encoded <= to_encode);
- pa_assert_fp((size_t) encoded == sbc_info->codesize);
-
- pa_assert_fp((size_t) written <= to_write);
- pa_assert_fp((size_t) written == sbc_info->frame_length);
-
- p = (const uint8_t*) p + encoded;
- to_encode -= encoded;
-
- d = (uint8_t*) d + written;
- to_write -= written;
-
- frame_count++;
- }
-
- pa_memblock_release(u->write_memchunk.memblock);
-
- pa_assert(to_encode == 0);
-
- PA_ONCE_BEGIN {
- pa_log_debug("Using SBC encoder implementation: %s", pa_strnull(sbc_get_implementation_info(&sbc_info->sbc)));
- } PA_ONCE_END;
-
- /* write it to the fifo */
- memset(sbc_info->buffer, 0, sizeof(*header) + sizeof(*payload));
- header->v = 2;
- header->pt = 1;
- header->sequence_number = htons(sbc_info->seq_num++);
- header->timestamp = htonl(u->write_index / pa_frame_size(&u->sample_spec));
- header->ssrc = htonl(1);
- payload->frame_count = frame_count;
-
- nbytes = (uint8_t*) d - (uint8_t*) sbc_info->buffer;
-
for (;;) {
ssize_t l;
- l = pa_write(u->stream_fd, sbc_info->buffer, nbytes, &u->stream_write_type);
+ l = pa_write(u->stream_fd, u->buffer, nbytes, &u->stream_write_type);
pa_assert(l != 0);
@@ -560,37 +467,65 @@ static int a2dp_process_render(struct userdata *u) {
}
/* Run from IO thread */
+static int a2dp_process_render(struct userdata *u) {
+ const pa_a2dp_codec_t *codec;
+ const uint8_t *ptr;
+ size_t length;
+
+ pa_assert(u);
+ pa_assert(u->sink);
+
+ codec = pa_bluetooth_profile_to_a2dp_codec(u->profile);
+ pa_assert(codec);
+
+ /* First, render some data */
+ if (!u->write_memchunk.memblock)
+ pa_sink_render_full(u->sink, u->write_block_size, &u->write_memchunk);
+
+ pa_assert(u->write_memchunk.length == u->write_block_size);
+
+ a2dp_prepare_buffer(u);
+
+ /* Try to create a packet of the full MTU */
+ ptr = (const uint8_t *) pa_memblock_acquire_chunk(&u->write_memchunk);
+
+ length = codec->encode(&u->encoder_info, u->write_index / pa_frame_size(&u->sample_spec), ptr, u->write_memchunk.length, u->buffer, u->buffer_size);
+
+ pa_memblock_release(u->write_memchunk.memblock);
+
+ if (length == 0)
+ return -1;
+
+ return a2dp_write_buffer(u, length);
+}
+
+/* Run from IO thread */
static int a2dp_process_push(struct userdata *u) {
+ const pa_a2dp_codec_t *codec;
int ret = 0;
pa_memchunk memchunk;
pa_assert(u);
- pa_assert(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE);
pa_assert(u->source);
pa_assert(u->read_smoother);
+ codec = pa_bluetooth_profile_to_a2dp_codec(u->profile);
+ pa_assert(codec);
+
memchunk.memblock = pa_memblock_new(u->core->mempool, u->read_block_size);
memchunk.index = memchunk.length = 0;
for (;;) {
bool found_tstamp = false;
pa_usec_t tstamp;
- struct sbc_info *sbc_info;
- struct rtp_header *header;
- struct rtp_payload *payload;
- const void *p;
- void *d;
+ uint8_t *ptr;
ssize_t l;
- size_t to_write, to_decode;
+ size_t processed;
size_t total_written = 0;
a2dp_prepare_buffer(u);
- sbc_info = &u->sbc_info;
- header = sbc_info->buffer;
- payload = (struct rtp_payload*) ((uint8_t*) sbc_info->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 +542,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,50 +550,18 @@ static int a2dp_process_push(struct userdata *u) {
tstamp = pa_rtclock_now();
}
- p = (uint8_t*) sbc_info->buffer + sizeof(*header) + sizeof(*payload);
- to_decode = l - sizeof(*header) - sizeof(*payload);
-
- d = pa_memblock_acquire(memchunk.memblock);
- to_write = memchunk.length = pa_memblock_get_length(memchunk.memblock);
-
- while (PA_LIKELY(to_decode > 0)) {
- size_t written;
- ssize_t decoded;
-
- decoded = sbc_decode(&sbc_info->sbc,
- p, to_decode,
- d, to_write,
- &written);
-
- if (PA_UNLIKELY(decoded <= 0)) {
- pa_log_error("SBC decoding error (%li)", (long) decoded);
- pa_memblock_release(memchunk.memblock);
- pa_memblock_unref(memchunk.memblock);
- return 0;
- }
-
- total_written += written;
-
- /* Reset frame length, it can be changed due to bitpool change */
- sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
-
- pa_assert_fp((size_t) decoded <= to_decode);
- pa_assert_fp((size_t) decoded == sbc_info->frame_length);
+ ptr = pa_memblock_acquire(memchunk.memblock);
+ memchunk.length = pa_memblock_get_length(memchunk.memblock);
- pa_assert_fp((size_t) written == sbc_info->codesize);
-
- p = (const uint8_t*) p + decoded;
- to_decode -= decoded;
-
- d = (uint8_t*) d + written;
- to_write -= written;
- }
+ total_written = codec->decode(&u->decoder_info, u->buffer, l, ptr, memchunk.length, &processed);
+ if (total_written == 0)
+ return 0;
u->read_index += (uint64_t) total_written;
pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec));
pa_smoother_resume(u->read_smoother, tstamp, true);
- memchunk.length -= to_write;
+ memchunk.length -= l - processed;
pa_memblock_release(memchunk.memblock);
@@ -705,72 +608,6 @@ static void update_buffer_size(struct userdata *u) {
}
}
-/* Run from I/O thread */
-static void a2dp_set_bitpool(struct userdata *u, uint8_t bitpool) {
- struct sbc_info *sbc_info;
-
- pa_assert(u);
-
- sbc_info = &u->sbc_info;
-
- if (sbc_info->sbc.bitpool == bitpool)
- return;
-
- if (bitpool > sbc_info->max_bitpool)
- bitpool = sbc_info->max_bitpool;
- else if (bitpool < sbc_info->min_bitpool)
- bitpool = sbc_info->min_bitpool;
-
- sbc_info->sbc.bitpool = bitpool;
-
- sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc);
- sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
-
- pa_log_debug("Bitpool has changed to %u", sbc_info->sbc.bitpool);
-
- u->read_block_size =
- (u->read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
- / sbc_info->frame_length * sbc_info->codesize;
-
- u->write_block_size =
- (u->write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
- / sbc_info->frame_length * sbc_info->codesize;
-
- pa_sink_set_max_request_within_thread(u->sink, u->write_block_size);
- pa_sink_set_fixed_latency_within_thread(u->sink,
- FIXED_LATENCY_PLAYBACK_A2DP + pa_bytes_to_usec(u->write_block_size, &u->sample_spec));
-
- /* If there is still data in the memchunk, we have to discard it
- * because the write_block_size may have changed. */
- if (u->write_memchunk.memblock) {
- pa_memblock_unref(u->write_memchunk.memblock);
- pa_memchunk_reset(&u->write_memchunk);
- }
-
- update_buffer_size(u);
-}
-
-/* Run from I/O thread */
-static void a2dp_reduce_bitpool(struct userdata *u) {
- struct sbc_info *sbc_info;
- uint8_t bitpool;
-
- pa_assert(u);
-
- sbc_info = &u->sbc_info;
-
- /* Check if bitpool is already at its limit */
- if (sbc_info->sbc.bitpool <= BITPOOL_DEC_LIMIT)
- return;
-
- bitpool = sbc_info->sbc.bitpool - BITPOOL_DEC_STEP;
-
- if (bitpool < BITPOOL_DEC_LIMIT)
- bitpool = BITPOOL_DEC_LIMIT;
-
- a2dp_set_bitpool(u, bitpool);
-}
-
static void teardown_stream(struct userdata *u) {
if (u->rtpoll_item) {
pa_rtpoll_item_free(u->rtpoll_item);
@@ -860,32 +697,37 @@ static void transport_config_mtu(struct userdata *u) {
u->write_block_size = pa_frame_align(u->write_block_size, &u->sink->sample_spec);
}
} else {
- 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;
-
- 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;
+ const pa_a2dp_codec_t *codec = pa_bluetooth_profile_to_a2dp_codec(u->profile);
+ pa_assert(codec);
+ pa_assert(u->sink || u->source);
+ codec->fill_blocksize(u->sink ? &u->encoder_info : &u->decoder_info, u->read_link_mtu, u->write_link_mtu, &u->read_block_size, &u->write_block_size);
}
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 ?
FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_SCO) +
pa_bytes_to_usec(u->write_block_size, &u->sample_spec));
+
+ /* If there is still data in the memchunk, we have to discard it
+ * because the write_block_size may have changed. */
+ if (u->write_memchunk.memblock) {
+ pa_memblock_unref(u->write_memchunk.memblock);
+ pa_memchunk_reset(&u->write_memchunk);
+ }
}
if (u->source)
pa_source_set_fixed_latency_within_thread(u->source,
- (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE ?
+ (pa_bluetooth_profile_to_a2dp_codec(u->profile) ?
FIXED_LATENCY_RECORD_A2DP : FIXED_LATENCY_RECORD_SCO) +
pa_bytes_to_usec(u->read_block_size, &u->sample_spec));
}
/* Run from I/O thread */
static void setup_stream(struct userdata *u) {
+ const pa_a2dp_codec_t *codec;
struct pollfd *pollfd;
int one;
@@ -895,6 +737,12 @@ static void setup_stream(struct userdata *u) {
pa_log_info("Transport %s resuming", u->transport->path);
+ codec = pa_bluetooth_profile_to_a2dp_codec(u->profile);
+ if (codec) {
+ pa_assert(u->sink || u->source);
+ codec->setup(u->sink ? &u->encoder_info : &u->decoder_info);
+ }
+
transport_config_mtu(u);
pa_make_fd_nonblock(u->stream_fd);
@@ -904,12 +752,10 @@ 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));
- 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)
update_buffer_size(u);
- }
+
+ pa_log_debug("Stream properly set up, we're ready to roll!");
u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
@@ -1078,7 +924,7 @@ static int add_source(struct userdata *u) {
if (!u->transport_acquired)
switch (u->profile) {
data.suspend_cause = PA_SUSPEND_USER;
break;
@@ -1090,8 +936,9 @@ static int add_source(struct userdata *u) {
else
pa_assert_not_reached();
break;
If a switch statement has cases for all members of an enumeration, as
is the case here, then it's better to leave the default case out,
because the default case removes compiler warnings about unhandled
cases. If we add a new profile, it's good to get a warning if it's not
handled here.
Post by Pali Rohár
pa_assert_not_reached();
break;
}
@@ -1263,10 +1110,11 @@ static int add_sink(struct userdata *u) {
else
pa_assert_not_reached();
break;
/* Profile switch should have failed */
Same as above.
Post by Pali Rohár
pa_assert_not_reached();
break;
}
@@ -1296,111 +1144,11 @@ static void transport_config(struct userdata *u) {
u->sample_spec.channels = 1;
u->sample_spec.rate = 8000;
} else {
- sbc_info_t *sbc_info = &u->sbc_info;
- a2dp_sbc_t *config;
-
+ 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);
+ pa_assert(codec);
pa_assert(u->transport);
-
- u->sample_spec.format = PA_SAMPLE_S16LE;
- config = (a2dp_sbc_t *) u->transport->config;
-
- if (sbc_info->sbc_initialized)
- sbc_reinit(&sbc_info->sbc, 0);
- else
- sbc_init(&sbc_info->sbc, 0);
- sbc_info->sbc_initialized = true;
-
- switch (config->frequency) {
- sbc_info->sbc.frequency = SBC_FREQ_16000;
- u->sample_spec.rate = 16000U;
- break;
- sbc_info->sbc.frequency = SBC_FREQ_32000;
- u->sample_spec.rate = 32000U;
- break;
- sbc_info->sbc.frequency = SBC_FREQ_44100;
- u->sample_spec.rate = 44100U;
- break;
- sbc_info->sbc.frequency = SBC_FREQ_48000;
- u->sample_spec.rate = 48000U;
- break;
- pa_assert_not_reached();
- }
-
- switch (config->channel_mode) {
- sbc_info->sbc.mode = SBC_MODE_MONO;
- u->sample_spec.channels = 1;
- break;
- sbc_info->sbc.mode = SBC_MODE_DUAL_CHANNEL;
- u->sample_spec.channels = 2;
- break;
- sbc_info->sbc.mode = SBC_MODE_STEREO;
- u->sample_spec.channels = 2;
- break;
- sbc_info->sbc.mode = SBC_MODE_JOINT_STEREO;
- u->sample_spec.channels = 2;
- break;
- pa_assert_not_reached();
- }
-
- switch (config->allocation_method) {
- sbc_info->sbc.allocation = SBC_AM_SNR;
- break;
- sbc_info->sbc.allocation = SBC_AM_LOUDNESS;
- break;
- pa_assert_not_reached();
- }
-
- switch (config->subbands) {
- sbc_info->sbc.subbands = SBC_SB_4;
- break;
- sbc_info->sbc.subbands = SBC_SB_8;
- break;
- pa_assert_not_reached();
- }
-
- switch (config->block_length) {
- sbc_info->sbc.blocks = SBC_BLK_4;
- break;
- sbc_info->sbc.blocks = SBC_BLK_8;
- break;
- sbc_info->sbc.blocks = SBC_BLK_12;
- break;
- sbc_info->sbc.blocks = SBC_BLK_16;
- break;
- pa_assert_not_reached();
- }
-
- sbc_info->min_bitpool = config->min_bitpool;
- 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->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);
+ codec->init(is_sink ? &u->encoder_info : &u->decoder_info, &u->sample_spec, is_sink, u->transport->config, u->transport->config_size);
}
}
@@ -1421,7 +1169,7 @@ static int setup_transport(struct userdata *u) {
u->transport = t;
- if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY)
+ if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SBC_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;
@@ -1439,8 +1187,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_SOURCE] = PA_DIRECTION_INPUT,
+ [PA_BLUETOOTH_PROFILE_A2DP_SBC_SINK] = PA_DIRECTION_OUTPUT,
+ [PA_BLUETOOTH_PROFILE_A2DP_SBC_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
@@ -1477,11 +1225,11 @@ 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_HEADSET_HEAD_UNIT || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
+ if ((n_written = sco_process_render(u)) < 0)
return -1;
} else {
- if ((n_written = sco_process_render(u)) < 0)
+ if ((n_written = a2dp_process_render(u)) < 0)
return -1;
}
@@ -1551,7 +1299,7 @@ static void thread_func(void *userdata) {
if (pollfd->revents & POLLIN) {
int n_read;
- if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE)
+ if (pa_bluetooth_profile_to_a2dp_codec(u->profile))
n_read = a2dp_process_push(u);
else
n_read = sco_process_push(u);
@@ -1651,8 +1399,13 @@ 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->sink) {
+ const pa_a2dp_codec_t *codec = pa_bluetooth_profile_to_a2dp_codec(u->profile);
+ if (codec && codec->fix_latency(&u->encoder_info)) {
+ transport_config_mtu(u);
+ update_buffer_size(u);
+ }
+ }
}
blocks_to_write = 1;
@@ -1767,7 +1520,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_SOURCE)
+ if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY || u->profile == PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE)
u->source->priority = 1500;
pa_source_put(u->source);
@@ -1980,8 +1733,8 @@ static pa_card_profile *create_card_profile(struct userdata *u, pa_bluetooth_pro
name = pa_bluetooth_profile_to_string(profile);
switch (profile) {
- cp = pa_card_profile_new(name, _("High Fidelity Playback (A2DP Sink)"), sizeof(pa_bluetooth_profile_t));
+ 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,8 +1745,8 @@ static pa_card_profile *create_card_profile(struct userdata *u, pa_bluetooth_pro
p = PA_CARD_PROFILE_DATA(cp);
break;
- cp = pa_card_profile_new(name, _("High Fidelity Capture (A2DP Source)"), sizeof(pa_bluetooth_profile_t));
+ cp = pa_card_profile_new(name, _("High Fidelity SBC Capture (A2DP Source)"), sizeof(pa_bluetooth_profile_t));
cp->priority = 20;
cp->n_sinks = 0;
cp->n_sources = 1;
static int uuid_to_profile(const char *uuid, pa_bluetooth_profile_t *_r) {
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;
+ *_r = PA_BLUETOOTH_PROFILE_A2DP_SBC_SOURCE;
else if (pa_bluetooth_uuid_is_hsp_hs(uuid) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_HF))
*_r = PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT;
else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_AG) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_AG))
void pa__done(pa_module *m) {
struct userdata *u;
+ const pa_a2dp_codec_t *codec;
pa_assert(m);
@@ -2492,11 +2246,14 @@ 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);
+ codec = pa_bluetooth_profile_to_a2dp_codec(u->profile);
+ if (codec) {
+ codec->finish(&u->encoder_info);
+ codec->finish(&u->decoder_info);
+ }
if (u->msg)
pa_xfree(u->msg);
diff --git a/src/modules/bluetooth/pa-a2dp-codec-sbc.c b/src/modules/bluetooth/pa-a2dp-codec-sbc.c
new file mode 100644
index 000000000..2a61114a6
--- /dev/null
+++ b/src/modules/bluetooth/pa-a2dp-codec-sbc.c
@@ -0,0 +1,579 @@
+/***
+ This file is part of PulseAudio.
+
+
+ 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/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/once.h>
+#include <pulse/sample.h>
+#include <pulse/xmalloc.h>
+
+#include <arpa/inet.h>
+
+#include <sbc/sbc.h>
+
+#include "a2dp-codecs.h"
+#include "pa-a2dp-codec.h"
+#include "rtp.h"
+
+#define SBC_BITPOOL_DEC_LIMIT 32
+#define SBC_BITPOOL_DEC_STEP 5
+
+typedef struct sbc_info {
+ sbc_t sbc; /* Codec data */
+ bool sbc_initialized; /* Keep track if the encoder is initialized */
+ size_t codesize, frame_length; /* SBC Codesize, frame_length. We simply cache those values here */
+ uint16_t seq_num; /* Cumulative packet sequence */
+ uint8_t min_bitpool;
+ uint8_t max_bitpool;
+} sbc_info_t;
According to our coding style, structs that aren't exported in any
headers should not be typedef'd. And if structs are typedef'd, they
shouldn't have a "_t" suffix. (Yes, the typedef existed in the old code
too, but I thought I'd mention this anyway.)
Post by Pali Rohár
+
+static size_t pa_sbc_fill_capabilities(uint8_t *capabilities_buffer, size_t capabilities_size) {
Static functions shouldn't have the "pa_" prefix. The "sbc_" prefix
feels a bit redundant too. This comment applies to all functions in
this file.
Post by Pali Rohár
+ a2dp_sbc_t *capabilities = (a2dp_sbc_t *) capabilities_buffer;
+
+ if (capabilities_size < sizeof(*capabilities)) {
+ pa_log_error("Invalid size of capabilities buffer");
+ return 0;
+ }
+
+ pa_zero(*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;
+
+ return sizeof(*capabilities);
+}
+
+static bool pa_sbc_validate_configuration(const uint8_t *config_buffer, size_t config_size) {
+ a2dp_sbc_t *config = (a2dp_sbc_t *) config_buffer;
+
+ if (config_size != sizeof(*config)) {
+ pa_log_error("Invalid size of config buffer");
+ return false;
+ }
+
+ if (config->frequency != SBC_SAMPLING_FREQ_16000 && config->frequency != SBC_SAMPLING_FREQ_32000 &&
+ config->frequency != SBC_SAMPLING_FREQ_44100 && config->frequency != SBC_SAMPLING_FREQ_48000) {
+ pa_log_error("Invalid sampling frequency in configuration");
+ return false;
+ }
+
+ if (config->channel_mode != SBC_CHANNEL_MODE_MONO && config->channel_mode != SBC_CHANNEL_MODE_DUAL_CHANNEL &&
+ config->channel_mode != SBC_CHANNEL_MODE_STEREO && config->channel_mode != SBC_CHANNEL_MODE_JOINT_STEREO) {
+ pa_log_error("Invalid channel mode in configuration");
+ return false;
+ }
+
+ if (config->allocation_method != SBC_ALLOCATION_SNR && config->allocation_method != SBC_ALLOCATION_LOUDNESS) {
+ pa_log_error("Invalid allocation method in configuration");
+ return false;
+ }
+
+ if (config->subbands != SBC_SUBBANDS_4 && config->subbands != SBC_SUBBANDS_8) {
+ pa_log_error("Invalid SBC subbands in configuration");
+ return false;
+ }
+
+ if (config->block_length != SBC_BLOCK_LENGTH_4 && config->block_length != SBC_BLOCK_LENGTH_8 &&
+ config->block_length != SBC_BLOCK_LENGTH_12 && config->block_length != SBC_BLOCK_LENGTH_16) {
+ pa_log_error("Invalid block length in configuration");
+ return false;
+ }
+
+ return true;
+}
+
+static uint8_t pa_sbc_default_bitpool(uint8_t freq, uint8_t mode) {
+ /* These bitpool values were chosen based on the A2DP spec recommendation */
+ switch (freq) {
+ return 53;
+
+
+ switch (mode) {
+ return 31;
+
+ return 53;
+ }
+
+ pa_log_warn("Invalid channel mode %u", mode);
+ return 53;
+
+
+ switch (mode) {
+ return 29;
+
+ return 51;
+ }
+
+ pa_log_warn("Invalid channel mode %u", mode);
+ return 51;
+ }
+
+ pa_log_warn("Invalid sampling freq %u", freq);
+ return 53;
+}
+
+static size_t pa_sbc_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_sbc_t *config = (a2dp_sbc_t *) config_buffer;
+ a2dp_sbc_t *capabilities = (a2dp_sbc_t *) capabilities_buffer;
This looks likely to cause alignment errors, since the data in the
uint8_t arrays doesn't have any kind of alignment guarantees. However,
a2dp_sbc_t happens to only contain uint8_t values, so the values can't
be badly aligned. It would be good to have a comment that reassures the
reader that there won't be any alignment errors. Suggestion: "We cast a
byte array to struct here, which can often cause alignment errors, but
in this case it's safe, because a2dp_sbc_t contains only single-byte
values."

There are several more functions that cast a byte array to a2dp_sbc_t,
the same applies to them.
Post by Pali Rohár
+ int i;
+
+ 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 (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);
+
+ /* 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;
+ }
+ }
+
+ pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table));
+
+ if (sample_spec->channels <= 1) {
+ if (capabilities->channel_mode & SBC_CHANNEL_MODE_MONO)
+ config->channel_mode = SBC_CHANNEL_MODE_MONO;
+ else if (capabilities->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
+ config->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
+ else if (capabilities->channel_mode & SBC_CHANNEL_MODE_STEREO)
+ config->channel_mode = SBC_CHANNEL_MODE_STEREO;
+ else if (capabilities->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
+ config->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
+ else {
+ pa_log_error("No supported channel modes");
+ return 0;
+ }
+ }
+
+ if (sample_spec->channels >= 2) {
+ if (capabilities->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
+ config->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
+ else if (capabilities->channel_mode & SBC_CHANNEL_MODE_STEREO)
+ config->channel_mode = SBC_CHANNEL_MODE_STEREO;
+ else if (capabilities->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
+ config->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
+ else if (capabilities->channel_mode & SBC_CHANNEL_MODE_MONO)
+ config->channel_mode = SBC_CHANNEL_MODE_MONO;
+ else {
+ pa_log_error("No supported channel modes");
+ return 0;
+ }
+ }
+
+ if (capabilities->block_length & SBC_BLOCK_LENGTH_16)
+ config->block_length = SBC_BLOCK_LENGTH_16;
+ else if (capabilities->block_length & SBC_BLOCK_LENGTH_12)
+ config->block_length = SBC_BLOCK_LENGTH_12;
+ else if (capabilities->block_length & SBC_BLOCK_LENGTH_8)
+ config->block_length = SBC_BLOCK_LENGTH_8;
+ else if (capabilities->block_length & SBC_BLOCK_LENGTH_4)
+ config->block_length = SBC_BLOCK_LENGTH_4;
+ else {
+ pa_log_error("No supported block lengths");
+ return 0;
+ }
+
+ if (capabilities->subbands & SBC_SUBBANDS_8)
+ config->subbands = SBC_SUBBANDS_8;
+ else if (capabilities->subbands & SBC_SUBBANDS_4)
+ config->subbands = SBC_SUBBANDS_4;
+ else {
+ pa_log_error("No supported subbands");
+ return 0;
+ }
+
+ if (capabilities->allocation_method & SBC_ALLOCATION_LOUDNESS)
+ config->allocation_method = SBC_ALLOCATION_LOUDNESS;
+ else if (capabilities->allocation_method & SBC_ALLOCATION_SNR)
+ config->allocation_method = SBC_ALLOCATION_SNR;
Error handling for unsupported allocation method is missing. Not your
fault, and this doesn't look serious (because the final configuration
is validated anyway), but you can fix this if you want.
Post by Pali Rohár
+
+ config->min_bitpool = (uint8_t) PA_MAX(SBC_MIN_BITPOOL, capabilities->min_bitpool);
+ config->max_bitpool = (uint8_t) PA_MIN(pa_sbc_default_bitpool(config->frequency, config->channel_mode), capabilities->max_bitpool);
+
+ if (config->min_bitpool > config->max_bitpool)
+ return 0;
+
+ return sizeof(*config);
+}
+
+static void pa_sbc_init(void **info_ptr, pa_sample_spec *sample_spec, bool is_a2dp_sink, const uint8_t *config_buffer, size_t config_size) {
+ sbc_info_t *sbc_info;
+ a2dp_sbc_t *config = (a2dp_sbc_t *) config_buffer;
+
+ pa_assert(config_size == sizeof(*config));
+
+ if (!*info_ptr)
+ *info_ptr = pa_xmalloc0(sizeof(*sbc_info));
+
+ sbc_info = (sbc_info_t *) *info_ptr;
+
+ sample_spec->format = PA_SAMPLE_S16LE;
+
+ if (sbc_info->sbc_initialized)
+ sbc_reinit(&sbc_info->sbc, 0);
+ else
+ sbc_init(&sbc_info->sbc, 0);
+ sbc_info->sbc_initialized = true;
+
+ switch (config->frequency) {
+ sbc_info->sbc.frequency = SBC_FREQ_16000;
+ sample_spec->rate = 16000U;
+ break;
+ sbc_info->sbc.frequency = SBC_FREQ_32000;
+ sample_spec->rate = 32000U;
+ break;
+ sbc_info->sbc.frequency = SBC_FREQ_44100;
+ sample_spec->rate = 44100U;
+ break;
+ sbc_info->sbc.frequency = SBC_FREQ_48000;
+ sample_spec->rate = 48000U;
+ break;
+ pa_assert_not_reached();
+ }
+
+ switch (config->channel_mode) {
+ sbc_info->sbc.mode = SBC_MODE_MONO;
+ sample_spec->channels = 1;
+ break;
+ sbc_info->sbc.mode = SBC_MODE_DUAL_CHANNEL;
+ sample_spec->channels = 2;
+ break;
+ sbc_info->sbc.mode = SBC_MODE_STEREO;
+ sample_spec->channels = 2;
+ break;
+ sbc_info->sbc.mode = SBC_MODE_JOINT_STEREO;
+ sample_spec->channels = 2;
+ break;
+ pa_assert_not_reached();
+ }
+
+ switch (config->allocation_method) {
+ sbc_info->sbc.allocation = SBC_AM_SNR;
+ break;
+ sbc_info->sbc.allocation = SBC_AM_LOUDNESS;
+ break;
+ pa_assert_not_reached();
+ }
+
+ switch (config->subbands) {
+ sbc_info->sbc.subbands = SBC_SB_4;
+ break;
+ sbc_info->sbc.subbands = SBC_SB_8;
+ break;
+ pa_assert_not_reached();
+ }
+
+ switch (config->block_length) {
+ sbc_info->sbc.blocks = SBC_BLK_4;
+ break;
+ sbc_info->sbc.blocks = SBC_BLK_8;
+ break;
+ sbc_info->sbc.blocks = SBC_BLK_12;
+ break;
+ sbc_info->sbc.blocks = SBC_BLK_16;
+ break;
+ pa_assert_not_reached();
+ }
+
+ sbc_info->min_bitpool = config->min_bitpool;
+ sbc_info->max_bitpool = config->max_bitpool;
+
+ /* Set minimum bitpool for source to get the maximum possible block_size */
+ sbc_info->sbc.bitpool = is_a2dp_sink ? sbc_info->max_bitpool : sbc_info->min_bitpool;
Do you understand the logic here? If you do, could you make the comment
clearer? Why does minimum bitpool for source result in maximum block
size, and why does it matter? I thought that small bitpools result in
small blocks. Also my understanding is that if we're the receiving side
of an a2dp stream, then the bitpool, codesize and frame length can
change without warning on any packet, so it doesn't really matter what
values we set here for sources. Is my understanding wrong?

This led me to wonder about another thing. sbc.h has this
documentation:

/* Decodes ONE input block into ONE output block */
ssize_t sbc_decode(sbc_t *sbc, const void *input, size_t input_len,
void *output, size_t output_len, size_t *written);

/* Encodes ONE input block into ONE output block */
ssize_t sbc_encode(sbc_t *sbc, const void *input, size_t input_len,
void *output, size_t output_len, ssize_t *written);

/* Returns the output block size in bytes */
size_t sbc_get_frame_length(sbc_t *sbc);

/* Returns the input block size in bytes */
size_t sbc_get_codesize(sbc_t *sbc);

According to those comments "input block" means compressed audio when
decoding and uncompressed audio when encoding, and vice versa for
"output block". That's fine, no problem. But then the comments say that
"frame length" is the same thing as the output block size and
"codesize" is the same thing as the input block size. That seems weird
- I would have expected that "frame length" means decompressed block
size and "codesize" means compressed size (or perhaps vice versa).
Which is wrong, my expectations or the documentation? In any case, it
would be good to document in more detail the meaning of frame length
and codesize in the sbc_info struct.

... Update after reading the encoding and decoding code: it seems that
"frame length" always means the compressed block size and "codesize"
means the uncompressed block size, so the sbc.h documentation is wrong
(assuming that we don't have bugs related to this in PulseAudio, but
I'm pretty sure that's not the case, because otherwise there would be
crashing all the time). Maybe it would be good to rename the variables
to "compressed_block_size" and "uncompressed_block_size"? That would
make their meaning more obvious at least to me.
Post by Pali Rohár
+ 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);
+}
+
+static void pa_sbc_finish(void **info_ptr) {
+ sbc_info_t *sbc_info = (sbc_info_t *) *info_ptr;
+
+ if (!sbc_info)
+ return;
+
+ if (sbc_info->sbc_initialized)
+ sbc_finish(&sbc_info->sbc);
+
+ pa_xfree(sbc_info);
+ *info_ptr = NULL;
+}
+
+static void pa_sbc_set_bitpool(sbc_info_t *sbc_info, uint8_t bitpool) {
+ if (bitpool > sbc_info->max_bitpool)
+ bitpool = sbc_info->max_bitpool;
+ else if (bitpool < sbc_info->min_bitpool)
+ bitpool = sbc_info->min_bitpool;
+
+ sbc_info->sbc.bitpool = bitpool;
+
+ sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc);
+ sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
+
+ pa_log_debug("Bitpool has changed to %u", sbc_info->sbc.bitpool);
+}
+
+static void pa_sbc_setup(void **info_ptr) {
+ sbc_info_t *sbc_info = (sbc_info_t *) *info_ptr;
+
+ pa_sbc_set_bitpool(sbc_info, sbc_info->max_bitpool);
+}
+
+static void pa_sbc_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) {
+ sbc_info_t *sbc_info = (sbc_info_t *) *info_ptr;
+
+ *read_block_size =
+ (read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+ / sbc_info->frame_length * sbc_info->codesize;
+
+ *write_block_size =
+ (write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+ / sbc_info->frame_length * sbc_info->codesize;
+}
+
+static bool pa_sbc_fix_latency(void **info_ptr) {
+ sbc_info_t *sbc_info = (sbc_info_t *) *info_ptr;
+ uint8_t bitpool;
+
+ /* Check if bitpool is already at its limit */
+ if (sbc_info->sbc.bitpool <= SBC_BITPOOL_DEC_LIMIT)
+ return false;
+
+ bitpool = sbc_info->sbc.bitpool - SBC_BITPOOL_DEC_STEP;
+
+ if (bitpool < SBC_BITPOOL_DEC_LIMIT)
+ bitpool = SBC_BITPOOL_DEC_LIMIT;
+
+ if (sbc_info->sbc.bitpool == bitpool)
+ return false;
+
+ pa_sbc_set_bitpool(sbc_info, bitpool);
+ return true;
+}
+
+static size_t pa_sbc_encode(void **info_ptr, uint32_t timestamp, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size) {
+ sbc_info_t *sbc_info = (sbc_info_t *) *info_ptr;
+ struct rtp_header *header;
+ struct rtp_payload *payload;
+ uint8_t *d;
+ const uint8_t *p;
+ size_t to_write, to_encode;
+ unsigned frame_count;
+
+ header = (struct rtp_header*) output_buffer;
+ payload = (struct rtp_payload*) (output_buffer + sizeof(*header));
+
+ frame_count = 0;
+
+ p = input_buffer;
+ to_encode = input_size;
+
+ d = output_buffer + sizeof(*header) + sizeof(*payload);
+ to_write = output_size - sizeof(*header) - sizeof(*payload);
+
+ while (PA_LIKELY(to_encode > 0 && to_write > 0)) {
+ ssize_t written;
+ ssize_t encoded;
+
+ encoded = sbc_encode(&sbc_info->sbc,
+ p, to_encode,
+ d, to_write,
+ &written);
+
+ if (PA_UNLIKELY(encoded <= 0)) {
+ pa_log_error("SBC encoding error (%li)", (long) encoded);
+ return 0;
+ }
+
+ pa_assert_fp((size_t) encoded <= to_encode);
+ pa_assert_fp((size_t) encoded == sbc_info->codesize);
+
+ pa_assert_fp((size_t) written <= to_write);
+ pa_assert_fp((size_t) written == sbc_info->frame_length);
+
+ p += encoded;
+ to_encode -= encoded;
+
+ d += written;
+ to_write -= written;
+
+ frame_count++;
+ }
+
+ pa_assert(to_encode == 0);
+
+ PA_ONCE_BEGIN {
+ pa_log_debug("Using SBC encoder implementation: %s", pa_strnull(sbc_get_implementation_info(&sbc_info->sbc)));
+ } PA_ONCE_END;
+
+ /* write it to the fifo */
+ memset(output_buffer, 0, sizeof(*header) + sizeof(*payload));
+ header->v = 2;
+ header->pt = 1;
+ header->sequence_number = htons(sbc_info->seq_num++);
+ header->timestamp = htonl(timestamp);
+ header->ssrc = htonl(1);
+ payload->frame_count = frame_count;
+
+ return d - output_buffer;
+}
+
+static size_t pa_sbc_decode(void **info_ptr, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) {
+ sbc_info_t *sbc_info = (sbc_info_t *) *info_ptr;
+
+ struct rtp_header *header;
+ struct rtp_payload *payload;
+ const uint8_t *p;
+ uint8_t *d;
+ size_t to_write, to_decode;
+
+ header = (struct rtp_header *) input_buffer;
+ payload = (struct rtp_payload*) (input_buffer + sizeof(*header));
+
+ p = input_buffer + sizeof(*header) + sizeof(*payload);
+ to_decode = input_size - sizeof(*header) - sizeof(*payload);
+
+ d = output_buffer;
+ to_write = output_size;
+
+ while (PA_LIKELY(to_decode > 0)) {
+ size_t written;
+ ssize_t decoded;
+
+ decoded = sbc_decode(&sbc_info->sbc,
+ p, to_decode,
+ d, to_write,
+ &written);
+
+ if (PA_UNLIKELY(decoded <= 0)) {
+ pa_log_error("SBC decoding error (%li)", (long) decoded);
+ *processed = p - input_buffer;
+ return 0;
+ }
+
+ /* Reset frame length, it can be changed due to bitpool change */
+ sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
+
+ pa_assert_fp((size_t) decoded <= to_decode);
+ pa_assert_fp((size_t) decoded == sbc_info->frame_length);
+
+ pa_assert_fp((size_t) written == sbc_info->codesize);
+
+ 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_sbc = {
+ .codec_id = A2DP_CODEC_SBC,
+ .fill_capabilities = pa_sbc_fill_capabilities,
+ .validate_configuration = pa_sbc_validate_configuration,
+ .select_configuration = pa_sbc_select_configuration,
+ .init = pa_sbc_init,
+ .finish = pa_sbc_finish,
+ .setup = pa_sbc_setup,
+ .fill_blocksize = pa_sbc_fill_blocksize,
+ .fix_latency = pa_sbc_fix_latency,
+ .encode = pa_sbc_encode,
+ .decode = pa_sbc_decode,
+};
diff --git a/src/modules/bluetooth/pa-a2dp-codec.h b/src/modules/bluetooth/pa-a2dp-codec.h
new file mode 100644
index 000000000..68b1619c2
--- /dev/null
+++ b/src/modules/bluetooth/pa-a2dp-codec.h
@@ -0,0 +1,40 @@
+#ifndef foopaa2dpcodechfoo
+#define foopaa2dpcodechfoo
+
+/***
+ This file is part of PulseAudio.
+
+
+ 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/>.
+***/
+
+typedef struct pa_a2dp_codec {
+ const char *codec_name;
This is currently unused, so it should be removed unless you come up
with some use for it.
Post by Pali Rohár
+ uint8_t codec_id;
+ size_t (*fill_capabilities)(uint8_t *, size_t);
It would be good to have some documentation for the callbacks. Naming
the function parameters also makes it easier to understand the code.
Imagine being someone who's not that familiar with the code but has
decided to add support for a new codec and needs to write
implementations for the callbacks.
Post by Pali Rohár
+ bool (*validate_configuration)(const uint8_t *, size_t);
The bool return value is used for reporting failures, but according to
our coding style failures should be reported using a negative int.
Post by Pali Rohár
+ size_t (*select_configuration)(const pa_sample_spec *, const uint8_t *, size_t, uint8_t *, size_t);
+ void (*init)(void **, pa_sample_spec *, bool, const uint8_t *, size_t);
+ void (*finish)(void **);
+ void (*setup)(void **);
+ void (*fill_blocksize)(void **, size_t, size_t, size_t *, size_t *);
+ bool (*fix_latency)(void **);
I feel this callback name is not very good. The callback reduces the
bitpool (in case of SBC), it doesn't fix the latency. Fixing the
latency is left to the caller.

I suggest changing the name to "reduce_bitrate" (sufficiently generic
to not be specific to SBC). I also suggest some restructuring, because
the logic behind calling transport_config_mtu() and
update_buffer_size() after fix_latency() wasn't obvious. Restructuring
ideas:

1) Allow the reduce_bitrate() callback to directly update the block
size. The callback should return true only if the block size was
changed.

This will require access to the MTU sizes from reduce_bitrate(), so
fill_blocksize() should save the MTU sizes in the codec private data
(or the MTU could be passed as arguments to reduce_bitrate(), but I
think it's cleaner to save the previously set MTU in the codec private
data). It might make sense to rename fill_blocksize() to set_mtu().

2) Add a handle_block_size_change() function and move there the sink
latency update code from transport_config_mtu() and the code from
update_buffer_size(). Call handle_block_size_change() from
transport_configu_mtu() and after the reduce_bitrate() call. Remove the
transport_mtu_config() and update_buffer_size() calls from
thread_function(). Remove the update_buffer_size() call from
setup_stream(), it's not needed because transport_mtu_config() will
update the kernel buffer size via handle_block_size_change().
update_buffer_size() can be removed altogheter.

As a result the code in thread_func() will look like this, which I find
much more self-explanatory:

bool block_size_changed = codec->reduce_bitrate(&u->encoder_info, &u->write_block_size);

if (block_size_changed)
handle_block_size_change(u);
Post by Pali Rohár
+ size_t (*encode)(void **, uint32_t, const uint8_t *, size_t, uint8_t *, size_t);
+ size_t (*decode)(void **, const uint8_t *, size_t, uint8_t *, size_t, size_t *);
+} pa_a2dp_codec_t;
According to our coding style, struct names shouldn't contain the _t
suffix. The _t suffix is only used with basic types like ints.
Post by Pali Rohár
+
+extern const pa_a2dp_codec_t pa_a2dp_codec_sbc;
+
+#endif
--
Tanu

https://www.patreon.com/tanuk
https://liberapay.com/tanuk
Tanu Kaskinen
2018-09-05 09:22:28 UTC
Permalink
Post by Tanu Kaskinen
Post by Pali Rohár
+static size_t pa_sbc_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_sbc_t *config = (a2dp_sbc_t *) config_buffer;
+ a2dp_sbc_t *capabilities = (a2dp_sbc_t *) capabilities_buffer;
This looks likely to cause alignment errors, since the data in the
uint8_t arrays doesn't have any kind of alignment guarantees. However,
a2dp_sbc_t happens to only contain uint8_t values, so the values can't
be badly aligned. It would be good to have a comment that reassures the
reader that there won't be any alignment errors. Suggestion: "We cast a
byte array to struct here, which can often cause alignment errors, but
in this case it's safe, because a2dp_sbc_t contains only single-byte
values."
a2dp_aptx_t has variables that are bigger than just one byte, so I
thought that we're going to have alignment issues with it. However, I
found out now that packed structs don't have alignment restrictions,
because the compiler will always be extra careful when dealing with
them. Therefore a better comment would be: "We cast a byte array to
struct here, which can often cause alignment errors, but in this case
it's safe, because a2dp_sbc_t uses the packed attribute, which makes
the compiler extra careful."

Copying that comment everywhere where we do the casting may not be such
a great idea after all, though. So much repetition. Maybe just add to
the struct definition a comment that explains what the packed attribute
does.
--
Tanu

https://www.patreon.com/tanuk
https://liberapay.com/tanuk
Tanu Kaskinen
2018-09-17 12:02:18 UTC
Permalink
This post might be inappropriate. Click to display it.
Pali Rohár
2018-09-27 08:27:06 UTC
Permalink
Post by Tanu Kaskinen
(I sent my review both to you and to the list, but you replied only
privately - probably not on purpose. I'll send this mail only to the
list.)
Sorry for that. I was not my intension.
Post by Tanu Kaskinen
Post by Tanu Kaskinen
Using libopenaptx directly
doesn't prevent us from switching to GStreamer later if someone writes
the code.
Yes, that is truth.
Post by Tanu Kaskinen
Another point that was raised that the codec choice shouldn't be bound
to the card profile. I tend to agree with that. There are situations
where module-bluetooth-policy or the user only wants to switch from HSP
to A2DP and not care about the codec. You asked how the codec should be
negotiated or switched by the user if it's not bound to the profile.
Regarding negotiation, we can add a hook that module-bluetooth-policy
can use to make the codec decision (if module-bluetooth-policy isn't
loaded, SBC seems like a sensible default).
Is there a need for the user to be able to choose the codec? Shouldn't
we always automatically pick the highest-quality one?
What is "highest-quality" codec? How you would compare e.g. our SBC
implementation which can dynamically change bitpool and therefore
quality?
Also how you can compare... has SBC higher quality as MPEG1? Or has aptX
HD higher quality as MPEG/AAC?
Fair point.
Until now everybody said that aptX "is better" then SBC. But today it
does not look like it is truth.
Also if you have MP3 files on disk, then the best quality which you can
achieve is to passthru it without any reencoding. In other cases AAC can
be better.
Passthrough is a separate thing. If someone implements passthrough
support for bluetooth and a client requests it, we should always switch
to the requested codec regardless of other user preferences.
So answer to this question depends on lot of things and either user
itself or user's application would better it.
Post by Tanu Kaskinen
I'm not against
adding the functionality, it would be useful for testing if nothing
else. It just doesn't seem very important.
We could have a "preferred-a2dp-codec" option in bluetooth.conf (that
file doesn't exist, but can be added). A per-card option would be
possible too, as would be having both a global option that could be
overridden with a per-card option.
Preferred codec is not helpful. The only mandatory codec is SBC, all
other are optional. And basically every manufactor supports different
set of codecs. So at least some kind of list or partially ordered set of
codec is needed.
Sure, a priority list can better capture the user preferences, but
surely a "preferred codec" option is better than nothing. A priority
list of codecs isn't an obvious ultimate solution either, since some
codecs have adjustable parameters that can affect the codec preference
SBC (high bitrate)
aptX
SBC (medium bitrate)
It's not clear to me how fine-grained control is optimal. A solution
that allows specifying a priority list with an arbitrary number of
codec parameters would obviously be sufficient, but it's unlikely we
want that complex system.
The best thing is allowing user to choose codec itself (in some GUI
under section additional settings, or similar) and ideally remember it
for every device separately.
Post by Tanu Kaskinen
Real use cases are needed. I can't provide any, since I don't use
bluetooth (and if I did, I probably wouldn't bother with the codec
settings at all - SBC and aptX seem both good enough for me). Now that
you found out that aptX isn't that great after all, do you have
personal use for aptX either?
This is interesting question. For more then month I'm using aptX codec
and I have not hear difference or something which would disturb me.

So I probably really do not need aptX at all.
Post by Tanu Kaskinen
Post by Tanu Kaskinen
For runtime configuration, we can add a command to set the codec
preference. This should be done with the message API that Georg Chini
has been working on (not yet finished). There's then need for saving
the choice too. We can either introduce a new database for bluetooth
stuff, or we can add some API to module-card-restore so that other
modules (module-bluetooth-discover in this case) can save arbitrary
parameters for cards.
I recall there was some lack of relevant APIs in bluetoothd for
choosing the codec from PulseAudio. Can you remind me, what are the
limitations, and how did you plan to deal with them?
Currently when bluetooth headset initialize connection, it is up to
headset which codec would choose. I did some testing and my headset
choose codec (sbc vs aptx) just randomly.
When bluez initialize connection, then it prefer codecs in order in
which pulseaudio did dbus endpoint registration. Order is global and
hardcoded in source code.
Once connection is established there is no way to change codec. bluez
does not provide API for it.
So everything which you wrote above would apply only when pulseaudio
starts a2dp connection, not headset.
And I think it is very bad if we cannot achieve stable codec selection.
So what do you propose? If your point was that my suggestions result in
unstable codec selection, I don't see how your original proposal of
having a separate card profile for each codec (or potentially each set
of codec parameters) makes the situation better.
Wait until bluez adds new API for choosing codec and use this new API in
pulseaudio for codec selection. This is really something which we need
for proper and deterministic (multi)codec support.
Post by Tanu Kaskinen
Post by Tanu Kaskinen
Comments on the code below. This review was done over several days,
sorry for any inconsistencies or strange repetition (I noticed I
explain some things multiple times).
Currently I do not have much time to work on this. So just few comments
below.
Post by Tanu Kaskinen
Post by Pali Rohár
static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const char *endpoint, const char *uuid) {
DBusMessage *m;
DBusMessageIter i, d;
- uint8_t codec = 0;
+ uint8_t capabilities[1024];
+ size_t capabilities_size;
+ uint8_t codec_id;
+ const pa_a2dp_codec_t *codec;
+
+ codec = pa_a2dp_endpoint_to_a2dp_codec(endpoint);
I think it would be better to change the function parameters so that
instead of an endpoint path the function would take a codec id.
What do you mean by codec id? Do you mean A2DP codec id? If yes, then it
would not work, as every non-sbc and non-mpeg codec has codec is 0xFF.
Codec itself is then determined by passed capabilities.
I thought we could use the id from the A2DP spec, but apparently not. I
propose having our own separate codec enumeration then that provides
the necessary ids.
Post by Tanu Kaskinen
There
would be a function called get_a2dp_codec() that would take the codec
id as a parameter and return the codec struct. The endpoint path could
be added to the codec struct.
Shuffling things around like this wouldn't have huge benefits, I just
find looking up the codec based on the id neater than based on the
endpoint path.
I hope you don't have a problem with that suggestion, but if you do and
you want to keep using pa_a2dp_endpoint_to_a2dp_codec(), then the
function should be made static (it's not used outside bluez5-utils.c)
and the pa_ prefix should be dropped, since functions that aren't
exported through headers should not use the pa_ prefix according to our
coding style.
Post by Pali Rohár
+ sbc_info->min_bitpool = config->min_bitpool;
+ sbc_info->max_bitpool = config->max_bitpool;
+
+ /* Set minimum bitpool for source to get the maximum possible block_size */
+ sbc_info->sbc.bitpool = is_a2dp_sink ? sbc_info->max_bitpool : sbc_info->min_bitpool;
Do you understand the logic here?
I have not looked deeply at this code. So I'm not fully sure. I just
moved existing code into new file.
Ok, so this remains a mystery to all of us.
--
Pali Rohár
***@gmail.com
Arun Raghavan
2018-09-17 12:20:42 UTC
Permalink
Post by Tanu Kaskinen
Hi Pali!
Thanks a lot for working on this! Arun has been strongly advocating
using GStreamer, and I don't know if his position is that "AptX must be
implemented with GStreamer or not implemented at all". I hope not. To
me the library choice is not important. Using libopenaptx directly
doesn't prevent us from switching to GStreamer later if someone writes
the code.
A couple of notes. Firstly, I really do appreciate all the work that Pali has put into this (it is clearly a lot).

Secondly, while I am dead against adding a codec as a dep, once we get things in shape where this is the only issue, I volunteer to help with the GStreamer bits so as to not block this.

Cheers,
Arun
Pali Rohár
2018-07-28 15:34:53 UTC
Permalink
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])])
+
+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 <***@holtmann.org>
+ * Copyright (C) 2018 Pali Rohár <***@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);
+#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);

} 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);

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);

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;
+
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);
+ }
+ }
+
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 <***@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;
+ }
+
+ 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;
+}
+
+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
--
2.11.0
Tanu Kaskinen
2018-09-05 10:57:08 UTC
Permalink
Post by Pali Rohár
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.
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.
Post by Pali Rohár
+
+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
*
*
* 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) {
return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_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?
Post by Pali Rohár
+#else
+ return false;
+#endif
return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_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
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.
Post by Pali Rohár
} 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) {
return "a2dp_sink";
+ return "a2dp_aptx_sink";
return "a2dp_source";
+ return "a2dp_aptx_source";
return "headset_head_unit";
@@ -1275,8 +1297,12 @@ const char *pa_bluetooth_profile_to_a2dp_endpoint(pa_bluetooth_profile_t profile
switch (profile) {
return A2DP_SOURCE_SBC_ENDPOINT;
+ return A2DP_SOURCE_APTX_ENDPOINT;
return A2DP_SINK_SBC_ENDPOINT;
+ return A2DP_SINK_APTX_ENDPOINT;
return NULL;
}
@@ -1289,6 +1315,11 @@ const pa_a2dp_codec_t *pa_bluetooth_profile_to_a2dp_codec(pa_bluetooth_profile_t
return &pa_a2dp_codec_sbc;
+#ifdef HAVE_OPENAPTX
+ return &pa_a2dp_codec_aptx;
+#endif
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.
Post by Pali Rohár
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).
Post by Pali Rohár
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) {
data.suspend_cause = PA_SUSPEND_USER;
break;
@@ -937,6 +939,7 @@ static int add_source(struct userdata *u) {
pa_assert_not_reached();
break;
pa_assert_not_reached();
@@ -1111,8 +1114,10 @@ static int add_sink(struct userdata *u) {
pa_assert_not_reached();
break;
/* Profile switch should have failed */
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;
+ 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;
+
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;
+ 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.
Post by Pali Rohár
cp = pa_card_profile_new(name, _("Headset Head Unit (HSP/HFP)"), sizeof(pa_bluetooth_profile_t));
cp->priority = 30;
}
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.
Post by Pali Rohár
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.
+
+
+ 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.
Post by Pali Rohár
+
+ 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) {
+ sample_spec->rate = 16000U;
+ break;
+ sample_spec->rate = 32000U;
+ break;
+ sample_spec->rate = 44100U;
+ break;
+ sample_spec->rate = 48000U;
+ break;
+ pa_assert_not_reached();
+ }
+
+ switch (config->channel_mode) {
+ sample_spec->channels = 2;
+ break;
+ 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.
Post by Pali Rohár
+}
+
+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
Tanu Kaskinen
2018-09-17 12:27:25 UTC
Permalink
Post by Tanu Kaskinen
Post by Pali Rohár
+#### 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.
Intension is that library is small and does not need any special cflags
or ldflags. So .pc file is not needed at all. And if library or include
file is in non-standard location then user really need to specify where
it is. But same argument can be used when .pc file is in non-standard
location. User again need to do some magic.
Post by Tanu Kaskinen
Post by Pali Rohár
+#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?
I do not know.
Post by Tanu Kaskinen
Post by Pali Rohár
+ 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.
I thought that it would be better to not wrap above lines in another
#ifdef, but rather function register_endpoint would do nothing when
particular codec (e.g. aptX) was not enabled at compile time.
I thought that we'd end up telling bluetoothd that we support aptX when
we don't, but since register_endpoint() returns early, that's not an
issue. I still think #ifdefs are preferable here, since it makes it
obvious that the register_endpoint() calls don't do anything, but
that's pretty minor thing.
Post by Tanu Kaskinen
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.
Yes, order is important. Bluez based on registration order choose codec.
Post by Tanu Kaskinen
Post by Pali Rohár
@@ -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).
ok.
Post by Tanu Kaskinen
Post by Pali Rohár
+ 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;
+
CC modules/bluetooth/module_bluez5_device_la-module-bluez5-device.lo
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.
What is the preferred way?
I suggest intializing p to NULL at the beginning of the function and
having pa_assert(p) before the *p = profile line.
Post by Tanu Kaskinen
Post by Pali Rohár
cp = pa_card_profile_new(name, _("Headset Head Unit (HSP/HFP)"), sizeof(pa_bluetooth_profile_t));
cp->priority = 30;
}
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.
Ok.
Post by Tanu Kaskinen
Post by Pali Rohár
+ 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.
Ok.
Post by Tanu Kaskinen
Post by Pali Rohár
+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.
Ok.
Input is sequence of 4 raw 24bit signed stereo samples. And at one time
we need to process multiple of 8 due to synchronization as aptX uses 3
bits in every eight sequence for checksum. aptX compression ratio is
fixed 6:1. Therefore 8*4*6 bytes are on input and 8*4 bytes on output.
Thanks! Makes sense.
--
Tanu

https://www.patreon.com/tanuk
https://liberapay.com/tanuk
Luiz Augusto von Dentz
2018-09-18 10:55:08 UTC
Permalink
Hi Pali,
Post by Tanu Kaskinen
Post by Pali Rohár
+#### 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.
Intension is that library is small and does not need any special cflags
or ldflags. So .pc file is not needed at all. And if library or include
file is in non-standard location then user really need to specify where
it is. But same argument can be used when .pc file is in non-standard
location. User again need to do some magic.
Long term I think it is best to use autotools to properly generate the
.pc file, etc, otherwise it might be difficult for distros to pick
this up. I might be able to help you with that if you are willing to
accept patches.
--
Luiz Augusto von Dentz
Pali Rohár
2018-09-27 08:52:19 UTC
Permalink
Post by Luiz Augusto von Dentz
Hi Pali,
Post by Tanu Kaskinen
Post by Pali Rohár
+#### 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.
Intension is that library is small and does not need any special cflags
or ldflags. So .pc file is not needed at all. And if library or include
file is in non-standard location then user really need to specify where
it is. But same argument can be used when .pc file is in non-standard
location. User again need to do some magic.
Long term I think it is best to use autotools to properly generate the
.pc file, etc, otherwise it might be difficult for distros to pick
this up. I might be able to help you with that if you are willing to
accept patches.
Because I know autotools, how to use it and how it works, I'm saying No.
For small library I explicitly chose something which is easy and not big
hell moloch. I really do not need anything special nor any custom or
specific functionality. Also library has no dependences.
--
Pali Rohár
***@gmail.com
Pali Rohár
2018-08-03 08:33:52 UTC
Permalink
Post by Pali Rohár
This patch series moves A2DP codec code into new modules and add
support for Bluetooth A2DP aptX codec.
Modular API for Bluetooth A2DP codec
Bluetooth A2DP aptX codec support
configure.ac | 19 +
src/Makefile.am | 14 +-
src/modules/bluetooth/a2dp-codecs.h | 123 +++++-
src/modules/bluetooth/bluez5-util.c | 377 +++++++----------
src/modules/bluetooth/bluez5-util.h | 12 +-
src/modules/bluetooth/module-bluez5-device.c | 542 ++++++++-----------------
src/modules/bluetooth/pa-a2dp-codec-aptx.c | 297 ++++++++++++++
src/modules/bluetooth/pa-a2dp-codec-sbc.c | 579 +++++++++++++++++++++++++++
src/modules/bluetooth/pa-a2dp-codec.h | 41 ++
9 files changed, 1393 insertions(+), 611 deletions(-)
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-aptx.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-sbc.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec.h
Hello, can somebody review this patch series for aptX support?
--
Pali Rohár
***@gmail.com
Tanu Kaskinen
2018-08-03 09:45:51 UTC
Permalink
Post by Pali Rohár
Post by Pali Rohár
This patch series moves A2DP codec code into new modules and add
support for Bluetooth A2DP aptX codec.
Modular API for Bluetooth A2DP codec
Bluetooth A2DP aptX codec support
configure.ac | 19 +
src/Makefile.am | 14 +-
src/modules/bluetooth/a2dp-codecs.h | 123 +++++-
src/modules/bluetooth/bluez5-util.c | 377 +++++++----------
src/modules/bluetooth/bluez5-util.h | 12 +-
src/modules/bluetooth/module-bluez5-device.c | 542 ++++++++-----------------
src/modules/bluetooth/pa-a2dp-codec-aptx.c | 297 ++++++++++++++
src/modules/bluetooth/pa-a2dp-codec-sbc.c | 579 +++++++++++++++++++++++++++
src/modules/bluetooth/pa-a2dp-codec.h | 41 ++
9 files changed, 1393 insertions(+), 611 deletions(-)
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-aptx.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-sbc.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec.h
Hello, can somebody review this patch series for aptX support?
Yes, eventually.

We're short on reviewer bandwidth - would you be interested in becoming
a regular reviewer yourself to improve the situation?
--
Tanu

https://www.patreon.com/tanuk
https://liberapay.com/tanuk
Pali Rohár
2018-08-06 08:25:02 UTC
Permalink
Post by Tanu Kaskinen
Post by Pali Rohár
Post by Pali Rohár
This patch series moves A2DP codec code into new modules and add
support for Bluetooth A2DP aptX codec.
Modular API for Bluetooth A2DP codec
Bluetooth A2DP aptX codec support
configure.ac | 19 +
src/Makefile.am | 14 +-
src/modules/bluetooth/a2dp-codecs.h | 123 +++++-
src/modules/bluetooth/bluez5-util.c | 377 +++++++----------
src/modules/bluetooth/bluez5-util.h | 12 +-
src/modules/bluetooth/module-bluez5-device.c | 542 ++++++++-----------------
src/modules/bluetooth/pa-a2dp-codec-aptx.c | 297 ++++++++++++++
src/modules/bluetooth/pa-a2dp-codec-sbc.c | 579 +++++++++++++++++++++++++++
src/modules/bluetooth/pa-a2dp-codec.h | 41 ++
9 files changed, 1393 insertions(+), 611 deletions(-)
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-aptx.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-sbc.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec.h
Hello, can somebody review this patch series for aptX support?
Yes, eventually.
We're short on reviewer bandwidth - would you be interested in becoming
a regular reviewer yourself to improve the situation?
Hi! I do not have time for reviewing all patches, nor I do not
understand pulseaudio such deeply. I played more with bluetooth
integration code, so I could help with reviewing bluetooth changes.
--
Pali Rohár
***@gmail.com
Tanu Kaskinen
2018-08-06 10:52:32 UTC
Permalink
Post by Pali Rohár
Post by Tanu Kaskinen
Post by Pali Rohár
Post by Pali Rohár
This patch series moves A2DP codec code into new modules and add
support for Bluetooth A2DP aptX codec.
Modular API for Bluetooth A2DP codec
Bluetooth A2DP aptX codec support
configure.ac | 19 +
src/Makefile.am | 14 +-
src/modules/bluetooth/a2dp-codecs.h | 123 +++++-
src/modules/bluetooth/bluez5-util.c | 377 +++++++----------
src/modules/bluetooth/bluez5-util.h | 12 +-
src/modules/bluetooth/module-bluez5-device.c | 542 ++++++++-----------------
src/modules/bluetooth/pa-a2dp-codec-aptx.c | 297 ++++++++++++++
src/modules/bluetooth/pa-a2dp-codec-sbc.c | 579 +++++++++++++++++++++++++++
src/modules/bluetooth/pa-a2dp-codec.h | 41 ++
9 files changed, 1393 insertions(+), 611 deletions(-)
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-aptx.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-sbc.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec.h
Hello, can somebody review this patch series for aptX support?
Yes, eventually.
We're short on reviewer bandwidth - would you be interested in becoming
a regular reviewer yourself to improve the situation?
Hi! I do not have time for reviewing all patches, nor I do not
understand pulseaudio such deeply. I played more with bluetooth
integration code, so I could help with reviewing bluetooth changes.
I certainly wasn't expecting you to review all patches - every little
bit helps! If you have time to keep an eye on incoming patches and
occasionally review some of them (concentrating on bluetooth patches is
a fine approach for example), that would be awesome! You can set up
email notifications in GitLab - see the drop-down menu with the bell
icon on this page: https://gitlab.freedesktop.org/pulseaudio/pulseaudio
- You can "watch" the whole project, but if you want to get emails
only for new merge requests, choose "Custom" which allows you to pick
only merge request related events.
--
Tanu

https://www.patreon.com/tanuk
https://liberapay.com/tanuk
Tanu Kaskinen
2018-08-06 11:02:34 UTC
Permalink
Post by Tanu Kaskinen
Post by Pali Rohár
Post by Tanu Kaskinen
Post by Pali Rohár
Post by Pali Rohár
This patch series moves A2DP codec code into new modules and add
support for Bluetooth A2DP aptX codec.
Modular API for Bluetooth A2DP codec
Bluetooth A2DP aptX codec support
configure.ac | 19 +
src/Makefile.am | 14 +-
src/modules/bluetooth/a2dp-codecs.h | 123 +++++-
src/modules/bluetooth/bluez5-util.c | 377 +++++++----------
src/modules/bluetooth/bluez5-util.h | 12 +-
src/modules/bluetooth/module-bluez5-device.c | 542 ++++++++-----------------
src/modules/bluetooth/pa-a2dp-codec-aptx.c | 297 ++++++++++++++
src/modules/bluetooth/pa-a2dp-codec-sbc.c | 579 +++++++++++++++++++++++++++
src/modules/bluetooth/pa-a2dp-codec.h | 41 ++
9 files changed, 1393 insertions(+), 611 deletions(-)
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-aptx.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-sbc.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec.h
Hello, can somebody review this patch series for aptX support?
Yes, eventually.
We're short on reviewer bandwidth - would you be interested in becoming
a regular reviewer yourself to improve the situation?
Hi! I do not have time for reviewing all patches, nor I do not
understand pulseaudio such deeply. I played more with bluetooth
integration code, so I could help with reviewing bluetooth changes.
I certainly wasn't expecting you to review all patches - every little
bit helps! If you have time to keep an eye on incoming patches and
occasionally review some of them (concentrating on bluetooth patches is
a fine approach for example), that would be awesome! You can set up
email notifications in GitLab - see the drop-down menu with the bell
icon on this page: https://gitlab.freedesktop.org/pulseaudio/pulseaudio
- You can "watch" the whole project, but if you want to get emails
only for new merge requests, choose "Custom" which allows you to pick
only merge request related events.
Forgot to mention: if you choose to contribute this way, I'll be happy
to answer any questions in irc if you want to understand the code
better.
--
Tanu

https://www.patreon.com/tanuk
https://liberapay.com/tanuk
ValdikSS
2018-08-03 13:22:05 UTC
Permalink
Doesn't work for me with Intel 7260 Bluetooth 4.0 and RealForce OverDrive D1.

When I connect headphones and change Pulseaudio profile from "Off" to "High Fidelity SBC playback (a2dp sink)", everything works as expected with SBC.
Profile does not switch if I choose "High Fidelity aptX playback (a2dp sink)" when SBC profile is already active, log message:

W: [pulseaudio] module-bluez5-device.c: Refused to switch profile to a2dp_aptx_sink: Not connected

When I try to switch to aptX profile from "off" profile, pulseaudio crashes:

E: [pulseaudio] module-bluez5-device.c: Assertion '!u->thread' failed at modules/bluetooth/module-bluez5-device.c:1491, function start_thread(). Aborting.

Thread 1 "pulseaudio" received signal SIGABRT, Aborted.
0x00007ffff44edfeb in raise () from /lib64/libc.so.6
(gdb) bt
#0 0x00007ffff44edfeb in raise () from /lib64/libc.so.6
#1 0x00007ffff44d85c1 in abort () from /lib64/libc.so.6
#2 0x00007fff7f3dab45 in start_thread (u=***@entry=0x55555593d640) at modules/bluetooth/module-bluez5-device.c:1491
#3 0x00007fff7f3dd263 in set_profile_cb (c=<optimized out>, new_profile=0x5555559251a0) at modules/bluetooth/module-bluez5-device.c:1859
#4 0x00007ffff7b5148e in pa_card_set_profile (c=***@entry=0x5555558e4c20, profile=***@entry=0x5555559251a0, save=***@entry=true) at pulsecore/card.c:318
#5 0x00007fffe0a0362d in command_set_card_profile (pd=<optimized out>, command=<optimized out>, tag=127, t=<optimized out>, userdata=<optimized out>) at pulsecore/protocol-native.c:4728
#6 0x00007ffff6d83813 in pa_pdispatch_run (pd=0x555555a2e4b0, packet=***@entry=0x5555558a3020, ancil_data=***@entry=0x555555975bf8, userdata=***@entry=0x5555558bebf0) at pulsecore/pdispatch.c:346
#7 0x00007fffe0a0bee9 in pstream_packet_callback (p=0x555555975960, packet=0x5555558a3020, ancil_data=0x555555975bf8, userdata=0x5555558bebf0) at pulsecore/protocol-native.c:4951
#8 0x00007ffff6d8629d in do_read (p=***@entry=0x555555975960, re=***@entry=0x555555975b28) at pulsecore/pstream.c:1012
#9 0x00007ffff6d890eb in do_pstream_read_write (p=0x555555975960) at pulsecore/pstream.c:248
#10 0x00007ffff6d8949d in srb_callback (srb=0x5555558b0660, userdata=0x555555975960) at pulsecore/pstream.c:287
#11 0x00007ffff6d89d2a in srbchannel_rwloop (sr=0x5555558b0660) at pulsecore/srbchannel.c:190
#12 0x00007ffff78fc8a8 in dispatch_pollfds (m=0x55555576f120) at pulse/mainloop.c:140
#13 pa_mainloop_dispatch (m=***@entry=0x55555576f120) at pulse/mainloop.c:898
#14 0x00007ffff78fcb80 in pa_mainloop_iterate (m=0x55555576f120, block=<optimized out>, retval=0x7fffffffdc18) at pulse/mainloop.c:929
#15 0x00007ffff78fcc20 in pa_mainloop_run (m=0x55555576f120, retval=0x7fffffffdc18) at pulse/mainloop.c:945
#16 0x000055555555b0c9 in main (argc=<optimized out>, argv=<optimized out>) at daemon/main.c:1144


I haven't installed any patches for bluez itself. Should I? If yes, which exactly?
I moved libopenaptx to autotools and made Fedora .spec file for openaptx, are you interested in autotools support for libopenaptx, should I create a pull request to your repository?
Post by Pali Rohár
This patch series moves A2DP codec code into new modules and add
support for Bluetooth A2DP aptX codec.
Modular API for Bluetooth A2DP codec
Bluetooth A2DP aptX codec support
configure.ac | 19 +
src/Makefile.am | 14 +-
src/modules/bluetooth/a2dp-codecs.h | 123 +++++-
src/modules/bluetooth/bluez5-util.c | 377 +++++++----------
src/modules/bluetooth/bluez5-util.h | 12 +-
src/modules/bluetooth/module-bluez5-device.c | 542 ++++++++-----------------
src/modules/bluetooth/pa-a2dp-codec-aptx.c | 297 ++++++++++++++
src/modules/bluetooth/pa-a2dp-codec-sbc.c | 579 +++++++++++++++++++++++++++
src/modules/bluetooth/pa-a2dp-codec.h | 41 ++
9 files changed, 1393 insertions(+), 611 deletions(-)
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-aptx.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-sbc.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec.h
ValdikSS
2018-08-03 13:29:13 UTC
Permalink
I applied the patch to stable Pulseaudio 12.2. The patch successfully applies with some fuzzing. Should it be compatible with 12.2 or should I check it with master?
Post by ValdikSS
Doesn't work for me with Intel 7260 Bluetooth 4.0 and RealForce OverDrive D1.
When I connect headphones and change Pulseaudio profile from "Off" to "High Fidelity SBC playback (a2dp sink)", everything works as expected with SBC.
W: [pulseaudio] module-bluez5-device.c: Refused to switch profile to a2dp_aptx_sink: Not connected
E: [pulseaudio] module-bluez5-device.c: Assertion '!u->thread' failed at modules/bluetooth/module-bluez5-device.c:1491, function start_thread(). Aborting.
Thread 1 "pulseaudio" received signal SIGABRT, Aborted.
0x00007ffff44edfeb in raise () from /lib64/libc.so.6
(gdb) bt
#0 0x00007ffff44edfeb in raise () from /lib64/libc.so.6
#1 0x00007ffff44d85c1 in abort () from /lib64/libc.so.6
#3 0x00007fff7f3dd263 in set_profile_cb (c=<optimized out>, new_profile=0x5555559251a0) at modules/bluetooth/module-bluez5-device.c:1859
#5 0x00007fffe0a0362d in command_set_card_profile (pd=<optimized out>, command=<optimized out>, tag=127, t=<optimized out>, userdata=<optimized out>) at pulsecore/protocol-native.c:4728
#7 0x00007fffe0a0bee9 in pstream_packet_callback (p=0x555555975960, packet=0x5555558a3020, ancil_data=0x555555975bf8, userdata=0x5555558bebf0) at pulsecore/protocol-native.c:4951
#9 0x00007ffff6d890eb in do_pstream_read_write (p=0x555555975960) at pulsecore/pstream.c:248
#10 0x00007ffff6d8949d in srb_callback (srb=0x5555558b0660, userdata=0x555555975960) at pulsecore/pstream.c:287
#11 0x00007ffff6d89d2a in srbchannel_rwloop (sr=0x5555558b0660) at pulsecore/srbchannel.c:190
#12 0x00007ffff78fc8a8 in dispatch_pollfds (m=0x55555576f120) at pulse/mainloop.c:140
#14 0x00007ffff78fcb80 in pa_mainloop_iterate (m=0x55555576f120, block=<optimized out>, retval=0x7fffffffdc18) at pulse/mainloop.c:929
#15 0x00007ffff78fcc20 in pa_mainloop_run (m=0x55555576f120, retval=0x7fffffffdc18) at pulse/mainloop.c:945
#16 0x000055555555b0c9 in main (argc=<optimized out>, argv=<optimized out>) at daemon/main.c:1144
I haven't installed any patches for bluez itself. Should I? If yes, which exactly?
I moved libopenaptx to autotools and made Fedora .spec file for openaptx, are you interested in autotools support for libopenaptx, should I create a pull request to your repository?
Post by Pali Rohár
This patch series moves A2DP codec code into new modules and add
support for Bluetooth A2DP aptX codec.
Modular API for Bluetooth A2DP codec
Bluetooth A2DP aptX codec support
configure.ac | 19 +
src/Makefile.am | 14 +-
src/modules/bluetooth/a2dp-codecs.h | 123 +++++-
src/modules/bluetooth/bluez5-util.c | 377 +++++++----------
src/modules/bluetooth/bluez5-util.h | 12 +-
src/modules/bluetooth/module-bluez5-device.c | 542 ++++++++-----------------
src/modules/bluetooth/pa-a2dp-codec-aptx.c | 297 ++++++++++++++
src/modules/bluetooth/pa-a2dp-codec-sbc.c | 579 +++++++++++++++++++++++++++
src/modules/bluetooth/pa-a2dp-codec.h | 41 ++
9 files changed, 1393 insertions(+), 611 deletions(-)
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-aptx.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-sbc.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec.h
Pali Rohár
2018-08-03 13:33:20 UTC
Permalink
Patch v1 I tested with pulseaudio 10 which is in Debian. Patch v2 I
tested with master version. So I guess after some fixes it should work
also with older versions...
Post by ValdikSS
I applied the patch to stable Pulseaudio 12.2. The patch successfully applies with some fuzzing. Should it be compatible with 12.2 or should I check it with master?
Post by ValdikSS
Doesn't work for me with Intel 7260 Bluetooth 4.0 and RealForce OverDrive D1.
When I connect headphones and change Pulseaudio profile from "Off" to "High Fidelity SBC playback (a2dp sink)", everything works as expected with SBC.
W: [pulseaudio] module-bluez5-device.c: Refused to switch profile to a2dp_aptx_sink: Not connected
E: [pulseaudio] module-bluez5-device.c: Assertion '!u->thread' failed at modules/bluetooth/module-bluez5-device.c:1491, function start_thread(). Aborting.
Thread 1 "pulseaudio" received signal SIGABRT, Aborted.
0x00007ffff44edfeb in raise () from /lib64/libc.so.6
(gdb) bt
#0 0x00007ffff44edfeb in raise () from /lib64/libc.so.6
#1 0x00007ffff44d85c1 in abort () from /lib64/libc.so.6
#3 0x00007fff7f3dd263 in set_profile_cb (c=<optimized out>, new_profile=0x5555559251a0) at modules/bluetooth/module-bluez5-device.c:1859
#5 0x00007fffe0a0362d in command_set_card_profile (pd=<optimized out>, command=<optimized out>, tag=127, t=<optimized out>, userdata=<optimized out>) at pulsecore/protocol-native.c:4728
#7 0x00007fffe0a0bee9 in pstream_packet_callback (p=0x555555975960, packet=0x5555558a3020, ancil_data=0x555555975bf8, userdata=0x5555558bebf0) at pulsecore/protocol-native.c:4951
#9 0x00007ffff6d890eb in do_pstream_read_write (p=0x555555975960) at pulsecore/pstream.c:248
#10 0x00007ffff6d8949d in srb_callback (srb=0x5555558b0660, userdata=0x555555975960) at pulsecore/pstream.c:287
#11 0x00007ffff6d89d2a in srbchannel_rwloop (sr=0x5555558b0660) at pulsecore/srbchannel.c:190
#12 0x00007ffff78fc8a8 in dispatch_pollfds (m=0x55555576f120) at pulse/mainloop.c:140
#14 0x00007ffff78fcb80 in pa_mainloop_iterate (m=0x55555576f120, block=<optimized out>, retval=0x7fffffffdc18) at pulse/mainloop.c:929
#15 0x00007ffff78fcc20 in pa_mainloop_run (m=0x55555576f120, retval=0x7fffffffdc18) at pulse/mainloop.c:945
#16 0x000055555555b0c9 in main (argc=<optimized out>, argv=<optimized out>) at daemon/main.c:1144
I haven't installed any patches for bluez itself. Should I? If yes, which exactly?
I moved libopenaptx to autotools and made Fedora .spec file for openaptx, are you interested in autotools support for libopenaptx, should I create a pull request to your repository?
Post by Pali Rohár
This patch series moves A2DP codec code into new modules and add
support for Bluetooth A2DP aptX codec.
Modular API for Bluetooth A2DP codec
Bluetooth A2DP aptX codec support
configure.ac | 19 +
src/Makefile.am | 14 +-
src/modules/bluetooth/a2dp-codecs.h | 123 +++++-
src/modules/bluetooth/bluez5-util.c | 377 +++++++----------
src/modules/bluetooth/bluez5-util.h | 12 +-
src/modules/bluetooth/module-bluez5-device.c | 542 ++++++++-----------------
src/modules/bluetooth/pa-a2dp-codec-aptx.c | 297 ++++++++++++++
src/modules/bluetooth/pa-a2dp-codec-sbc.c | 579 +++++++++++++++++++++++++++
src/modules/bluetooth/pa-a2dp-codec.h | 41 ++
9 files changed, 1393 insertions(+), 611 deletions(-)
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-aptx.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-sbc.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec.h
--
Pali Rohár
***@gmail.com
Pali Rohár
2018-08-03 13:32:01 UTC
Permalink
Post by ValdikSS
Doesn't work for me with Intel 7260 Bluetooth 4.0 and RealForce OverDrive D1.
When I connect headphones and change Pulseaudio profile from "Off" to "High Fidelity SBC playback (a2dp sink)", everything works as expected with SBC.
W: [pulseaudio] module-bluez5-device.c: Refused to switch profile to a2dp_aptx_sink: Not connected
Profile switching does not work -- bluez does not provide API for it.

Codec is chosen by bluez and headset when doing handshake. Try to
initialize A2DP connection from computer, not from headset. Then bluez
should choose aptX codec in case your headset supports it.
Post by ValdikSS
E: [pulseaudio] module-bluez5-device.c: Assertion '!u->thread' failed at modules/bluetooth/module-bluez5-device.c:1491, function start_thread(). Aborting.
Try:

$ pactl unload-module module-bluetooth-policy

Seems that policy module needs to be fixed for new aptx profiles.
Post by ValdikSS
Thread 1 "pulseaudio" received signal SIGABRT, Aborted.
0x00007ffff44edfeb in raise () from /lib64/libc.so.6
(gdb) bt
#0 0x00007ffff44edfeb in raise () from /lib64/libc.so.6
#1 0x00007ffff44d85c1 in abort () from /lib64/libc.so.6
#3 0x00007fff7f3dd263 in set_profile_cb (c=<optimized out>, new_profile=0x5555559251a0) at modules/bluetooth/module-bluez5-device.c:1859
#5 0x00007fffe0a0362d in command_set_card_profile (pd=<optimized out>, command=<optimized out>, tag=127, t=<optimized out>, userdata=<optimized out>) at pulsecore/protocol-native.c:4728
#7 0x00007fffe0a0bee9 in pstream_packet_callback (p=0x555555975960, packet=0x5555558a3020, ancil_data=0x555555975bf8, userdata=0x5555558bebf0) at pulsecore/protocol-native.c:4951
#9 0x00007ffff6d890eb in do_pstream_read_write (p=0x555555975960) at pulsecore/pstream.c:248
#10 0x00007ffff6d8949d in srb_callback (srb=0x5555558b0660, userdata=0x555555975960) at pulsecore/pstream.c:287
#11 0x00007ffff6d89d2a in srbchannel_rwloop (sr=0x5555558b0660) at pulsecore/srbchannel.c:190
#12 0x00007ffff78fc8a8 in dispatch_pollfds (m=0x55555576f120) at pulse/mainloop.c:140
#14 0x00007ffff78fcb80 in pa_mainloop_iterate (m=0x55555576f120, block=<optimized out>, retval=0x7fffffffdc18) at pulse/mainloop.c:929
#15 0x00007ffff78fcc20 in pa_mainloop_run (m=0x55555576f120, retval=0x7fffffffdc18) at pulse/mainloop.c:945
#16 0x000055555555b0c9 in main (argc=<optimized out>, argv=<optimized out>) at daemon/main.c:1144
I haven't installed any patches for bluez itself. Should I? If yes, which exactly?
There are no bluez patches.
Post by ValdikSS
I moved libopenaptx to autotools and made Fedora .spec file for openaptx, are you interested in autotools support for libopenaptx, should I create a pull request to your repository?
Nope, I'm not interested to use autohell, simple Makefile for simple
library is enough :-) Basically I see no reason for conversion to tool
which then just generate Makefile back.
Post by ValdikSS
Post by Pali Rohár
This patch series moves A2DP codec code into new modules and add
support for Bluetooth A2DP aptX codec.
Modular API for Bluetooth A2DP codec
Bluetooth A2DP aptX codec support
configure.ac | 19 +
src/Makefile.am | 14 +-
src/modules/bluetooth/a2dp-codecs.h | 123 +++++-
src/modules/bluetooth/bluez5-util.c | 377 +++++++----------
src/modules/bluetooth/bluez5-util.h | 12 +-
src/modules/bluetooth/module-bluez5-device.c | 542 ++++++++-----------------
src/modules/bluetooth/pa-a2dp-codec-aptx.c | 297 ++++++++++++++
src/modules/bluetooth/pa-a2dp-codec-sbc.c | 579 +++++++++++++++++++++++++++
src/modules/bluetooth/pa-a2dp-codec.h | 41 ++
9 files changed, 1393 insertions(+), 611 deletions(-)
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-aptx.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-sbc.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec.h
--
Pali Rohár
***@gmail.com
ValdikSS
2018-08-03 13:43:23 UTC
Permalink
Post by Pali Rohár
Post by ValdikSS
Doesn't work for me with Intel 7260 Bluetooth 4.0 and RealForce OverDrive D1.
When I connect headphones and change Pulseaudio profile from "Off" to "High Fidelity SBC playback (a2dp sink)", everything works as expected with SBC.
W: [pulseaudio] module-bluez5-device.c: Refused to switch profile to a2dp_aptx_sink: Not connected
Profile switching does not work -- bluez does not provide API for it.
Codec is chosen by bluez and headset when doing handshake. Try to
initialize A2DP connection from computer, not from headset. Then bluez
should choose aptX codec in case your headset supports it.
Works now:
      AVDTP: Set Configuration (0x03) Command (0x00) type 0x00 label 10 nosp 0
        ACP SEID: 5
        INT SEID: 1
        Service Category: Media Transport (0x01)
        Service Category: Media Codec (0x07)
          Media Type: Audio (0x00)
          Media Codec: Non-A2DP (0xff)
            Vendor ID: APT Licensing Ltd. (0x0000004f)
            Vendor Specific Codec ID: aptX (0x0001)
              Frequency: 44100 (0x20)
              Channel Mode: Stereo (0x02)
Post by Pali Rohár
Post by ValdikSS
E: [pulseaudio] module-bluez5-device.c: Assertion '!u->thread' failed at modules/bluetooth/module-bluez5-device.c:1491, function start_thread(). Aborting.
$ pactl unload-module module-bluetooth-policy
Seems that policy module needs to be fixed for new aptx profiles.
That works, thanks.
Post by Pali Rohár
Post by ValdikSS
Thread 1 "pulseaudio" received signal SIGABRT, Aborted.
0x00007ffff44edfeb in raise () from /lib64/libc.so.6
(gdb) bt
#0 0x00007ffff44edfeb in raise () from /lib64/libc.so.6
#1 0x00007ffff44d85c1 in abort () from /lib64/libc.so.6
#3 0x00007fff7f3dd263 in set_profile_cb (c=<optimized out>, new_profile=0x5555559251a0) at modules/bluetooth/module-bluez5-device.c:1859
#5 0x00007fffe0a0362d in command_set_card_profile (pd=<optimized out>, command=<optimized out>, tag=127, t=<optimized out>, userdata=<optimized out>) at pulsecore/protocol-native.c:4728
#7 0x00007fffe0a0bee9 in pstream_packet_callback (p=0x555555975960, packet=0x5555558a3020, ancil_data=0x555555975bf8, userdata=0x5555558bebf0) at pulsecore/protocol-native.c:4951
#9 0x00007ffff6d890eb in do_pstream_read_write (p=0x555555975960) at pulsecore/pstream.c:248
#10 0x00007ffff6d8949d in srb_callback (srb=0x5555558b0660, userdata=0x555555975960) at pulsecore/pstream.c:287
#11 0x00007ffff6d89d2a in srbchannel_rwloop (sr=0x5555558b0660) at pulsecore/srbchannel.c:190
#12 0x00007ffff78fc8a8 in dispatch_pollfds (m=0x55555576f120) at pulse/mainloop.c:140
#14 0x00007ffff78fcb80 in pa_mainloop_iterate (m=0x55555576f120, block=<optimized out>, retval=0x7fffffffdc18) at pulse/mainloop.c:929
#15 0x00007ffff78fcc20 in pa_mainloop_run (m=0x55555576f120, retval=0x7fffffffdc18) at pulse/mainloop.c:945
#16 0x000055555555b0c9 in main (argc=<optimized out>, argv=<optimized out>) at daemon/main.c:1144
I haven't installed any patches for bluez itself. Should I? If yes, which exactly?
There are no bluez patches.
Post by ValdikSS
I moved libopenaptx to autotools and made Fedora .spec file for openaptx, are you interested in autotools support for libopenaptx, should I create a pull request to your repository?
Nope, I'm not interested to use autohell, simple Makefile for simple
library is enough :-) Basically I see no reason for conversion to tool
which then just generate Makefile back.
I don't like autotools either, but it make packaging much easier since all distros support autotools packaging almost automatically. It also handles shared/static libraries, libtool, different paths and cross-compilation with just configure flags.
It would be maintenance burden to package library with a simple custom makefile.
Post by Pali Rohár
Post by ValdikSS
Post by Pali Rohár
This patch series moves A2DP codec code into new modules and add
support for Bluetooth A2DP aptX codec.
Modular API for Bluetooth A2DP codec
Bluetooth A2DP aptX codec support
configure.ac | 19 +
src/Makefile.am | 14 +-
src/modules/bluetooth/a2dp-codecs.h | 123 +++++-
src/modules/bluetooth/bluez5-util.c | 377 +++++++----------
src/modules/bluetooth/bluez5-util.h | 12 +-
src/modules/bluetooth/module-bluez5-device.c | 542 ++++++++-----------------
src/modules/bluetooth/pa-a2dp-codec-aptx.c | 297 ++++++++++++++
src/modules/bluetooth/pa-a2dp-codec-sbc.c | 579 +++++++++++++++++++++++++++
src/modules/bluetooth/pa-a2dp-codec.h | 41 ++
9 files changed, 1393 insertions(+), 611 deletions(-)
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-aptx.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec-sbc.c
create mode 100644 src/modules/bluetooth/pa-a2dp-codec.h
Arun Raghavan
2018-08-05 05:38:25 UTC
Permalink
Post by ValdikSS
Post by Pali Rohár
Post by ValdikSS
Doesn't work for me with Intel 7260 Bluetooth 4.0 and RealForce OverDrive D1.
When I connect headphones and change Pulseaudio profile from "Off" to "High Fidelity SBC playback (a2dp sink)", everything works as expected with SBC.
W: [pulseaudio] module-bluez5-device.c: Refused to switch profile to a2dp_aptx_sink: Not connected
Profile switching does not work -- bluez does not provide API for it.
Codec is chosen by bluez and headset when doing handshake. Try to
initialize A2DP connection from computer, not from headset. Then bluez
should choose aptX codec in case your headset supports it.
      AVDTP: Set Configuration (0x03) Command (0x00) type 0x00 label 10 nosp 0
        ACP SEID: 5
        INT SEID: 1
        Service Category: Media Transport (0x01)
        Service Category: Media Codec (0x07)
          Media Type: Audio (0x00)
          Media Codec: Non-A2DP (0xff)
            Vendor ID: APT Licensing Ltd. (0x0000004f)
            Vendor Specific Codec ID: aptX (0x0001)
              Frequency: 44100 (0x20)
              Channel Mode: Stereo (0x02)
Post by Pali Rohár
Post by ValdikSS
E: [pulseaudio] module-bluez5-device.c: Assertion '!u->thread' failed at modules/bluetooth/module-bluez5-device.c:1491, function start_thread(). Aborting.
$ pactl unload-module module-bluetooth-policy
Seems that policy module needs to be fixed for new aptx profiles.
That works, thanks.
Post by Pali Rohár
Post by ValdikSS
Thread 1 "pulseaudio" received signal SIGABRT, Aborted.
0x00007ffff44edfeb in raise () from /lib64/libc.so.6
(gdb) bt
#0 0x00007ffff44edfeb in raise () from /lib64/libc.so.6
#1 0x00007ffff44d85c1 in abort () from /lib64/libc.so.6
#3 0x00007fff7f3dd263 in set_profile_cb (c=<optimized out>, new_profile=0x5555559251a0) at modules/bluetooth/module-bluez5-device.c:1859
#5 0x00007fffe0a0362d in command_set_card_profile (pd=<optimized out>, command=<optimized out>, tag=127, t=<optimized out>, userdata=<optimized out>) at pulsecore/protocol-native.c:4728
#7 0x00007fffe0a0bee9 in pstream_packet_callback (p=0x555555975960, packet=0x5555558a3020, ancil_data=0x555555975bf8, userdata=0x5555558bebf0) at pulsecore/protocol-native.c:4951
#9 0x00007ffff6d890eb in do_pstream_read_write (p=0x555555975960) at pulsecore/pstream.c:248
#10 0x00007ffff6d8949d in srb_callback (srb=0x5555558b0660, userdata=0x555555975960) at pulsecore/pstream.c:287
#11 0x00007ffff6d89d2a in srbchannel_rwloop (sr=0x5555558b0660) at pulsecore/srbchannel.c:190
#12 0x00007ffff78fc8a8 in dispatch_pollfds (m=0x55555576f120) at pulse/mainloop.c:140
#14 0x00007ffff78fcb80 in pa_mainloop_iterate (m=0x55555576f120, block=<optimized out>, retval=0x7fffffffdc18) at pulse/mainloop.c:929
#15 0x00007ffff78fcc20 in pa_mainloop_run (m=0x55555576f120, retval=0x7fffffffdc18) at pulse/mainloop.c:945
#16 0x000055555555b0c9 in main (argc=<optimized out>, argv=<optimized out>) at daemon/main.c:1144
I haven't installed any patches for bluez itself. Should I? If yes, which exactly?
There are no bluez patches.
Post by ValdikSS
I moved libopenaptx to autotools and made Fedora .spec file for openaptx, are you interested in autotools support for libopenaptx, should I create a pull request to your repository?
Nope, I'm not interested to use autohell, simple Makefile for simple
library is enough :-) Basically I see no reason for conversion to tool
which then just generate Makefile back.
I don't like autotools either, but it make packaging much easier since
all distros support autotools packaging almost automatically. It also
handles shared/static libraries, libtool, different paths and cross-
compilation with just configure flags.
It would be maintenance burden to package library with a simple custom makefile.
You can consider meson for this as well.

-- Arun
Pali Rohár
2018-08-09 13:55:14 UTC
Permalink
Post by Arun Raghavan
Post by ValdikSS
Post by Pali Rohár
Post by ValdikSS
Doesn't work for me with Intel 7260 Bluetooth 4.0 and RealForce OverDrive D1.
When I connect headphones and change Pulseaudio profile from "Off" to "High Fidelity SBC playback (a2dp sink)", everything works as expected with SBC.
W: [pulseaudio] module-bluez5-device.c: Refused to switch profile to a2dp_aptx_sink: Not connected
Profile switching does not work -- bluez does not provide API for it.
Codec is chosen by bluez and headset when doing handshake. Try to
initialize A2DP connection from computer, not from headset. Then bluez
should choose aptX codec in case your headset supports it.
      AVDTP: Set Configuration (0x03) Command (0x00) type 0x00 label 10 nosp 0
        ACP SEID: 5
        INT SEID: 1
        Service Category: Media Transport (0x01)
        Service Category: Media Codec (0x07)
          Media Type: Audio (0x00)
          Media Codec: Non-A2DP (0xff)
            Vendor ID: APT Licensing Ltd. (0x0000004f)
            Vendor Specific Codec ID: aptX (0x0001)
              Frequency: 44100 (0x20)
              Channel Mode: Stereo (0x02)
Post by Pali Rohár
Post by ValdikSS
E: [pulseaudio] module-bluez5-device.c: Assertion '!u->thread' failed at modules/bluetooth/module-bluez5-device.c:1491, function start_thread(). Aborting.
$ pactl unload-module module-bluetooth-policy
Seems that policy module needs to be fixed for new aptx profiles.
That works, thanks.
Post by Pali Rohár
Post by ValdikSS
Thread 1 "pulseaudio" received signal SIGABRT, Aborted.
0x00007ffff44edfeb in raise () from /lib64/libc.so.6
(gdb) bt
#0 0x00007ffff44edfeb in raise () from /lib64/libc.so.6
#1 0x00007ffff44d85c1 in abort () from /lib64/libc.so.6
#3 0x00007fff7f3dd263 in set_profile_cb (c=<optimized out>, new_profile=0x5555559251a0) at modules/bluetooth/module-bluez5-device.c:1859
#5 0x00007fffe0a0362d in command_set_card_profile (pd=<optimized out>, command=<optimized out>, tag=127, t=<optimized out>, userdata=<optimized out>) at pulsecore/protocol-native.c:4728
#7 0x00007fffe0a0bee9 in pstream_packet_callback (p=0x555555975960, packet=0x5555558a3020, ancil_data=0x555555975bf8, userdata=0x5555558bebf0) at pulsecore/protocol-native.c:4951
#9 0x00007ffff6d890eb in do_pstream_read_write (p=0x555555975960) at pulsecore/pstream.c:248
#10 0x00007ffff6d8949d in srb_callback (srb=0x5555558b0660, userdata=0x555555975960) at pulsecore/pstream.c:287
#11 0x00007ffff6d89d2a in srbchannel_rwloop (sr=0x5555558b0660) at pulsecore/srbchannel.c:190
#12 0x00007ffff78fc8a8 in dispatch_pollfds (m=0x55555576f120) at pulse/mainloop.c:140
#14 0x00007ffff78fcb80 in pa_mainloop_iterate (m=0x55555576f120, block=<optimized out>, retval=0x7fffffffdc18) at pulse/mainloop.c:929
#15 0x00007ffff78fcc20 in pa_mainloop_run (m=0x55555576f120, retval=0x7fffffffdc18) at pulse/mainloop.c:945
#16 0x000055555555b0c9 in main (argc=<optimized out>, argv=<optimized out>) at daemon/main.c:1144
I haven't installed any patches for bluez itself. Should I? If yes, which exactly?
There are no bluez patches.
Post by ValdikSS
I moved libopenaptx to autotools and made Fedora .spec file for openaptx, are you interested in autotools support for libopenaptx, should I create a pull request to your repository?
Nope, I'm not interested to use autohell, simple Makefile for simple
library is enough :-) Basically I see no reason for conversion to tool
which then just generate Makefile back.
I don't like autotools either, but it make packaging much easier since
all distros support autotools packaging almost automatically. It also
handles shared/static libraries, libtool, different paths and cross-
compilation with just configure flags.
It would be maintenance burden to package library with a simple custom makefile.
I do not think so. This is regular and simple Makefile. You just supply
*standard* variables likes CC, CPPFLAGS, CFLAGS, LDFLAGS and everything
compiles correctly. There is no problem with cross-compilation. Just set
correct CC. What makes cross-compilation, specifying different paths
(rpaths, lpaths,...) problematic is autohell and its autodetection which
more times is broken.
Post by Arun Raghavan
You can consider meson for this as well.
I'm not doing any autodetection, system checks or trying to figure out
for dependences and external headers or libraries. This is simple
standalone library without any dependency and without any external
tools. Therefore I really do not need any special build system as hard
dependency.

Also I do not need any tool for generating complicated Makefile. My is
very simple.
--
Pali Rohár
***@gmail.com
Loading...