diff --git a/configure.ac b/configure.ac
index c9c414f81..b736fc3d7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1069,7 +1069,7 @@ PA_MACHINE_ID_FALLBACK="${localstatedir}/lib/dbus/machine-id"
 AX_DEFINE_DIR(PA_MACHINE_ID_FALLBACK, PA_MACHINE_ID_FALLBACK,
 	      [Fallback machine-id file])
 
-#### BlueZ support (optional, dependent on D-Bus and SBC) ####
+#### BlueZ support (optional, dependent on FFmpeg, FDK-AAC, D-Bus and SBC) ####
 
 AC_ARG_ENABLE([bluez4],
     AS_HELP_STRING([--disable-bluez4],[Disable optional BlueZ 4 support]))
@@ -1089,11 +1089,25 @@ AS_IF([test "x$enable_bluez4" = "xyes" && test "x$HAVE_BLUEZ_4" != "x1"],
 AC_SUBST(HAVE_BLUEZ_4)
 AM_CONDITIONAL([HAVE_BLUEZ_4], [test "x$HAVE_BLUEZ_4" = x1])
 
+## FDK-AAC ##
+AS_IF([test "x$enable_bluez5" != "xno"],
+    [PKG_CHECK_MODULES(FDK_AAC, [ fdk-aac >= 0.1.5 ], HAVE_FDK_AAC=1, HAVE_FDK_AAC=0)],
+    HAVE_FDK_AAC=0)
+
+
+## FFmpeg libavcodec ##
+AS_IF([test "x$enable_bluez5" != "xno"],
+    [PKG_CHECK_MODULES(FF_AVCODEC, [ libavcodec >= 58.18.100 ], HAVE_FF_AVCODEC=1, HAVE_FF_AVCODEC=0)],
+    HAVE_FF_AVCODEC=0)
+AS_IF([test "x$enable_bluez5" != "xno"],
+    [PKG_CHECK_MODULES(FF_AVUTIL, [ libavcodec >= 56.14.100 ], HAVE_FF_AVUTIL=1, HAVE_FF_AVUTIL=0)],
+    HAVE_FF_AVUTIL=0)
+
 ## BlueZ 5 ##
-AS_IF([test "x$enable_bluez5" != "xno" && test "x$HAVE_DBUS" = "x1" && test "x$HAVE_SBC" = "x1"], HAVE_BLUEZ_5=1,
+AS_IF([test "x$enable_bluez5" != "xno" && test "x$HAVE_DBUS" = "x1" && test "x$HAVE_SBC" = "x1" && test "x$HAVE_FDK_AAC" = "x1" && test "x$HAVE_FF_AVCODEC" = "x1" && test "x$HAVE_FF_AVUTIL" = "x1"], HAVE_BLUEZ_5=1,
       HAVE_BLUEZ_5=0)
 AS_IF([test "x$enable_bluez5" = "xyes" && test "x$HAVE_BLUEZ_5" != "x1"],
-    [AC_MSG_ERROR([*** BLUEZ 5 support not found (requires sbc and D-Bus)])])
+    [AC_MSG_ERROR([*** BLUEZ 5 support not found (requires FFmpeg, FDK-AAC, sbc and D-Bus)])])
 AC_SUBST(HAVE_BLUEZ_5)
 AM_CONDITIONAL([HAVE_BLUEZ_5], [test "x$HAVE_BLUEZ_5" = x1])
 
diff --git a/src/Makefile.am b/src/Makefile.am
index f4464d253..80cd56357 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2137,8 +2137,16 @@ module_bluez4_device_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) $(SBC_CFLAGS) -DPA_
 # Bluetooth BlueZ 5 sink / source
 libbluez5_util_la_SOURCES = \
 		modules/bluetooth/bluez5-util.c \
-		modules/bluetooth/bluez5-util.h \
-		modules/bluetooth/a2dp-codecs.h
+		modules/bluetooth/a2dp/a2dp_util.c \
+		modules/bluetooth/a2dp/a2dp_sbc.c \
+		modules/bluetooth/a2dp/a2dp_aac.c \
+		modules/bluetooth/a2dp/a2dp_aptx.c \
+		modules/bluetooth/a2dp/a2dp_ldac.c \
+		modules/bluetooth/a2dp/a2dp-api.h \
+		modules/bluetooth/a2dp/a2dp-codecs.h \
+		modules/bluetooth/a2dp/ldacBT.h \
+		modules/bluetooth/a2dp/ldacBT_abr.h \
+		modules/bluetooth/bluez5-util.h
 if HAVE_BLUEZ_5_OFONO_HEADSET
 libbluez5_util_la_SOURCES += \
 		modules/bluetooth/backend-ofono.c
@@ -2149,8 +2157,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) $(SBC_LIBS) $(DBUS_LIBS) $(FDK_AAC_LIBS)
+libbluez5_util_la_CFLAGS = $(AM_CFLAGS) $(SBC_CFLAGS) $(DBUS_CFLAGS) $(FF_AVCODEC_CFLAGS) $(FF_AVUTIL_CFLAGS) $(FDK_AAC_CFLAGS)
 
 module_bluez5_discover_la_SOURCES = modules/bluetooth/module-bluez5-discover.c
 module_bluez5_discover_la_LDFLAGS = $(MODULE_LDFLAGS)
