[pulseaudio-discuss] [PATCH 1/4] A2DP api with multi-codec support
EHfive
eh5 at sokka.cn
Thu Dec 13 11:43:36 UTC 2018
Separate A2DP processes from bluez5-util, module-bluez5-device.
Add prop "bluetooth.a2dp_codec" to pulseaudio sink/source properties.
---
src/Makefile.am | 15 +-
src/modules/bluetooth/a2dp-codecs.h | 115 ---
src/modules/bluetooth/a2dp/a2dp-api.h | 170 +++++
src/modules/bluetooth/a2dp/a2dp-codecs.h | 266 +++++++
src/modules/bluetooth/a2dp/a2dp_sbc.c | 659 ++++++++++++++++++
src/modules/bluetooth/a2dp/a2dp_util.c | 298 ++++++++
src/modules/bluetooth/{ => a2dp}/rtp.h | 0
src/modules/bluetooth/bluez5-util.c | 307 +++-----
src/modules/bluetooth/bluez5-util.h | 6 +
src/modules/bluetooth/module-bluez5-device.c | 460 ++++--------
.../bluetooth/module-bluez5-discover.c | 14 +-
11 files changed, 1672 insertions(+), 638 deletions(-)
delete mode 100644 src/modules/bluetooth/a2dp-codecs.h
create mode 100644 src/modules/bluetooth/a2dp/a2dp-api.h
create mode 100644 src/modules/bluetooth/a2dp/a2dp-codecs.h
create mode 100644 src/modules/bluetooth/a2dp/a2dp_sbc.c
create mode 100644 src/modules/bluetooth/a2dp/a2dp_util.c
rename src/modules/bluetooth/{ => a2dp}/rtp.h (100%)
diff --git a/src/Makefile.am b/src/Makefile.am
index 2dbb4563b..521b9b684 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2123,8 +2123,11 @@ 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/a2dp-codecs.h \
- modules/bluetooth/rtp.h
+ modules/bluetooth/a2dp/a2dp_util.c \
+ modules/bluetooth/a2dp/a2dp_sbc.c \
+ modules/bluetooth/a2dp/a2dp-api.h \
+ modules/bluetooth/a2dp/a2dp-codecs.h \
+ modules/bluetooth/a2dp/rtp.h
if HAVE_BLUEZ_5_OFONO_HEADSET
libbluez5_util_la_SOURCES += \
modules/bluetooth/backend-ofono.c
@@ -2135,8 +2138,8 @@ libbluez5_util_la_SOURCES += \
endif
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_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) $(SBC_LIBS)
+libbluez5_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) $(SBC_CFLAGS)
module_bluez5_discover_la_SOURCES = modules/bluetooth/module-bluez5-discover.c
module_bluez5_discover_la_LDFLAGS = $(MODULE_LDFLAGS)
@@ -2145,8 +2148,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
deleted file mode 100644
index 8afcfcb24..000000000
--- a/src/modules/bluetooth/a2dp-codecs.h
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- *
- * BlueZ - Bluetooth protocol stack for Linux
- *
- * Copyright (C) 2006-2010 Nokia Corporation
- * Copyright (C) 2004-2010 Marcel Holtmann <marcel at holtmann.org>
- *
- *
- * This library 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.
- *
- * This library 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-#define A2DP_CODEC_SBC 0x00
-#define A2DP_CODEC_MPEG12 0x01
-#define A2DP_CODEC_MPEG24 0x02
-#define A2DP_CODEC_ATRAC 0x03
-
-#define SBC_SAMPLING_FREQ_16000 (1 << 3)
-#define SBC_SAMPLING_FREQ_32000 (1 << 2)
-#define SBC_SAMPLING_FREQ_44100 (1 << 1)
-#define SBC_SAMPLING_FREQ_48000 1
-
-#define SBC_CHANNEL_MODE_MONO (1 << 3)
-#define SBC_CHANNEL_MODE_DUAL_CHANNEL (1 << 2)
-#define SBC_CHANNEL_MODE_STEREO (1 << 1)
-#define SBC_CHANNEL_MODE_JOINT_STEREO 1
-
-#define SBC_BLOCK_LENGTH_4 (1 << 3)
-#define SBC_BLOCK_LENGTH_8 (1 << 2)
-#define SBC_BLOCK_LENGTH_12 (1 << 1)
-#define SBC_BLOCK_LENGTH_16 1
-
-#define SBC_SUBBANDS_4 (1 << 1)
-#define SBC_SUBBANDS_8 1
-
-#define SBC_ALLOCATION_SNR (1 << 1)
-#define SBC_ALLOCATION_LOUDNESS 1
-
-#define MPEG_CHANNEL_MODE_MONO (1 << 3)
-#define MPEG_CHANNEL_MODE_DUAL_CHANNEL (1 << 2)
-#define MPEG_CHANNEL_MODE_STEREO (1 << 1)
-#define MPEG_CHANNEL_MODE_JOINT_STEREO 1
-
-#define MPEG_LAYER_MP1 (1 << 2)
-#define MPEG_LAYER_MP2 (1 << 1)
-#define MPEG_LAYER_MP3 1
-
-#define MPEG_SAMPLING_FREQ_16000 (1 << 5)
-#define MPEG_SAMPLING_FREQ_22050 (1 << 4)
-#define MPEG_SAMPLING_FREQ_24000 (1 << 3)
-#define MPEG_SAMPLING_FREQ_32000 (1 << 2)
-#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
-
-typedef struct {
- uint8_t channel_mode:4;
- uint8_t frequency:4;
- uint8_t allocation_method:2;
- uint8_t subbands:2;
- uint8_t block_length:4;
- uint8_t min_bitpool;
- uint8_t max_bitpool;
-} __attribute__ ((packed)) a2dp_sbc_t;
-
-typedef struct {
- uint8_t channel_mode:4;
- uint8_t crc:1;
- uint8_t layer:3;
- uint8_t frequency:6;
- uint8_t mpf:1;
- uint8_t rfa:1;
- uint16_t bitrate;
-} __attribute__ ((packed)) a2dp_mpeg_t;
-
-#elif __BYTE_ORDER == __BIG_ENDIAN
-
-typedef struct {
- uint8_t frequency:4;
- uint8_t channel_mode:4;
- uint8_t block_length:4;
- uint8_t subbands:2;
- uint8_t allocation_method:2;
- uint8_t min_bitpool;
- uint8_t max_bitpool;
-} __attribute__ ((packed)) a2dp_sbc_t;
-
-typedef struct {
- uint8_t layer:3;
- uint8_t crc:1;
- uint8_t channel_mode:4;
- uint8_t rfa:1;
- uint8_t mpf:1;
- uint8_t frequency:6;
- uint16_t bitrate;
-} __attribute__ ((packed)) a2dp_mpeg_t;
-
-#else
-#error "Unknown byte order"
-#endif
diff --git a/src/modules/bluetooth/a2dp/a2dp-api.h b/src/modules/bluetooth/a2dp/a2dp-api.h
new file mode 100644
index 000000000..b357555d0
--- /dev/null
+++ b/src/modules/bluetooth/a2dp/a2dp-api.h
@@ -0,0 +1,170 @@
+#ifndef fooa2dpcodecapifoo
+#define fooa2dpcodecapifoo
+
+#ifdef HAVE_CONFIG_H
+
+#include <config.h>
+
+#endif
+
+#include <pulse/sample.h>
+#include <pulse/proplist.h>
+#include <pulsecore/hashmap.h>
+
+#include "a2dp-codecs.h"
+#include "rtp.h"
+
+typedef struct pa_a2dp_codec pa_a2dp_codec_t;
+typedef struct pa_a2dp_config pa_a2dp_config_t;
+
+extern const pa_a2dp_codec_t pa_a2dp_sbc;
+
+
+/* Run from <pa_a2dp_sink_t>.encode */
+
+typedef void (*pa_a2dp_source_read_cb_t)(const void **read_buf, size_t read_buf_size, void *data);
+
+typedef void (*pa_a2dp_source_read_buf_free_cb_t)(const void **read_buf, void *data);
+
+
+// Larger index stands for higher priority by default
+typedef enum pa_a2dp_codec_index {
+ PA_A2DP_SINK_MIN,
+ PA_A2DP_SINK_SBC,
+ PA_A2DP_SINK_MAX,
+ PA_A2DP_SOURCE_MIN = PA_A2DP_SINK_MAX,
+ PA_A2DP_SOURCE_SBC,
+ PA_A2DP_SOURCE_MAX,
+ PA_A2DP_CODEC_INDEX_UNAVAILABLE
+} pa_a2dp_codec_index_t;
+
+typedef struct pa_a2dp_sink {
+ int priority;
+
+ /* Load decoder if it's not loaded; Return true if it's loaded */
+ bool (*decoder_load)();
+
+ /* Memory management is pa_a2dp_sink's work */
+ bool (*init)(void **codec_data);
+
+ /* Optional. Update user configurations
+ * Note: not transport 'configuration' or 'capabilities' */
+ int (*update_user_config)(pa_proplist *user_config, void **codec_data);
+
+ void (*config_transport)(pa_sample_spec default_sample_spec, const void *configuration, size_t configuration_size,
+ pa_sample_spec *sample_spec, void **codec_data);
+
+ void (*get_block_size)(size_t read_link_mtu, size_t *read_block_size, void **codec_data);
+
+ void (*setup_stream)(void **codec_data);
+
+ size_t
+ (*decode)(const void *read_buf, size_t read_buf_size, void *write_buf, size_t write_buf_size, size_t *decoded,
+ uint32_t *timestamp, void **codec_data);
+
+ void (*free)(void **codec_data);
+} pa_a2dp_sink_t;
+
+
+typedef struct pa_a2dp_source {
+ int priority;
+
+ /* Load encoder if it's not loaded; Return true if it's loaded */
+ bool (*encoder_load)();
+
+ /* Memory management is pa_a2dp_source's work */
+ bool (*init)(pa_a2dp_source_read_cb_t read_cb, pa_a2dp_source_read_buf_free_cb_t free_cb, void **codec_data);
+
+ /* Optional. Update user configurations
+ * Note: not transport 'configuration' or 'capabilities' */
+ int (*update_user_config)(pa_proplist *user_config, void **codec_data);
+
+ void (*config_transport)(pa_sample_spec default_sample_spec, const void *configuration, size_t configuration_size,
+ pa_sample_spec *sample_spec, void **codec_data);
+
+ void (*get_block_size)(size_t write_link_mtu, size_t *write_block_size, void **codec_data);
+
+ void (*setup_stream)(void **codec_data);
+
+ /* The implement should pass read_cb_data to pa_a2dp_source_read_cb, pa_a2dp_source_read_buf_free_cb */
+ size_t (*encode)(uint32_t timestamp, void *write_buf, size_t write_buf_size, size_t *encoded,
+ void *read_cb_data, void **codec_data);
+
+ /* Optional */
+ void (*set_tx_length)(size_t len, void **codec_data);
+
+ /* Optional */
+ void (*decrease_quality)(void **codec_data);
+
+ void (*free)(void **codec_data);
+} pa_a2dp_source_t;
+
+
+struct pa_a2dp_codec {
+ const char *name;
+ uint8_t codec;
+ const a2dp_vendor_codec_t *vendor_codec;
+ pa_a2dp_sink_t *a2dp_sink;
+ pa_a2dp_source_t *a2dp_source;
+
+ /* Memory management is pa_a2dp_codec's work */
+ size_t (*get_capabilities)(void **capabilities);
+
+ void (*free_capabilities)(void **capabilities);
+
+ size_t (*select_configuration)(const pa_sample_spec default_sample_spec, const uint8_t *supported_capabilities,
+ const size_t capabilities_size, void **configuration);
+
+ void (*free_configuration)(void **configuration);
+
+ /* Return true if configuration valid */
+ bool (*set_configuration)(const uint8_t *selected_configuration, const size_t configuration_size);
+
+};
+
+
+typedef struct a_a2dp_freq_cap {
+ uint32_t rate;
+ uint32_t cap;
+} pa_a2dp_freq_cap_t;
+
+
+/* Utils */
+
+bool pa_a2dp_select_cap_frequency(uint32_t freq_cap, pa_sample_spec default_sample_spec,
+ const pa_a2dp_freq_cap_t *freq_cap_table,
+ size_t n, pa_a2dp_freq_cap_t *result);
+
+void pa_a2dp_init(pa_a2dp_config_t **a2dp_config);
+
+void pa_a2dp_set_max_priority(pa_a2dp_codec_index_t codec_index, pa_a2dp_config_t **a2dp_config);
+
+void pa_a2dp_set_disable(pa_a2dp_codec_index_t codec_index, pa_a2dp_config_t **a2dp_config);
+
+void pa_a2dp_free(pa_a2dp_config_t **a2dp_config);
+
+
+void pa_a2dp_get_sink_indices(pa_hashmap **sink_indices, pa_a2dp_config_t **a2dp_config);
+
+void pa_a2dp_get_source_indices(pa_hashmap **source_indices, pa_a2dp_config_t **a2dp_config);
+
+void pa_a2dp_get_ordered_indices(pa_hashmap **ordered_indices, pa_a2dp_config_t **a2dp_config);
+
+
+void pa_a2dp_codec_index_to_endpoint(pa_a2dp_codec_index_t codec_index, const char **endpoint);
+
+void pa_a2dp_endpoint_to_codec_index(const char *endpoint, pa_a2dp_codec_index_t *codec_index);
+
+void pa_a2dp_codec_index_to_a2dp_codec(pa_a2dp_codec_index_t codec_index, const pa_a2dp_codec_t **a2dp_codec);
+
+void
+pa_a2dp_a2dp_codec_to_codec_index(const pa_a2dp_codec_t *a2dp_codec, bool is_sink, pa_a2dp_codec_index_t *codec_index);
+
+void pa_a2dp_get_a2dp_codec(uint8_t codec, const a2dp_vendor_codec_t *vendor_codec, const pa_a2dp_codec_t **a2dp_codec);
+
+bool pa_a2dp_codec_index_is_sink(pa_a2dp_codec_index_t codec_index);
+
+bool pa_a2dp_codec_index_is_source(pa_a2dp_codec_index_t codec_index);
+
+
+#endif
diff --git a/src/modules/bluetooth/a2dp/a2dp-codecs.h b/src/modules/bluetooth/a2dp/a2dp-codecs.h
new file mode 100644
index 000000000..aefafb635
--- /dev/null
+++ b/src/modules/bluetooth/a2dp/a2dp-codecs.h
@@ -0,0 +1,266 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel at holtmann.org>
+ *
+ *
+ * This library 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.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#define A2DP_CODEC_SBC 0x00
+#define A2DP_CODEC_MPEG12 0x01
+#define A2DP_CODEC_MPEG24 0x02
+#define A2DP_CODEC_ATRAC 0x03
+#define A2DP_CODEC_VENDOR 0xFF
+
+#define SBC_SAMPLING_FREQ_16000 (1 << 3)
+#define SBC_SAMPLING_FREQ_32000 (1 << 2)
+#define SBC_SAMPLING_FREQ_44100 (1 << 1)
+#define SBC_SAMPLING_FREQ_48000 1
+
+#define SBC_CHANNEL_MODE_MONO (1 << 3)
+#define SBC_CHANNEL_MODE_DUAL_CHANNEL (1 << 2)
+#define SBC_CHANNEL_MODE_STEREO (1 << 1)
+#define SBC_CHANNEL_MODE_JOINT_STEREO 1
+
+#define SBC_BLOCK_LENGTH_4 (1 << 3)
+#define SBC_BLOCK_LENGTH_8 (1 << 2)
+#define SBC_BLOCK_LENGTH_12 (1 << 1)
+#define SBC_BLOCK_LENGTH_16 1
+
+#define SBC_SUBBANDS_4 (1 << 1)
+#define SBC_SUBBANDS_8 1
+
+#define SBC_ALLOCATION_SNR (1 << 1)
+#define SBC_ALLOCATION_LOUDNESS 1
+
+#define MAX_BITPOOL 64
+#define MIN_BITPOOL 2
+
+#define MPEG_CHANNEL_MODE_MONO (1 << 3)
+#define MPEG_CHANNEL_MODE_DUAL_CHANNEL (1 << 2)
+#define MPEG_CHANNEL_MODE_STEREO (1 << 1)
+#define MPEG_CHANNEL_MODE_JOINT_STEREO 1
+
+#define MPEG_LAYER_MP1 (1 << 2)
+#define MPEG_LAYER_MP2 (1 << 1)
+#define MPEG_LAYER_MP3 1
+
+#define MPEG_SAMPLING_FREQ_16000 (1 << 5)
+#define MPEG_SAMPLING_FREQ_22050 (1 << 4)
+#define MPEG_SAMPLING_FREQ_24000 (1 << 3)
+#define MPEG_SAMPLING_FREQ_32000 (1 << 2)
+#define MPEG_SAMPLING_FREQ_44100 (1 << 1)
+#define MPEG_SAMPLING_FREQ_48000 1
+
+#define MPEG_BIT_RATE_VBR 0x8000
+#define MPEG_BIT_RATE_320000 0x4000
+#define MPEG_BIT_RATE_256000 0x2000
+#define MPEG_BIT_RATE_224000 0x1000
+#define MPEG_BIT_RATE_192000 0x0800
+#define MPEG_BIT_RATE_160000 0x0400
+#define MPEG_BIT_RATE_128000 0x0200
+#define MPEG_BIT_RATE_112000 0x0100
+#define MPEG_BIT_RATE_96000 0x0080
+#define MPEG_BIT_RATE_80000 0x0040
+#define MPEG_BIT_RATE_64000 0x0020
+#define MPEG_BIT_RATE_56000 0x0010
+#define MPEG_BIT_RATE_48000 0x0008
+#define MPEG_BIT_RATE_40000 0x0004
+#define MPEG_BIT_RATE_32000 0x0002
+#define MPEG_BIT_RATE_FREE 0x0001
+
+#define AAC_OBJECT_TYPE_MPEG2_AAC_LC 0x80
+#define AAC_OBJECT_TYPE_MPEG4_AAC_LC 0x40
+#define AAC_OBJECT_TYPE_MPEG4_AAC_LTP 0x20
+#define AAC_OBJECT_TYPE_MPEG4_AAC_SCA 0x10
+
+#define AAC_SAMPLING_FREQ_8000 0x0800
+#define AAC_SAMPLING_FREQ_11025 0x0400
+#define AAC_SAMPLING_FREQ_12000 0x0200
+#define AAC_SAMPLING_FREQ_16000 0x0100
+#define AAC_SAMPLING_FREQ_22050 0x0080
+#define AAC_SAMPLING_FREQ_24000 0x0040
+#define AAC_SAMPLING_FREQ_32000 0x0020
+#define AAC_SAMPLING_FREQ_44100 0x0010
+#define AAC_SAMPLING_FREQ_48000 0x0008
+#define AAC_SAMPLING_FREQ_64000 0x0004
+#define AAC_SAMPLING_FREQ_88200 0x0002
+#define AAC_SAMPLING_FREQ_96000 0x0001
+
+#define AAC_CHANNELS_1 0x02
+#define AAC_CHANNELS_2 0x01
+
+#define AAC_GET_BITRATE(a) ((a).bitrate1 << 16 | \
+ (a).bitrate2 << 8 | (a).bitrate3)
+#define AAC_GET_FREQUENCY(a) ((a).frequency1 << 4 | (a).frequency2)
+
+#define AAC_SET_BITRATE(a, b) \
+ do { \
+ (a).bitrate1 = (b >> 16) & 0x7f; \
+ (a).bitrate2 = (b >> 8) & 0xff; \
+ (a).bitrate3 = b & 0xff; \
+ } while (0)
+#define AAC_SET_FREQUENCY(a, f) \
+ do { \
+ (a).frequency1 = (f >> 4) & 0xff; \
+ (a).frequency2 = f & 0x0f; \
+ } while (0)
+
+#define AAC_INIT_BITRATE(b) \
+ .bitrate1 = (b >> 16) & 0x7f, \
+ .bitrate2 = (b >> 8) & 0xff, \
+ .bitrate3 = b & 0xff,
+#define AAC_INIT_FREQUENCY(f) \
+ .frequency1 = (f >> 4) & 0xff, \
+ .frequency2 = f & 0x0f,
+
+#define APTX_VENDOR_ID 0x0000004f
+#define APTX_CODEC_ID 0x0001
+
+#define APTX_CHANNEL_MODE_MONO 0x01
+#define APTX_CHANNEL_MODE_STEREO 0x02
+
+#define APTX_SAMPLING_FREQ_16000 0x08
+#define APTX_SAMPLING_FREQ_32000 0x04
+#define APTX_SAMPLING_FREQ_44100 0x02
+#define APTX_SAMPLING_FREQ_48000 0x01
+
+#define LDAC_VENDOR_ID 0x0000012d
+#define LDAC_CODEC_ID 0x00aa
+
+typedef struct {
+ uint32_t vendor_id;
+ uint16_t codec_id;
+} __attribute__ ((packed)) a2dp_vendor_codec_t;
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+typedef struct {
+ uint8_t channel_mode:4;
+ uint8_t frequency:4;
+ uint8_t allocation_method:2;
+ uint8_t subbands:2;
+ uint8_t block_length:4;
+ uint8_t min_bitpool;
+ uint8_t max_bitpool;
+} __attribute__ ((packed)) a2dp_sbc_t;
+
+typedef struct {
+ uint8_t channel_mode:4;
+ uint8_t crc:1;
+ uint8_t layer:3;
+ uint8_t frequency:6;
+ uint8_t mpf:1;
+ uint8_t rfa:1;
+ uint16_t bitrate;
+} __attribute__ ((packed)) a2dp_mpeg_t;
+
+typedef struct {
+ uint8_t object_type;
+ uint8_t frequency1;
+ uint8_t rfa:2;
+ uint8_t channels:2;
+ uint8_t frequency2:4;
+ uint8_t bitrate1:7;
+ uint8_t vbr:1;
+ uint8_t bitrate2;
+ uint8_t bitrate3;
+} __attribute__ ((packed)) a2dp_aac_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 channel_mode:4;
+ uint8_t frequency:4;
+ uint8_t acl_sprint_reserved[4];
+} __attribute__ ((packed)) a2dp_aptxhd_t;
+
+//Codec Specific Information Elements for LDAC(the datasheet is in ldacBT.h)
+typedef struct {
+ a2dp_vendor_codec_t info;
+ uint8_t frequency:6;
+ uint8_t rfa0:2;
+ uint8_t channel_mode:3;
+ uint8_t rfa1:5;
+} __attribute__ ((packed)) a2dp_ldac_t;
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+typedef struct {
+ uint8_t frequency:4;
+ uint8_t channel_mode:4;
+ uint8_t block_length:4;
+ uint8_t subbands:2;
+ uint8_t allocation_method:2;
+ uint8_t min_bitpool;
+ uint8_t max_bitpool;
+} __attribute__ ((packed)) a2dp_sbc_t;
+
+typedef struct {
+ uint8_t layer:3;
+ uint8_t crc:1;
+ uint8_t channel_mode:4;
+ uint8_t rfa:1;
+ uint8_t mpf:1;
+ uint8_t frequency:6;
+ uint16_t bitrate;
+} __attribute__ ((packed)) a2dp_mpeg_t;
+
+typedef struct {
+ uint8_t object_type;
+ uint8_t frequency1;
+ uint8_t frequency2:4;
+ uint8_t channels:2;
+ uint8_t rfa:2;
+ uint8_t vbr:1;
+ uint8_t bitrate1:7;
+ uint8_t bitrate2;
+ uint8_t bitrate3;
+} __attribute__ ((packed)) a2dp_aac_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 frequency:4;
+ uint8_t channel_mode:4;
+ uint8_t acl_sprint_reserved[4];
+} __attribute__ ((packed)) a2dp_aptxhd_t;
+
+//Codec Specific Information Elements for LDAC(the datasheet is in ldacBT.h)
+typedef struct {
+ a2dp_vendor_codec_t info;
+ uint8_t rfa0:2;
+ uint8_t frequency:6;
+ uint8_t rfa1:5;
+ uint8_t channel_mode:3;
+} __attribute__ ((packed)) a2dp_ldac_t;
+
+#else
+#error "Unknown byte order"
+#endif
diff --git a/src/modules/bluetooth/a2dp/a2dp_sbc.c b/src/modules/bluetooth/a2dp/a2dp_sbc.c
new file mode 100644
index 000000000..4ad072bc9
--- /dev/null
+++ b/src/modules/bluetooth/a2dp/a2dp_sbc.c
@@ -0,0 +1,659 @@
+
+#include <sbc/sbc.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#ifdef HAVE_CONFIG_H
+
+#include <config.h>
+
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/once.h>
+
+#include "a2dp-api.h"
+
+#define streq(a, b) (!strcmp((a),(b)))
+
+#define BITPOOL_DEC_LIMIT 32
+#define BITPOOL_DEC_STEP 5
+
+typedef struct sbc_info {
+ pa_a2dp_source_read_cb_t read_pcm;
+ pa_a2dp_source_read_buf_free_cb_t read_buf_free;
+
+ bool is_a2dp_sink;
+
+ int channel_mode;
+ 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;
+
+ size_t read_block_size;
+ size_t write_block_size;
+
+} sbc_info_t;
+
+static bool pa_sbc_decoder_load() {
+ /* SBC libs dynamically linked */
+ return true;
+}
+
+static bool pa_sbc_encoder_load() {
+ /* SBC libs dynamically linked */
+ return true;
+}
+
+static bool
+pa_sbc_decoder_init(void **codec_data) {
+ sbc_info_t *info = pa_xmalloc0(sizeof(sbc_info_t));
+ *codec_data = info;
+ info->is_a2dp_sink = true;
+ return true;
+}
+
+static bool
+pa_sbc_encoder_init(pa_a2dp_source_read_cb_t read_cb, pa_a2dp_source_read_buf_free_cb_t free_cb, void **codec_data) {
+ sbc_info_t *info = pa_xmalloc0(sizeof(sbc_info_t));
+ *codec_data = info;
+ info->is_a2dp_sink = false;
+ info->read_pcm = read_cb;
+ info->read_buf_free = free_cb;
+ return true;
+}
+
+static int pa_sbc_update_user_config(pa_proplist *user_config, void **codec_data) {
+ return 0;
+}
+
+static size_t
+pa_sbc_decode(const void *read_buf, size_t read_buf_size, void *write_buf, size_t write_buf_size, size_t *_decoded,
+ uint32_t *timestamp, void **codec_data) {
+ const struct rtp_header *header;
+ const struct rtp_payload *payload;
+ const void *p;
+ void *d;
+ size_t to_write, to_decode;
+ size_t total_written = 0;
+ sbc_info_t *sbc_info = *codec_data;
+ pa_assert(sbc_info);
+
+ header = read_buf;
+ payload = (struct rtp_payload *) ((uint8_t *) read_buf + sizeof(*header));
+
+ *timestamp = ntohl(header->timestamp);
+
+ p = (uint8_t *) read_buf + sizeof(*header) + sizeof(*payload);
+ to_decode = read_buf_size - sizeof(*header) - sizeof(*payload);
+
+ d = write_buf;
+ to_write = write_buf_size;
+
+ *_decoded = 0;
+ 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);
+ *_decoded = 0;
+ 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);
+
+ pa_assert_fp((size_t) written == sbc_info->codesize);
+
+ *_decoded += decoded;
+ p = (const uint8_t *) p + decoded;
+ to_decode -= decoded;
+
+ d = (uint8_t *) d + written;
+ to_write -= written;
+ }
+
+ return total_written;
+}
+
+static size_t
+pa_sbc_encode(uint32_t timestamp, void *write_buf, size_t write_buf_size, size_t *_encoded, void *read_cb_data,
+ void **codec_data) {
+ 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;
+ sbc_info_t *sbc_info = *codec_data;
+ pa_assert(sbc_info);
+
+ header = write_buf;
+ payload = (struct rtp_payload *) ((uint8_t *) write_buf + sizeof(*header));
+
+ frame_count = 0;
+
+ /* Try to create a packet of the full MTU */
+
+ sbc_info->read_pcm(&p, (size_t) sbc_info->write_block_size, read_cb_data);
+
+ to_encode = sbc_info->write_block_size;
+
+ d = (uint8_t *) write_buf + sizeof(*header) + sizeof(*payload);
+ to_write = write_buf_size - sizeof(*header) - sizeof(*payload);
+
+ *_encoded = 0;
+ 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);
+ sbc_info->read_buf_free(&p, read_cb_data);
+ *_encoded = 0;
+ 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 = (const uint8_t *) p + encoded;
+ to_encode -= encoded;
+ *_encoded += encoded;
+
+ d = (uint8_t *) d + written;
+ to_write -= written;
+
+ frame_count++;
+ }
+
+ sbc_info->read_buf_free(&p, read_cb_data);
+
+ pa_assert(to_encode == 0);
+
+ PA_ONCE_BEGIN
+ {
+ const char *impl = sbc_get_implementation_info(&sbc_info->sbc);
+ pa_log_debug("Using SBC encoder implementation: %s", impl ? impl : "NULL");
+ }
+ PA_ONCE_END;
+
+ /* write it to the fifo */
+ memset(write_buf, 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;
+
+ nbytes = (uint8_t *) d - (uint8_t *) write_buf;
+
+ return nbytes;
+}
+
+static void
+pa_sbc_config_transport(pa_sample_spec default_sample_spec, const void *configuration, size_t configuration_size,
+ pa_sample_spec *sample_spec, void **codec_data) {
+ sbc_info_t *sbc_info = *codec_data;
+ a2dp_sbc_t *config = (a2dp_sbc_t *) configuration;
+
+ pa_assert(sbc_info);
+ pa_assert_se(configuration_size == sizeof(*config));
+
+ if (sbc_info->sbc_initialized)
+ sbc_reinit(&sbc_info->sbc, 0);
+ else
+ sbc_init(&sbc_info->sbc, 0);
+ sbc_info->sbc_initialized = true;
+
+ sample_spec->format = PA_SAMPLE_S16LE;
+
+ 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 = sbc_info->is_a2dp_sink ? sbc_info->min_bitpool : sbc_info->max_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_get_read_block_size(size_t read_link_mtu, size_t *read_block_size, void **codec_data) {
+ sbc_info_t *sbc_info = *codec_data;
+ pa_assert(sbc_info);
+ *read_block_size =
+ (read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+ / sbc_info->frame_length * sbc_info->codesize;
+ sbc_info->read_block_size = *read_block_size;
+};
+
+static void pa_sbc_get_write_block_size(size_t write_link_mtu, size_t *write_block_size, void **codec_data) {
+ sbc_info_t *sbc_info = *codec_data;
+ pa_assert(sbc_info);
+ *write_block_size =
+ (write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+ / sbc_info->frame_length * sbc_info->codesize;
+ sbc_info->write_block_size = *write_block_size;
+};
+
+static void a2dp_set_bitpool(uint8_t bitpool, void **codec_data) {
+ sbc_info_t *sbc_info = *codec_data;
+
+ 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);
+}
+
+static void a2dp_reduce_bitpool(void **codec_data) {
+ sbc_info_t *sbc_info = *codec_data;
+ uint8_t bitpool;
+
+ /* Check if bitpool is already at its limit */
+ if (sbc_info->sbc.bitpool <= BITPOOL_DEC_LIMIT)
+ return;
+
+ bitpool = (uint8_t)(sbc_info->sbc.bitpool - BITPOOL_DEC_STEP);
+
+ if (bitpool < BITPOOL_DEC_LIMIT)
+ bitpool = BITPOOL_DEC_LIMIT;
+
+ a2dp_set_bitpool(bitpool, codec_data);
+}
+
+static void pa_sbc_setup_stream(void **codec_data) {
+ sbc_info_t *sbc_info = *codec_data;
+ pa_assert(sbc_info);
+ if (!sbc_info->is_a2dp_sink)
+ a2dp_set_bitpool(sbc_info->max_bitpool, codec_data);
+};
+
+static void pa_sbc_free(void **codec_data) {
+ sbc_info_t *sbc_info = *codec_data;
+ if (!sbc_info)
+ return;
+
+
+ pa_xfree(sbc_info);
+ *codec_data = NULL;
+
+};
+
+static size_t pa_sbc_get_capabilities(void **_capabilities) {
+ a2dp_sbc_t *capabilities = pa_xmalloc0(sizeof(a2dp_sbc_t));
+
+ 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;
+
+ *_capabilities = capabilities;
+
+ return sizeof(*capabilities);
+};
+
+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;
+ default:
+ break;
+ }
+
+ 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;
+ default:
+ break;
+ }
+
+ pa_log_warn("Invalid channel mode %u", mode);
+ return 51;
+ default:
+ break;
+ }
+
+ pa_log_warn("Invalid sampling freq %u", freq);
+ return 53;
+}
+
+static size_t
+pa_sbc_select_configuration(const pa_sample_spec default_sample_spec, const uint8_t *supported_capabilities,
+ const size_t capabilities_size, void **configuration) {
+ a2dp_sbc_t *cap = (a2dp_sbc_t *) supported_capabilities;
+ a2dp_sbc_t *config = pa_xmalloc0(sizeof(a2dp_sbc_t));
+ pa_a2dp_freq_cap_t sbc_freq_cap, sbc_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(a2dp_sbc_t))
+ return 0;
+
+ if (!pa_a2dp_select_cap_frequency(cap->frequency, default_sample_spec, sbc_freq_table,
+ PA_ELEMENTSOF(sbc_freq_table), &sbc_freq_cap))
+ return 0;
+
+ config->frequency = (uint8_t) sbc_freq_cap.cap;
+
+ if (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");
+ return 0;
+ }
+ }
+
+ if (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");
+ return 0;
+ }
+ }
+ 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");
+ return 0;
+ }
+
+ 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");
+ return 0;
+ }
+
+ 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)
+ return 0;
+
+ *configuration = config;
+ return sizeof(*config);
+};
+
+static void pa_sbc_free_capabilities(void **capabilities) {
+ if (!capabilities || !*capabilities)
+ return;
+ pa_xfree(*capabilities);
+ *capabilities = NULL;
+}
+
+static bool pa_sbc_set_configuration(const uint8_t *selected_configuration, const size_t configuration_size) {
+ a2dp_sbc_t *c = (a2dp_sbc_t *) selected_configuration;
+
+ if (configuration_size != sizeof(a2dp_sbc_t)) {
+ pa_log_error("SBC configuration array of invalid size");
+ return false;
+ }
+
+ switch (c->frequency) {
+ case SBC_SAMPLING_FREQ_16000:
+ case SBC_SAMPLING_FREQ_32000:
+ case SBC_SAMPLING_FREQ_44100:
+ case SBC_SAMPLING_FREQ_48000:
+ break;
+ default:
+ pa_log_error("Invalid sampling frequency in SBC configuration");
+ return false;
+ }
+
+ switch (c->channel_mode) {
+ case SBC_CHANNEL_MODE_MONO:
+ case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+ case SBC_CHANNEL_MODE_STEREO:
+ case SBC_CHANNEL_MODE_JOINT_STEREO:
+ break;
+ default:
+ pa_log_error("Invalid channel mode in SBC Configuration");
+ return false;
+ }
+
+ switch (c->allocation_method) {
+ case SBC_ALLOCATION_SNR:
+ case SBC_ALLOCATION_LOUDNESS:
+ break;
+ default:
+ pa_log_error("Invalid allocation method in SBC configuration");
+ return false;
+ }
+
+ switch (c->subbands) {
+ case SBC_SUBBANDS_4:
+ case SBC_SUBBANDS_8:
+ break;
+ default:
+ pa_log_error("Invalid SBC subbands in SBC configuration");
+ return false;
+ }
+
+ switch (c->block_length) {
+ case SBC_BLOCK_LENGTH_4:
+ case SBC_BLOCK_LENGTH_8:
+ case SBC_BLOCK_LENGTH_12:
+ case SBC_BLOCK_LENGTH_16:
+ break;
+ default:
+ pa_log_error("Invalid block length in configuration");
+ return false;
+ }
+
+ return true;
+};
+
+
+static pa_a2dp_source_t pa_sbc_source = {
+ .encoder_load = pa_sbc_encoder_load,
+ .init = pa_sbc_encoder_init,
+ .update_user_config = pa_sbc_update_user_config,
+ .encode = pa_sbc_encode,
+ .config_transport=pa_sbc_config_transport,
+ .get_block_size=pa_sbc_get_write_block_size,
+ .setup_stream = pa_sbc_setup_stream,
+ .set_tx_length = NULL,
+ .decrease_quality = a2dp_reduce_bitpool,
+ .free = pa_sbc_free
+};
+
+static pa_a2dp_sink_t pa_sbc_sink = {
+ .decoder_load = pa_sbc_decoder_load,
+ .init = pa_sbc_decoder_init,
+ .update_user_config = pa_sbc_update_user_config,
+ .config_transport = pa_sbc_config_transport,
+ .get_block_size = pa_sbc_get_read_block_size,
+ .setup_stream = pa_sbc_setup_stream,
+ .decode = pa_sbc_decode,
+ .free = pa_sbc_free
+};
+
+const pa_a2dp_codec_t pa_a2dp_sbc = {
+ .name = "SBC",
+ .codec = A2DP_CODEC_SBC,
+ .vendor_codec = NULL,
+ .a2dp_sink = &pa_sbc_sink,
+ .a2dp_source = &pa_sbc_source,
+ .get_capabilities = pa_sbc_get_capabilities,
+ .select_configuration = pa_sbc_select_configuration,
+ .free_capabilities = pa_sbc_free_capabilities,
+ .free_configuration = pa_sbc_free_capabilities,
+ .set_configuration = pa_sbc_set_configuration
+};
\ No newline at end of file
diff --git a/src/modules/bluetooth/a2dp/a2dp_util.c b/src/modules/bluetooth/a2dp/a2dp_util.c
new file mode 100644
index 000000000..e73d8c8ff
--- /dev/null
+++ b/src/modules/bluetooth/a2dp/a2dp_util.c
@@ -0,0 +1,298 @@
+
+#include <string.h>
+#include <pulse/xmalloc.h>
+
+#include "a2dp-api.h"
+
+#define streq(a, b) (!strcmp((a),(b)))
+
+#define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource"
+#define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink"
+
+#define A2DP_SBC_SRC_ENDPOINT A2DP_SOURCE_ENDPOINT "/SBC"
+#define A2DP_SBC_SNK_ENDPOINT A2DP_SINK_ENDPOINT "/SBC"
+
+#define A2DP_VENDOR_SRC_ENDPOINT A2DP_SOURCE_ENDPOINT "/VENDOR"
+#define A2DP_VENDOR_SNK_ENDPOINT A2DP_SINK_ENDPOINT "/VENDOR"
+
+
+#define PA_A2DP_PRIORITY_DISABLE 0
+#define PA_A2DP_PRIORITY_MIN 1
+
+
+struct pa_a2dp_config {
+ int max_priority;
+ pa_hashmap *a2dp_sinks;
+ pa_hashmap *a2dp_sources;
+ pa_hashmap *active_index_priorities;
+ pa_hashmap *ordered_indices;
+};
+
+static unsigned int_hash_func(const void *p) {
+ return (unsigned) *((const int *) p);
+}
+
+static int int_compare_func(const void *a, const void *b) {
+ const int x = *((const int *) a);
+ const int y = *((const int *) b);
+ return x < y ? -1 : (x > y ? 1 : 0);
+};
+
+
+void pa_a2dp_init(pa_a2dp_config_t **a2dp_config) {
+ pa_a2dp_config_t *config;
+ pa_a2dp_codec_index_t codec_index = PA_A2DP_SINK_MIN;
+ const pa_a2dp_codec_t *a2dp_codec;
+
+ config = pa_xmalloc(sizeof(pa_a2dp_config_t));
+ *a2dp_config = config;
+
+ config->a2dp_sinks = pa_hashmap_new_full(int_hash_func, int_compare_func, pa_xfree, pa_xfree);
+ config->a2dp_sources = pa_hashmap_new_full(int_hash_func, int_compare_func, pa_xfree, pa_xfree);
+ config->active_index_priorities = pa_hashmap_new_full(int_hash_func, int_compare_func,
+ pa_xfree, pa_xfree);
+ config->ordered_indices = NULL;
+
+ config->max_priority = PA_A2DP_PRIORITY_MIN - 1;
+ while (++codec_index < PA_A2DP_SINK_MAX) {
+ pa_a2dp_codec_index_to_a2dp_codec(codec_index, &a2dp_codec);
+ if (!a2dp_codec || !a2dp_codec->a2dp_sink || !a2dp_codec->a2dp_sink->decoder_load())
+ continue;
+ ++config->max_priority;
+ pa_hashmap_put(config->a2dp_sinks, pa_xmemdup(&config->max_priority, sizeof(int)),
+ pa_xmemdup(&codec_index, sizeof(pa_a2dp_codec_index_t)));
+ pa_hashmap_put(config->active_index_priorities, pa_xmemdup(&codec_index, sizeof(pa_a2dp_codec_index_t)),
+ pa_xmemdup(&config->max_priority, sizeof(int)));
+ a2dp_codec->a2dp_sink->priority = config->max_priority;
+ }
+ codec_index = PA_A2DP_SOURCE_MIN;
+ while (++codec_index < PA_A2DP_SOURCE_MAX) {
+ pa_a2dp_codec_index_to_a2dp_codec(codec_index, &a2dp_codec);
+ if (!a2dp_codec || !a2dp_codec->a2dp_source || !a2dp_codec->a2dp_source->encoder_load())
+ continue;
+ ++config->max_priority;
+ pa_hashmap_put(config->a2dp_sources, pa_xmemdup(&config->max_priority, sizeof(int)),
+ pa_xmemdup(&codec_index, sizeof(pa_a2dp_codec_index_t)));
+ pa_hashmap_put(config->active_index_priorities, pa_xmemdup(&codec_index, sizeof(pa_a2dp_codec_index_t)),
+ pa_xmemdup(&config->max_priority, sizeof(int)));
+ a2dp_codec->a2dp_source->priority = config->max_priority;
+ }
+};
+
+void pa_a2dp_set_max_priority(pa_a2dp_codec_index_t codec_index, pa_a2dp_config_t **a2dp_config) {
+ const pa_a2dp_codec_t *a2dp_codec;
+ pa_a2dp_config_t *config = *a2dp_config;
+
+ pa_a2dp_codec_index_to_a2dp_codec(codec_index, &a2dp_codec);
+
+ if (!a2dp_codec || !pa_hashmap_remove(config->active_index_priorities, &codec_index)) {
+ printf("no entry;");
+ pa_log_debug("No such codec: %d", codec_index);
+ return;
+ }
+
+ ++config->max_priority;
+ pa_hashmap_put(config->active_index_priorities, pa_xmemdup(&codec_index, sizeof(pa_a2dp_codec_index_t)),
+ pa_xmemdup(&config->max_priority, sizeof(int)));
+
+ if (pa_a2dp_codec_index_is_sink(codec_index))
+ a2dp_codec->a2dp_sink->priority = config->max_priority;
+ else
+ a2dp_codec->a2dp_source->priority = config->max_priority;
+};
+
+void pa_a2dp_set_disable(pa_a2dp_codec_index_t codec_index, pa_a2dp_config_t **a2dp_config) {
+ const pa_a2dp_codec_t *a2dp_codec;
+ pa_a2dp_config_t *config = *a2dp_config;
+ pa_a2dp_codec_index_to_a2dp_codec(codec_index, &a2dp_codec);
+
+ if (!a2dp_codec || !pa_hashmap_remove(config->active_index_priorities, &codec_index)) {
+ pa_log_debug("No such codec: %d", codec_index);
+ return;
+ }
+
+ if (pa_a2dp_codec_index_is_sink(codec_index))
+ a2dp_codec->a2dp_sink->priority = PA_A2DP_PRIORITY_DISABLE;
+ else
+ a2dp_codec->a2dp_source->priority = PA_A2DP_PRIORITY_DISABLE;
+};
+
+void pa_a2dp_free(pa_a2dp_config_t **a2dp_config) {
+ pa_a2dp_config_t *config = *a2dp_config;
+
+ if (!config)
+ return;
+ if (config->ordered_indices)
+ pa_hashmap_free(config->ordered_indices);
+
+ if (config->active_index_priorities)
+ pa_hashmap_free(config->active_index_priorities);
+
+ if (config->a2dp_sinks)
+ pa_hashmap_free(config->a2dp_sinks);
+
+ if (config->a2dp_sources)
+ pa_hashmap_free(config->a2dp_sources);
+
+ pa_xfree(config);
+ *a2dp_config = NULL;
+}
+
+
+void pa_a2dp_get_sink_indices(pa_hashmap **sink_indices, pa_a2dp_config_t **a2dp_config) {
+ pa_a2dp_config_t *config = *a2dp_config;
+ *sink_indices = config->a2dp_sinks;
+};
+
+void pa_a2dp_get_source_indices(pa_hashmap **source_indices, pa_a2dp_config_t **a2dp_config) {
+ pa_a2dp_config_t *config = *a2dp_config;
+ *source_indices = config->a2dp_sources;
+};
+
+void pa_a2dp_get_ordered_indices(pa_hashmap **ordered_indices, pa_a2dp_config_t **a2dp_config) {
+ void *state;
+ pa_a2dp_codec_index_t *index, *indices;
+ int *priority, i;
+ pa_a2dp_config_t *config = *a2dp_config;
+
+ indices = pa_xmalloc(sizeof(pa_a2dp_codec_index_t) * (config->max_priority + 1));
+
+ for (i = 0; i <= config->max_priority; i++)
+ indices[i] = PA_A2DP_CODEC_INDEX_UNAVAILABLE;
+
+ PA_HASHMAP_FOREACH_KV(index, priority, config->active_index_priorities, state)
+ {
+ if (*priority <= 0)
+ continue;
+ indices[*priority] = *index;
+ }
+
+ if (config->ordered_indices)
+ pa_hashmap_free(config->ordered_indices);
+ config->ordered_indices = pa_hashmap_new_full(int_hash_func, int_compare_func, pa_xfree, pa_xfree);
+
+ for (i = config->max_priority; i >= PA_A2DP_PRIORITY_MIN; i--) {
+ if (indices[i] == PA_A2DP_CODEC_INDEX_UNAVAILABLE)
+ continue;
+ priority = pa_xmemdup(&i, sizeof(int));
+ index = pa_xmemdup(indices + i, sizeof(pa_a2dp_codec_index_t));
+ pa_hashmap_put(config->ordered_indices, priority, index);
+ }
+
+ *ordered_indices = config->ordered_indices;
+};
+
+
+void pa_a2dp_codec_index_to_endpoint(pa_a2dp_codec_index_t codec_index, const char **endpoint) {
+ switch (codec_index) {
+ case PA_A2DP_SINK_SBC:
+ *endpoint = A2DP_SBC_SNK_ENDPOINT;
+ break;
+ case PA_A2DP_SOURCE_SBC:
+ *endpoint = A2DP_SBC_SRC_ENDPOINT;
+ break;
+ default:
+ *endpoint = NULL;
+ }
+};
+
+void pa_a2dp_endpoint_to_codec_index(const char *endpoint, pa_a2dp_codec_index_t *codec_index) {
+ if (streq(endpoint, A2DP_SBC_SNK_ENDPOINT))
+ *codec_index = PA_A2DP_SINK_SBC;
+ else if (streq(endpoint, A2DP_SBC_SRC_ENDPOINT))
+ *codec_index = PA_A2DP_SOURCE_SBC;
+ else
+ *codec_index = PA_A2DP_CODEC_INDEX_UNAVAILABLE;
+};
+
+void pa_a2dp_codec_index_to_a2dp_codec(pa_a2dp_codec_index_t codec_index, const pa_a2dp_codec_t **a2dp_codec) {
+ switch (codec_index) {
+ case PA_A2DP_SINK_SBC:
+ case PA_A2DP_SOURCE_SBC:
+ *a2dp_codec = &pa_a2dp_sbc;
+ break;
+ default:
+ *a2dp_codec = NULL;
+ }
+};
+
+void pa_a2dp_a2dp_codec_to_codec_index(const pa_a2dp_codec_t *a2dp_codec, bool is_a2dp_sink,
+ pa_a2dp_codec_index_t *codec_index) {
+ if (!a2dp_codec) {
+ *codec_index = PA_A2DP_CODEC_INDEX_UNAVAILABLE;
+ return;
+ }
+ switch (a2dp_codec->codec) {
+ case A2DP_CODEC_SBC:
+ *codec_index = is_a2dp_sink ? PA_A2DP_SINK_SBC : PA_A2DP_SOURCE_SBC;
+ return;
+ case A2DP_CODEC_VENDOR:
+ if (!a2dp_codec->vendor_codec) {
+ *codec_index = PA_A2DP_CODEC_INDEX_UNAVAILABLE;
+ return;
+ }
+ *codec_index = PA_A2DP_CODEC_INDEX_UNAVAILABLE;
+ break;
+ default:
+ *codec_index = PA_A2DP_CODEC_INDEX_UNAVAILABLE;
+ }
+};
+
+void
+pa_a2dp_get_a2dp_codec(uint8_t codec, const a2dp_vendor_codec_t *vendor_codec, const pa_a2dp_codec_t **a2dp_codec) {
+ switch (codec) {
+ case A2DP_CODEC_SBC:
+ *a2dp_codec = &pa_a2dp_sbc;
+ return;
+ case A2DP_CODEC_VENDOR:
+ if (!vendor_codec) {
+ *a2dp_codec = NULL;
+ pa_assert_not_reached();
+ }
+ *a2dp_codec = NULL;
+ break;
+ default:
+ *a2dp_codec = NULL;
+ }
+};
+
+bool pa_a2dp_codec_index_is_sink(pa_a2dp_codec_index_t codec_index) {
+ if (codec_index > PA_A2DP_SINK_MIN && codec_index < PA_A2DP_SINK_MAX)
+ return true;
+ return false;
+};
+
+bool pa_a2dp_codec_index_is_source(pa_a2dp_codec_index_t codec_index) {
+ if (codec_index > PA_A2DP_SOURCE_MIN && codec_index < PA_A2DP_SOURCE_MAX)
+ return true;
+ return false;
+};
+
+bool
+pa_a2dp_select_cap_frequency(uint32_t freq_cap, pa_sample_spec default_sample_spec,
+ const pa_a2dp_freq_cap_t *freq_cap_table,
+ size_t n, pa_a2dp_freq_cap_t *result) {
+ int i;
+ /* Find the lowest freq that is at least as high as the requested sampling rate */
+ for (i = 0; (unsigned) i < n; i++)
+ if (freq_cap_table[i].rate >= default_sample_spec.rate && (freq_cap & freq_cap_table[i].cap)) {
+ *result = freq_cap_table[i];
+ break;
+ }
+
+ if ((unsigned) i == n) {
+ for (--i; i >= 0; i--) {
+ if (freq_cap & freq_cap_table[i].cap) {
+ *result = freq_cap_table[i];
+ break;
+ }
+ }
+
+ if (i < 0) {
+ pa_log_error("Not suitable sample rate");
+ return false;
+ }
+ }
+ pa_assert((unsigned) i < n);
+ return true;
+};
diff --git a/src/modules/bluetooth/rtp.h b/src/modules/bluetooth/a2dp/rtp.h
similarity index 100%
rename from src/modules/bluetooth/rtp.h
rename to src/modules/bluetooth/a2dp/rtp.h
diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 2d8337317..202652712 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -33,7 +33,8 @@
#include <pulsecore/refcnt.h>
#include <pulsecore/shared.h>
-#include "a2dp-codecs.h"
+#include "a2dp/a2dp-api.h"
+
#include "bluez5-util.h"
@@ -48,8 +49,6 @@
#define BLUEZ_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported"
-#define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource"
-#define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink"
#define ENDPOINT_INTROSPECT_XML \
DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
@@ -89,6 +88,8 @@ struct pa_bluetooth_discovery {
pa_hashmap *devices;
pa_hashmap *transports;
+ pa_a2dp_config_t *a2dp_config;
+
int headset_backend;
pa_bluetooth_backend *ofono_backend, *native_backend;
PA_LLIST_HEAD(pa_dbus_pending, pending);
@@ -888,7 +889,9 @@ 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 codec;
+ pa_a2dp_codec_index_t index;
+ const pa_a2dp_codec_t *a2dp_codec;
pa_log_debug("Registering %s on adapter %s", endpoint, path);
@@ -899,22 +902,23 @@ 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_a2dp_endpoint_to_codec_index(endpoint, &index);
+ pa_a2dp_codec_index_to_a2dp_codec(index, &a2dp_codec);
+
+ if(!a2dp_codec)
+ return;
+
+ codec = a2dp_codec->codec;
+
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));
+ void * capabilities;
+ size_t capabilities_size = a2dp_codec->get_capabilities(&capabilities);
+ pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, capabilities,
+ (unsigned int) capabilities_size);
+ a2dp_codec->free_capabilities(&capabilities);
}
dbus_message_iter_close_container(&i, &d);
@@ -927,6 +931,9 @@ static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessa
const char *path;
void *state;
pa_bluetooth_device *d;
+ pa_a2dp_codec_index_t *index;
+ pa_hashmap *indices;
+ const char *endpoint;
pa_assert(dbus_message_iter_get_arg_type(dict_i) == DBUS_TYPE_OBJECT_PATH);
dbus_message_iter_get_basic(dict_i, &path);
@@ -964,8 +971,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);
+ pa_a2dp_get_ordered_indices(&indices, &y->a2dp_config);
+ PA_HASHMAP_FOREACH(index, indices, state) {
+ pa_a2dp_codec_index_to_endpoint(*index, &endpoint);
+ register_endpoint(y, path, endpoint,
+ pa_a2dp_codec_index_is_sink(*index) ? PA_BLUETOOTH_UUID_A2DP_SINK
+ : PA_BLUETOOTH_UUID_A2DP_SOURCE);
+ }
} else if (pa_streq(interface, BLUEZ_DEVICE_INTERFACE)) {
@@ -1257,47 +1269,6 @@ 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) {
@@ -1326,6 +1297,18 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
pa_bluetooth_profile_t p = PA_BLUETOOTH_PROFILE_OFF;
DBusMessageIter args, props;
DBusMessage *r;
+ pa_a2dp_codec_index_t index;
+ const pa_a2dp_codec_t * a2dp_codec;
+ pa_a2dp_sink_t *a2dp_sink = NULL;
+ pa_a2dp_source_t *a2dp_source = NULL;
+
+ endpoint_path = dbus_message_get_path(m);
+
+ pa_a2dp_endpoint_to_codec_index(endpoint_path, &index);
+ pa_a2dp_codec_index_to_a2dp_codec(index, &a2dp_codec);
+
+ if(!a2dp_codec)
+ goto fail2;
if (!dbus_message_iter_init(m, &args) || !pa_streq(dbus_message_get_signature(m), "oa{sv}")) {
pa_log_error("Invalid signature for method SetConfiguration()");
@@ -1367,13 +1350,14 @@ 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_a2dp_codec_index_is_source(index)) {
if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE))
p = PA_BLUETOOTH_PROFILE_A2DP_SINK;
- } else if (pa_streq(endpoint_path, A2DP_SINK_ENDPOINT)) {
+ a2dp_source = a2dp_codec->a2dp_source;
+ } else if (pa_a2dp_codec_index_is_sink(index)) {
if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK))
p = PA_BLUETOOTH_PROFILE_A2DP_SOURCE;
+ a2dp_sink = a2dp_codec->a2dp_sink;
}
if (p == PA_BLUETOOTH_PROFILE_OFF) {
@@ -1389,7 +1373,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,40 +1387,9 @@ 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");
+ if(!a2dp_codec->set_configuration(config, (const size_t) 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;
- }
-
- 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;
- }
}
dbus_message_iter_next(&props);
@@ -1459,6 +1411,11 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
goto fail2;
}
+ if(!a2dp_sink && !a2dp_source){
+ pa_log_error("No a2dp_sink or a2dp_source available for endpoint %s", endpoint_path);
+ goto fail2;
+ }
+
sender = dbus_message_get_sender(m);
pa_assert_se(r = dbus_message_new_method_return(m));
@@ -1468,6 +1425,10 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
t = pa_bluetooth_transport_new(d, sender, path, p, config, size);
t->acquire = bluez5_transport_acquire_cb;
t->release = bluez5_transport_release_cb;
+ t->codec = a2dp_codec->codec;
+ t->a2dp_codec = a2dp_codec;
+ t->a2dp_sink = a2dp_sink;
+ t->a2dp_source = a2dp_source;
pa_bluetooth_transport_put(t);
pa_log_debug("Transport %s available for profile %s", t->path, pa_bluetooth_profile_to_string(t->profile));
@@ -1484,22 +1445,15 @@ 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;
+ void *cap, *pconf;
+ int size,config_size;
DBusMessage *r;
DBusError err;
+ pa_a2dp_codec_index_t index;
+ const pa_a2dp_codec_t *a2dp_codec;
+ const char * endpoint = dbus_message_get_path(m);
- 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 }
- };
-
+ pa_log_debug("selecing configuration");
dbus_error_init(&err);
if (!dbus_message_get_args(m, &err, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &cap, &size, DBUS_TYPE_INVALID)) {
@@ -1508,102 +1462,22 @@ 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;
- }
- }
+ pa_a2dp_endpoint_to_codec_index(endpoint, &index);
+ pa_a2dp_codec_index_to_a2dp_codec(index, &a2dp_codec);
- 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");
+ if(!a2dp_codec)
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");
+ config_size = (int) a2dp_codec->select_configuration(y->core->default_sample_spec, cap, (const size_t) size, &pconf);
+ if (size != config_size) {
+ pa_log_error("Capabilities array has invalid size %d, %d",size, config_size);
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(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));
+ a2dp_codec->free_configuration(&pconf);
return r;
fail:
@@ -1668,6 +1542,7 @@ static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, voi
struct pa_bluetooth_discovery *y = userdata;
DBusMessage *r = NULL;
const char *path, *interface, *member;
+ pa_a2dp_codec_index_t index;
pa_assert(y);
@@ -1677,7 +1552,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))
+ pa_a2dp_endpoint_to_codec_index(path, &index);
+ if (index == PA_A2DP_CODEC_INDEX_UNAVAILABLE)
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
@@ -1706,6 +1582,10 @@ static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, voi
}
static void endpoint_init(pa_bluetooth_discovery *y, pa_bluetooth_profile_t profile) {
+ void *state;
+ pa_a2dp_codec_index_t *index;
+ pa_hashmap *indices;
+ const char *endpoint;
static const DBusObjectPathVTable vtable_endpoint = {
.message_function = endpoint_handler,
};
@@ -1714,33 +1594,49 @@ static void endpoint_init(pa_bluetooth_discovery *y, pa_bluetooth_profile_t prof
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));
+ pa_a2dp_get_source_indices(&indices, &y->a2dp_config);
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));
+ pa_a2dp_get_sink_indices(&indices, &y->a2dp_config);
break;
default:
pa_assert_not_reached();
break;
}
+
+ PA_HASHMAP_FOREACH(index, indices, state){
+ pa_a2dp_codec_index_to_endpoint(*index, &endpoint);
+ if(!endpoint)
+ continue;
+ 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) {
+ void *state;
+ pa_a2dp_codec_index_t *index;
+ pa_hashmap *indices;
+ const char *endpoint;
+
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);
+ pa_a2dp_get_source_indices(&indices, &y->a2dp_config);
break;
case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
- dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT);
+ pa_a2dp_get_sink_indices(&indices, &y->a2dp_config);
break;
default:
pa_assert_not_reached();
break;
}
+
+ PA_HASHMAP_FOREACH(index,indices,state){
+ pa_a2dp_codec_index_to_endpoint(*index, &endpoint);
+ 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) {
@@ -1760,6 +1656,8 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backe
y->transports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
PA_LLIST_HEAD_INIT(pa_dbus_pending, y->pending);
+ pa_a2dp_init(&y->a2dp_config);
+
for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++)
pa_hook_init(&y->hooks[i], y);
@@ -1871,6 +1769,9 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
endpoint_done(y, PA_BLUETOOTH_PROFILE_A2DP_SINK);
endpoint_done(y, PA_BLUETOOTH_PROFILE_A2DP_SOURCE);
+ if(y->a2dp_config)
+ pa_a2dp_free(&y->a2dp_config);
+
pa_dbus_connection_unref(y->connection);
}
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index ad30708f0..c3be423e8 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -22,6 +22,8 @@
#include <pulsecore/core.h>
+#include "a2dp/a2dp-api.h"
+
#define PA_BLUETOOTH_UUID_A2DP_SOURCE "0000110a-0000-1000-8000-00805f9b34fb"
#define PA_BLUETOOTH_UUID_A2DP_SINK "0000110b-0000-1000-8000-00805f9b34fb"
@@ -78,6 +80,10 @@ struct pa_bluetooth_transport {
char *path;
pa_bluetooth_profile_t profile;
+ const pa_a2dp_codec_t *a2dp_codec;
+ pa_a2dp_sink_t *a2dp_sink;
+ pa_a2dp_source_t *a2dp_source;
+
uint8_t codec;
uint8_t *config;
size_t config_size;
diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index d983efdec..0fcde4941 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -46,9 +46,8 @@
#include <pulsecore/thread-mq.h>
#include <pulsecore/time-smoother.h>
-#include "a2dp-codecs.h"
+#include "a2dp/a2dp-api.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,13 +61,12 @@ 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[] = {
"path",
"autodetect_mtu",
+ "a2dp_config",
NULL
};
@@ -94,17 +92,13 @@ 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;
+typedef struct pa_a2dp_info {
+ pa_proplist *a2dp_config;
+ void *a2dp_sink_data;
+ void *a2dp_source_data;
+ void *buffer;
+ size_t buffer_size;
+} pa_a2dp_info_t;
struct userdata {
pa_module *module;
@@ -146,7 +140,7 @@ struct userdata {
pa_smoother *read_smoother;
pa_memchunk write_memchunk;
pa_sample_spec sample_spec;
- struct sbc_info sbc_info;
+ pa_a2dp_info_t a2dp_info;
};
typedef enum pa_bluetooth_form_factor {
@@ -418,105 +412,55 @@ static void a2dp_prepare_buffer(struct userdata *u) {
pa_assert(u);
- if (u->sbc_info.buffer_size >= min_buffer_size)
+ if (u->a2dp_info.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->a2dp_info.buffer_size = 2 * min_buffer_size;
+ pa_xfree(u->a2dp_info.buffer);
+ u->a2dp_info.buffer = pa_xmalloc(u->a2dp_info.buffer_size);
+}
+
+static void a2dp_encoder_buffer_read_cb(const void **read_buf, size_t read_buf_size, void *userdata) {
+ struct userdata *u = (struct userdata *) userdata;
+ if (!u->write_memchunk.memblock)
+ pa_sink_render_full(u->sink, read_buf_size, &u->write_memchunk);
+ pa_assert(u->write_memchunk.length == read_buf_size);
+ *read_buf = (const uint8_t *) pa_memblock_acquire_chunk(&u->write_memchunk);
+}
+
+static void a2dp_encoder_buffer_free_cb(const void **read_buf, void *userdata) {
+ struct userdata *u = (struct userdata *) userdata;
+ if (!read_buf)
+ return;;
+ pa_memblock_release(u->write_memchunk.memblock);
+ pa_memblock_unref(u->write_memchunk.memblock);
+ pa_memchunk_reset(&u->write_memchunk);
+ *read_buf = NULL;
}
/* 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;
+ size_t nbytes, encoded;
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);
+ pa_assert(u->transport);
+ pa_assert(u->transport->a2dp_source);
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;
+ nbytes = u->transport->a2dp_source->encode((uint32_t) (u->write_index / pa_frame_size(&u->sample_spec)),
+ u->a2dp_info.buffer, u->a2dp_info.buffer_size,
+ &encoded, u, &u->a2dp_info.a2dp_source_data);
+ if(nbytes == 0)
+ return -1;
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->a2dp_info.buffer, nbytes, &u->stream_write_type);
pa_assert(l != 0);
@@ -547,9 +491,7 @@ static int a2dp_process_render(struct userdata *u) {
break;
}
- u->write_index += (uint64_t) u->write_memchunk.length;
- pa_memblock_unref(u->write_memchunk.memblock);
- pa_memchunk_reset(&u->write_memchunk);
+ u->write_index += encoded;
ret = 1;
@@ -568,6 +510,8 @@ static int a2dp_process_push(struct userdata *u) {
pa_assert(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE);
pa_assert(u->source);
pa_assert(u->read_smoother);
+ pa_assert(u->transport);
+ pa_assert(u->transport->a2dp_sink);
memchunk.memblock = pa_memblock_new(u->core->mempool, u->read_block_size);
memchunk.index = memchunk.length = 0;
@@ -575,22 +519,18 @@ static int a2dp_process_push(struct userdata *u) {
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;
+ pa_a2dp_info_t *a2dp_info;
void *d;
ssize_t l;
- size_t to_write, to_decode;
+ size_t decoded;
size_t total_written = 0;
+ uint32_t timestamp;
a2dp_prepare_buffer(u);
- sbc_info = &u->sbc_info;
- header = sbc_info->buffer;
- payload = (struct rtp_payload*) ((uint8_t*) sbc_info->buffer + sizeof(*header));
+ a2dp_info = &u->a2dp_info;
- l = pa_read(u->stream_fd, sbc_info->buffer, sbc_info->buffer_size, &u->stream_write_type);
+ l = pa_read(u->stream_fd, a2dp_info->buffer, a2dp_info->buffer_size, &u->stream_write_type);
if (l <= 0) {
@@ -607,7 +547,7 @@ static int a2dp_process_push(struct userdata *u) {
break;
}
- pa_assert((size_t) l <= sbc_info->buffer_size);
+ pa_assert((size_t) l <= a2dp_info->buffer_size);
/* TODO: get timestamp from rtp */
if (!found_tstamp) {
@@ -615,50 +555,22 @@ 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;
- }
+ memchunk.length = pa_memblock_get_length(memchunk.memblock);
- 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);
-
- 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 = u->transport->a2dp_sink->decode(a2dp_info->buffer, (size_t) l, d, memchunk.length, &decoded,
+ ×tamp, &a2dp_info->a2dp_sink_data);
+ if(total_written == 0){
+ pa_memblock_release(memchunk.memblock);
+ pa_memblock_unref(memchunk.memblock);
+ 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 = total_written;
pa_memblock_release(memchunk.memblock);
@@ -705,40 +617,10 @@ 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;
-
+static void a2dp_set_sink(struct userdata *u){
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));
+ 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. */
@@ -750,27 +632,6 @@ static void a2dp_set_bitpool(struct userdata *u, uint8_t bitpool) {
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,13 +721,15 @@ 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;
+ u->read_block_size = u->read_link_mtu;
+ u->write_block_size = u->write_link_mtu;
+ if (u->transport->a2dp_sink)
+ u->transport->a2dp_sink->get_block_size(u->read_link_mtu, &u->read_block_size, &u->a2dp_info.a2dp_sink_data);
+ else if (u->transport->a2dp_source)
+ u->transport->a2dp_source->get_block_size(u->write_link_mtu, &u->write_block_size,
+ &u->a2dp_info.a2dp_source_data);
+ else
+ pa_assert_not_reached();
}
if (u->sink) {
@@ -906,9 +769,19 @@ 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);
- update_buffer_size(u);
+ if (u->transport->a2dp_sink) {
+ u->transport->a2dp_sink->setup_stream(&u->a2dp_info.a2dp_sink_data);
+ u->transport->a2dp_sink->get_block_size(u->read_link_mtu, &u->read_block_size,
+ &u->a2dp_info.a2dp_sink_data);
+ pa_proplist_sets(u->source->proplist, "bluetooth.a2dp_codec", u->transport->a2dp_codec->name);
+
+ } else if (u->transport->a2dp_source) {
+ u->transport->a2dp_source->setup_stream(&u->a2dp_info.a2dp_source_data);
+ u->transport->a2dp_source->get_block_size(u->write_link_mtu, &u->write_block_size,
+ &u->a2dp_info.a2dp_source_data);
+ a2dp_set_sink(u);
+ pa_proplist_sets(u->sink->proplist, "bluetooth.a2dp_codec", u->transport->a2dp_codec->name);
+
}
u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
@@ -1095,6 +968,8 @@ static int add_source(struct userdata *u) {
pa_assert_not_reached();
break;
}
+ else if(u->transport->a2dp_codec && u->transport->a2dp_sink)
+ pa_proplist_sets(data.proplist, "bluetooth.a2dp_codec", u->transport->a2dp_codec->name);
u->source = pa_source_new(u->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY);
pa_source_new_data_done(&data);
@@ -1270,6 +1145,9 @@ static int add_sink(struct userdata *u) {
pa_assert_not_reached();
break;
}
+ else if(u->transport->a2dp_codec && u->transport->a2dp_source)
+ pa_proplist_sets(data.proplist, "bluetooth.a2dp_codec", u->transport->a2dp_codec->name);
+
u->sink = pa_sink_new(u->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY);
pa_sink_new_data_done(&data);
@@ -1296,111 +1174,39 @@ 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;
-
+ pa_proplist *user_config = NULL;
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();
- }
+ if (u->a2dp_info.a2dp_config)
+ user_config = pa_proplist_copy(u->a2dp_info.a2dp_config);
- 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();
- }
+ if (u->transport->a2dp_sink) {
+ pa_assert_se(u->transport->a2dp_sink->init(&u->a2dp_info.a2dp_sink_data));
- 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();
- }
+ if (u->transport->a2dp_sink->update_user_config && user_config)
+ u->transport->a2dp_sink->update_user_config(user_config,
+ &u->a2dp_info.a2dp_sink_data);
- 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();
- }
+ u->transport->a2dp_sink->config_transport(u->core->default_sample_spec, u->transport->config,
+ u->transport->config_size,
+ &u->sample_spec, &u->a2dp_info.a2dp_sink_data);
+ } else if (u->transport->a2dp_source) {
+ pa_assert_se(
+ u->transport->a2dp_source->init(a2dp_encoder_buffer_read_cb, a2dp_encoder_buffer_free_cb,
+ &u->a2dp_info.a2dp_source_data));
- sbc_info->min_bitpool = config->min_bitpool;
- sbc_info->max_bitpool = config->max_bitpool;
+ if (u->transport->a2dp_source->update_user_config && user_config)
+ u->transport->a2dp_source->update_user_config(user_config,
+ &u->a2dp_info.a2dp_source_data);
- /* 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);
+ u->transport->a2dp_source->config_transport(u->core->default_sample_spec, u->transport->config,
+ u->transport->config_size,
+ &u->sample_spec, &u->a2dp_info.a2dp_source_data);
+ } else
+ pa_assert_not_reached();
- 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);
+ if(user_config)
+ pa_proplist_free(user_config);
}
}
@@ -1620,6 +1426,11 @@ static void thread_func(void *userdata) {
if (audio_sent <= time_passed) {
size_t bytes_to_send = pa_usec_to_bytes(time_passed - audio_sent, &u->sample_spec);
+ if (u->transport->a2dp_source && u->transport->a2dp_source->set_tx_length){
+ u->transport->a2dp_source->set_tx_length(bytes_to_send, &u->a2dp_info.a2dp_source_data);
+ u->transport->a2dp_source->get_block_size(u->write_link_mtu, &u->write_block_size, &u->a2dp_info.a2dp_source_data);
+ }
+
/* There are more than two blocks that need to be written. It seems that
* the socket has not been accepting data fast enough (could be due to
* hiccups in the wireless transmission). We need to discard everything
@@ -1651,8 +1462,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->transport->a2dp_source &&
+ u->transport->a2dp_source->decrease_quality) {
+
+ u->transport->a2dp_source->decrease_quality(&u->a2dp_info.a2dp_source_data);
+ u->transport->a2dp_source->get_block_size(u->write_link_mtu, &u->write_block_size, &u->a2dp_info.a2dp_source_data);
+ }
+
}
blocks_to_write = 1;
@@ -1807,6 +1623,14 @@ static void stop_thread(struct userdata *u) {
}
if (u->transport) {
+ if(u->transport->a2dp_sink && u->a2dp_info.a2dp_sink_data){
+ u->transport->a2dp_sink->free(&u->a2dp_info.a2dp_sink_data);
+ u->a2dp_info.a2dp_sink_data = NULL;
+ }
+ if(u->transport->a2dp_source && u->a2dp_info.a2dp_sink_data){
+ u->transport->a2dp_source->free(&u->a2dp_info.a2dp_source_data);
+ u->a2dp_info.a2dp_source_data = NULL;
+ }
transport_release(u);
u->transport = NULL;
}
@@ -2415,6 +2239,12 @@ int pa__init(pa_module* m) {
u->device->autodetect_mtu = autodetect_mtu;
+ u->a2dp_info.a2dp_config = pa_proplist_new();
+ if (pa_modargs_get_proplist(ma, "a2dp_config", u->a2dp_info.a2dp_config, PA_UPDATE_REPLACE) < 0) {
+ pa_log("Invalid proplist key=value pairs for a2dp_config parameter");
+ goto fail_free_modargs;
+ }
+
pa_modargs_free(ma);
u->device_connection_changed_slot =
@@ -2492,11 +2322,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->a2dp_info.buffer)
+ pa_xfree(u->a2dp_info.buffer);
+
+ if (u->a2dp_info.a2dp_sink_data)
+ pa_xfree(u->a2dp_info.a2dp_sink_data);
+
+ if (u->a2dp_info.a2dp_source_data)
+ pa_xfree(u->a2dp_info.a2dp_source_data);
- if (u->sbc_info.sbc_initialized)
- sbc_finish(&u->sbc_info.sbc);
+ if (u->a2dp_info.a2dp_config)
+ pa_proplist_free(u->a2dp_info.a2dp_config);
if (u->msg)
pa_xfree(u->msg);
diff --git a/src/modules/bluetooth/module-bluez5-discover.c b/src/modules/bluetooth/module-bluez5-discover.c
index b6c8eb050..b5a813968 100644
--- a/src/modules/bluetooth/module-bluez5-discover.c
+++ b/src/modules/bluetooth/module-bluez5-discover.c
@@ -42,6 +42,7 @@ PA_MODULE_USAGE(
static const char* const valid_modargs[] = {
"headset",
"autodetect_mtu",
+ "a2dp_config",
NULL
};
@@ -52,6 +53,7 @@ struct userdata {
pa_hook_slot *device_connection_changed_slot;
pa_bluetooth_discovery *discovery;
bool autodetect_mtu;
+ const char *a2dp_config;
};
static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y, const pa_bluetooth_device *d, struct userdata *u) {
@@ -72,7 +74,8 @@ static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y,
if (!module_loaded && pa_bluetooth_device_any_transport_connected(d)) {
/* a new device has been connected */
pa_module *m;
- char *args = pa_sprintf_malloc("path=%s autodetect_mtu=%i", d->path, (int)u->autodetect_mtu);
+ char *args = pa_sprintf_malloc("path=%s autodetect_mtu=%i a2dp_config=\"%s\"",
+ d->path, (int) u->autodetect_mtu, u->a2dp_config);
pa_log_debug("Loading module-bluez5-device %s", args);
pa_module_load(&m, u->module->core, "module-bluez5-device", args);
@@ -100,7 +103,7 @@ const char *default_headset_backend = "ofono";
int pa__init(pa_module *m) {
struct userdata *u;
pa_modargs *ma;
- const char *headset_str;
+ const char *headset_str, *a2dp_config;
int headset_backend;
bool autodetect_mtu;
@@ -129,10 +132,14 @@ int pa__init(pa_module *m) {
goto fail;
}
+ pa_assert_se(a2dp_config = pa_modargs_get_value(ma, "a2dp_config", ""));
+
+
m->userdata = u = pa_xnew0(struct userdata, 1);
u->module = m;
u->core = m->core;
u->autodetect_mtu = autodetect_mtu;
+ u->a2dp_config = pa_xmemdup(a2dp_config, strlen(a2dp_config) + 1);
u->loaded_device_paths = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
if (!(u->discovery = pa_bluetooth_discovery_get(u->core, headset_backend)))
@@ -169,5 +176,8 @@ void pa__done(pa_module *m) {
if (u->loaded_device_paths)
pa_hashmap_free(u->loaded_device_paths);
+ if (u->a2dp_config)
+ pa_xfree((void *) u->a2dp_config);
+
pa_xfree(u);
}
--
2.19.2
More information about the pulseaudio-discuss
mailing list