@@ -2159,8 +2167,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/a2dp-api.h b/src/modules/bluetooth/a2dp/a2dp-api.h
new file mode 100644
index 000000000..a5f4eeb22
--- /dev/null
+++ b/src/modules/bluetooth/a2dp/a2dp-api.h
@@ -0,0 +1,181 @@
+#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;
+extern const pa_a2dp_codec_t pa_a2dp_aac;
+extern const pa_a2dp_codec_t pa_a2dp_aptx;
+extern const pa_a2dp_codec_t pa_a2dp_aptx_hd;
+extern const pa_a2dp_codec_t pa_a2dp_ldac;
+
+
+/* Implement in module-bluez5-device.c, 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);
+
+
+typedef enum pa_a2dp_codec_index {
+    PA_A2DP_SINK_MIN,
+    PA_A2DP_SINK_SBC,
+    PA_A2DP_SINK_AAC,
+    PA_A2DP_SINK_APTX,
+    PA_A2DP_SINK_APTX_HD,
+    PA_A2DP_SINK_MAX,
+    PA_A2DP_SOURCE_MIN = PA_A2DP_SINK_MAX,
+    PA_A2DP_SOURCE_SBC,
+    PA_A2DP_SOURCE_AAC,
+    PA_A2DP_SOURCE_APTX,
+    PA_A2DP_SOURCE_APTX_HD,
+    PA_A2DP_SOURCE_LDAC,
+    PA_A2DP_SOURCE_MAX,
+    PA_A2DP_CODEC_INDEX_UNAVAILABLE
+} pa_a2dp_codec_index_t;
+
+typedef struct pa_a2dp_sink {
+    int priority;
+
+    /* Load decoder syms 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 syms 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);
+
+    /* 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 if configuration valid */
+    bool (*validate_configuration)(const uint8_t *selected_configuration, const size_t configuration_size);
+
+};
+
+
+typedef struct pa_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..0bdd29110
--- /dev/null
+++ b/src/modules/bluetooth/a2dp/a2dp-codecs.h
@@ -0,0 +1,453 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2018       Pali Rohár <pali.rohar@gmail.com>
+ *
+ *
+ *  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
+ *
+ */
+
+#include <endian.h>
+#include <stdint.h>
+
+#define A2DP_CODEC_SBC			0x00
+#define A2DP_CODEC_MPEG12		0x01
+#define A2DP_CODEC_MPEG24		0x02
+#define A2DP_CODEC_ATRAC		0x04
+#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 SBC_MAX_BITPOOL			64
+#define SBC_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_INDEX_0		(1 << 0)
+#define MPEG_BIT_RATE_INDEX_1		(1 << 1)
+#define MPEG_BIT_RATE_INDEX_2		(1 << 2)
+#define MPEG_BIT_RATE_INDEX_3		(1 << 3)
+#define MPEG_BIT_RATE_INDEX_4		(1 << 4)
+#define MPEG_BIT_RATE_INDEX_5		(1 << 5)
+#define MPEG_BIT_RATE_INDEX_6		(1 << 6)
+#define MPEG_BIT_RATE_INDEX_7		(1 << 7)
+#define MPEG_BIT_RATE_INDEX_8		(1 << 8)
+#define MPEG_BIT_RATE_INDEX_9		(1 << 9)
+#define MPEG_BIT_RATE_INDEX_10		(1 << 10)
+#define MPEG_BIT_RATE_INDEX_11		(1 << 11)
+#define MPEG_BIT_RATE_INDEX_12		(1 << 12)
+#define MPEG_BIT_RATE_INDEX_13		(1 << 13)
+#define MPEG_BIT_RATE_INDEX_14		(1 << 14)
+
+#define MPEG_MP1_BIT_RATE_32000		MPEG_BIT_RATE_INDEX_1
+#define MPEG_MP1_BIT_RATE_64000		MPEG_BIT_RATE_INDEX_2
+#define MPEG_MP1_BIT_RATE_96000		MPEG_BIT_RATE_INDEX_3
+#define MPEG_MP1_BIT_RATE_128000	MPEG_BIT_RATE_INDEX_4
+#define MPEG_MP1_BIT_RATE_160000	MPEG_BIT_RATE_INDEX_5
+#define MPEG_MP1_BIT_RATE_192000	MPEG_BIT_RATE_INDEX_6
+#define MPEG_MP1_BIT_RATE_224000	MPEG_BIT_RATE_INDEX_7
+#define MPEG_MP1_BIT_RATE_256000	MPEG_BIT_RATE_INDEX_8
+#define MPEG_MP1_BIT_RATE_288000	MPEG_BIT_RATE_INDEX_9
+#define MPEG_MP1_BIT_RATE_320000	MPEG_BIT_RATE_INDEX_10
+#define MPEG_MP1_BIT_RATE_352000	MPEG_BIT_RATE_INDEX_11
+#define MPEG_MP1_BIT_RATE_384000	MPEG_BIT_RATE_INDEX_12
+#define MPEG_MP1_BIT_RATE_416000	MPEG_BIT_RATE_INDEX_13
+#define MPEG_MP1_BIT_RATE_448000	MPEG_BIT_RATE_INDEX_14
+
+#define MPEG_MP2_BIT_RATE_32000		MPEG_BIT_RATE_INDEX_1
+#define MPEG_MP2_BIT_RATE_48000		MPEG_BIT_RATE_INDEX_2
+#define MPEG_MP2_BIT_RATE_56000		MPEG_BIT_RATE_INDEX_3
+#define MPEG_MP2_BIT_RATE_64000		MPEG_BIT_RATE_INDEX_4
+#define MPEG_MP2_BIT_RATE_80000		MPEG_BIT_RATE_INDEX_5
+#define MPEG_MP2_BIT_RATE_96000		MPEG_BIT_RATE_INDEX_6
+#define MPEG_MP2_BIT_RATE_112000	MPEG_BIT_RATE_INDEX_7
+#define MPEG_MP2_BIT_RATE_128000	MPEG_BIT_RATE_INDEX_8
+#define MPEG_MP2_BIT_RATE_160000	MPEG_BIT_RATE_INDEX_9
+#define MPEG_MP2_BIT_RATE_192000	MPEG_BIT_RATE_INDEX_10
+#define MPEG_MP2_BIT_RATE_224000	MPEG_BIT_RATE_INDEX_11
+#define MPEG_MP2_BIT_RATE_256000	MPEG_BIT_RATE_INDEX_12
+#define MPEG_MP2_BIT_RATE_320000	MPEG_BIT_RATE_INDEX_13
+#define MPEG_MP2_BIT_RATE_384000	MPEG_BIT_RATE_INDEX_14
+
+#define MPEG_MP3_BIT_RATE_32000		MPEG_BIT_RATE_INDEX_1
+#define MPEG_MP3_BIT_RATE_40000		MPEG_BIT_RATE_INDEX_2
+#define MPEG_MP3_BIT_RATE_48000		MPEG_BIT_RATE_INDEX_3
+#define MPEG_MP3_BIT_RATE_56000		MPEG_BIT_RATE_INDEX_4
+#define MPEG_MP3_BIT_RATE_64000		MPEG_BIT_RATE_INDEX_5
+#define MPEG_MP3_BIT_RATE_80000		MPEG_BIT_RATE_INDEX_6
+#define MPEG_MP3_BIT_RATE_96000		MPEG_BIT_RATE_INDEX_7
+#define MPEG_MP3_BIT_RATE_112000	MPEG_BIT_RATE_INDEX_8
+#define MPEG_MP3_BIT_RATE_128000	MPEG_BIT_RATE_INDEX_9
+#define MPEG_MP3_BIT_RATE_160000	MPEG_BIT_RATE_INDEX_10
+#define MPEG_MP3_BIT_RATE_192000	MPEG_BIT_RATE_INDEX_11
+#define MPEG_MP3_BIT_RATE_224000	MPEG_BIT_RATE_INDEX_12
+#define MPEG_MP3_BIT_RATE_256000	MPEG_BIT_RATE_INDEX_13
+#define MPEG_MP3_BIT_RATE_320000	MPEG_BIT_RATE_INDEX_14
+
+#define MPEG_BIT_RATE_FREE		MPEG_BIT_RATE_INDEX_0
+
+#define MPEG_GET_BITRATE(a) ((uint16_t)(a).bitrate1 << 8 | (a).bitrate2)
+#define MPEG_SET_BITRATE(a, b) \
+	do { \
+		(a).bitrate1 = ((b) >> 8) & 0x7f; \
+		(a).bitrate2 = (b) & 0xff; \
+	} while (0)
+
+#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 FASTSTREAM_VENDOR_ID		0x0000000a
+#define FASTSTREAM_CODEC_ID		0x0001
+
+#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
+
+#define APTX_LL_VENDOR_ID		0x0000000a
+#define APTX_LL_CODEC_ID		0x0002
+
+#define APTX_LL_CHANNEL_MODE_MONO	0x01
+#define APTX_LL_CHANNEL_MODE_STEREO	0x02
+
+#define APTX_LL_SAMPLING_FREQ_16000	0x08
+#define APTX_LL_SAMPLING_FREQ_32000	0x04
+#define APTX_LL_SAMPLING_FREQ_44100	0x02
+#define APTX_LL_SAMPLING_FREQ_48000	0x01
+
+/* Default parameters for aptX Low Latency encoder */
+
+/* Target codec buffer level = 180 */
+#define APTX_LL_TARGET_LEVEL2	0xb4
+#define APTX_LL_TARGET_LEVEL1	0x00
+
+/* Initial codec buffer level = 360 */
+#define APTX_LL_INITIAL_LEVEL2	0x68
+#define APTX_LL_INITIAL_LEVEL1	0x01
+
+/* SRA max rate 0.005 * 10000 = 50 */
+#define APTX_LL_SRA_MAX_RATE		0x32
+
+/* SRA averaging time = 1s */
+#define APTX_LL_SRA_AVG_TIME		0x01
+
+/* Good working codec buffer level = 180 */
+#define APTX_LL_GOOD_WORKING_LEVEL2	0xB4
+#define APTX_LL_GOOD_WORKING_LEVEL1	0x00
+
+#define APTX_HD_VENDOR_ID		0x000000D7
+#define APTX_HD_CODEC_ID		0x0024
+
+#define APTX_HD_CHANNEL_MODE_MONO	0x1
+#define APTX_HD_CHANNEL_MODE_STEREO	0x2
+
+#define APTX_HD_SAMPLING_FREQ_16000	0x8
+#define APTX_HD_SAMPLING_FREQ_32000	0x4
+#define APTX_HD_SAMPLING_FREQ_44100	0x2
+#define APTX_HD_SAMPLING_FREQ_48000	0x1
+
+#define LDAC_VENDOR_ID			0x0000012d
+#define LDAC_CODEC_ID			0x00aa
+
+#define LDAC_SAMPLING_FREQ_44100	0x20
+#define LDAC_SAMPLING_FREQ_48000	0x10
+#define LDAC_SAMPLING_FREQ_88200	0x08
+#define LDAC_SAMPLING_FREQ_96000	0x04
+#define LDAC_SAMPLING_FREQ_176400	0x02
+#define LDAC_SAMPLING_FREQ_192000	0x01
+
+#define LDAC_CHANNEL_MODE_MONO		0x04
+#define LDAC_CHANNEL_MODE_DUAL		0x02
+#define LDAC_CHANNEL_MODE_STEREO	0x01
+
+typedef struct {
+	uint8_t vendor_id4;
+	uint8_t vendor_id3;
+	uint8_t vendor_id2;
+	uint8_t vendor_id1;
+	uint8_t codec_id2;
+	uint8_t codec_id1;
+} __attribute__ ((packed)) a2dp_vendor_codec_t;
+
+#define A2DP_GET_VENDOR_ID(a) ( \
+		(((uint32_t)(a).vendor_id4) <<  0) | \
+		(((uint32_t)(a).vendor_id3) <<  8) | \
+		(((uint32_t)(a).vendor_id2) << 16) | \
+		(((uint32_t)(a).vendor_id1) << 24) \
+	)
+#define A2DP_GET_CODEC_ID(a) ((a).codec_id2 | (((uint16_t)(a).codec_id1) << 8))
+#define A2DP_SET_VENDOR_ID_CODEC_ID(v, c) ((a2dp_vendor_codec_t){ \
+		.vendor_id4 = (((v) >>  0) & 0xff), \
+		.vendor_id3 = (((v) >>  8) & 0xff), \
+		.vendor_id2 = (((v) >> 16) & 0xff), \
+		.vendor_id1 = (((v) >> 24) & 0xff), \
+		.codec_id2 = (((c) >> 0) & 0xff), \
+		.codec_id1 = (((c) >> 8) & 0xff), \
+	})
+
+typedef struct {
+	uint8_t reserved;
+	uint8_t target_level2;
+	uint8_t target_level1;
+	uint8_t initial_level2;
+	uint8_t initial_level1;
+	uint8_t sra_max_rate;
+	uint8_t sra_avg_time;
+	uint8_t good_working_level2;
+	uint8_t good_working_level1;
+} __attribute__ ((packed)) a2dp_aptx_ll_new_caps_t;
+
+typedef struct {
+	a2dp_vendor_codec_t info;
+	uint8_t frequency;
+	uint8_t channel_mode;
+} __attribute__ ((packed)) a2dp_ldac_t;
+
+#if defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \
+	__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;
+	uint8_t bitrate1:7;
+	uint8_t vbr:1;
+	uint8_t bitrate2;
+} __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 direction;
+	uint8_t sink_frequency:4;
+	uint8_t source_frequency:4;
+} __attribute__ ((packed)) a2dp_faststream_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 defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \
+	__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;
+	uint8_t vbr:1;
+	uint8_t bitrate1:7;
+	uint8_t bitrate2;
+} __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 direction;
+	uint8_t source_frequency:4;
+	uint8_t sink_frequency:4;
+} __attribute__ ((packed)) a2dp_faststream_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/a2dp/a2dp_aac.c b/src/modules/bluetooth/a2dp/a2dp_aac.c
new file mode 100644
index 000000000..64556e8ab
--- /dev/null
+++ b/src/modules/bluetooth/a2dp/a2dp_aac.c
@@ -0,0 +1,743 @@
+#include <arpa/inet.h>
+#include <string.h>
+
+#include <fdk-aac/aacenc_lib.h>
+#include <fdk-aac/aacdecoder_lib.h>
+
+#include <pulse/xmalloc.h>
+
+#include "a2dp-api.h"
+
+#define streq(a, b) (!strcmp((a),(b)))
+
+#define AAC_DEFAULT_BITRATE 320000u
+
+typedef struct aac_info {
+    pa_a2dp_source_read_cb_t read_pcm;
+    pa_a2dp_source_read_buf_free_cb_t read_buf_free;
+
+    bool is_a2dp_sink;
+
+    uint16_t seq_num;
+
+    HANDLE_AACDECODER aacdecoder_handle;
+    bool aacdecoder_handle_opened;
+
+    HANDLE_AACENCODER aacenc_handle;
+    bool aacenc_handle_opened;
+    AACENC_InfoStruct aacenc_info;
+
+    uint32_t bitrate;
+    size_t mtu;
+
+    /* Constant Bitrate: 0
+     * Variable Bitrate: 1-5 (Only effects when both bluetooth devices have vbr support ) */
+    int aac_enc_bitrate_mode;
+    uint32_t aac_afterburner;
+    pa_sample_format_t force_pa_fmt;
+
+    pa_sample_spec sample_spec;
+
+    size_t read_block_size;
+    size_t write_block_size;
+
+} aac_info_t;
+
+static bool pa_aac_decoder_load() {
+    /* AAC libs dynamically linked */
+    return true;
+}
+
+static bool pa_aac_encoder_load() {
+    /* AAC libs dynamically linked */
+    return true;
+}
+
+static bool
+pa_aac_decoder_init(void **codec_data) {
+    aac_info_t *info = pa_xmalloc0(sizeof(aac_info_t));
+    *codec_data = info;
+    info->is_a2dp_sink = true;
+    return true;
+}
+
+static bool
+pa_aac_encoder_init(pa_a2dp_source_read_cb_t read_cb, pa_a2dp_source_read_buf_free_cb_t free_cb, void **codec_data) {
+    aac_info_t *info = pa_xmalloc0(sizeof(aac_info_t));
+    *codec_data = info;
+    info->is_a2dp_sink = false;
+    info->read_pcm = read_cb;
+    info->read_buf_free = free_cb;
+    info->aacenc_handle_opened = false;
+    info->aac_enc_bitrate_mode = 5;
+    info->aac_afterburner = false;
+    info->force_pa_fmt = PA_SAMPLE_INVALID;
+    return true;
+}
+
+/* KEY                 VALUE    DESC                                      DEFAULT
+ * aac_bitrate_mode    [1, 5]   Variable Bitrate (VBR) (encoder)          5
+ *                     0        Constant Bitrate (CBR) (encoder)
+ *
+ * aac_fmt             s16      16-bit signed LE (encoder)                auto
+ *                     s32      32-bit signed LE (encoder)
+ *                     auto
+ *
+ * aac_afterburner     <on/off> FDK-AAC afterburner feature (encoder)     off
+ */
+static int pa_aac_update_user_config(pa_proplist *user_config, void **codec_data) {
+    aac_info_t *i = *codec_data;
+    const char *aac_bitrate_mode_str, *aac_fmt_str, *aac_afterburner_str;
+    int aac_bitrate_mode = 0, ret = 0;
+    pa_assert(i);
+
+    aac_bitrate_mode_str = pa_proplist_gets(user_config, "aac_bitrate_mode");
+    aac_fmt_str = pa_proplist_gets(user_config, "aac_fmt");
+    aac_afterburner_str = pa_proplist_gets(user_config, "aac_afterburner");
+
+    if (aac_bitrate_mode_str) {
+        aac_bitrate_mode = atoi(aac_bitrate_mode_str);
+
+        if (aac_bitrate_mode >= 0 && aac_bitrate_mode <= 5) {
+            i->aac_enc_bitrate_mode = aac_bitrate_mode;
+            ret++;
+        } else
+            pa_log ("aac_bitrate_mode parameter must in range [0, 5] (found %s)", aac_bitrate_mode_str);
+    }
+
+    if (aac_fmt_str) {
+        if (streq(aac_fmt_str, "s16")) {
+            i->force_pa_fmt = PA_SAMPLE_S16LE;
+            ret++;
+        } else if (streq(aac_fmt_str, "s32")) {
+            i->force_pa_fmt = PA_SAMPLE_S32LE;
+            ret++;
+        } else if (streq(aac_fmt_str, "auto")) {
+            i->force_pa_fmt = PA_SAMPLE_INVALID;
+            ret++;
+        } else
+            pa_log ("aac_fmt parameter must be either s16, s32 or auto (found %s)", aac_fmt_str);
+    }
+
+    if (aac_afterburner_str) {
+        if (streq("on", aac_afterburner_str)) {
+            i->aac_afterburner = 1;
+            ret++;
+        } else if (streq("off", aac_afterburner_str)) {
+            i->aac_afterburner = 0;
+            ret++;
+        } else
+            pa_log ("aac_afterburner parameter must be either on or off (found %s)", aac_afterburner_str);
+    }
+
+    return ret;
+}
+
+static size_t
+pa_aac_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 UCHAR *p;
+    INT_PCM *d;
+    UINT to_decode, pkt_size;
+    UINT total_written = 0;
+    aac_info_t *aac_info = *codec_data;
+    pa_assert(aac_info);
+
+    header = read_buf;
+    *timestamp = ntohl(header->timestamp);
+
+    p = (UCHAR *) read_buf + sizeof(*header);
+    pkt_size = to_decode = (UINT) (read_buf_size - sizeof(*header));
+
+    d = write_buf;
+
+    *_decoded = 0;
+    while (PA_LIKELY(to_decode > 0)) {
+        CStreamInfo* info;
+
+        AAC_DECODER_ERROR aac_err = aacDecoder_Fill(aac_info->aacdecoder_handle,
+                                                    (UCHAR **) &p, &pkt_size, &to_decode);
+
+        if (PA_UNLIKELY(aac_err != AAC_DEC_OK)) {
+            pa_log_error("aacDecoder_Fill() error 0x%x", aac_err);
+            *_decoded = 0;
+            return 0;
+        }
+
+        while (true) {
+            INT written;
+            aac_err = aacDecoder_DecodeFrame(aac_info->aacdecoder_handle, d, (INT) write_buf_size, 0);
+            if (PA_UNLIKELY(aac_err == AAC_DEC_NOT_ENOUGH_BITS))
+                break;
+            if (PA_UNLIKELY(aac_err != AAC_DEC_OK)){
+                pa_log_error("aacDecoder_DecodeFrame() error 0x%x", aac_err);
+                break;
+            }
+
+            info = aacDecoder_GetStreamInfo(aac_info->aacdecoder_handle);
+            if(PA_UNLIKELY(!info || info->sampleRate <= 0)) {
+                pa_log_error("Invalid stream info");
+                break;
+            }
+
+            written = info->frameSize * info->numChannels * 2;
+            d += written;
+            total_written += (UINT) written;
+        }
+    }
+
+    *_decoded = pkt_size;
+
+    return total_written;
+}
+
+static size_t
+pa_aac_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;
+    size_t nbytes;
+    uint8_t *d;
+    const uint8_t *p;
+    int to_write;
+    unsigned frame_count;
+    aac_info_t *aac_info = *codec_data;
+    const size_t sample_size = pa_sample_size(&aac_info->sample_spec),
+            frame_size = pa_frame_size(&aac_info->sample_spec);
+    void *in_bufs[1] = {NULL};
+    void *out_bufs[1] = {NULL};
+    int in_bufferIdentifiers[1] = {IN_AUDIO_DATA};
+    int out_bufferIdentifiers[1] = {OUT_BITSTREAM_DATA};
+    int in_bufSizes[1] = {(int) (aac_info->aacenc_info.frameLength * frame_size)};
+    int out_bufSizes[1];
+    int bufElSizes[1] = {(int) sample_size};
+    AACENC_BufDesc in_bufDesc = {
+            .numBufs = 1,
+            .bufs = in_bufs,
+            .bufferIdentifiers = in_bufferIdentifiers,
+            .bufSizes = in_bufSizes,
+            .bufElSizes = bufElSizes
+    };
+    AACENC_BufDesc out_bufDesc = {
+            .numBufs = 1,
+            .bufs = out_bufs,
+            .bufferIdentifiers = out_bufferIdentifiers,
+            .bufSizes = out_bufSizes,
+            .bufElSizes = bufElSizes
+    };
+    AACENC_InArgs in_args = {
+            .numAncBytes = 0,
+            .numInSamples = aac_info->aacenc_info.frameLength * aac_info->aacenc_info.inputChannels
+    };
+    AACENC_OutArgs out_args;
+
+    pa_assert(aac_info);
+
+    header = write_buf;
+
+    frame_count = 0;
+
+    aac_info->read_pcm((const void **) &p, (size_t) in_bufSizes[0], read_cb_data);
+
+    in_bufDesc.bufs[0] = (void *) p;
+
+    d = (uint8_t *) write_buf + sizeof(*header);
+    to_write = (int) (write_buf_size - sizeof(*header));
+    out_bufDesc.bufs[0] = d;
+    out_bufSizes[0] = to_write;
+
+
+    *_encoded = 0;
+
+    while (PA_UNLIKELY(in_args.numInSamples && to_write > 0)) {
+        size_t encoded;
+
+        AACENC_ERROR aac_err = aacEncEncode(aac_info->aacenc_handle, &in_bufDesc, &out_bufDesc, &in_args, &out_args);
+
+        if (PA_UNLIKELY(aac_err != AACENC_OK)) {
+            pa_log_error("AAC encoding error, 0x%x", aac_err);
+            aac_info->read_buf_free((const void **) &p, read_cb_data);
+            *_encoded = 0;
+            return 0;
+        }
+
+        encoded = out_args.numInSamples * sample_size;
+
+        in_args.numInSamples -= out_args.numInSamples;
+        p += encoded;
+        *_encoded += encoded;
+
+        to_write -= out_args.numOutBytes;
+        d += out_args.numOutBytes;
+
+        frame_count++;
+    }
+
+    aac_info->read_buf_free((const void **) &p, read_cb_data);
+
+    memset(write_buf, 0, sizeof(*header));
+    header->v = 2;
+    header->pt = 1;
+    header->sequence_number = htons(aac_info->seq_num++);
+    header->timestamp = htonl(timestamp);
+    header->ssrc = htonl(1);
+
+    nbytes = d - (uint8_t *) write_buf;
+
+    return nbytes;
+}
+
+static void
+pa_aac_config_transport(pa_sample_spec default_sample_spec, const void *configuration, size_t configuration_size,
+                        pa_sample_spec *sample_spec, void **codec_data) {
+    AACENC_ERROR aac_err;
+    aac_info_t *aac_info = *codec_data;
+    a2dp_aac_t *config = (a2dp_aac_t *) configuration;
+    UINT aot, sample_rate, channels;
+    pa_sample_format_t fmt;
+
+    pa_assert(aac_info);
+    pa_assert_se(configuration_size == sizeof(*config));
+
+    aac_info->bitrate = PA_MIN(AAC_DEFAULT_BITRATE, ((uint32_t) AAC_GET_BITRATE(*config)));
+
+
+    if(aac_info->is_a2dp_sink)
+        sample_spec->format = PA_SAMPLE_S16LE;
+    else{
+        if (aac_info->force_pa_fmt == PA_SAMPLE_INVALID)
+            fmt = default_sample_spec.format;
+        else
+            fmt = aac_info->force_pa_fmt;
+
+        switch (fmt) {
+            case PA_SAMPLE_S24LE:
+            case PA_SAMPLE_S24BE:
+            case PA_SAMPLE_S24_32LE:
+            case PA_SAMPLE_S24_32BE:
+            case PA_SAMPLE_S32LE:
+            case PA_SAMPLE_S32BE:
+            case PA_SAMPLE_FLOAT32LE:
+            case PA_SAMPLE_FLOAT32BE:
+                sample_spec->format = PA_SAMPLE_S32LE;
+                break;
+            default:
+                sample_spec->format = PA_SAMPLE_S16LE;
+        }
+    }
+
+    switch (config->object_type) {
+        case AAC_OBJECT_TYPE_MPEG2_AAC_LC:
+            aot = AOT_AAC_LC;
+            break;
+        case AAC_OBJECT_TYPE_MPEG4_AAC_LC:
+            aot = AOT_AAC_LC;
+            break;
+        case AAC_OBJECT_TYPE_MPEG4_AAC_LTP:
+            aot = AOT_AAC_LTP;
+            break;
+        case AAC_OBJECT_TYPE_MPEG4_AAC_SCA:
+            aot = AOT_AAC_SCAL;
+            break;
+        default:
+            pa_log_error("Invalid AAC object type");
+            pa_assert_not_reached();
+    }
+
+    switch (AAC_GET_FREQUENCY(*config)) {
+        case AAC_SAMPLING_FREQ_8000:
+            sample_rate = 8000;
+            sample_spec->rate = 8000;
+            break;
+        case AAC_SAMPLING_FREQ_11025:
+            sample_rate = 11025;
+            sample_spec->rate = 11025;
+            break;
+        case AAC_SAMPLING_FREQ_12000:
+            sample_rate = 12000;
+            sample_spec->rate = 12000;
+            break;
+        case AAC_SAMPLING_FREQ_16000:
+            sample_rate = 16000;
+            sample_spec->rate = 16000;
+            break;
+        case AAC_SAMPLING_FREQ_22050:
+            sample_rate = 22050;
+            sample_spec->rate = 22050;
+            break;
+        case AAC_SAMPLING_FREQ_24000:
+            sample_rate = 24000;
+            sample_spec->rate = 24000;
+            break;
+        case AAC_SAMPLING_FREQ_32000:
+            sample_rate = 32000;
+            sample_spec->rate = 32000;
+            break;
+        case AAC_SAMPLING_FREQ_44100:
+            sample_rate = 44100;
+            sample_spec->rate = 44100;
+            break;
+        case AAC_SAMPLING_FREQ_48000:
+            sample_rate = 48000;
+            sample_spec->rate = 48000;
+            break;
+        case AAC_SAMPLING_FREQ_64000:
+            sample_rate = 64000;
+            sample_spec->rate = 64000;
+            break;
+        case AAC_SAMPLING_FREQ_88200:
+            sample_rate = 88200;
+            sample_spec->rate = 88200;
+            break;
+        case AAC_SAMPLING_FREQ_96000:
+            sample_rate = 96000;
+            sample_spec->rate = 96000;
+            break;
+        default:
+            pa_log_error("Invalid AAC frequency");
+            pa_assert_not_reached();
+    }
+
+    switch (config->channels) {
+        case AAC_CHANNELS_1:
+            channels = MODE_1;
+            sample_spec->channels = 1;
+            break;
+        case AAC_CHANNELS_2:
+            channels = MODE_2;
+            sample_spec->channels = 2;
+            break;
+        default:
+            pa_log_error("Invalid AAC channel mode");
+            pa_assert_not_reached();
+    }
+
+    aac_info->sample_spec = *sample_spec;
+
+    /* AAC SINK */
+    if (aac_info->is_a2dp_sink) {
+        if (!aac_info->aacdecoder_handle_opened) {
+            aac_info->aacdecoder_handle = aacDecoder_Open(TT_MP4_LATM_MCP1, 1);
+            aac_info->aacdecoder_handle_opened = true;
+        }
+
+        pa_assert_se(AAC_DEC_OK == aacDecoder_SetParam(aac_info->aacdecoder_handle, AAC_PCM_MIN_OUTPUT_CHANNELS,
+                                                       sample_spec->channels));
+        pa_assert_se(AAC_DEC_OK == aacDecoder_SetParam(aac_info->aacdecoder_handle, AAC_PCM_MAX_OUTPUT_CHANNELS,
+                                                       sample_spec->channels));
+
+        return;
+    }
+
+
+    /* AAC SOURCE */
+
+    if (!aac_info->aacenc_handle_opened) {
+        aac_err = aacEncOpen(&aac_info->aacenc_handle, 0, 2);
+
+        if (aac_err != AACENC_OK) {
+            pa_log_error("Cannot open AAC encoder handle: AAC error 0x%x", aac_err);
+            pa_assert_not_reached();
+        }
+        aac_info->aacenc_handle_opened = true;
+    }
+
+    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_AOT, aot);
+    if (aac_err != AACENC_OK)
+        pa_assert_not_reached();
+
+    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_SAMPLERATE, sample_rate);
+    if (aac_err != AACENC_OK)
+        pa_assert_not_reached();
+
+    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_CHANNELMODE, channels);
+    if (aac_err != AACENC_OK)
+        pa_assert_not_reached();
+
+    if (config->vbr) {
+        aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_BITRATEMODE,
+                                      (UINT) aac_info->aac_enc_bitrate_mode);
+        if (aac_err != AACENC_OK)
+            pa_assert_not_reached();
+    }
+
+    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_AUDIOMUXVER, 2);
+    if (aac_err != AACENC_OK)
+        pa_assert_not_reached();
+
+    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_SIGNALING_MODE, 1);
+    if (aac_err != AACENC_OK)
+        pa_assert_not_reached();
+
+    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_BITRATE, aac_info->bitrate);
+    if (aac_err != AACENC_OK)
+        pa_assert_not_reached();
+
+    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_TRANSMUX, TT_MP4_LATM_MCP1);
+    if (aac_err != AACENC_OK)
+        pa_assert_not_reached();
+
+    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_HEADER_PERIOD, 1);
+    if (aac_err != AACENC_OK)
+        pa_assert_not_reached();
+
+    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_AFTERBURNER, aac_info->aac_afterburner);
+    if (aac_err != AACENC_OK)
+        pa_assert_not_reached();
+
+    aac_err = aacEncEncode(aac_info->aacenc_handle, NULL, NULL, NULL, NULL);
+    if (aac_err != AACENC_OK)
+        pa_assert_not_reached();
+
+    pa_assert_se(AACENC_OK == aacEncInfo(aac_info->aacenc_handle, &aac_info->aacenc_info));
+
+    pa_assert(aac_info->aacenc_info.inputChannels == aac_info->sample_spec.channels);
+
+};
+
+static void pa_aac_get_read_block_size(size_t read_link_mtu, size_t *read_block_size, void **codec_data) {
+    aac_info_t *aac_info = *codec_data;
+    pa_assert(aac_info);
+
+    aac_info->mtu = read_link_mtu;
+
+    /* aacEncoder.pdf Section 3.2.1
+     * AAC-LC audio frame contains 1024 PCM samples per channel */
+    *read_block_size = 1024 * pa_frame_size(&aac_info->sample_spec);
+    aac_info->read_block_size = *read_block_size;
+};
+
+static void pa_aac_get_write_block_size(size_t write_link_mtu, size_t *write_block_size, void **codec_data) {
+    aac_info_t *aac_info = *codec_data;
+    pa_assert(aac_info);
+
+    aac_info->mtu = write_link_mtu;
+
+    /* aacEncoder.pdf section 3.2.1
+     * AAC-LC audio frame contains 1024 PCM samples per channel */
+    *write_block_size = 1024 * pa_frame_size(&aac_info->sample_spec);
+    aac_info->write_block_size = *write_block_size;
+};
+
+static void pa_aac_setup_stream(void **codec_data) {
+    AACENC_ERROR aac_err;
+    aac_info_t *aac_info = *codec_data;
+    uint32_t max_bitrate;
+    pa_assert(aac_info);
+
+    max_bitrate = (uint32_t) ((8 * (aac_info->mtu - sizeof(struct rtp_header)) * aac_info->sample_spec.rate) / 1024);
+
+    aac_info->bitrate = PA_MIN(max_bitrate, aac_info->bitrate);
+
+    pa_log_debug("Maximum AAC transmission bitrate: %d bps; Bitrate in use: %d bps", max_bitrate, aac_info->bitrate);
+
+    /* AAC SINK */
+    if (aac_info->is_a2dp_sink) {
+        return;
+    }
+
+
+    /* AAC SOURCE */
+
+    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_BITRATE, aac_info->bitrate);
+    if (aac_err != AACENC_OK)
+        pa_assert_not_reached();
+
+    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_PEAK_BITRATE, (UINT) max_bitrate);
+    if (aac_err != AACENC_OK)
+        pa_assert_not_reached();
+
+};
+
+static void pa_aac_free(void **codec_data) {
+    aac_info_t *aac_info = *codec_data;
+    if (!aac_info)
+        return;
+
+    if (aac_info->aacenc_handle_opened)
+        aacEncClose(&aac_info->aacenc_handle);
+
+    if (aac_info->aacdecoder_handle_opened)
+        aacDecoder_Close(aac_info->aacdecoder_handle);
+
+    pa_xfree(aac_info);
+    *codec_data = NULL;
+
+};
+
+static size_t pa_aac_get_capabilities(void **_capabilities) {
+    a2dp_aac_t *capabilities = pa_xmalloc0(sizeof(a2dp_aac_t));
+
+    capabilities->object_type = AAC_OBJECT_TYPE_MPEG2_AAC_LC | AAC_OBJECT_TYPE_MPEG4_AAC_LC;
+    capabilities->channels = AAC_CHANNELS_1 | AAC_CHANNELS_2;
+    AAC_SET_BITRATE(*capabilities, AAC_DEFAULT_BITRATE);
+    AAC_SET_FREQUENCY(*capabilities, (AAC_SAMPLING_FREQ_8000 | AAC_SAMPLING_FREQ_11025 | AAC_SAMPLING_FREQ_12000 |
+                                      AAC_SAMPLING_FREQ_16000 | AAC_SAMPLING_FREQ_22050 | AAC_SAMPLING_FREQ_24000 |
+                                      AAC_SAMPLING_FREQ_32000 | AAC_SAMPLING_FREQ_44100 | AAC_SAMPLING_FREQ_48000 |
+                                      AAC_SAMPLING_FREQ_64000 | AAC_SAMPLING_FREQ_88200 | AAC_SAMPLING_FREQ_96000));
+    capabilities->vbr = 1;
+    *_capabilities = capabilities;
+
+    return sizeof(*capabilities);
+};
+
+static size_t
+pa_aac_select_configuration(const pa_sample_spec default_sample_spec, const uint8_t *supported_capabilities,
+                            const size_t capabilities_size, void **configuration) {
+    a2dp_aac_t *cap = (a2dp_aac_t *) supported_capabilities;
+    a2dp_aac_t *config = pa_xmalloc0(sizeof(a2dp_aac_t));
+    pa_a2dp_freq_cap_t aac_freq_cap, aac_freq_table[] = {
+            {8000U,  AAC_SAMPLING_FREQ_8000},
+            {11025U, AAC_SAMPLING_FREQ_11025},
+            {12000U, AAC_SAMPLING_FREQ_12000},
+            {16000U, AAC_SAMPLING_FREQ_16000},
+            {22050U, AAC_SAMPLING_FREQ_22050},
+            {24000U, AAC_SAMPLING_FREQ_24000},
+            {32000U, AAC_SAMPLING_FREQ_32000},
+            {44100U, AAC_SAMPLING_FREQ_44100},
+            {48000U, AAC_SAMPLING_FREQ_48000},
+            {64000U, AAC_SAMPLING_FREQ_64000},
+            {88200U, AAC_SAMPLING_FREQ_88200},
+            {96000U, AAC_SAMPLING_FREQ_96000}
+    };
+
+    if (capabilities_size != sizeof(a2dp_aac_t))
+        return 0;
+
+    if (!pa_a2dp_select_cap_frequency(AAC_GET_FREQUENCY(*cap), default_sample_spec, aac_freq_table,
+                                      PA_ELEMENTSOF(aac_freq_table), &aac_freq_cap))
+        return 0;
+
+    AAC_SET_FREQUENCY(*config, aac_freq_cap.cap);
+
+    AAC_SET_BITRATE(*config, AAC_GET_BITRATE(*cap));
+
+    if (default_sample_spec.channels <= 1) {
+        if (cap->channels & AAC_CHANNELS_1)
+            config->channels = AAC_CHANNELS_1;
+        else if (cap->channels & AAC_CHANNELS_2)
+            config->channels = AAC_CHANNELS_2;
+        else {
+            pa_log_error("No supported channel modes");
+            return 0;
+        }
+    }
+
+    if (default_sample_spec.channels >= 2) {
+        if (cap->channels & AAC_CHANNELS_2)
+            config->channels = AAC_CHANNELS_2;
+        else if (cap->channels & AAC_CHANNELS_1)
+            config->channels = AAC_CHANNELS_1;
+        else {
+            pa_log_error("No supported channel modes");
+            return 0;
+        }
+    }
+
+    if (cap->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_LC)
+        config->object_type = AAC_OBJECT_TYPE_MPEG4_AAC_LC;
+    else if (cap->object_type & AAC_OBJECT_TYPE_MPEG2_AAC_LC)
+        config->object_type = AAC_OBJECT_TYPE_MPEG2_AAC_LC;
+    else {
+        pa_log_error("No supported aac object type");
+        return 0;
+    }
+
+    config->vbr = cap->vbr;
+
+    *configuration = config;
+    return sizeof(*config);
+};
+
+static void pa_aac_free_capabilities(void **capabilities) {
+    if (!capabilities || !*capabilities)
+        return;
+    pa_xfree(*capabilities);
+    *capabilities = NULL;
+}
+
+static bool pa_aac_validate_configuration(const uint8_t *selected_configuration, const size_t configuration_size) {
+    a2dp_aac_t *c = (a2dp_aac_t *) selected_configuration;
+
+    if (configuration_size != sizeof(a2dp_aac_t)) {
+        pa_log_error("AAC configuration array of invalid size");
+        return false;
+    }
+
+    switch (c->object_type) {
+        case AAC_OBJECT_TYPE_MPEG2_AAC_LC:
+        case AAC_OBJECT_TYPE_MPEG4_AAC_LC:
+            break;
+        default:
+            pa_log_error("Invalid object type in AAC configuration");
+            return false;
+    }
+
+    switch (AAC_GET_FREQUENCY(*c)) {
+        case AAC_SAMPLING_FREQ_8000:
+        case AAC_SAMPLING_FREQ_11025:
+        case AAC_SAMPLING_FREQ_12000:
+        case AAC_SAMPLING_FREQ_16000:
+        case AAC_SAMPLING_FREQ_22050:
+        case AAC_SAMPLING_FREQ_24000:
+        case AAC_SAMPLING_FREQ_32000:
+        case AAC_SAMPLING_FREQ_44100:
+        case AAC_SAMPLING_FREQ_48000:
+        case AAC_SAMPLING_FREQ_64000:
+        case AAC_SAMPLING_FREQ_88200:
+        case AAC_SAMPLING_FREQ_96000:
+            break;
+        default:
+            pa_log_error("Invalid sampling frequency in AAC configuration");
+            return false;
+    }
+
+    switch (c->channels) {
+        case AAC_CHANNELS_1:
+        case AAC_CHANNELS_2:
+            break;
+        default:
+            pa_log_error("Invalid channel mode in AAC Configuration");
+            return false;
+    }
+
+    return true;
+};
+
+
+static pa_a2dp_source_t pa_aac_source = {
+        .encoder_load = pa_aac_encoder_load,
+        .init = pa_aac_encoder_init,
+        .update_user_config = pa_aac_update_user_config,
+        .encode = pa_aac_encode,
+        .config_transport=pa_aac_config_transport,
+        .get_block_size=pa_aac_get_write_block_size,
+        .setup_stream = pa_aac_setup_stream,
+        .set_tx_length = NULL,
+        .decrease_quality = NULL,
+        .free = pa_aac_free
+};
+
+static pa_a2dp_sink_t pa_aac_sink = {
+        .decoder_load = pa_aac_decoder_load,
+        .init = pa_aac_decoder_init,
+        .update_user_config = NULL,
+        .config_transport=pa_aac_config_transport,
+        .get_block_size=pa_aac_get_read_block_size,
+        .setup_stream = pa_aac_setup_stream,
+        .decode = pa_aac_decode,
+        .free = pa_aac_free
+};
+
+const pa_a2dp_codec_t pa_a2dp_aac = {
+        .name = "AAC",
+        .codec = A2DP_CODEC_MPEG24,
+        .vendor_codec = NULL,
+        .a2dp_sink = &pa_aac_sink,
+        .a2dp_source = &pa_aac_source,
+        .get_capabilities = pa_aac_get_capabilities,
+        .select_configuration = pa_aac_select_configuration,
+        .free_capabilities = pa_aac_free_capabilities,
+        .free_configuration = pa_aac_free_capabilities,
+        .validate_configuration = pa_aac_validate_configuration
+};
diff --git a/src/modules/bluetooth/a2dp/a2dp_aptx.c b/src/modules/bluetooth/a2dp/a2dp_aptx.c
new file mode 100644
index 000000000..6a08a5aad
--- /dev/null
+++ b/src/modules/bluetooth/a2dp/a2dp_aptx.c
@@ -0,0 +1,608 @@
+
+#include <arpa/inet.h>
+#include <string.h>
+
+#include <libavcodec/avcodec.h>
+#include <libavutil/samplefmt.h>
+
+#include <pulse/xmalloc.h>
+
+#include "a2dp-api.h"
+
+#include "aptx_libs.c"
+
+#define streq(a, b) (!strcmp((a),(b)))
+
+
+typedef struct aptx_info {
+    pa_a2dp_source_read_cb_t read_pcm;
+    pa_a2dp_source_read_buf_free_cb_t read_buf_free;
+
+    bool is_a2dp_sink;
+    bool is_hd;
+
+    size_t aptx_frame_size;
+    int nb_samples;
+    const AVCodec *av_codec;
+    AVCodecContext *av_codec_ctx;
+    int channel_mode;
+    uint16_t seq_num;
+
+
+    size_t block_size;
+
+
+} aptx_info_t;
+
+static const AVCodec *av_codec_aptx_decoder = NULL;
+static const AVCodec *av_codec_aptx_encoder = NULL;
+
+static const AVCodec *av_codec_aptx_hd_decoder = NULL;
+static const AVCodec *av_codec_aptx_hd_encoder = NULL;
+
+
+static bool pa_aptx_decoder_load() {
+    if(!aptx_libs_load())
+        return false;
+    if (av_codec_aptx_decoder)
+        return true;
+    av_codec_aptx_decoder = avcodec_find_decoder_func(AV_CODEC_ID_APTX);
+    if (!av_codec_aptx_decoder) {
+        pa_log_debug("Cannot find APTX decoder in FFmpeg avcodec library");
+        return false;
+    }
+
+    return true;
+}
+
+static bool pa_aptx_encoder_load() {
+    if(!aptx_libs_load())
+        return false;
+    if (av_codec_aptx_encoder)
+        return true;
+    av_codec_aptx_encoder = avcodec_find_encoder_func(AV_CODEC_ID_APTX);
+    if (!av_codec_aptx_encoder) {
+        pa_log_debug("Cannot find APTX encoder in FFmpeg avcodec library");
+        return false;
+    }
+
+    return true;
+}
+
+static bool pa_aptx_hd_decoder_load() {
+    if(!aptx_libs_load())
+        return false;
+    if (av_codec_aptx_hd_decoder)
+        return true;
+    av_codec_aptx_hd_decoder = avcodec_find_decoder_func(AV_CODEC_ID_APTX_HD);
+    if (!av_codec_aptx_hd_decoder) {
+        pa_log_debug("Cannot find APTX HD decoder in FFmpeg avcodec library");
+        return false;
+    }
+
+    return true;
+}
+
+static bool pa_aptx_hd_encoder_load() {
+    if(!aptx_libs_load())
+        return false;
+    if (av_codec_aptx_hd_encoder)
+        return true;
+    av_codec_aptx_hd_encoder = avcodec_find_encoder_func(AV_CODEC_ID_APTX_HD);
+    if (!av_codec_aptx_hd_encoder) {
+        pa_log_debug("Cannot find APTX HD encoder in FFmpeg avcodec library");
+        return false;
+    }
+
+    return true;
+}
+
+static bool _internal_pa_dual_decoder_init(bool is_hd, void **codec_data) {
+    aptx_info_t *info = pa_xmalloc0(sizeof(aptx_info_t));
+    *codec_data = info;
+    info->is_hd = is_hd;
+    info->is_a2dp_sink = true;
+    info->aptx_frame_size = is_hd ? 6 : 4;
+    info->av_codec = is_hd ? av_codec_aptx_hd_decoder : av_codec_aptx_decoder;
+    return true;
+}
+
+static bool
+_internal_pa_dual_encoder_init(bool is_hd, pa_a2dp_source_read_cb_t read_cb, pa_a2dp_source_read_buf_free_cb_t free_cb,
+                               void **codec_data) {
+    aptx_info_t *info = pa_xmalloc0(sizeof(aptx_info_t));
+    *codec_data = info;
+    info->is_hd = is_hd;
+    info->is_a2dp_sink = false;
+    info->read_pcm = read_cb;
+    info->read_buf_free = free_cb;
+    info->aptx_frame_size = is_hd ? 6 : 4;
+    info->av_codec = is_hd ? av_codec_aptx_hd_encoder : av_codec_aptx_encoder;
+    return true;
+}
+
+static int pa_dual_update_user_config(pa_proplist *user_config, void **codec_data) {
+    return 0;
+}
+
+static size_t
+pa_dual_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;
+    void *p;
+    int ret;
+    size_t i;
+    AVPacket *pkt;
+    size_t to_decode;
+    size_t total_written = 0;
+    AVFrame *av_frame = NULL;
+    aptx_info_t *aptx_info = *codec_data;
+
+
+    pa_assert(aptx_info);
+    pa_assert(aptx_info->av_codec);
+    pa_assert(aptx_info->av_codec_ctx);
+
+    if (aptx_info->is_hd) {
+        header = read_buf;
+
+        *timestamp = ntohl(header->timestamp);
+
+        p = (uint8_t *) read_buf + sizeof(*header);
+        to_decode = read_buf_size - sizeof(*header);
+    } else {
+        *timestamp = (uint32_t) -1;
+
+        p = (uint8_t *) read_buf;
+        to_decode = read_buf_size;
+    }
+
+    av_frame = av_frame_alloc_func();
+    pkt = av_packet_alloc_func();
+    pkt->data = p;
+    pkt->size = (int) to_decode;
+
+
+    *_decoded = 0;
+
+    ret = avcodec_send_packet_func(aptx_info->av_codec_ctx, pkt);
+    if (PA_UNLIKELY(ret < 0)) {
+        pa_log_debug("Error submitting the packet to the decoder");
+        goto done;
+    }
+    ret = avcodec_receive_frame_func(aptx_info->av_codec_ctx, av_frame);
+    if (PA_UNLIKELY(ret < 0)) {
+        pa_log_debug("Error during decoding");
+        goto done;
+    }
+
+    *_decoded = aptx_info->aptx_frame_size * av_frame->nb_samples / 4;
+
+    total_written = (size_t) av_frame->nb_samples * (4 * 2);
+
+    pa_assert_fp(_decoded <= read_buf_size);
+    pa_assert_fp(total_written <= write_buf_size);
+
+    for (i = 0; i < av_frame->nb_samples * sizeof(uint32_t); i += sizeof(uint32_t)) {
+        memcpy((uint8_t *) write_buf + i * 2, av_frame->data[0] + i, sizeof(uint32_t));
+        memcpy((uint8_t *) write_buf + i * 2 + sizeof(uint32_t), av_frame->data[1] + i, sizeof(uint32_t));
+    }
+
+done:
+    av_frame_free_func(&av_frame);
+    av_packet_free_func(&pkt);
+    return total_written;
+}
+
+static size_t
+pa_dual_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;
+    size_t nbytes;
+    void *d;
+    const void *p;
+    AVFrame *av_frame;
+    AVPacket *pkt;
+    aptx_info_t *aptx_info = *codec_data;
+    int ret;
+    size_t i;
+
+    pa_assert(aptx_info);
+    pa_assert(aptx_info->av_codec);
+    pa_assert(aptx_info->av_codec_ctx);
+
+    pa_assert_fp(aptx_info->av_codec_ctx->frame_size <= write_buf_size);
+
+    aptx_info->read_pcm(&p, (size_t) aptx_info->block_size, read_cb_data);
+
+    if (aptx_info->is_hd) {
+        header = write_buf;
+        memset(write_buf, 0, sizeof(*header));
+        header->v = 2;
+        header->pt = 1;
+        header->sequence_number = htons(aptx_info->seq_num++);
+        header->timestamp = htonl(timestamp);
+        header->ssrc = htonl(1);
+        d = (uint8_t *) write_buf + sizeof(*header);
+    } else {
+        d = (uint8_t *) write_buf;
+    }
+
+    av_frame = av_frame_alloc_func();
+    av_frame->nb_samples = aptx_info->nb_samples;
+    av_frame->format = aptx_info->av_codec_ctx->sample_fmt;
+    av_frame->channel_layout = aptx_info->av_codec_ctx->channel_layout;
+
+    pkt = av_packet_alloc_func();
+
+    pa_assert_se(av_frame_get_buffer_func(av_frame, 0) >= 0);
+    pa_assert_se(av_frame_make_writable_func(av_frame) >= 0);
+
+
+    for (i = 0; i < av_frame->nb_samples * sizeof(uint32_t); i += sizeof(uint32_t)) {
+        memcpy(av_frame->data[0] + i, (uint8_t *) p + i * 2, sizeof(uint32_t));
+        memcpy(av_frame->data[1] + i, (uint8_t *) p + i * 2 + sizeof(uint32_t), sizeof(uint32_t));
+    }
+    *_encoded = 0;
+
+    ret = avcodec_send_frame_func(aptx_info->av_codec_ctx, av_frame);
+
+    if (PA_UNLIKELY(ret < 0)) {
+        fprintf(stderr, "Error sending the frame to the encoder\n");
+        nbytes = 0;
+        goto done;
+    }
+
+    ret = avcodec_receive_packet_func(aptx_info->av_codec_ctx, pkt);
+
+    if (PA_UNLIKELY(ret != 0)) {
+        fprintf(stderr, "Error receiving the packet from the encoder\n");
+        nbytes = 0;
+        goto done;
+    }
+
+    memcpy(d, pkt->data, (size_t) pkt->size);
+    d = (uint8_t *) d + pkt->size;
+
+    nbytes = (uint8_t *) d - (uint8_t *) write_buf;
+    *_encoded += aptx_info->block_size;
+
+done:
+    av_frame_free_func(&av_frame);
+    av_packet_free_func(&pkt);
+    aptx_info->read_buf_free(&p, read_cb_data);
+    return nbytes;
+}
+
+static void
+pa_dual_config_transport(pa_sample_spec default_sample_spec, const void *configuration, size_t configuration_size,
+                         pa_sample_spec *sample_spec, void **codec_data) {
+    aptx_info_t *aptx_info = *codec_data;
+    a2dp_aptx_t *config = (a2dp_aptx_t *) configuration;
+    AVCodecContext *aptx_ctx;
+    pa_assert(aptx_info);
+    pa_assert(aptx_info->av_codec);
+    pa_assert_se(configuration_size == (aptx_info->is_hd ? sizeof(a2dp_aptx_hd_t) : sizeof(a2dp_aptx_t)));
+
+    if (aptx_info->av_codec_ctx)
+        avcodec_free_context_func(&aptx_info->av_codec_ctx);
+
+    aptx_info->av_codec_ctx = avcodec_alloc_context3_func(aptx_info->av_codec);
+    aptx_ctx = aptx_info->av_codec_ctx;
+
+    aptx_ctx->sample_fmt = AV_SAMPLE_FMT_S32P;
+    sample_spec->format = PA_SAMPLE_S32LE;
+
+    switch (config->frequency) {
+        case APTX_SAMPLING_FREQ_16000:
+            aptx_ctx->sample_rate = 16000;
+            aptx_ctx->bit_rate = 16000;
+            sample_spec->rate = 16000U;
+            break;
+        case APTX_SAMPLING_FREQ_32000:
+            aptx_ctx->sample_rate = 32000;
+            aptx_ctx->bit_rate = 32000;
+            sample_spec->rate = 32000U;
+            break;
+        case APTX_SAMPLING_FREQ_44100:
+            aptx_ctx->sample_rate = 44100;
+            aptx_ctx->bit_rate = 44100;
+            sample_spec->rate = 44100U;
+            break;
+        case APTX_SAMPLING_FREQ_48000:
+            aptx_ctx->sample_rate = 48000;
+            aptx_ctx->bit_rate = 48000;
+            sample_spec->rate = 48000U;
+            break;
+        default:
+            pa_assert_not_reached();
+    }
+
+    switch (config->channel_mode) {
+        case APTX_CHANNEL_MODE_STEREO:
+            aptx_ctx->channel_layout = AV_CH_LAYOUT_STEREO;
+            aptx_ctx->channels = 2;
+            sample_spec->channels = 2;
+            break;
+        default:
+            pa_assert_not_reached();
+    }
+
+    pa_assert_se(avcodec_open2_func(aptx_info->av_codec_ctx, aptx_info->av_codec, NULL) == 0);
+
+};
+
+static void pa_dual_get_read_block_size(size_t read_link_mtu, size_t *read_block_size, void **codec_data) {
+    aptx_info_t *aptx_info = *codec_data;
+    size_t aptx_frame_size = aptx_info->aptx_frame_size;
+    size_t rtp_use_size = aptx_info->is_hd ? sizeof(struct rtp_header) : 0;
+    pa_assert(aptx_info);
+
+    /*
+    　* PCM 32-bit, 2 channel (4 bytes * 2)
+    　* PCM frames/APTX frames == 4
+    　* */
+    *read_block_size = (read_link_mtu - rtp_use_size) / aptx_frame_size * (4 * 2) * 4;
+    aptx_info->block_size = *read_block_size;
+};
+
+static void pa_dual_get_write_block_size(size_t write_link_mtu, size_t *write_block_size, void **codec_data) {
+    aptx_info_t *aptx_info = *codec_data;
+    size_t aptx_frame_size = aptx_info->aptx_frame_size;
+    size_t rtp_use_size = aptx_info->is_hd ? sizeof(struct rtp_header) : 0;
+    pa_assert(aptx_info);
+
+    /*
+     * PCM 32-bit, 2 channel (4 bytes * 2)
+     * PCM frames/APTX frames == 4
+     * */
+    *write_block_size = (write_link_mtu - rtp_use_size) / aptx_frame_size * (4 * 2) * 4;
+    aptx_info->block_size = *write_block_size;
+};
+
+
+static void pa_dual_setup_stream(void **codec_data) {
+    aptx_info_t *aptx_info = *codec_data;
+    pa_assert(aptx_info);
+    aptx_info->nb_samples = (int) (aptx_info->block_size / (4 * 2));
+    aptx_info->av_codec_ctx->frame_size = (int) (aptx_info->aptx_frame_size * aptx_info->nb_samples / 4);
+};
+
+static void pa_dual_free(void **codec_data) {
+    aptx_info_t *aptx_info = *codec_data;
+    if (!aptx_info)
+        return;
+    if (aptx_info->av_codec_ctx)
+        avcodec_free_context_func(&aptx_info->av_codec_ctx);
+    pa_xfree(aptx_info);
+    *codec_data = NULL;
+
+};
+
+static size_t _internal_pa_dual_get_capabilities(bool is_hd, void **_capabilities) {
+
+    const size_t cap_size = is_hd ? sizeof(a2dp_aptx_hd_t) : sizeof(a2dp_aptx_t);
+    a2dp_aptx_t *capabilities = (a2dp_aptx_t *) pa_xmalloc0(cap_size);
+
+    if (is_hd) {
+        capabilities->info = A2DP_SET_VENDOR_ID_CODEC_ID(APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID);
+    } else {
+        capabilities->info = A2DP_SET_VENDOR_ID_CODEC_ID(APTX_VENDOR_ID, APTX_CODEC_ID);
+    }
+
+    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;
+    *_capabilities = capabilities;
+
+    return cap_size;
+};
+
+
+static size_t
+_internal_pa_dual_select_configuration(bool is_hd, const pa_sample_spec default_sample_spec,
+                                       const uint8_t *supported_capabilities,
+                                       const size_t capabilities_size, void **configuration) {
+    a2dp_aptx_t *cap;
+    a2dp_aptx_t *config;
+    const size_t cap_size = is_hd ? sizeof(a2dp_aptx_hd_t) : sizeof(a2dp_aptx_t);
+    pa_a2dp_freq_cap_t aptx_freq_cap, aptx_freq_table[] = {
+            {16000U, APTX_SAMPLING_FREQ_16000},
+            {32000U, APTX_SAMPLING_FREQ_32000},
+            {44100U, APTX_SAMPLING_FREQ_44100},
+            {48000U, APTX_SAMPLING_FREQ_48000}
+    };
+
+    cap = (a2dp_aptx_t *) supported_capabilities;
+
+    if (capabilities_size != cap_size)
+        return 0;
+
+    config = (a2dp_aptx_t *) pa_xmalloc0(cap_size);
+
+    if (is_hd) {
+        config->info = A2DP_SET_VENDOR_ID_CODEC_ID(APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID);
+    } else {
+        config->info = A2DP_SET_VENDOR_ID_CODEC_ID(APTX_VENDOR_ID, APTX_CODEC_ID);
+    }
+
+    if (!pa_a2dp_select_cap_frequency(cap->frequency, default_sample_spec, aptx_freq_table,
+                                      PA_ELEMENTSOF(aptx_freq_table), &aptx_freq_cap))
+        return 0;
+
+    config->frequency = (uint8_t) aptx_freq_cap.cap;
+
+    if (cap->channel_mode & APTX_CHANNEL_MODE_STEREO)
+        config->channel_mode = APTX_CHANNEL_MODE_STEREO;
+    else {
+        pa_log_error("No supported channel modes");
+        return 0;
+    }
+
+    *configuration = config;
+    return cap_size;
+};
+
+static void pa_dual_free_capabilities(void **capabilities) {
+    if (!capabilities || !*capabilities)
+        return;
+    pa_xfree(*capabilities);
+    *capabilities = NULL;
+}
+
+static bool _internal_pa_dual_validate_configuration(bool is_hd, const uint8_t *selected_configuration,
+                                                const size_t configuration_size) {
+    a2dp_aptx_t *c = (a2dp_aptx_t *) selected_configuration;
+
+    if (configuration_size != (is_hd ? sizeof(a2dp_aptx_hd_t) : sizeof(a2dp_aptx_t))) {
+        pa_log_error("APTX configuration array of invalid size");
+        return false;
+    }
+
+    switch (c->frequency) {
+        case APTX_SAMPLING_FREQ_16000:
+        case APTX_SAMPLING_FREQ_32000:
+        case APTX_SAMPLING_FREQ_44100:
+        case APTX_SAMPLING_FREQ_48000:
+            break;
+        default:
+            pa_log_error("Invalid sampling frequency in APTX configuration");
+            return false;
+    }
+
+    switch (c->channel_mode) {
+        case APTX_CHANNEL_MODE_STEREO:
+            break;
+        default:
+            pa_log_error("Invalid channel mode in APTX Configuration");
+            return false;
+    }
+    return true;
+};
+
+
+static bool pa_aptx_decoder_init(void **codec_data) {
+    return _internal_pa_dual_decoder_init(false, codec_data);
+}
+
+static bool pa_aptx_hd_decoder_init(void **codec_data) {
+    return _internal_pa_dual_decoder_init(true, codec_data);
+}
+
+static bool
+pa_aptx_encoder_init(pa_a2dp_source_read_cb_t read_cb, pa_a2dp_source_read_buf_free_cb_t free_cb, void **codec_data) {
+    return _internal_pa_dual_encoder_init(false, read_cb, free_cb, codec_data);
+}
+
+static bool
+pa_aptx_hd_encoder_init(pa_a2dp_source_read_cb_t read_cb, pa_a2dp_source_read_buf_free_cb_t free_cb,
+                        void **codec_data) {
+    return _internal_pa_dual_encoder_init(true, read_cb, free_cb, codec_data);
+}
+
+
+static size_t pa_aptx_get_capabilities(void **_capabilities) {
+    return _internal_pa_dual_get_capabilities(false, _capabilities);
+}
+
+static size_t pa_aptx_select_configuration(const pa_sample_spec default_sample_spec,
+                                           const uint8_t *supported_capabilities,
+                                           const size_t capabilities_size, void **configuration) {
+    return _internal_pa_dual_select_configuration(false, default_sample_spec, supported_capabilities, capabilities_size,
+                                                  configuration);
+}
+
+static bool pa_aptx_validate_configuration(const uint8_t *selected_configuration,
+                                      const size_t configuration_size) {
+    return _internal_pa_dual_validate_configuration(false, selected_configuration, configuration_size);
+}
+
+static size_t pa_aptx_hd_get_capabilities(void **_capabilities) {
+    return _internal_pa_dual_get_capabilities(true, _capabilities);
+}
+
+static size_t pa_aptx_hd_select_configuration(const pa_sample_spec default_sample_spec,
+                                              const uint8_t *supported_capabilities,
+                                              const size_t capabilities_size, void **configuration) {
+    return _internal_pa_dual_select_configuration(true, default_sample_spec, supported_capabilities, capabilities_size,
+                                                  configuration);
+}
+
+static bool pa_aptx_hd_validate_configuration(const uint8_t *selected_configuration,
+                                         const size_t configuration_size) {
+    return _internal_pa_dual_validate_configuration(true, selected_configuration, configuration_size);
+}
+
+static pa_a2dp_source_t pa_aptx_source = {
+        .encoder_load = pa_aptx_encoder_load,
+        .init = pa_aptx_encoder_init,
+        .update_user_config = pa_dual_update_user_config,
+        .encode = pa_dual_encode,
+        .config_transport = pa_dual_config_transport,
+        .get_block_size = pa_dual_get_write_block_size,
+        .setup_stream = pa_dual_setup_stream,
+        .set_tx_length = NULL,
+        .decrease_quality = NULL,
+        .free = pa_dual_free
+};
+
+static pa_a2dp_sink_t pa_aptx_sink = {
+        .decoder_load = pa_aptx_decoder_load,
+        .init = pa_aptx_decoder_init,
+        .update_user_config = pa_dual_update_user_config,
+        .config_transport = pa_dual_config_transport,
+        .get_block_size = pa_dual_get_read_block_size,
+        .setup_stream = pa_dual_setup_stream,
+        .decode = pa_dual_decode,
+        .free = pa_dual_free
+};
+
+const pa_a2dp_codec_t pa_a2dp_aptx = {
+        .name = "aptX",
+        .codec = A2DP_CODEC_VENDOR,
+        .vendor_codec = &A2DP_SET_VENDOR_ID_CODEC_ID(APTX_VENDOR_ID, APTX_CODEC_ID),
+        .a2dp_sink = &pa_aptx_sink,
+        .a2dp_source = &pa_aptx_source,
+        .get_capabilities = pa_aptx_get_capabilities,
+        .select_configuration = pa_aptx_select_configuration,
+        .free_capabilities = pa_dual_free_capabilities,
+        .free_configuration = pa_dual_free_capabilities,
+        .validate_configuration = pa_aptx_validate_configuration
+};
+
+static pa_a2dp_source_t pa_aptx_hd_source = {
+        .encoder_load = pa_aptx_hd_encoder_load,
+        .init = pa_aptx_hd_encoder_init,
+        .update_user_config = pa_dual_update_user_config,
+        .encode = pa_dual_encode,
+        .config_transport = pa_dual_config_transport,
+        .get_block_size = pa_dual_get_write_block_size,
+        .setup_stream = pa_dual_setup_stream,
+        .set_tx_length = NULL,
+        .decrease_quality = NULL,
+        .free = pa_dual_free
+};
+
+static pa_a2dp_sink_t pa_aptx_hd_sink = {
+        .decoder_load = pa_aptx_hd_decoder_load,
+        .init = pa_aptx_hd_decoder_init,
+        .update_user_config = pa_dual_update_user_config,
+        .config_transport = pa_dual_config_transport,
+        .get_block_size = pa_dual_get_read_block_size,
+        .setup_stream = pa_dual_setup_stream,
+        .decode = pa_dual_decode,
+        .free = pa_dual_free
+};
+
+const pa_a2dp_codec_t pa_a2dp_aptx_hd = {
+        .name = "aptX_HD",
+        .codec = A2DP_CODEC_VENDOR,
+        .vendor_codec = &A2DP_SET_VENDOR_ID_CODEC_ID(APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID),
+        .a2dp_sink = &pa_aptx_hd_sink,
+        .a2dp_source = &pa_aptx_hd_source,
+        .get_capabilities = pa_aptx_hd_get_capabilities,
+        .select_configuration = pa_aptx_hd_select_configuration,
+        .free_capabilities = pa_dual_free_capabilities,
+        .free_configuration = pa_dual_free_capabilities,
+        .validate_configuration = pa_aptx_hd_validate_configuration
+};
diff --git a/src/modules/bluetooth/a2dp/a2dp_ldac.c b/src/modules/bluetooth/a2dp/a2dp_ldac.c
new file mode 100644
index 000000000..1e728a023
--- /dev/null
+++ b/src/modules/bluetooth/a2dp/a2dp_ldac.c
@@ -0,0 +1,574 @@
+
+#include <arpa/inet.h>
+#include <string.h>
+
+#ifdef HAVE_CONFIG_H
+
+#include <config.h>
+
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/once.h>
+
+#include "ldacBT.h"
+#include "ldacBT_abr.h"
+
+#include "a2dp-api.h"
+
+#include "ldac_libs.c"
+
+#define streq(a, b) (!strcmp((a),(b)))
+
+#define AVDT_MEDIA_HDR_SIZE 12
+
+#define LDAC_ABR_THRESHOLD_CRITICAL 6
+#define LDAC_ABR_THRESHOLD_DANGEROUSTREND 4
+#define LDAC_ABR_THRESHOLD_SAFETY_FOR_HQSQ 2
+
+
+typedef struct ldac_info {
+    HANDLE_LDAC_BT hLdacBt;
+    HANDLE_LDAC_ABR hLdacAbr;
+
+    pa_a2dp_source_read_cb_t read_pcm;
+    pa_a2dp_source_read_buf_free_cb_t read_buf_free;
+
+    int eqmid;
+    bool enable_abr;
+    int channel_mode;
+    pa_sample_format_t force_pa_fmt;
+    LDACBT_SMPL_FMT_T pcm_fmt;
+    uint32_t pcm_frequency;
+
+    uint16_t pcm_lsu;
+    size_t ldac_frame_size;
+    size_t pcm_read_size;
+    size_t q_write_block_size;
+    pa_sample_spec sample_spec;
+
+    uint16_t seq_num;
+    uint32_t layer_specific;
+    uint32_t written;
+    size_t tx_length;
+
+    size_t mtu;
+
+} ldac_info_t;
+
+static bool pa_ldac_encoder_load() {
+    return ldac_encoder_load();
+}
+
+static bool
+pa_ldac_encoder_init(pa_a2dp_source_read_cb_t read_cb, pa_a2dp_source_read_buf_free_cb_t free_cb, void **codec_data) {
+    ldac_info_t *info = pa_xmalloc0(sizeof(ldac_info_t));
+    *codec_data = info;
+    info->read_pcm = read_cb;
+    info->read_buf_free = free_cb;
+    info->eqmid = LDACBT_EQMID_HQ;
+    if(ldac_abr_loaded)
+        info->enable_abr = true;
+    info->force_pa_fmt = PA_SAMPLE_INVALID;
+    return true;
+}
+
+static int pa_ldac_update_user_config(pa_proplist *user_config, void **codec_data) {
+    ldac_info_t *i = *codec_data;
+    const char *ldac_eqmid_str, *ldac_fmt_str;
+    int ret = 0;
+    ldac_eqmid_str = pa_proplist_gets(user_config, "ldac_eqmid");
+    ldac_fmt_str = pa_proplist_gets(user_config, "ldac_fmt");
+
+    pa_log_debug("LDAC ABR library loaded: %s",ldac_abr_loaded?"true":"false");
+
+    if (ldac_eqmid_str) {
+        if (streq(ldac_eqmid_str, "hq")) {
+            i->eqmid = LDACBT_EQMID_HQ;
+            i->enable_abr = false;
+            ret++;
+        } else if (streq(ldac_eqmid_str, "sq")) {
+            i->eqmid = LDACBT_EQMID_SQ;
+            i->enable_abr = false;
+            ret++;
+        } else if (streq(ldac_eqmid_str, "mq")) {
+            i->eqmid = LDACBT_EQMID_MQ;
+            i->enable_abr = false;
+            ret++;
+        } else if (streq(ldac_eqmid_str, "auto") ||
+                   streq(ldac_eqmid_str, "abr")) {
+            i->eqmid = LDACBT_EQMID_HQ;
+            if(ldac_abr_loaded)
+                i->enable_abr = true;
+            ret++;
+        } else {
+            pa_log("ldac_eqmid parameter must be either hq, sq, mq, or auto/abr (found %s)", ldac_eqmid_str);
+        }
+    }
+
+    if (ldac_fmt_str) {
+        if (streq(ldac_fmt_str, "s16")) {
+            i->force_pa_fmt = PA_SAMPLE_S16LE;
+            ret++;
+        } else if (streq(ldac_fmt_str, "s24")) {
+            i->force_pa_fmt = PA_SAMPLE_S24LE;
+            ret++;
+        } else if (streq(ldac_fmt_str, "s32")) {
+            i->force_pa_fmt = PA_SAMPLE_S32LE;
+            ret++;
+        } else if (streq(ldac_fmt_str, "f32")) {
+            i->force_pa_fmt = PA_SAMPLE_FLOAT32LE;
+            ret++;
+        } else if (streq(ldac_fmt_str, "auto")) {
+            i->force_pa_fmt = PA_SAMPLE_INVALID;
+            ret++;
+        } else {
+            pa_log("ldac_fmt parameter must be either s16, s24, s32, f32 or auto (found %s)", ldac_fmt_str);
+        }
+    }
+
+    return ret;
+}
+
+static size_t
+pa_ldac_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, ldac_enc_read;
+    unsigned frame_count;
+    ldac_info_t *ldac_info = *codec_data;
+    pa_assert(ldac_info);
+    pa_assert(ldac_info->hLdacBt);
+
+
+    if (ldac_info->hLdacAbr && ldac_info->enable_abr) {
+        ldac_ABR_Proc_func(ldac_info->hLdacBt, ldac_info->hLdacAbr,
+                      (unsigned int) (ldac_info->tx_length / ldac_info->q_write_block_size),
+                      (unsigned int) ldac_info->enable_abr);
+    }
+
+
+    ldac_enc_read = (pa_frame_size(&ldac_info->sample_spec) * LDACBT_ENC_LSU);
+
+    header = write_buf;
+    payload = (struct rtp_payload *) ((uint8_t *) write_buf + sizeof(*header));
+
+    frame_count = 0;
+
+    /* MAX pcm size for 1 LDAC packet (LDAC MQ) */
+    to_encode = (ldac_info->mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+                / 110 * ldac_info->pcm_read_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 && frame_count == 0)) {
+        int written;
+        int encoded;
+        int ldac_frame_num;
+        int ret_code;
+        ldac_info->read_pcm(&p, ldac_enc_read, read_cb_data);
+
+        ret_code = ldacBT_encode_func(ldac_info->hLdacBt, (void *) p, &encoded, (uint8_t *) d, &written, &ldac_frame_num);
+
+        ldac_info->read_buf_free(&p, read_cb_data);
+
+        if (PA_UNLIKELY(ret_code < 0)) {
+            int err;
+            pa_log_error("LDAC encoding error, written:%d encoded:%d ldac_frame_num:%d", written, encoded,
+                         ldac_frame_num);
+            err = ldacBT_get_error_code_func(ldac_info->hLdacBt);
+            pa_log_error("LDACBT_API_ERR:%d  LDACBT_HANDLE_ERR:%d  LDACBT_BLOCK_ERR:%d", LDACBT_API_ERR(err),
+                         LDACBT_HANDLE_ERR(err), LDACBT_BLOCK_ERR(err));
+            *_encoded = 0;
+            return 0;
+        }
+
+        pa_assert_fp(encoded == (int) ldac_enc_read);
+        pa_assert_fp(written <= (int) to_write);
+
+        *_encoded += encoded;
+        to_encode -= encoded;
+
+        d = (uint8_t *) d + written;
+        to_write -= written;
+
+        frame_count += ldac_frame_num;
+
+    }
+
+
+    PA_ONCE_BEGIN
+                {
+                    const int v = ldacBT_get_version_func();
+                    pa_log_notice("Using LDAC library: version: %x.%02x.%02x",
+                                  v >> 16,
+                                  (v >> 8) & 0x0ff,
+                                  v & 0x0ff
+                    );
+                }
+    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(ldac_info->seq_num++);
+    header->timestamp = htonl(timestamp);
+    header->ssrc = htonl(1);
+    payload->frame_count = frame_count;
+    ldac_info->layer_specific += frame_count;
+
+    nbytes = (uint8_t *) d - (uint8_t *) write_buf;
+
+    ldac_info->written += nbytes - sizeof(*header) - sizeof(*payload);
+
+    return nbytes;
+}
+
+static void
+pa_ldac_config_transport(pa_sample_spec default_sample_spec, const void *configuration, size_t configuration_size,
+                         pa_sample_spec *sample_spec, void **codec_data) {
+    ldac_info_t *ldac_info = *codec_data;
+    a2dp_ldac_t *config = (a2dp_ldac_t *) configuration;
+    pa_sample_format_t fmt;
+    pa_assert(ldac_info);
+    pa_assert_se(configuration_size == sizeof(*config));
+
+    ldac_info->hLdacBt = NULL;
+    ldac_info->hLdacAbr = NULL;
+
+    if (ldac_info->force_pa_fmt == PA_SAMPLE_INVALID)
+        fmt = default_sample_spec.format;
+    else
+        fmt = ldac_info->force_pa_fmt;
+
+    switch (fmt) {
+        case PA_SAMPLE_FLOAT32LE:
+        case PA_SAMPLE_FLOAT32BE:
+            ldac_info->pcm_fmt = LDACBT_SMPL_FMT_F32;
+            sample_spec->format = PA_SAMPLE_FLOAT32LE;
+            break;
+        case PA_SAMPLE_S32LE:
+        case PA_SAMPLE_S32BE:
+            ldac_info->pcm_fmt = LDACBT_SMPL_FMT_S32;
+            sample_spec->format = PA_SAMPLE_S32LE;
+            break;
+        case PA_SAMPLE_S24LE:
+        case PA_SAMPLE_S24BE:
+        case PA_SAMPLE_S24_32LE:
+        case PA_SAMPLE_S24_32BE:
+            ldac_info->pcm_fmt = LDACBT_SMPL_FMT_S24;
+            sample_spec->format = PA_SAMPLE_S24LE;
+            break;
+        default:
+            ldac_info->pcm_fmt = LDACBT_SMPL_FMT_S16;
+            sample_spec->format = PA_SAMPLE_S16LE;
+    }
+
+
+    switch (config->frequency) {
+        case LDACBT_SAMPLING_FREQ_044100:
+            ldac_info->pcm_frequency = 44100U;
+            sample_spec->rate = 44100U;
+            break;
+        case LDACBT_SAMPLING_FREQ_048000:
+            ldac_info->pcm_frequency = 48000U;
+            sample_spec->rate = 48000U;
+            break;
+        case LDACBT_SAMPLING_FREQ_088200:
+            ldac_info->pcm_frequency = 88200U;
+            sample_spec->rate = 88200U;
+            break;
+        case LDACBT_SAMPLING_FREQ_096000:
+            ldac_info->pcm_frequency = 96000U;
+            sample_spec->rate = 96000U;
+            break;
+        case LDACBT_SAMPLING_FREQ_176400:
+            ldac_info->pcm_frequency = 176400U;
+            sample_spec->rate = 176400U;
+            break;
+        case LDACBT_SAMPLING_FREQ_192000:
+            ldac_info->pcm_frequency = 192000U;
+            sample_spec->rate = 192000U;
+            break;
+        default:
+            pa_assert_not_reached();
+    }
+
+    switch (config->channel_mode) {
+        case LDACBT_CHANNEL_MODE_MONO:
+            ldac_info->channel_mode = LDACBT_CHANNEL_MODE_MONO;
+            sample_spec->channels = 1;
+            break;
+        case LDACBT_CHANNEL_MODE_DUAL_CHANNEL:
+            ldac_info->channel_mode = LDACBT_CHANNEL_MODE_DUAL_CHANNEL;
+            sample_spec->channels = 2;
+            break;
+        case LDACBT_CHANNEL_MODE_STEREO:
+            ldac_info->channel_mode = LDACBT_CHANNEL_MODE_STEREO;
+            sample_spec->channels = 2;
+            break;
+        default:
+            pa_assert_not_reached();
+    }
+
+    switch (ldac_info->pcm_frequency) {
+        case 44100:
+        case 48000:
+            ldac_info->pcm_lsu = 128;
+            break;
+        case 88200:
+        case 96000:
+            ldac_info->pcm_lsu = 256;
+            break;
+        case 176400:
+        case 192000:
+            ldac_info->pcm_lsu = 512;
+            break;
+        default:
+            pa_assert_not_reached();
+    }
+
+    switch (ldac_info->eqmid) {
+        case LDACBT_EQMID_HQ:
+            ldac_info->ldac_frame_size = 330;
+            break;
+        case LDACBT_EQMID_SQ:
+            ldac_info->ldac_frame_size = 220;
+            break;
+        case LDACBT_EQMID_MQ:
+            ldac_info->ldac_frame_size = 110;
+            break;
+        default:
+            pa_assert_not_reached();
+    }
+
+    ldac_info->sample_spec = *sample_spec;
+    ldac_info->pcm_read_size = (ldac_info->pcm_lsu * pa_frame_size(&ldac_info->sample_spec));
+
+};
+
+static void pa_ldac_get_block_size(size_t write_link_mtu, size_t *write_block_size, void **codec_data) {
+    ldac_info_t *ldac_info = *codec_data;
+    pa_assert(ldac_info);
+
+    ldac_info->mtu = write_link_mtu;
+
+    ldac_info->q_write_block_size = ((write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+                                     / ldac_info->ldac_frame_size * ldac_info->pcm_read_size);
+    *write_block_size = LDACBT_MAX_LSU * pa_frame_size(&ldac_info->sample_spec);
+};
+
+
+static void pa_ldac_setup_stream(void **codec_data) {
+    int ret;
+    ldac_info_t *ldac_info = *codec_data;
+    pa_assert(ldac_info);
+
+    ldac_info->layer_specific = 0;
+    ldac_info->written = 0;
+    if (ldac_info->hLdacBt)
+        ldacBT_free_handle_func(ldac_info->hLdacBt);
+    ldac_info->hLdacBt = ldacBT_get_handle_func();
+
+
+    ret = ldacBT_init_handle_encode_func(ldac_info->hLdacBt,
+                                    (int) ldac_info->mtu + AVDT_MEDIA_HDR_SIZE,
+                                    ldac_info->eqmid,
+                                    ldac_info->channel_mode,
+                                    ldac_info->pcm_fmt,
+                                    ldac_info->pcm_frequency);
+    if (ret != 0) {
+        pa_log_warn("Failed to init ldacBT handle");
+        goto fail;
+    }
+
+    if (!ldac_abr_loaded)
+        return;
+
+    if (ldac_info->hLdacAbr)
+        ldac_ABR_free_handle_func(ldac_info->hLdacAbr);
+    ldac_info->hLdacAbr = ldac_ABR_get_handle_func();
+
+    ret = ldac_ABR_Init_func(ldac_info->hLdacAbr,
+                        (unsigned int) pa_bytes_to_usec(ldac_info->q_write_block_size, &ldac_info->sample_spec) / 1000);
+    if (ret != 0) {
+        pa_log_warn("Failed to init ldacBT_ABR handle");
+        goto fail1;
+    }
+
+    ldac_ABR_set_thresholds_func(ldac_info->hLdacAbr, LDAC_ABR_THRESHOLD_CRITICAL,
+                            LDAC_ABR_THRESHOLD_DANGEROUSTREND, LDAC_ABR_THRESHOLD_SAFETY_FOR_HQSQ);
+    return;
+
+fail:
+    ldacBT_free_handle_func(ldac_info->hLdacBt);
+    ldac_info->hLdacBt = NULL;
+    if (!ldac_abr_loaded)
+        return;
+fail1:
+    ldac_ABR_free_handle_func(ldac_info->hLdacAbr);
+    ldac_info->hLdacAbr = NULL;
+    ldac_info->enable_abr = false;
+};
+
+static void pa_ldac_set_tx_length(size_t len, void **codec_data) {
+    ldac_info_t *ldac_info = *codec_data;
+    pa_assert(ldac_info);
+    ldac_info->tx_length = len;
+};
+
+static void pa_ldac_free(void **codec_data) {
+    ldac_info_t *ldac_info = *codec_data;
+    if (!ldac_info)
+        return;
+
+    if (ldac_info->hLdacBt)
+        ldacBT_free_handle_func(ldac_info->hLdacBt);
+
+    if (ldac_info->hLdacAbr && ldac_abr_loaded)
+        ldac_ABR_free_handle_func(ldac_info->hLdacAbr);
+
+    pa_xfree(ldac_info);
+    *codec_data = NULL;
+
+};
+
+static size_t pa_ldac_get_capabilities(void **_capabilities) {
+    a2dp_ldac_t *capabilities = pa_xmalloc0(sizeof(a2dp_ldac_t));
+
+    capabilities->info = A2DP_SET_VENDOR_ID_CODEC_ID(LDAC_VENDOR_ID, LDAC_CODEC_ID);
+    capabilities->frequency = LDACBT_SAMPLING_FREQ_044100 | LDACBT_SAMPLING_FREQ_048000 |
+                              LDACBT_SAMPLING_FREQ_088200 | LDACBT_SAMPLING_FREQ_096000;
+    capabilities->channel_mode = LDACBT_CHANNEL_MODE_MONO | LDACBT_CHANNEL_MODE_DUAL_CHANNEL |
+                                 LDACBT_CHANNEL_MODE_STEREO;
+    *_capabilities = capabilities;
+
+    return sizeof(*capabilities);
+};
+
+static size_t
+pa_ldac_select_configuration(const pa_sample_spec default_sample_spec, const uint8_t *supported_capabilities,
+                             const size_t capabilities_size, void **configuration) {
+    a2dp_ldac_t *cap = (a2dp_ldac_t *) supported_capabilities;
+    a2dp_ldac_t *config = pa_xmalloc0(sizeof(a2dp_ldac_t));
+    pa_a2dp_freq_cap_t ldac_freq_cap, ldac_freq_table[] = {
+            {44100U,  LDACBT_SAMPLING_FREQ_044100},
+            {48000U,  LDACBT_SAMPLING_FREQ_048000},
+            {88200U,  LDACBT_SAMPLING_FREQ_088200},
+            {96000U,  LDACBT_SAMPLING_FREQ_096000}
+    };
+
+    if (capabilities_size != sizeof(a2dp_ldac_t))
+        return 0;
+
+    config->info = A2DP_SET_VENDOR_ID_CODEC_ID(LDAC_VENDOR_ID, LDAC_CODEC_ID);
+
+    if (!pa_a2dp_select_cap_frequency(cap->frequency, default_sample_spec, ldac_freq_table,
+                                      PA_ELEMENTSOF(ldac_freq_table), &ldac_freq_cap))
+        return 0;
+
+    config->frequency = (uint8_t) ldac_freq_cap.cap;
+
+    if (default_sample_spec.channels <= 1) {
+        if (cap->channel_mode & LDACBT_CHANNEL_MODE_MONO)
+            config->channel_mode = LDACBT_CHANNEL_MODE_MONO;
+        else if (cap->channel_mode & LDACBT_CHANNEL_MODE_STEREO)
+            config->channel_mode = LDACBT_CHANNEL_MODE_STEREO;
+        else if (cap->channel_mode & LDACBT_CHANNEL_MODE_DUAL_CHANNEL)
+            config->channel_mode = LDACBT_CHANNEL_MODE_DUAL_CHANNEL;
+        else {
+            pa_log_error("No supported channel modes");
+            return 0;
+        }
+    }
+
+    if (default_sample_spec.channels >= 2) {
+        if (cap->channel_mode & LDACBT_CHANNEL_MODE_STEREO)
+            config->channel_mode = LDACBT_CHANNEL_MODE_STEREO;
+        else if (cap->channel_mode & LDACBT_CHANNEL_MODE_DUAL_CHANNEL)
+            config->channel_mode = LDACBT_CHANNEL_MODE_DUAL_CHANNEL;
+        else if (cap->channel_mode & LDACBT_CHANNEL_MODE_MONO)
+            config->channel_mode = LDACBT_CHANNEL_MODE_MONO;
+        else {
+            pa_log_error("No supported channel modes");
+            return 0;
+        }
+    }
+    *configuration = config;
+    return sizeof(*config);
+};
+
+static void pa_ldac_free_capabilities(void **capabilities) {
+    if (!capabilities || !*capabilities)
+        return;
+    pa_xfree(*capabilities);
+    *capabilities = NULL;
+}
+
+static bool pa_ldac_validate_configuration(const uint8_t *selected_configuration, const size_t configuration_size) {
+    a2dp_ldac_t *c = (a2dp_ldac_t *) selected_configuration;
+
+    if (configuration_size != sizeof(a2dp_ldac_t)) {
+        pa_log_error("LDAC configuration array of invalid size");
+        return false;
+    }
+
+    switch (c->frequency) {
+        case LDACBT_SAMPLING_FREQ_044100:
+        case LDACBT_SAMPLING_FREQ_048000:
+        case LDACBT_SAMPLING_FREQ_088200:
+        case LDACBT_SAMPLING_FREQ_096000:
+        case LDACBT_SAMPLING_FREQ_176400:
+        case LDACBT_SAMPLING_FREQ_192000:
+            break;
+        default:
+            pa_log_error("Invalid sampling frequency in LDAC configuration");
+            return false;
+    }
+
+    switch (c->channel_mode) {
+        case LDACBT_CHANNEL_MODE_STEREO:
+        case LDACBT_CHANNEL_MODE_DUAL_CHANNEL:
+        case LDACBT_CHANNEL_MODE_MONO:
+            break;
+        default:
+            pa_log_error("Invalid channel mode in LDAC Configuration");
+            return false;
+    }
+
+    return true;
+};
+
+
+static pa_a2dp_source_t pa_ldac_source = {
+        .encoder_load = pa_ldac_encoder_load,
+        .init = pa_ldac_encoder_init,
+        .update_user_config = pa_ldac_update_user_config,
+        .encode = pa_ldac_encode,
+        .config_transport = pa_ldac_config_transport,
+        .get_block_size = pa_ldac_get_block_size,
+        .setup_stream = pa_ldac_setup_stream,
+        .set_tx_length = pa_ldac_set_tx_length,
+        .decrease_quality = NULL,
+        .free = pa_ldac_free
+};
+
+const pa_a2dp_codec_t pa_a2dp_ldac = {
+        .name = "LDAC",
+        .codec = A2DP_CODEC_VENDOR,
+        .vendor_codec = &A2DP_SET_VENDOR_ID_CODEC_ID(LDAC_VENDOR_ID, LDAC_CODEC_ID),
+        .a2dp_sink = NULL,
+        .a2dp_source = &pa_ldac_source,
+        .get_capabilities = pa_ldac_get_capabilities,
+        .select_configuration = pa_ldac_select_configuration,
+        .free_capabilities = pa_ldac_free_capabilities,
+        .free_configuration = pa_ldac_free_capabilities,
+        .validate_configuration = pa_ldac_validate_configuration
+};
diff --git a/src/modules/bluetooth/a2dp/a2dp_sbc.c b/src/modules/bluetooth/a2dp/a2dp_sbc.c
new file mode 100644
index 000000000..d546d31ea
--- /dev/null
+++ b/src/modules/bluetooth/a2dp/a2dp_sbc.c
@@ -0,0 +1,658 @@
+#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 = SBC_MIN_BITPOOL;
+    capabilities->max_bitpool = SBC_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(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)
+        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_validate_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,
+        .validate_configuration = pa_sbc_validate_configuration
+};
diff --git a/src/modules/bluetooth/a2dp/a2dp_util.c b/src/modules/bluetooth/a2dp/a2dp_util.c
new file mode 100644
index 000000000..b07b47cb0
--- /dev/null
+++ b/src/modules/bluetooth/a2dp/a2dp_util.c
@@ -0,0 +1,386 @@
+
+#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_AAC_SRC_ENDPOINT A2DP_SOURCE_ENDPOINT "/AAC"
+#define A2DP_AAC_SNK_ENDPOINT A2DP_SINK_ENDPOINT "/AAC"
+
+#define A2DP_VENDOR_SRC_ENDPOINT A2DP_SOURCE_ENDPOINT "/VENDOR"
+#define A2DP_VENDOR_SNK_ENDPOINT A2DP_SINK_ENDPOINT "/VENDOR"
+
+#define A2DP_APTX_SRC_ENDPOINT A2DP_VENDOR_SRC_ENDPOINT "/APTX"
+#define A2DP_APTX_SNK_ENDPOINT A2DP_VENDOR_SNK_ENDPOINT "/APTX"
+
+#define A2DP_APTX_HD_SRC_ENDPOINT A2DP_VENDOR_SRC_ENDPOINT "/APTXHD"
+#define A2DP_APTX_HD_SNK_ENDPOINT A2DP_VENDOR_SNK_ENDPOINT "/APTXHD"
+
+#define A2DP_LDAC_SRC_ENDPOINT A2DP_VENDOR_SRC_ENDPOINT "/LDAC"
+
+#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;
+    }
+    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;
+        case PA_A2DP_SINK_AAC:
+            *endpoint = A2DP_AAC_SNK_ENDPOINT;
+            break;
+        case PA_A2DP_SOURCE_AAC:
+            *endpoint = A2DP_AAC_SRC_ENDPOINT;
+            break;
+        case PA_A2DP_SINK_APTX:
+            *endpoint = A2DP_APTX_SNK_ENDPOINT;
+            break;
+        case PA_A2DP_SOURCE_APTX:
+            *endpoint = A2DP_APTX_SRC_ENDPOINT;
+            break;
+        case PA_A2DP_SINK_APTX_HD:
+            *endpoint = A2DP_APTX_HD_SNK_ENDPOINT;
+            break;
+        case PA_A2DP_SOURCE_APTX_HD:
+            *endpoint = A2DP_APTX_HD_SRC_ENDPOINT;
+            break;
+        case PA_A2DP_SOURCE_LDAC:
+            *endpoint = A2DP_LDAC_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 if (streq(endpoint, A2DP_AAC_SNK_ENDPOINT))
+        *codec_index = PA_A2DP_SINK_AAC;
+    else if (streq(endpoint, A2DP_AAC_SRC_ENDPOINT))
+        *codec_index = PA_A2DP_SOURCE_AAC;
+    else if (streq(endpoint, A2DP_APTX_SNK_ENDPOINT))
+        *codec_index = PA_A2DP_SINK_APTX;
+    else if (streq(endpoint, A2DP_APTX_SRC_ENDPOINT))
+        *codec_index = PA_A2DP_SOURCE_APTX;
+    else if (streq(endpoint, A2DP_APTX_HD_SNK_ENDPOINT))
+        *codec_index = PA_A2DP_SINK_APTX_HD;
+    else if (streq(endpoint, A2DP_APTX_HD_SRC_ENDPOINT))
+        *codec_index = PA_A2DP_SOURCE_APTX_HD;
+    else if (streq(endpoint, A2DP_LDAC_SRC_ENDPOINT))
+        *codec_index = PA_A2DP_SOURCE_LDAC;
+    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;
+        case PA_A2DP_SINK_AAC:
+        case PA_A2DP_SOURCE_AAC:
+            *a2dp_codec = &pa_a2dp_aac;
+            break;
+        case PA_A2DP_SINK_APTX:
+        case PA_A2DP_SOURCE_APTX:
+            *a2dp_codec = &pa_a2dp_aptx;
+            break;
+        case PA_A2DP_SINK_APTX_HD:
+        case PA_A2DP_SOURCE_APTX_HD:
+            *a2dp_codec = &pa_a2dp_aptx_hd;
+            break;
+        case PA_A2DP_SOURCE_LDAC:
+            *a2dp_codec = &pa_a2dp_ldac;
+            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_MPEG24:
+            *codec_index = is_a2dp_sink ? PA_A2DP_SINK_AAC : PA_A2DP_SOURCE_AAC;
+            return;
+        case A2DP_CODEC_VENDOR:
+            if (!a2dp_codec->vendor_codec) {
+                *codec_index = PA_A2DP_CODEC_INDEX_UNAVAILABLE;
+                return;
+            } else if (A2DP_GET_VENDOR_ID(*a2dp_codec->vendor_codec) == APTX_VENDOR_ID &&
+                       A2DP_GET_CODEC_ID(*a2dp_codec->vendor_codec) == APTX_CODEC_ID) {
+                *codec_index = is_a2dp_sink ? PA_A2DP_SINK_APTX : PA_A2DP_SOURCE_APTX;
+                return;
+            } else if (A2DP_GET_VENDOR_ID(*a2dp_codec->vendor_codec) == APTX_HD_VENDOR_ID &&
+                       A2DP_GET_CODEC_ID(*a2dp_codec->vendor_codec) == APTX_HD_CODEC_ID) {
+                *codec_index = is_a2dp_sink ? PA_A2DP_SINK_APTX_HD : PA_A2DP_SOURCE_APTX_HD;
+                return;
+            } else if (A2DP_GET_VENDOR_ID(*a2dp_codec->vendor_codec) == LDAC_VENDOR_ID &&
+                       A2DP_GET_CODEC_ID(*a2dp_codec->vendor_codec) == LDAC_CODEC_ID) {
+                *codec_index = is_a2dp_sink ? PA_A2DP_CODEC_INDEX_UNAVAILABLE : PA_A2DP_SOURCE_LDAC;
+                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_MPEG24:
+            *a2dp_codec = &pa_a2dp_aac;
+            return;
+        case A2DP_CODEC_VENDOR:
+            if (!vendor_codec) {
+                *a2dp_codec = NULL;
+                pa_assert_not_reached();
+            } else if (A2DP_GET_VENDOR_ID(*vendor_codec) == APTX_VENDOR_ID &&
+                       A2DP_GET_CODEC_ID(*vendor_codec) == APTX_CODEC_ID) {
+                *a2dp_codec = &pa_a2dp_aptx;
+                return;
+            } else if (A2DP_GET_VENDOR_ID(*vendor_codec) == APTX_HD_VENDOR_ID &&
+                       A2DP_GET_CODEC_ID(*vendor_codec) == APTX_HD_CODEC_ID) {
+                *a2dp_codec = &pa_a2dp_aptx_hd;
+                return;
+            } else if (A2DP_GET_VENDOR_ID(*vendor_codec) == LDAC_VENDOR_ID &&
+                       A2DP_GET_CODEC_ID(*vendor_codec) == LDAC_CODEC_ID) {
+                *a2dp_codec = &pa_a2dp_ldac;
+                return;
+            }
+            *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/a2dp/aptx_libs.c b/src/modules/bluetooth/a2dp/aptx_libs.c
new file mode 100644
index 000000000..69e905687
--- /dev/null
+++ b/src/modules/bluetooth/a2dp/aptx_libs.c
@@ -0,0 +1,206 @@
+#include <dlfcn.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <libavcodec/avcodec.h>
+#include <libavutil/samplefmt.h>
+
+#ifdef HAVE_CONFIG_H
+
+#include <config.h>
+
+#endif
+
+#include <pulsecore/log.h>
+
+static const char *AVCODEC_LIB_NAME = "libavcodec.so";
+
+static const char *avcodec_find_decoder_func_name = "avcodec_find_decoder";
+static const char *avcodec_find_encoder_func_name = "avcodec_find_encoder";
+static const char *av_packet_alloc_func_name = "av_packet_alloc";
+static const char *av_packet_free_func_name = "av_packet_free";
+static const char *avcodec_send_packet_func_name = "avcodec_send_packet";
+static const char *avcodec_receive_frame_func_name = "avcodec_receive_frame";
+static const char *avcodec_send_frame_func_name = "avcodec_send_frame";
+static const char *avcodec_receive_packet_func_name = "avcodec_receive_packet";
+static const char *avcodec_alloc_context3_func_name = "avcodec_alloc_context3";
+static const char *avcodec_free_context_func_name = "avcodec_free_context";
+static const char *avcodec_open2_func_name = "avcodec_open2";
+
+typedef AVCodec *(*avcodec_find_decoder_func_t)(enum AVCodecID id);
+
+typedef AVCodec *(*avcodec_find_encoder_func_t)(enum AVCodecID id);
+
+typedef AVPacket *(*av_packet_alloc_func_t)(void);
+
+typedef void (*av_packet_free_func_t)(AVPacket **pkt);
+
+typedef int (*avcodec_send_packet_func_t)(AVCodecContext *avctx, const AVPacket *avpkt);
+
+typedef int (*avcodec_receive_frame_func_t)(AVCodecContext *avctx, AVFrame *frame);
+
+typedef int (*avcodec_send_frame_func_t)(AVCodecContext *avctx, const AVFrame *frame);
+
+typedef int (*avcodec_receive_packet_func_t)(AVCodecContext *avctx, AVPacket *avpkt);
+
+typedef AVCodecContext *(*avcodec_alloc_context3_func_t)(const AVCodec *codec);
+
+typedef void (*avcodec_free_context_func_t)(AVCodecContext **avctx);
+
+typedef int (*avcodec_open2_func_t)(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
+
+
+static avcodec_find_decoder_func_t avcodec_find_decoder_func;
+static avcodec_find_encoder_func_t avcodec_find_encoder_func;
+static av_packet_alloc_func_t av_packet_alloc_func;
+static av_packet_free_func_t av_packet_free_func;
+static avcodec_send_packet_func_t avcodec_send_packet_func;
+static avcodec_receive_frame_func_t avcodec_receive_frame_func;
+static avcodec_send_frame_func_t avcodec_send_frame_func;
+static avcodec_receive_packet_func_t avcodec_receive_packet_func;
+static avcodec_alloc_context3_func_t avcodec_alloc_context3_func;
+static avcodec_free_context_func_t avcodec_free_context_func;
+static avcodec_open2_func_t avcodec_open2_func;
+
+static const char *AVUTIL_LIB_NAME = "libavutil.so";
+
+static const char *av_frame_alloc_func_name = "av_frame_alloc";
+static const char *av_frame_get_buffer_func_name = "av_frame_get_buffer";
+static const char *av_frame_make_writable_func_name = "av_frame_make_writable";
+static const char *av_frame_free_func_name = "av_frame_free";
+
+
+typedef AVFrame *(*av_frame_alloc_func_t)(void);
+
+
+typedef int (*av_frame_get_buffer_func_t)(AVFrame *frame, int align);
+
+typedef int (*av_frame_make_writable_func_t)(AVFrame *frame);
+
+typedef void (*av_frame_free_func_t)(AVFrame **frame);
+
+
+static av_frame_alloc_func_t av_frame_alloc_func;
+static av_frame_get_buffer_func_t av_frame_get_buffer_func;
+static av_frame_make_writable_func_t av_frame_make_writable_func;
+static av_frame_free_func_t av_frame_free_func;
+
+
+static void *libavcodec_h = NULL;
+
+static void *libavutil_h = NULL;
+
+
+static void *load_func(void *lib_handle, const char *func_name) {
+    void *func = dlsym(lib_handle, func_name);
+    if (func == NULL) {
+        pa_log_error("No function %s in provide library. %s", func_name, dlerror());
+        return NULL;
+    }
+    return func;
+}
+
+static void libavcodec_unload() {
+    avcodec_find_decoder_func = NULL;
+    avcodec_find_encoder_func = NULL;
+    av_packet_alloc_func = NULL;
+    av_packet_free_func = NULL;
+    avcodec_send_packet_func = NULL;
+    avcodec_receive_frame_func = NULL;
+    avcodec_send_frame_func = NULL;
+    avcodec_receive_packet_func = NULL;
+    avcodec_alloc_context3_func = NULL;
+    avcodec_free_context_func = NULL;
+    avcodec_open2_func = NULL;
+    if (libavcodec_h) {
+        dlclose(libavcodec_h);
+        libavcodec_h = NULL;
+    }
+}
+
+static bool libavcodec_load() {
+    if (libavcodec_h)
+        return true;
+    libavcodec_h = dlopen(AVCODEC_LIB_NAME, RTLD_NOW);
+    if (libavcodec_h == NULL) {
+        pa_log_error("Cannot open libavcodec library: %s. %s", AVCODEC_LIB_NAME, dlerror());
+        return false;
+    }
+    avcodec_find_decoder_func = load_func(libavcodec_h, avcodec_find_decoder_func_name);
+    if (avcodec_find_decoder_func == NULL)
+        return false;
+    avcodec_find_encoder_func = load_func(libavcodec_h, avcodec_find_encoder_func_name);
+    if (avcodec_find_encoder_func == NULL)
+        return false;
+    av_packet_alloc_func = load_func(libavcodec_h, av_packet_alloc_func_name);
+    if (av_packet_alloc_func == NULL)
+        return false;
+    av_packet_free_func = load_func(libavcodec_h, av_packet_free_func_name);
+    if (av_packet_free_func == NULL)
+        return false;
+    avcodec_send_packet_func = load_func(libavcodec_h, avcodec_send_packet_func_name);
+    if (avcodec_send_packet_func == NULL)
+        return false;
+    avcodec_receive_frame_func = load_func(libavcodec_h, avcodec_receive_frame_func_name);
+    if (avcodec_receive_frame_func == NULL)
+        return false;
+    avcodec_send_frame_func = load_func(libavcodec_h, avcodec_send_frame_func_name);
+    if (avcodec_send_frame_func == NULL)
+        return false;
+    avcodec_receive_packet_func = load_func(libavcodec_h, avcodec_receive_packet_func_name);
+    if (avcodec_receive_packet_func == NULL)
+        return false;
+    avcodec_alloc_context3_func = load_func(libavcodec_h, avcodec_alloc_context3_func_name);
+    if (avcodec_alloc_context3_func == NULL)
+        return false;
+    avcodec_free_context_func = load_func(libavcodec_h, avcodec_free_context_func_name);
+    if (avcodec_free_context_func == NULL)
+        return false;
+    avcodec_open2_func = load_func(libavcodec_h, avcodec_open2_func_name);
+    if (avcodec_open2_func == NULL)
+        return false;
+    return true;
+}
+
+static void libavutil_unload() {
+    av_frame_alloc_func = NULL;
+    av_frame_get_buffer_func = NULL;
+    av_frame_make_writable_func = NULL;
+    av_frame_free_func = NULL;
+    if (libavutil_h) {
+        dlclose(libavutil_h);
+        libavutil_h = NULL;
+    }
+}
+
+static bool libavutil_load() {
+    if (libavutil_h)
+        return true;
+    libavutil_h = dlopen(AVUTIL_LIB_NAME, RTLD_NOW);
+    if (libavutil_h == NULL) {
+        pa_log_error("Cannot open libavutil library: %s. %s", AVUTIL_LIB_NAME, dlerror());
+        return false;
+    }
+    av_frame_alloc_func = load_func(libavutil_h, av_frame_alloc_func_name);
+    if (av_frame_alloc_func == NULL)
+        return false;
+    av_frame_get_buffer_func = load_func(libavutil_h, av_frame_get_buffer_func_name);
+    if (av_frame_get_buffer_func == NULL)
+        return false;
+    av_frame_make_writable_func = load_func(libavutil_h, av_frame_make_writable_func_name);
+    if (av_frame_make_writable_func == NULL)
+        return false;
+    av_frame_free_func = load_func(libavutil_h, av_frame_free_func_name);
+    if (av_frame_free_func == NULL)
+        return false;
+
+    return true;
+}
+
+static bool aptx_libs_load() {
+    if (libavcodec_load() && libavutil_load())
+        return true;
+    libavcodec_unload();
+    libavutil_unload();
+    return false;
+}
\ No newline at end of file
diff --git a/src/modules/bluetooth/a2dp/ldacBT.h b/src/modules/bluetooth/a2dp/ldacBT.h
new file mode 100644
index 000000000..f43820cc3
--- /dev/null
+++ b/src/modules/bluetooth/a2dp/ldacBT.h
@@ -0,0 +1,540 @@
+/*
+ * Copyright (C) 2013 - 2016 Sony Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _LDACBT_H_
+#define _LDACBT_H_
+#ifdef __cplusplus
+extern "C" {
+#endif
+#ifndef LDACBT_API
+#define LDACBT_API
+#endif /* LDACBT_API  */
+
+/* This file contains the definitions, declarations and macros for an implimentation of 
+ * LDAC encode processing.
+ *
+ * The basic flow of the encode processing is as follows:
+ * - The program creates an handle of an LDAC api using ldacBT_get_handle().
+ * - The program initialize the handle for encode using ldacBT_init_handle_encode().
+ * - The program calls ldacBT_encode() to encode data.
+ * - If the program demands to control the Encode Quality Mode Index, then one of the following
+ *   should be called:
+ *     - ldacBT_set_eqmid()
+ *     - ldacBT_alter_eqmid()
+ * - The program finishes the encoding with passing NULL to input pcm buffer for ldacBT_encode(),
+ *   which enables the encoder to encode remaining data in its input buffers.
+ * - The handle may be closed using ldacBT_close_handle() then used again, or released with
+ *   ldacBT_free_handle().
+ * - The rest of the set functions should be called only if it is needed by the client.
+ *
+ *
+ * Note for an implimentation
+ * - Error processing
+ *     When continuous processing for next frame is performed after error detection, following
+ *     processing must be carried out using C function provided in the library.
+ *      - Release of internal variables in encode processing using ldacBT_close_handle().
+ *      - Allocation and initialization of internal variables in encode processing using
+ *        ldacBT_init_handle_encode().
+ *     Note that the encoded output for a few frames will not be present just after error recovery.
+ *
+ * - Resuming of the encode processing from an interruption
+ *     In case of resuming of the encode processing from interruption (such as changing
+ *     configuration, seeking and playback), initialization of internal variables in encode
+ *     processing must be carried out as error processing described above.
+ *     Note that the encoded output for a few frames will not be present just after initialization
+ *     as above.
+ *
+ *
+ * Glossary
+ *  channel_config_index (cci)
+ *    The channel setting information for ldaclib.
+ *    See ldacBT_cm_to_cci() to get value from channel_mode.
+ *
+ *  channel_mode (cm)
+ *    The channel setting information for LDAC specification of Bluetooth A2DP.
+ *    See ldacBT_cci_to_cm() to get value from channel_config_index.
+ *
+ *  ldac_transport_frame
+ *    See LDAC specification of bluetooth A2DP.
+ *
+ *  Maximum Transmission Unit (MTU)
+ *    The minimum MTU that a L2CAP implementation for LDAC shall support is 679 bytes, because LDAC
+ *    is optimized with 2-DH5 packet as its target.
+ *
+ *  frame
+ *    An audio signal sequence representing a certain number of PCM audio signals.
+ *    Encoding and decoding are processed frame by frame in LDAC. Number of samples in a frame is
+ *    determined by sampling frequency as described below.
+ *
+ *  Sampling frequency and frame sample.
+ *    Supported sampling frequencies are 44.1, 48, 88.2 and 96 kHz.
+ *    The relationship between sampling frequency and frame sample in LDAC are shown below.
+ *       --------------------------------------------------------
+ *      | sampling frequency       [kHz] | 44.1 | 48 | 88.2 | 96 |
+ *      | frame sample [samples/channel] |     128   |     256   |
+ *       --------------------------------------------------------
+ *    Though the frame size varies in LDAC core as described in the table, the number of samples in
+ *    input PCM signal for encoding is fixed to 128 sample/channel, and it is not affected by
+ *    sampling frequency.
+ */
+#define LDACBT_ENC_LSU 128
+#define LDACBT_MAX_LSU 512
+
+/* channel_config_index.
+ * Supported value are below.
+ */
+#define LDAC_CCI_MONO         0 /* MONO */
+#define LDAC_CCI_DUAL_CHANNEL 1 /* DUAL CHANNEL */
+#define LDAC_CCI_STEREO       2 /* STEREO */
+
+/* PCM format.
+ * Supported PCM format are shown below.
+ *   - LDACBT_SMPL_FMT_S16 : signed 16bits little endian.
+ *   - LDACBT_SMPL_FMT_S24 : signed 24bits little endian.
+ *   - LDACBT_SMPL_FMT_S32 : signed 32bits little endian.
+ *   - LDACBT_SMPL_FMT_F32 : single-precision floating point.
+ * The data sequency must be interleaved format by 1 sample.
+ * Ex) 2 channel audio, the data sequences are aligned as below.
+ *       seq : |L[0]|R[0]|L[1]|R[1]|...
+ */
+typedef enum {
+    LDACBT_SMPL_FMT_S16 = 0x2,
+    LDACBT_SMPL_FMT_S24 = 0x3,
+    LDACBT_SMPL_FMT_S32 = 0x4,
+    LDACBT_SMPL_FMT_F32 = 0x5,
+} LDACBT_SMPL_FMT_T;
+
+/* Encode Quality Mode Index. (EQMID)
+ *  The configuration of encoding in LDAC will be coordinated by "Encode Quality Mode Index"
+ *  parameter. Configurable values are shown below.
+ *   - LDACBT_EQMID_HQ : Encode setting for High Quality.
+ *   - LDACBT_EQMID_SQ : Encode setting for Standard Quality.
+ *   - LDACBT_EQMID_MQ : Encode setting for Mobile use Quality.
+ *   - LDACBT_EQMID_ABR: Reserved EQMID for ABR. The value shall be 0x7F.
+ */
+enum {
+    LDACBT_EQMID_HQ = 0,
+    LDACBT_EQMID_SQ,
+    LDACBT_EQMID_MQ,
+    LDACBT_EQMID_NUM,     /* terminator */
+    LDACBT_EQMID_ABR = 0x7F,
+};
+
+/* Bit rates
+ *  Bit rates in each EQMID are depend on sampling frequency.
+ *  In this API specification, these relations are shown below.
+ *     ___________________________________________
+ *    |                 | Sampling Frequency[kHz] |
+ *    |      EQMID      | 44.1, 88.2 |   48, 96   |
+ *    +-----------------+------------+------------+
+ *    | LDACBT_EQMID_HQ |   909kbps  |   990kbps  |
+ *    | LDACBT_EQMID_SQ |   606kbps  |   660kbps  |
+ *    | LDACBT_EQMID_MQ |   303kbps  |   330kbps  |
+ *     -------------------------------------------
+ */
+
+/* Maximum size of the "ldac_transport_frame" sequence at transportation. */
+#define LDACBT_MAX_NBYTES 1024 /* byte */
+
+/* Maximum number of channel for LDAC */
+#define LDAC_PRCNCH 2
+
+/* LDAC handle type */
+typedef struct _st_ldacbt_handle * HANDLE_LDAC_BT;
+
+/* Allocation of LDAC handle.
+ *  Format
+ *      HANDLE_LDAC_BT ldacBT_get_handle( void );
+ *  Arguments
+ *      None.
+ *  Return value
+ *      HANDLE_LDAC_BT for success, NULL for failure.
+ */
+LDACBT_API HANDLE_LDAC_BT ldacBT_get_handle( void );
+
+/* Release of LDAC handle.
+ *  Format
+ *      void ldacBT_free_handle( HANDLE_LDAC_BT hLdacBt );
+ *  Arguments
+ *      hLdacBt    HANDLE_LDAC_BT    LDAC handle.
+ *  Return value
+ *      None.
+ */
+LDACBT_API void ldacBT_free_handle( HANDLE_LDAC_BT hLdacBt );
+
+/* Closing of initialized LDAC handle.
+ * Closed handle can be initialized and used again.
+ *  Format
+ *      void ldacBT_close_handle( HANDLE_LDAC_BT hLdacBt );
+ *  Arguments
+ *      hLdacBt    HANDLE_LDAC_BT    LDAC handle.
+ *  Return value
+ *      None.
+ */
+LDACBT_API void ldacBT_close_handle( HANDLE_LDAC_BT hLdacBt );
+
+/* Acquisition of the library version.
+ *  Format
+ *      int  ldacBT_get_version( void );
+ *  Arguments
+ *      None.
+ *  Return value
+ *      int : version number.
+ *              23-16 bit : major version
+ *              15- 8 bit : minor version
+ *               7- 0 bit : branch version
+ *              Ex) 0x00010203 -> version 1.02.03
+ */
+LDACBT_API int  ldacBT_get_version( void );
+
+/* Acquisition of the sampling frequency in current configuration.
+ * The LDAC handle must be initialized by API function ldacBT_init_handle_encode() prior to
+ * calling this function.
+ *  Format
+ *      int  ldacBT_get_sampling_freq( HANDLE_LDAC_BT hLdacBt );
+ *  Arguments
+ *      hLdacBt    HANDLE_LDAC_BT    LDAC handle.
+ *  Return value
+ *      int : sampling frequency in current configuration. -1 for failure.
+ */
+LDACBT_API int  ldacBT_get_sampling_freq( HANDLE_LDAC_BT hLdacBt );
+
+/* Acquisition of the Bit-rate.
+ * The LDAC handle must be initialized by API function ldacBT_init_handle_encode() prior to
+ * calling this function.
+ *  Format
+ *      int  ldacBT_get_bitrate( HANDLE_LDAC_BT hLdacBt );
+ *  Arguments
+ *      hLdacBt    HANDLE_LDAC_BT    LDAC handle.
+ *  Return value
+ *      int : Bit-rate for previously processed ldac_transport_frame for success. -1 for failure.
+ */
+LDACBT_API int  ldacBT_get_bitrate( HANDLE_LDAC_BT hLdacBt );
+
+/* Initialization of a LDAC handle for encode processing.
+ * The LDAC handle must be allocated by API function ldacBT_get_handle() prior to calling this API.
+ * "mtu" value should be configured to MTU size of AVDTP Transport Channel, which is determined by
+ * SRC and SNK devices in Bluetooth transmission.
+ * "eqmid" is configured to desired value of "Encode Quality Mode Index".
+ * "cm" is configured to channel_mode in LDAC, which is determined by SRC and SNK devices in
+ * Bluetooth transmission.
+ * "fmt" is configured to input pcm audio format.
+ * When the configuration of "mtu", "cm", or "sf" changed, the re-initialization is required. 
+ *
+ *  Format
+ *      int  ldacBT_init_handle_encode( HANDLE_LDAC_BT hLdacBt, int mtu, int eqmid, int cm,
+ *                                      LDACBT_SMPL_FMT_T fmt, int sf );
+ *  Arguments
+ *      hLdacBt    HANDLE_LDAC_BT    LDAC handle.
+ *      mtu        int               MTU value. Unit:Byte.
+ *      eqmid      int               Encode Quality Mode Index.
+ *      cm         int               Information of the channel_mode.
+ *      fmt        LDACBT_SMPL_FMT_T Audio format type of input pcm.
+ *      sf         int               Sampling frequency of input pcm.
+ *  Return value
+ *      int : 0 for success, -1 for failure.
+ */
+LDACBT_API int  ldacBT_init_handle_encode( HANDLE_LDAC_BT hLdacBt, int mtu, int eqmid, int cm,
+                                           LDACBT_SMPL_FMT_T fmt, int sf );
+
+/* Configuration of Encode Quality Mode Index.
+ * The LDAC handle must be initialized by API function ldacBT_init_handle_encode() prior to
+ * calling this function.
+ * The API function can be called at any time, after the completion of initializing.
+ *  Format
+ *      int  ldacBT_set_eqmid( HANDLE_LDAC_BT hLdacBt, int eqmid );
+ *  Arguments
+ *      hLdacBt    HANDLE_LDAC_BT    LDAC handle.
+ *      eqmid      int               Encode Quality Mode Index.
+ *  Return value
+ *      int : 0 for success, -1 for failure.
+ */
+LDACBT_API int  ldacBT_set_eqmid( HANDLE_LDAC_BT hLdacBt, int eqmid );
+
+/* Acquisition of prescribed Encode Quality Mode Index in current configuration.
+ * The LDAC handle must be initialized by API function ldacBT_init_handle_encode() prior to
+ * calling this function.
+ *  Format
+ *      int  ldacBT_get_eqmid( HANDLE_LDAC_BT hLdacBt );
+ *  Arguments
+ *      hLdacBt    HANDLE_LDAC_BT    LDAC handle.
+ *  Return value
+ *      int : Encode Quality Mode Index for success, -1 for failure.
+ */
+LDACBT_API int  ldacBT_get_eqmid( HANDLE_LDAC_BT hLdacBt );
+
+/* Changing of configuration for Encode Quality Mode Index by one step.
+ * The LDAC handle must be initialized by API function ldacBT_init_handle_encode() prior to
+ * calling this function.
+ * Configuralbe values for "priority" are shown below.
+ *   - LDACBT_EQMID_INC_QUALITY    : Adjustment for EQMID by one step for the direction of
+ *                                   getting close to LDACBT_EQMID_HQ.
+ *   - LDACBT_EQMID_INC_CONNECTION : Adjustment for EQMID by one step for the direction of
+ *                                   getting away from LDACBT_EQMID_HQ.
+ * For restoring prescribed value for "Encode Quality Mode Index", it must be configured again by
+ * API function ldacBT_init_handle_encode() or ldacBT_set_qmode().
+ * A transition to the state other than "Encode Quality Mode Index" mention before may be occurred
+ * caused by an adjustment using this API function.
+ * The API function can be called at any time, after the completion of initializing.
+ *  Format
+ *      int  ldacBT_alter_eqmid_priority( HANDLE_LDAC_BT hLdacBt, int priority );
+ *  Arguments
+ *      hLdacBt    HANDLE_LDAC_BT    LDAC handle.
+ *      priority   int               The direction of changing EQMID.
+ *  Return value
+ *      int : 0 for success, -1 for failure.
+ */
+#define LDACBT_EQMID_INC_QUALITY     1
+#define LDACBT_EQMID_INC_CONNECTION -1
+LDACBT_API int  ldacBT_alter_eqmid_priority( HANDLE_LDAC_BT hLdacBt, int priority );
+
+
+/* LDAC encode processing.
+ * The LDAC handle must be initialized by API function ldacBT_init_handle_encode() prior to calling
+ * this API function.
+ * <Regarding on a input PCM signal>
+ *  Number of samples in input PCM signal for encoding is fixed to 128 samples per channel, and it
+ *  is not affected by sampling frequency.
+ *
+ *  The region in input signal buffer without any PCM signal must be filled with zero, if the
+ *  number of samples is less than 128 samples.
+ *
+ *  The format of PCM signal is determined by "fmt" configured by API function
+ *  ldacBT_init_handle_encode().
+ *
+ *  Total size of referenced PCM signal (in byte) will be set in "pcm_used" on return. The value of
+ *  "Number of input samples * Number of channels * sizeof(PCM word length)" will be set in normal.
+ *
+ *  Finalize processing of encode will be carried out with setting "p_pcm" as zero.
+ *
+ * <Regarding on output encoded data>
+ *  An output data in "ldac_transport_frame" sequence will be set to "p_stream" after several frame
+ *  processing. So the output is not necessarily present at each calling of this API function.
+ *
+ *  The presence of the output can be verified by checking whether the value of "stream_wrote",
+ *  representing the number of written bytes for "p_stream", is positive or not.
+ *
+ *  In addition, encoded data size for output will be determined by the value of "mtu" configured
+ *  by API function ldacBT_init_handle_encode().
+ *
+ *  The number of "ldac_transport_frame" corresponding to "ldac_transport_frame" sequence as output
+ *  will be set to "frame_num".
+ *
+ *  Format
+ *      int  ldacBT_encode( HANDLE_LDAC_BT hLdacBt, void *p_pcm, int *pcm_used,
+ *                          unsigned char *p_stream, int *stream_sz, int *frame_num );
+ *  Arguments
+ *      hLdacBt    HANDLE_LDAC_BT    LDAC handle.
+ *      p_pcm      void *            Input PCM signal sequence
+ *      pcm_used   int *             Data size of referenced PCM singnal. Unit:Byte.
+ *      p_stream   unsigned char *   Output "ldac_transport_frame" sequence.
+ *      stream_sz  int *             Size of output data. Unit:Byte.
+ *      frame_num  int *             Number of output "ldac_transport_frame"
+ *  Return value
+ *      int : 0 for success, -1 for failure.
+ */
+LDACBT_API int  ldacBT_encode( HANDLE_LDAC_BT hLdacBt, void *p_pcm, int *pcm_used,
+                               unsigned char *p_stream, int *stream_sz, int *frame_num );
+
+/* Acquisition of previously established error code.
+ * The LDAC handle must be allocated by API function ldacBT_get_handle() prior to calling this function.
+ * The details of error code are described below at the end of this header file.
+ * Tips for error code handling.
+ * The macro function LDACBT_FATAL() is useful to determine whether the error code is Fatal or not.
+ *      Ex.) if( LDACBT_FATAL(err) ) // Fatal Error occurred.
+ *
+ * The macro function LDACBT_ERROR() is useful to determine whether the error occurred or not.
+ *      Ex.) if( LDACBT_ERROR(err) ) // Error occurred.
+ *
+ * The macro function LDACBT_HANDLE_ERR() is useful to get the handle level error code.
+ *      Ex.) err_handle_lv = LDACBT_HANDLE_ERR(err);
+ *
+ * The macro function LDACBT_BLOCK_ERR() is useful to get the block level error code.
+ *      Ex.) err_block_lv = LDACBT_BLOCK_ERR(err);
+ *
+ *  Format
+ *      int  ldacBT_get_error_code( HANDLE_LDAC_BT hLdacBt );
+ *  Arguments
+ *      hLdacBt    HANDLE_LDAC_BT    LDAC handle.
+ *  Return value
+ *      int : Error code.
+ */
+LDACBT_API int  ldacBT_get_error_code( HANDLE_LDAC_BT hLdacBt );
+
+/*******************************************************************************
+    Error Code
+*******************************************************************************/
+#define LDACBT_ERR_NONE                     0
+
+/*    Non Fatal Error ***********************************************************/
+#define LDACBT_ERR_NON_FATAL                1
+
+/*    Non Fatal Error (Block Level) *********************************************/
+#define LDACBT_ERR_BIT_ALLOCATION           5
+
+/*    Non Fatal Error (Handle Level) ********************************************/
+#define LDACBT_ERR_NOT_IMPLEMENTED          128
+#define LDACBT_ERR_NON_FATAL_ENCODE         132
+
+/*    Fatal Error ***************************************************************/
+#define LDACBT_ERR_FATAL                    256
+
+/*    Fatal Error (Block Level) *************************************************/
+#define LDACBT_ERR_SYNTAX_BAND              260
+#define LDACBT_ERR_SYNTAX_GRAD_A            261
+#define LDACBT_ERR_SYNTAX_GRAD_B            262
+#define LDACBT_ERR_SYNTAX_GRAD_C            263
+#define LDACBT_ERR_SYNTAX_GRAD_D            264
+#define LDACBT_ERR_SYNTAX_GRAD_E            265
+#define LDACBT_ERR_SYNTAX_IDSF              266
+#define LDACBT_ERR_SYNTAX_SPEC              267
+
+#define LDACBT_ERR_BIT_PACKING              280
+
+#define LDACBT_ERR_ALLOC_MEMORY             300
+
+/*    Fatal Error (Handle Level) ************************************************/
+#define LDACBT_ERR_FATAL_HANDLE             512
+
+#define LDACBT_ERR_ILL_SYNCWORD             516
+#define LDACBT_ERR_ILL_SMPL_FORMAT          517
+#define LDACBT_ERR_ILL_PARAM                518
+
+#define LDACBT_ERR_ASSERT_SAMPLING_FREQ     530
+#define LDACBT_ERR_ASSERT_SUP_SAMPLING_FREQ 531
+#define LDACBT_ERR_CHECK_SAMPLING_FREQ      532
+#define LDACBT_ERR_ASSERT_CHANNEL_CONFIG    533
+#define LDACBT_ERR_CHECK_CHANNEL_CONFIG     534
+#define LDACBT_ERR_ASSERT_FRAME_LENGTH      535
+#define LDACBT_ERR_ASSERT_SUP_FRAME_LENGTH  536
+#define LDACBT_ERR_ASSERT_FRAME_STATUS      537
+#define LDACBT_ERR_ASSERT_NSHIFT            538
+#define LDACBT_ERR_ASSERT_CHANNEL_MODE      539
+
+#define LDACBT_ERR_ENC_INIT_ALLOC           550
+#define LDACBT_ERR_ENC_ILL_GRADMODE         551
+#define LDACBT_ERR_ENC_ILL_GRADPAR_A        552
+#define LDACBT_ERR_ENC_ILL_GRADPAR_B        553
+#define LDACBT_ERR_ENC_ILL_GRADPAR_C        554
+#define LDACBT_ERR_ENC_ILL_GRADPAR_D        555
+#define LDACBT_ERR_ENC_ILL_NBANDS           556
+#define LDACBT_ERR_PACK_BLOCK_FAILED        557
+
+#define LDACBT_ERR_DEC_INIT_ALLOC           570
+#define LDACBT_ERR_INPUT_BUFFER_SIZE        571
+#define LDACBT_ERR_UNPACK_BLOCK_FAILED      572
+#define LDACBT_ERR_UNPACK_BLOCK_ALIGN       573
+#define LDACBT_ERR_UNPACK_FRAME_ALIGN       574
+#define LDACBT_ERR_FRAME_LENGTH_OVER        575
+#define LDACBT_ERR_FRAME_ALIGN_OVER         576
+
+
+/* LDAC API for Encode */
+#define LDACBT_ERR_ALTER_EQMID_LIMITED      21
+#define LDACBT_ERR_HANDLE_NOT_INIT          1000
+#define LDACBT_ERR_ILL_EQMID                1024
+#define LDACBT_ERR_ILL_SAMPLING_FREQ        1025
+#define LDACBT_ERR_ILL_NUM_CHANNEL          1026
+#define LDACBT_ERR_ILL_MTU_SIZE             1027
+/* LDAC API for Decode */
+#define LDACBT_ERR_DEC_CONFIG_UPDATED       40
+
+
+/* Macro Functions for Error Code ********************************************/
+#define LDACBT_API_ERR(err)    ((err >> 20) & 0x0FFF)
+#define LDACBT_HANDLE_ERR(err) ((err >> 10) & 0x03FF)
+#define LDACBT_BLOCK_ERR(err)  ( err & 0x03FF)
+#define LDACBT_ERROR(err)      ((LDACBT_ERR_NON_FATAL) <= LDACBT_API_ERR(err) ? 1 : 0)
+#define LDACBT_FATAL(err)      ((LDACBT_ERR_FATAL) <= LDACBT_API_ERR(err) ? 1 : 0)
+
+
+
+/* Codec Specific Information Elements for LDAC
+ * (based on "LDAC Specification of Bluetooth A2DP Rev.2.0.1")
+ *                  |  7  |  6  |  5  |  4  |  3  |  2  |  1  |  0  |
+ *  service_caps[4] |   SONY ID                                     | Octet0
+ *  service_caps[5] |   SONY ID                                     | Octet1
+ *  service_caps[6] |   SONY ID                                     | Octet2
+ *  service_caps[7] |   SONY ID                                     | Octet3
+ *  service_caps[8] |   SONY Specific Codec ID                      | Octet4
+ *  service_caps[9] |   SONY Specific Codec ID                      | Octet5
+ *  service_caps[A] |   RFA     |   Sampling Frequency              | Octet6
+ *  service_caps[B] |   RFA                       | Channel Mode ID | Octet7
+ */
+#define LDACBT_MEDIA_CODEC_SC_SZ         (10+2)
+
+/* [Octet 0-3] Vendor ID for SONY */
+#define LDACBT_VENDOR_ID0 0x2D
+#define LDACBT_VENDOR_ID1 0x01
+#define LDACBT_VENDOR_ID2 0x0
+#define LDACBT_VENDOR_ID3 0x0
+
+/* [Octet 4-5] Vendor Specific A2DP Codec ID for LDAC */
+#define LDACBT_CODEC_ID0 0xAA
+#define LDACBT_CODEC_ID1 0x00
+
+/* [Octet 6]
+ * [b7,b6] : RFA
+ *       Reserved for future additions.
+ *       Bits with this designation shall be set to zero.
+ *       Receivers shall ignore these bits.
+ * -----------------------------------------------------
+ * [b5-b0] : Sampling frequency and its associated bit field in LDAC are shown below.
+ *    |  5  |  4  |  3  |  2  |  1  |  0  |
+ *    |  o  |     |     |     |     |     |  44100
+ *    |     |  o  |     |     |     |     |  48000
+ *    |     |     |  o  |     |     |     |  88200
+ *    |     |     |     |  o  |     |     |  96000
+ *    |     |     |     |     |  o  |     | 176400
+ *    |     |     |     |     |     |  o  | 192000
+ *
+ */
+/* Support for 44.1kHz sampling frequency */
+#define LDACBT_SAMPLING_FREQ_044100        0x20
+/* Support for 48kHz sampling frequency */
+#define LDACBT_SAMPLING_FREQ_048000        0x10
+/* Support for 88.2kHz sampling frequency */
+#define LDACBT_SAMPLING_FREQ_088200        0x08
+/* Support for 96kHz sampling frequency */
+#define LDACBT_SAMPLING_FREQ_096000        0x04
+/* Support for 176.4kHz sampling frequency */
+#define LDACBT_SAMPLING_FREQ_176400        0x02
+/* Support for 192kHz sampling frequency */
+#define LDACBT_SAMPLING_FREQ_192000        0x01
+
+/* [Octet 7]
+ * [b7-b3] : RFA
+ *       Reserved for future additions.
+ *       Bits with this designation shall be set to zero.
+ *       Receivers shall ignore these bits.
+ * ------------------------------------------------------
+ * [b2-b0] : Channel mode and its associated bit field in LDAC are shown below.
+ *    |  2  |  1  |  0  |
+ *    |  o  |     |     | MONO
+ *    |     |  o  |     | DUAL CHANNEL
+ *    |     |     |  o  | STEREO
+ */
+/* Support for MONO */
+#define LDACBT_CHANNEL_MODE_MONO           0x04
+/* Support for DUAL CHANNEL */
+#define LDACBT_CHANNEL_MODE_DUAL_CHANNEL   0x02
+/* Support for STEREO */
+#define LDACBT_CHANNEL_MODE_STEREO         0x01
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* _LDACBT_H_ */
diff --git a/src/modules/bluetooth/a2dp/ldacBT_abr.h b/src/modules/bluetooth/a2dp/ldacBT_abr.h
new file mode 100644
index 000000000..76d7a7062
--- /dev/null
+++ b/src/modules/bluetooth/a2dp/ldacBT_abr.h
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2014 - 2017 Sony Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _LDACBT_ABR_H_
+#define _LDACBT_ABR_H_
+
+/* This file contains the definitions, declarations and macros for an implementation of
+ * LDAC Adaptive Bit Rate (hereinafter ABR) processing.
+ *
+ * The basic flow of the ABR processing is as follows:
+ * - The program creates a handle of LDAC ABR API using ldac_ABR_get_handle().
+ * - The program initializes the handle by setting the ldac_ABR_Proc() call interval to
+ *   ldac_ABR_Init().
+ *       The interval shall be as short as possible at the timing without accumulation
+ *       of packet in the buffer if propagation environment is fine.
+ * - The program reinitializes the handle by calling ldac_ABR_Init() again when the
+ *   state of the TX queue changes greatly, such as clearing the queue.
+ * - If the program demands to control the thresholds, then ldac_ABR_set_thresholds()
+ *   should be called.
+ * - The program sets flagEnable to "1" when allowing LDAC encode bitrate to be
+ *   adjusted by ABR, and sets it to "0" if it is not allowed.
+ * - The program calls ldac_ABR_Proc() at the interval set to ldac_ABR_Init() even if
+ *   flagEnable is "0".
+ *       The program passes TxQueueDepth and flagEnable to ldac_ABR_Proc() at this call,
+ *       LDAC encode bitrate is adjusted only when flagEnable is "1".
+ *       Otherwise, the internal parameters are updated and analyzed then returned.
+ *       The ABR handle adjusts eqmid based on TxQueueDepth which is passed from the program.
+ *       The ABR handle calls LDAC encode API ldacBT_alter_eqmid_priority() to adjust eqmid.
+ *       The ABR handle calls LDAC encode API ldacBT_get_eqmid() to get current eqmid.
+ * - The handle may be released with ldac_ABR_free_handle().
+ *
+ * Notes on debugging LDAC ABR:
+ * The meaning of "works fine" is that the bit rate will be low in case of bad radio situation
+ * and high in case of good radio situation.
+ *
+ * The bit rate transition can be debug by checking logcat messages from LDAC ABR library which
+ * built with the following changes in Android.bp:
+ *  - Adding "liblog" to shared_libs.
+ *  - Adding "-DLOCAL_DEBUG" to cflags.
+ * The messages are formated as follows:
+ *       [LDAC ABR] - abrQualityModeID : 0 -- eqmid : 0 -- TxQue : 0
+ *     where abrQualityModeID and eqmid related to the current bit rate and TxQue shows the depth
+ *     of current Tx queue.
+ *     The relationship between abrQualityModeID, eqmid and the bit rate is described in
+ *     "ldacBT_abr.c".
+ *
+ * The bit rate transition can be estimated/debug by listening to the audio played on the SNK
+ * device. This method cannot use to confirm the details of the bit rate transition, but useful
+ * to know how LDAC ABR algorithm works in a field test without checking the log.
+ * To try this method, rebuilding of the "libldacBT_enc" library with the following change in
+ * Android.bp is required:
+ *  - Adding "-DUSE_LDAC_ENC_SETTING_FOR_ABR_DEBUG" to cflags.
+ * By defining the above macro, the lower the bit rate, the greatly lower the bandwidth of the audio
+ * played on the SNK device. Therefore, the audio played on the SNK device will sounds like a
+ * low-pass filtered sound when the bit rate is low and will sounds as usual when the bit rate is
+ * enough high. It is recommend using sound such as white noise to hear those changes for the first
+ * time.
+ *
+ * IMPORTANT:
+ * These libraries modified as described above shall be used only to confirm the bit rate transition
+ * and SHALL NOT BE USED FOR FINAL PRODUCTS.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef LDAC_ABR_API
+#define LDAC_ABR_API
+#endif /* LDAC_ABR_API */
+
+#include "ldacBT.h" /* HANDLE_LDAC_BT */
+
+/* LDAC ABR handle type*/
+typedef struct _ldacbt_abr_param * HANDLE_LDAC_ABR;
+
+/* Allocation of LDAC ABR handle.
+ *  Format
+ *      HANDLE_LDAC_ABR  ldacBT_get_handle( void );
+ *  Arguments
+ *      None.
+ *  Return value
+ *      HANDLE_LDAC_ABR for success, NULL for failure.
+ */
+LDAC_ABR_API HANDLE_LDAC_ABR ldac_ABR_get_handle(void);
+
+/* Release of LDAC ABR handle.
+ *  Format
+ *      void  ldac_ABR_free_handle( HANDLE_LDAC_ABR );
+ *  Arguments
+ *      hLdacAbr    HANDLE_LDAC_ABR    LDAC ABR handle.
+ *  Return value
+ *      None.
+ */
+LDAC_ABR_API void ldac_ABR_free_handle(HANDLE_LDAC_ABR hLdacAbr);
+
+/* Initialize LDAC ABR.
+ *  Format
+ *      int  ldac_ABR_Init( HANDLE_LDAC_ABR, unsigned int );
+ *  Arguments
+ *      hLdacAbr        HANDLE_LDAC_ABR    LDAC ABR handle.
+ *      interval_ms     unsigned int       interval in ms for calling ldac_ABR_Proc().
+ *                                         interval of 1ms to 500ms is valid.
+ *  Return value
+ *      int: 0 for success, -1 for failure.
+ */
+LDAC_ABR_API int ldac_ABR_Init(HANDLE_LDAC_ABR hLdacAbr, unsigned int interval_ms);
+
+/* Setup thresholds for LDAC ABR.
+ *  Format
+ *      int ldac_ABR_set_thresholds( HANDLE_LDAC_ABR, unsigned int, unsigned int, unsigned int );
+ *  Arguments
+ *      hLdacAbr            HANDLE_LDAC_ABR  LDAC ABR handle.
+ *      thCritical          unsigned int     threshold for critical TxQueueDepth status.
+ *      thDangerousTrend    unsigned int     threshold for dangerous trend of TxQueueDepth.
+ *      thSafety4HQSQ       unsigned int     safety threshold for LDACBT_EQMID_HQ and
+ *                                           LDACBT_EQMID_SQ.
+ *  Return value
+ *      int: 0 for success, -1 for failure.
+ *  Remarks
+ *    Those thresholds should be the number of packets stored in the TX queue and should be
+ *    greater than 0.
+ *    The thCritical and thDangerousTrend are used for all eqmid and thSafety4HQSQ is used
+ *    only for LDACBT_EQMID_HQ and LDACBT_EQMID_SQ. Therefore, those thresholds must satisfy
+ *    the following releationship:
+ *        thCritical >= thDangerousTrend >= thSafety4HQSQ
+ */
+LDAC_ABR_API int ldac_ABR_set_thresholds(HANDLE_LDAC_ABR hLdacAbr, unsigned int thCritical,
+                                    unsigned int thDangerousTrend, unsigned int thSafety4HQSQ);
+
+/* LDAC ABR main process.
+ *  Format
+ *      int  ldac_ABR_Proc( HANDLE_LDAC_BT, HANDLE_LDAC_ABR, unsigned int, unsigned int );
+ *  Arguments
+ *      hLdacBt        HANDLE_LDAC_BT    LDAC handle.
+ *      hLdacAbr       HANDLE_LDAC_ABR   LDAC ABR handle.
+ *      TxQueueDepth   unsigned int      depth of TX queue.
+ *      flagEnable     unsigned int      flag indicating whether ABR is allowed to adjust LDAC
+ *                                       encode bitrate
+ *  Return value
+ *      int: updated Encode Quality Mode Index for success, -1 for failure.
+ */
+LDAC_ABR_API int ldac_ABR_Proc(HANDLE_LDAC_BT hLdacBt, HANDLE_LDAC_ABR hLdacAbr,
+                                 unsigned int TxQueueDepth, unsigned int flagEnable);
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LDACBT_ABR_H_ */
+
diff --git a/src/modules/bluetooth/a2dp/ldac_libs.c b/src/modules/bluetooth/a2dp/ldac_libs.c
new file mode 100644
index 000000000..4dbb913f1
--- /dev/null
+++ b/src/modules/bluetooth/a2dp/ldac_libs.c
@@ -0,0 +1,248 @@
+
+#include <dlfcn.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include "ldacBT.h"
+#include "ldacBT_abr.h"
+
+#ifdef HAVE_CONFIG_H
+
+#include <config.h>
+
+#endif
+
+#include <pulsecore/log.h>
+
+static const char *LDAC_ENCODER_LIB_NAME = "libldacBT_enc.so";
+
+static const char *LDAC_GET_HANDLE_FUNC_NAME = "ldacBT_get_handle";
+static const char *LDAC_FREE_HANDLE_FUNC_NAME = "ldacBT_free_handle";
+static const char *LDAC_CLOSE_HANDLE_FUNC_NAME = "ldacBT_close_handle";
+static const char *LDAC_GET_VERSION_FUNC_NAME = "ldacBT_get_version";
+static const char *LDAC_GET_SAMPLING_FREQ_FUNC_NAME = "ldacBT_get_sampling_freq";
+static const char *LDAC_GET_BITRATE_FUNC_NAME = "ldacBT_get_bitrate";
+static const char *LDAC_INIT_HANDLE_ENCODE_FUNC_NAME = "ldacBT_init_handle_encode";
+static const char *LDAC_SET_EQMID_FUNC_NAME = "ldacBT_set_eqmid";
+static const char *LDAC_GET_EQMID_FUNC_NAME = "ldacBT_get_eqmid";
+static const char *LDAC_ALTER_EQMID_PRIORITY_FUNC_NAME = "ldacBT_alter_eqmid_priority";
+static const char *LDAC_ENCODE_FUNC_NAME = "ldacBT_encode";
+static const char *LDAC_GET_ERROR_CODE_FUNC_NAME = "ldacBT_get_error_code";
+
+
+static const char *LDAC_ABR_LIB_NAME = "libldacBT_abr.so";
+
+static const char *LDAC_ABR_GET_HANDLE_FUNC_NAME = "ldac_ABR_get_handle";
+static const char *LDAC_ABR_FREE_HANDLE_FUNC_NAME = "ldac_ABR_free_handle";
+static const char *LDAC_ABR_INIT_FUNC_NAME = "ldac_ABR_Init";
+static const char *LDAC_ABR_SET_THRESHOLDS_FUNC_NAME = "ldac_ABR_set_thresholds";
+static const char *LDAC_ABR_PROC_FUNC_NAME = "ldac_ABR_Proc";
+
+
+typedef HANDLE_LDAC_BT (*ldacBT_get_handle_func_t)(void);
+
+typedef void (*ldacBT_free_handle_func_t)(HANDLE_LDAC_BT hLdacBt);
+
+typedef void (*ldacBT_close_handle_func_t)(HANDLE_LDAC_BT hLdacBt);
+
+typedef int  (*ldacBT_get_version_func_t)(void);
+
+typedef int  (*ldacBT_get_sampling_freq_func_t)(HANDLE_LDAC_BT hLdacBt);
+
+typedef int  (*ldacBT_get_bitrate_func_t)(HANDLE_LDAC_BT hLdacBt);
+
+typedef int  (*ldacBT_init_handle_encode_func_t)(HANDLE_LDAC_BT hLdacBt, int mtu, int eqmid, int cm,
+                                                 LDACBT_SMPL_FMT_T fmt, int sf);
+
+typedef int  (*ldacBT_set_eqmid_func_t)(HANDLE_LDAC_BT hLdacBt, int eqmid);
+
+typedef int  (*ldacBT_get_eqmid_func_t)(HANDLE_LDAC_BT hLdacBt);
+
+typedef int  (*ldacBT_alter_eqmid_priority_func_t)(HANDLE_LDAC_BT hLdacBt, int priority);
+
+typedef int  (*ldacBT_encode_func_t)(HANDLE_LDAC_BT hLdacBt, void *p_pcm, int *pcm_used,
+                                     unsigned char *p_stream, int *stream_sz, int *frame_num);
+
+typedef int  (*ldacBT_get_error_code_func_t)(HANDLE_LDAC_BT hLdacBt);
+
+
+typedef HANDLE_LDAC_ABR (*ldac_ABR_get_handle_func_t)(void);
+
+typedef void (*ldac_ABR_free_handle_func_t)(HANDLE_LDAC_ABR hLdacAbr);
+
+typedef int (*ldac_ABR_Init_func_t)(HANDLE_LDAC_ABR hLdacAbr, unsigned int interval_ms);
+
+typedef int (*ldac_ABR_set_thresholds_func_t)(HANDLE_LDAC_ABR hLdacAbr, unsigned int thCritical,
+                                              unsigned int thDangerousTrend, unsigned int thSafety4HQSQ);
+
+typedef int (*ldac_ABR_Proc_func_t)(HANDLE_LDAC_BT hLdacBt, HANDLE_LDAC_ABR hLdacAbr,
+                                    unsigned int TxQueueDepth, unsigned int flagEnable);
+
+static ldacBT_get_handle_func_t ldacBT_get_handle_func;
+static ldacBT_free_handle_func_t ldacBT_free_handle_func;
+static ldacBT_close_handle_func_t ldacBT_close_handle_func;
+static ldacBT_get_version_func_t ldacBT_get_version_func;
+static ldacBT_get_sampling_freq_func_t ldacBT_get_sampling_freq_func;
+static ldacBT_get_bitrate_func_t ldacBT_get_bitrate_func;
+static ldacBT_init_handle_encode_func_t ldacBT_init_handle_encode_func;
+static ldacBT_set_eqmid_func_t ldacBT_set_eqmid_func;
+static ldacBT_get_eqmid_func_t ldacBT_get_eqmid_func;
+static ldacBT_alter_eqmid_priority_func_t ldacBT_alter_eqmid_priority_func;
+static ldacBT_encode_func_t ldacBT_encode_func;
+static ldacBT_get_error_code_func_t ldacBT_get_error_code_func;
+
+
+static ldac_ABR_get_handle_func_t ldac_ABR_get_handle_func;
+static ldac_ABR_free_handle_func_t ldac_ABR_free_handle_func;
+static ldac_ABR_Init_func_t ldac_ABR_Init_func;
+static ldac_ABR_set_thresholds_func_t ldac_ABR_set_thresholds_func;
+static ldac_ABR_Proc_func_t ldac_ABR_Proc_func;
+
+static void *ldac_encoder_lib_h = NULL;
+static void *ldac_abr_lib_h = NULL;
+
+static bool ldac_abr_loaded = false;
+
+
+static void *load_func(void *lib_handle, const char *func_name) {
+    void *func = dlsym(lib_handle, func_name);
+    if (func == NULL) {
+        pa_log_error("No function %s in provide library. %s", func_name, dlerror());
+        return NULL;
+    }
+    return func;
+}
+
+static bool ldac_abr_load() {
+    if (ldac_abr_lib_h)
+        return true;
+    ldac_abr_lib_h = dlopen(LDAC_ABR_LIB_NAME, RTLD_NOW);
+    if (ldac_abr_lib_h == NULL) {
+        pa_log_error("Cannot open LDAC abr library: %s. %s", LDAC_ABR_LIB_NAME, dlerror());
+        return false;
+    }
+
+    ldac_ABR_get_handle_func = (ldac_ABR_get_handle_func_t) load_func(ldac_abr_lib_h,
+                                                                      LDAC_ABR_GET_HANDLE_FUNC_NAME);
+    if (ldac_ABR_get_handle_func == NULL)
+        return false;
+    ldac_ABR_free_handle_func = (ldac_ABR_free_handle_func_t) load_func(ldac_abr_lib_h,
+                                                                        LDAC_ABR_FREE_HANDLE_FUNC_NAME);
+    if (ldac_ABR_free_handle_func == NULL)
+        return false;
+    ldac_ABR_Init_func = (ldac_ABR_Init_func_t) load_func(ldac_abr_lib_h, LDAC_ABR_INIT_FUNC_NAME);
+    if (ldac_ABR_Init_func == NULL)
+        return false;
+    ldac_ABR_set_thresholds_func = (ldac_ABR_set_thresholds_func_t) load_func(ldac_abr_lib_h,
+                                                                              LDAC_ABR_SET_THRESHOLDS_FUNC_NAME);
+    if (ldac_ABR_set_thresholds_func == NULL)
+        return false;
+    ldac_ABR_Proc_func = (ldac_ABR_Proc_func_t) load_func(ldac_abr_lib_h, LDAC_ABR_PROC_FUNC_NAME);
+    if (ldac_ABR_Proc_func == NULL)
+        return false;
+    return true;
+}
+
+static void ldac_abr_unload() {
+    if (ldac_abr_lib_h != NULL) {
+        dlclose(ldac_abr_lib_h);
+        ldac_abr_lib_h = NULL;
+    }
+    ldac_ABR_get_handle_func = NULL;
+    ldac_ABR_free_handle_func = NULL;
+    ldac_ABR_Init_func = NULL;
+    ldac_ABR_set_thresholds_func = NULL;
+    ldac_ABR_Proc_func = NULL;
+}
+
+static bool _ldac_encoder_load() {
+    if (ldac_encoder_lib_h)
+        return true;
+    ldac_encoder_lib_h = dlopen(LDAC_ENCODER_LIB_NAME, RTLD_NOW);
+    if (ldac_encoder_lib_h == NULL) {
+        pa_log_error("Cannot open LDAC encoder library: %s. %s", LDAC_ENCODER_LIB_NAME, dlerror());
+        return false;
+    }
+
+    ldacBT_get_handle_func = (ldacBT_get_handle_func_t) load_func(ldac_encoder_lib_h, LDAC_GET_HANDLE_FUNC_NAME);
+    if (ldacBT_get_handle_func == NULL)
+        return false;
+    ldacBT_free_handle_func = (ldacBT_free_handle_func_t) load_func(ldac_encoder_lib_h, LDAC_FREE_HANDLE_FUNC_NAME);
+    if (ldacBT_free_handle_func == NULL)
+        return false;
+    ldacBT_close_handle_func = (ldacBT_close_handle_func_t) load_func(ldac_encoder_lib_h, LDAC_CLOSE_HANDLE_FUNC_NAME);
+    if (ldacBT_close_handle_func == NULL)
+        return false;
+    ldacBT_get_version_func = (ldacBT_get_version_func_t) load_func(ldac_encoder_lib_h, LDAC_GET_VERSION_FUNC_NAME);
+    if (ldacBT_get_version_func == NULL)
+        return false;
+    ldacBT_get_sampling_freq_func = (ldacBT_get_sampling_freq_func_t) load_func(ldac_encoder_lib_h,
+                                                                                LDAC_GET_SAMPLING_FREQ_FUNC_NAME);
+    if (ldacBT_get_sampling_freq_func == NULL)
+        return false;
+    ldacBT_get_bitrate_func = (ldacBT_get_bitrate_func_t) load_func(ldac_encoder_lib_h, LDAC_GET_BITRATE_FUNC_NAME);
+    if (ldacBT_get_bitrate_func == NULL)
+        return false;
+    ldacBT_init_handle_encode_func = (ldacBT_init_handle_encode_func_t) load_func(ldac_encoder_lib_h,
+                                                                                  LDAC_INIT_HANDLE_ENCODE_FUNC_NAME);
+    if (ldacBT_init_handle_encode_func == NULL)
+        return false;
+    ldacBT_set_eqmid_func = (ldacBT_set_eqmid_func_t) load_func(ldac_encoder_lib_h, LDAC_SET_EQMID_FUNC_NAME);
+    if (ldacBT_set_eqmid_func == NULL)
+        return false;
+    ldacBT_get_eqmid_func = (ldacBT_get_eqmid_func_t) load_func(ldac_encoder_lib_h, LDAC_GET_EQMID_FUNC_NAME);
+    if (ldacBT_get_eqmid_func == NULL)
+        return false;
+    ldacBT_alter_eqmid_priority_func = (ldacBT_alter_eqmid_priority_func_t) load_func(ldac_encoder_lib_h,
+                                                                                      LDAC_ALTER_EQMID_PRIORITY_FUNC_NAME);
+    if (ldacBT_alter_eqmid_priority_func == NULL)
+        return false;
+    ldacBT_encode_func = (ldacBT_encode_func_t) load_func(ldac_encoder_lib_h, LDAC_ENCODE_FUNC_NAME);
+    if (ldacBT_encode_func == NULL)
+        return false;
+    ldacBT_get_error_code_func = (ldacBT_get_error_code_func_t) load_func(ldac_encoder_lib_h,
+                                                                          LDAC_GET_ERROR_CODE_FUNC_NAME);
+    if (ldacBT_get_error_code_func == NULL)
+        return false;
+
+    if (!ldac_abr_load()) {
+        pa_log_debug("Cannot load the LDAC ABR library");
+        ldac_abr_unload();
+        ldac_abr_loaded = false;
+    } else
+        ldac_abr_loaded = true;
+    return true;
+}
+
+
+static void ldac_encoder_unload() {
+    if (ldac_encoder_lib_h != NULL) {
+        dlclose(ldac_encoder_lib_h);
+        ldac_encoder_lib_h = NULL;
+    }
+    ldacBT_get_handle_func = NULL;
+    ldacBT_free_handle_func = NULL;
+    ldacBT_close_handle_func = NULL;
+    ldacBT_get_version_func = NULL;
+    ldacBT_get_sampling_freq_func = NULL;
+    ldacBT_get_bitrate_func = NULL;
+    ldacBT_init_handle_encode_func = NULL;
+    ldacBT_set_eqmid_func = NULL;
+    ldacBT_get_eqmid_func = NULL;
+    ldacBT_alter_eqmid_priority_func = NULL;
+    ldacBT_encode_func = NULL;
+    ldacBT_get_error_code_func = NULL;
+    ldac_ABR_get_handle_func = NULL;
+    ldac_ABR_free_handle_func = NULL;
+    ldac_ABR_Init_func = NULL;
+    ldac_ABR_set_thresholds_func = NULL;
+    ldac_ABR_Proc_func = NULL;
+}
+
+static bool ldac_encoder_load() {
+    if (!_ldac_encoder_load()) {
+        pa_log_debug("Cannot load the LDAC encoder library");
+        ldac_encoder_unload();
+        return false;
+    }
+    return true;
+}
diff --git a/src/modules/bluetooth/a2dp/rtp.h b/src/modules/bluetooth/a2dp/rtp.h
new file mode 100644
index 000000000..20694c1e1
--- /dev/null
+++ b/src/modules/bluetooth/a2dp/rtp.h
@@ -0,0 +1,74 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@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/>.
+ */
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct rtp_header {
+	unsigned cc:4;
+	unsigned x:1;
+	unsigned p:1;
+	unsigned v:2;
+
+	unsigned pt:7;
+	unsigned m:1;
+
+	uint16_t sequence_number;
+	uint32_t timestamp;
+	uint32_t ssrc;
+	uint32_t csrc[0];
+} __attribute__ ((packed));
+
+struct rtp_payload {
+	unsigned frame_count:4;
+	unsigned rfa0:1;
+	unsigned is_last_fragment:1;
+	unsigned is_first_fragment:1;
+	unsigned is_fragmented:1;
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct rtp_header {
+	unsigned v:2;
+	unsigned p:1;
+	unsigned x:1;
+	unsigned cc:4;
+
+	unsigned m:1;
+	unsigned pt:7;
+
+	uint16_t sequence_number;
+	uint32_t timestamp;
+	uint32_t ssrc;
+	uint32_t csrc[0];
+} __attribute__ ((packed));
+
+struct rtp_payload {
+	unsigned is_fragmented:1;
+	unsigned is_first_fragment:1;
+	unsigned is_last_fragment:1;
+	unsigned rfa0:1;
+	unsigned frame_count:4;
+} __attribute__ ((packed));
+
+#else
+#error "Unknown byte order"
+#endif
diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 2d8337317..a60c25d1e 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -33,7 +33,7 @@
 #include <pulsecore/refcnt.h>
 #include <pulsecore/shared.h>
 
-#include "a2dp-codecs.h"
+#include "a2dp/a2dp-api.h"
 
 #include "bluez5-util.h"
 
@@ -48,8 +48,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 +87,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 +888,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 +901,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 +930,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 +970,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 +1268,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 +1296,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 +1349,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 +1372,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 +1386,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->validate_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 +1410,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 +1424,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 +1444,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 +1461,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);
+    pa_a2dp_endpoint_to_codec_index(endpoint, &index);
+    pa_a2dp_codec_index_to_a2dp_codec(index, &a2dp_codec);
 
-    /* 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");
+    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 +1541,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 +1551,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 +1581,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 +1593,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 +1655,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 +1768,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 2a36adfdb..77a12e6ed 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -45,9 +45,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");
@@ -61,13 +60,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
 };
 
@@ -93,17 +91,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;
@@ -145,7 +139,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 {
@@ -417,105 +411,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);
 
@@ -546,9 +490,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;
 
@@ -567,6 +509,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;
@@ -574,22 +518,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) {
 
@@ -606,7 +546,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) {
@@ -614,50 +554,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,
+                                                        &timestamp, &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);
 
@@ -704,40 +616,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. */
@@ -749,27 +631,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);
@@ -859,13 +720,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) {
@@ -905,9 +768,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);
@@ -1094,6 +967,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);
@@ -1269,6 +1144,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);
@@ -1295,111 +1173,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);
     }
 }
 
@@ -1619,6 +1425,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
@@ -1650,8 +1461,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;
@@ -1806,6 +1622,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;
     }
@@ -2414,6 +2238,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 =
@@ -2491,11 +2321,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 44578214c..68a05e754 100644
--- a/src/modules/bluetooth/module-bluez5-discover.c
+++ b/src/modules/bluetooth/module-bluez5-discover.c
@@ -41,6 +41,7 @@ PA_MODULE_USAGE(
 static const char* const valid_modargs[] = {
     "headset",
     "autodetect_mtu",
+    "a2dp_config",
     NULL
 };
 
@@ -51,6 +52,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) {
@@ -71,7 +73,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);
@@ -99,7 +102,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;
 
@@ -128,10 +131,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)))
@@ -168,5 +175,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);
 }
