[pulseaudio-discuss] [PATCH v3 5/7] bluetooth: Modular API for A2DP codecs

Pali Rohár pali.rohar at gmail.com
Sat Jan 12 15:21:54 UTC 2019


Move current SBC codec implementation into separate source file and use
this new API for providing all needed functionality for Bluetooth A2DP.

Both bluez5-util and module-bluez5-device are changed to use this new
modular codec API. All codec specific functions are structures were
removed from these files.

For adding new A2DP codec it is needed just to adjust a2dp-codec-util.c
file. bluez5-util and module-bluez5-device are now codec independent.

Every A2DP codec has its own profile name. List of supported codecs by
remote device is provided only by new version of bluez. If old version is
detected then all codecs are exported. Old version of bluez does not
provide codec switching, so user cannot change from one A2DP profile to
another A2DP profile when using old bluez version.

Some A2DP codecs are bidirectional support backchannel for microphone
voice. This A2DP codec API is prepared for this support.

API is also prepared for supporting more "variants" of one A2DP codec. E.g.
low quality SBC, high quality SBC, automatic mode SBC.

Limitations:

* Codec switching is not implemented yet.
* Voice backchannel is not implemented yet.
---
 src/Makefile.am                              |  12 +-
 src/modules/bluetooth/a2dp-codec-api.h       |  78 +++
 src/modules/bluetooth/a2dp-codec-sbc.c       | 621 ++++++++++++++++++++++
 src/modules/bluetooth/a2dp-codec-util.c      |  56 ++
 src/modules/bluetooth/a2dp-codec-util.h      |  34 ++
 src/modules/bluetooth/bluez5-util.c          | 702 ++++++++++++++++---------
 src/modules/bluetooth/bluez5-util.h          |  38 +-
 src/modules/bluetooth/module-bluez5-device.c | 740 +++++++++++----------------
 8 files changed, 1579 insertions(+), 702 deletions(-)
 create mode 100644 src/modules/bluetooth/a2dp-codec-api.h
 create mode 100644 src/modules/bluetooth/a2dp-codec-sbc.c
 create mode 100644 src/modules/bluetooth/a2dp-codec-util.c
 create mode 100644 src/modules/bluetooth/a2dp-codec-util.h

diff --git a/src/Makefile.am b/src/Makefile.am
index 2dbb4563b..f65783308 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2123,6 +2123,9 @@ module_bluetooth_discover_la_CFLAGS = $(AM_CFLAGS) -DPA_MODULE_NAME=module_bluet
 libbluez5_util_la_SOURCES = \
 		modules/bluetooth/bluez5-util.c \
 		modules/bluetooth/bluez5-util.h \
+		modules/bluetooth/a2dp-codec-api.h \
+		modules/bluetooth/a2dp-codec-util.c \
+		modules/bluetooth/a2dp-codec-util.h \
 		modules/bluetooth/a2dp-codecs.h \
 		modules/bluetooth/rtp.h
 if HAVE_BLUEZ_5_OFONO_HEADSET
@@ -2137,6 +2140,11 @@ 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_CPPFLAGS = $(AM_CPPFLAGS)
+
+libbluez5_util_la_SOURCES += modules/bluetooth/a2dp-codec-sbc.c
+libbluez5_util_la_LIBADD += $(SBC_LIBS)
+libbluez5_util_la_CFLAGS += $(SBC_CFLAGS)
 
 module_bluez5_discover_la_SOURCES = modules/bluetooth/module-bluez5-discover.c
 module_bluez5_discover_la_LDFLAGS = $(MODULE_LDFLAGS)
@@ -2145,8 +2153,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-codec-api.h b/src/modules/bluetooth/a2dp-codec-api.h
new file mode 100644
index 000000000..ec7c64336
--- /dev/null
+++ b/src/modules/bluetooth/a2dp-codec-api.h
@@ -0,0 +1,78 @@
+#ifndef fooa2dpcodechfoo
+#define fooa2dpcodechfoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2018-2019 Pali Rohár <pali.rohar at gmail.com>
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as
+  published by the Free Software Foundation; either version 2.1 of the
+  License, or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/core.h>
+
+typedef struct pa_a2dp_codec_capabilities {
+    uint8_t size;
+    uint8_t buffer[]; /* max size is 254 bytes */
+} pa_a2dp_codec_capabilities;
+
+typedef struct pa_a2dp_codec_id {
+    uint8_t codec_id;
+    uint32_t vendor_id;
+    uint16_t vendor_codec_id;
+} pa_a2dp_codec_id;
+
+typedef struct pa_a2dp_codec {
+    /* Unique name of the codec, lowercase and without whitespaces, used for constructing identifier, D-Bus paths, ... */
+    const char *codec_name;
+    /* Human readable codec description */
+    const char *codec_description;
+
+    /* A2DP codec id */
+    pa_a2dp_codec_id codec_id;
+
+    /* True if codec is bi-directional and supports backchannel */
+    bool support_backchannel;
+
+    /* Returns true if codec accepts capabilities, for_encoding is true when capabilities are used for encoding */
+    bool (*accept_capabilities)(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding);
+    /* Choose preferred capabilities from hash map (const char * -> const pa_a2dp_codec_capabilities *) and returns corresponding key, for encoder is true when capabilities hash map is used for encoding */
+    const char *(*choose_capabilities)(const pa_hashmap *capabilities_hashmap, bool for_encoding);
+    /* Fill codec capabilities, returns size of filled buffer */
+    uint8_t (*fill_capabilities)(uint8_t capabilities_buffer[254]);
+    /* Validate codec configuration, returns true on success */
+    bool (*validate_configuration)(const uint8_t *config_buffer, uint8_t config_size);
+    /* Fill preferred codec configuration, returns size of filled buffer */
+    uint8_t (*fill_preferred_configuration)(const pa_sample_spec *sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[254]);
+
+    /* Initialize codec, returns codec info data and set sample_spec, for_encoding is true when codec_info is used for encoding, for_backchannel is true when codec_info is used for backchannel */
+    void *(*init_codec)(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec);
+    /* Deinitialize and release codec info data in codec_info */
+    void (*finish_codec)(void *codec_info);
+    /* Reset internal state of codec info data in codec_info */
+    void (*reset_codec)(void *codec_info);
+
+    /* Fill read_block_size and write_block_size for codec */
+    void (*fill_blocksize)(void *codec_info, size_t read_link_mtu, size_t write_link_mtu, size_t *read_block_size, size_t *write_block_size);
+
+    /* Reduce encoder bitrate for codec, returns true of block size was changed, called when socket is not accepting encoded data fast enough */
+    bool (*reduce_encoder_bitrate)(void *codec_info, size_t write_link_mtu, size_t *write_block_size);
+
+    /* Encode input_buffer of input_size to output_buffer of output_size, returns size of filled ouput_buffer and set processed to size of processed input_buffer */
+    size_t (*encode_buffer)(void *codec_info, uint32_t timestamp, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed);
+    /* Decode input_buffer of input_size to output_buffer of output_size, returns size of filled ouput_buffer and set processed to size of processed input_buffer */
+    size_t (*decode_buffer)(void *codec_info, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed);
+} pa_a2dp_codec;
+
+#endif
diff --git a/src/modules/bluetooth/a2dp-codec-sbc.c b/src/modules/bluetooth/a2dp-codec-sbc.c
new file mode 100644
index 000000000..f41c27c7f
--- /dev/null
+++ b/src/modules/bluetooth/a2dp-codec-sbc.c
@@ -0,0 +1,621 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2018-2019 Pali Rohár <pali.rohar at gmail.com>
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as
+  published by the Free Software Foundation; either version 2.1 of the
+  License, or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/once.h>
+#include <pulse/sample.h>
+#include <pulse/xmalloc.h>
+
+#include <arpa/inet.h>
+
+#include <sbc/sbc.h>
+
+#include "a2dp-codecs.h"
+#include "a2dp-codec-api.h"
+#include "rtp.h"
+
+#define SBC_BITPOOL_DEC_LIMIT 32
+#define SBC_BITPOOL_DEC_STEP 5
+
+struct sbc_info {
+    sbc_t sbc;                           /* Codec data */
+    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;
+};
+
+static bool accept_capabilities(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
+    const a2dp_sbc_t *capabilities = (const a2dp_sbc_t *) capabilities_buffer;
+
+    if (capabilities_size != sizeof(*capabilities))
+        return false;
+
+    if (!(capabilities->frequency & (SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 | SBC_SAMPLING_FREQ_44100 | SBC_SAMPLING_FREQ_48000)))
+        return false;
+
+    if (!(capabilities->channel_mode & (SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL | SBC_CHANNEL_MODE_STEREO | SBC_CHANNEL_MODE_JOINT_STEREO)))
+        return false;
+
+    if (!(capabilities->allocation_method & (SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS)))
+        return false;
+
+    if (!(capabilities->subbands & (SBC_SUBBANDS_4 | SBC_SUBBANDS_8)))
+        return false;
+
+    if (!(capabilities->block_length & (SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16)))
+        return false;
+
+    return true;
+}
+
+static const char *choose_capabilities(const pa_hashmap *capabilities_hashmap, bool for_encoding) {
+    const pa_a2dp_codec_capabilities *a2dp_capabilities;
+    const char *key;
+    void *state;
+
+    /* There is no preference, just choose random valid entry */
+    PA_HASHMAP_FOREACH_KV(key, a2dp_capabilities, capabilities_hashmap, state) {
+        if (accept_capabilities(a2dp_capabilities->buffer, a2dp_capabilities->size, for_encoding))
+            return key;
+    }
+
+    return NULL;
+}
+
+static uint8_t fill_capabilities(uint8_t capabilities_buffer[254]) {
+    a2dp_sbc_t *capabilities = (a2dp_sbc_t *) capabilities_buffer;
+
+    pa_zero(*capabilities);
+
+    capabilities->channel_mode = SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL | SBC_CHANNEL_MODE_STEREO |
+                                 SBC_CHANNEL_MODE_JOINT_STEREO;
+    capabilities->frequency = SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 | SBC_SAMPLING_FREQ_44100 |
+                              SBC_SAMPLING_FREQ_48000;
+    capabilities->allocation_method = SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS;
+    capabilities->subbands = SBC_SUBBANDS_4 | SBC_SUBBANDS_8;
+    capabilities->block_length = SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16;
+    capabilities->min_bitpool = SBC_MIN_BITPOOL;
+    capabilities->max_bitpool = SBC_MAX_BITPOOL;
+
+    return sizeof(*capabilities);
+}
+
+static bool validate_configuration(const uint8_t *config_buffer, uint8_t config_size) {
+    const a2dp_sbc_t *config = (const a2dp_sbc_t *) config_buffer;
+
+    if (config_size != sizeof(*config)) {
+        pa_log_error("Invalid size of config buffer");
+        return false;
+    }
+
+    if (config->frequency != SBC_SAMPLING_FREQ_16000 && config->frequency != SBC_SAMPLING_FREQ_32000 &&
+        config->frequency != SBC_SAMPLING_FREQ_44100 && config->frequency != SBC_SAMPLING_FREQ_48000) {
+        pa_log_error("Invalid sampling frequency in configuration");
+        return false;
+    }
+
+    if (config->channel_mode != SBC_CHANNEL_MODE_MONO && config->channel_mode != SBC_CHANNEL_MODE_DUAL_CHANNEL &&
+        config->channel_mode != SBC_CHANNEL_MODE_STEREO && config->channel_mode != SBC_CHANNEL_MODE_JOINT_STEREO) {
+        pa_log_error("Invalid channel mode in configuration");
+        return false;
+    }
+
+    if (config->allocation_method != SBC_ALLOCATION_SNR && config->allocation_method != SBC_ALLOCATION_LOUDNESS) {
+        pa_log_error("Invalid allocation method in configuration");
+        return false;
+    }
+
+    if (config->subbands != SBC_SUBBANDS_4 && config->subbands != SBC_SUBBANDS_8) {
+        pa_log_error("Invalid SBC subbands in configuration");
+        return false;
+    }
+
+    if (config->block_length != SBC_BLOCK_LENGTH_4 && config->block_length != SBC_BLOCK_LENGTH_8 &&
+        config->block_length != SBC_BLOCK_LENGTH_12 && config->block_length != SBC_BLOCK_LENGTH_16) {
+        pa_log_error("Invalid block length in configuration");
+        return false;
+    }
+
+    return true;
+}
+
+static uint8_t default_bitpool(uint8_t freq, uint8_t mode) {
+    /* These bitpool values were chosen based on the A2DP spec recommendation */
+    switch (freq) {
+        case SBC_SAMPLING_FREQ_16000:
+        case SBC_SAMPLING_FREQ_32000:
+            return 53;
+
+        case SBC_SAMPLING_FREQ_44100:
+
+            switch (mode) {
+                case SBC_CHANNEL_MODE_MONO:
+                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+                    return 31;
+
+                case SBC_CHANNEL_MODE_STEREO:
+                case SBC_CHANNEL_MODE_JOINT_STEREO:
+                    return 53;
+            }
+
+            pa_log_warn("Invalid channel mode %u", mode);
+            return 53;
+
+        case SBC_SAMPLING_FREQ_48000:
+
+            switch (mode) {
+                case SBC_CHANNEL_MODE_MONO:
+                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+                    return 29;
+
+                case SBC_CHANNEL_MODE_STEREO:
+                case SBC_CHANNEL_MODE_JOINT_STEREO:
+                    return 51;
+            }
+
+            pa_log_warn("Invalid channel mode %u", mode);
+            return 51;
+    }
+
+    pa_log_warn("Invalid sampling freq %u", freq);
+    return 53;
+}
+
+static uint8_t fill_preferred_configuration(const pa_sample_spec *sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[254]) {
+    a2dp_sbc_t *config = (a2dp_sbc_t *) config_buffer;
+    const a2dp_sbc_t *capabilities = (const a2dp_sbc_t *) capabilities_buffer;
+    int i;
+
+    static const struct {
+        uint32_t rate;
+        uint8_t cap;
+    } freq_table[] = {
+        { 16000U, SBC_SAMPLING_FREQ_16000 },
+        { 32000U, SBC_SAMPLING_FREQ_32000 },
+        { 44100U, SBC_SAMPLING_FREQ_44100 },
+        { 48000U, SBC_SAMPLING_FREQ_48000 }
+    };
+
+    if (capabilities_size != sizeof(*capabilities)) {
+        pa_log_error("Invalid size of capabilities buffer");
+        return 0;
+    }
+
+    pa_zero(*config);
+
+    /* Find the lowest freq that is at least as high as the requested sampling rate */
+    for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++)
+        if (freq_table[i].rate >= sample_spec->rate && (capabilities->frequency & freq_table[i].cap)) {
+            config->frequency = freq_table[i].cap;
+            break;
+        }
+
+    if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {
+        for (--i; i >= 0; i--) {
+            if (capabilities->frequency & freq_table[i].cap) {
+                config->frequency = freq_table[i].cap;
+                break;
+            }
+        }
+
+        if (i < 0) {
+            pa_log_error("Not suitable sample rate");
+            return 0;
+        }
+    }
+
+    pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table));
+
+    if (sample_spec->channels <= 1) {
+        if (capabilities->channel_mode & SBC_CHANNEL_MODE_MONO)
+            config->channel_mode = SBC_CHANNEL_MODE_MONO;
+        else if (capabilities->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
+            config->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
+        else if (capabilities->channel_mode & SBC_CHANNEL_MODE_STEREO)
+            config->channel_mode = SBC_CHANNEL_MODE_STEREO;
+        else if (capabilities->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
+            config->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
+        else {
+            pa_log_error("No supported channel modes");
+            return 0;
+        }
+    }
+
+    if (sample_spec->channels >= 2) {
+        if (capabilities->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
+            config->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
+        else if (capabilities->channel_mode & SBC_CHANNEL_MODE_STEREO)
+            config->channel_mode = SBC_CHANNEL_MODE_STEREO;
+        else if (capabilities->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
+            config->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
+        else if (capabilities->channel_mode & SBC_CHANNEL_MODE_MONO)
+            config->channel_mode = SBC_CHANNEL_MODE_MONO;
+        else {
+            pa_log_error("No supported channel modes");
+            return 0;
+        }
+    }
+
+    if (capabilities->block_length & SBC_BLOCK_LENGTH_16)
+        config->block_length = SBC_BLOCK_LENGTH_16;
+    else if (capabilities->block_length & SBC_BLOCK_LENGTH_12)
+        config->block_length = SBC_BLOCK_LENGTH_12;
+    else if (capabilities->block_length & SBC_BLOCK_LENGTH_8)
+        config->block_length = SBC_BLOCK_LENGTH_8;
+    else if (capabilities->block_length & SBC_BLOCK_LENGTH_4)
+        config->block_length = SBC_BLOCK_LENGTH_4;
+    else {
+        pa_log_error("No supported block lengths");
+        return 0;
+    }
+
+    if (capabilities->subbands & SBC_SUBBANDS_8)
+        config->subbands = SBC_SUBBANDS_8;
+    else if (capabilities->subbands & SBC_SUBBANDS_4)
+        config->subbands = SBC_SUBBANDS_4;
+    else {
+        pa_log_error("No supported subbands");
+        return 0;
+    }
+
+    if (capabilities->allocation_method & SBC_ALLOCATION_LOUDNESS)
+        config->allocation_method = SBC_ALLOCATION_LOUDNESS;
+    else if (capabilities->allocation_method & SBC_ALLOCATION_SNR)
+        config->allocation_method = SBC_ALLOCATION_SNR;
+    else {
+        pa_log_error("No supported allocation method");
+        return 0;
+    }
+
+    config->min_bitpool = (uint8_t) PA_MAX(SBC_MIN_BITPOOL, capabilities->min_bitpool);
+    config->max_bitpool = (uint8_t) PA_MIN(default_bitpool(config->frequency, config->channel_mode), capabilities->max_bitpool);
+
+    if (config->min_bitpool > config->max_bitpool)
+        return 0;
+
+    return sizeof(*config);
+}
+
+static void *init_codec(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec) {
+    struct sbc_info *sbc_info;
+    const a2dp_sbc_t *config = (const a2dp_sbc_t *) config_buffer;
+    int ret;
+
+    pa_assert(config_size == sizeof(*config));
+    pa_assert(!for_backchannel);
+
+    sbc_info = pa_xnew0(struct sbc_info, 1);
+
+    ret = sbc_init(&sbc_info->sbc, 0);
+    if (ret != 0) {
+        pa_xfree(sbc_info);
+        pa_log_error("SBC initialization failed: %d", ret);
+        return NULL;
+    }
+
+    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 = for_encoding ? sbc_info->max_bitpool : sbc_info->min_bitpool;
+    sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc);
+    sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
+
+    PA_ONCE_BEGIN {
+        pa_log_debug("Using SBC codec implementation: %s", pa_strnull(sbc_get_implementation_info(&sbc_info->sbc)));
+    } PA_ONCE_END;
+
+    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);
+
+    return sbc_info;
+}
+
+static void finish_codec(void *codec_info) {
+    struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
+
+    sbc_finish(&sbc_info->sbc);
+    pa_xfree(sbc_info);
+}
+
+static void set_bitpool(struct sbc_info *sbc_info, uint8_t bitpool) {
+    if (bitpool > sbc_info->max_bitpool)
+        bitpool = sbc_info->max_bitpool;
+    else if (bitpool < sbc_info->min_bitpool)
+        bitpool = sbc_info->min_bitpool;
+
+    sbc_info->sbc.bitpool = bitpool;
+
+    sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc);
+    sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
+
+    pa_log_debug("Bitpool has changed to %u", sbc_info->sbc.bitpool);
+}
+
+static void reset_codec(void *codec_info) {
+    struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
+    int ret;
+
+    ret = sbc_reinit(&sbc_info->sbc, 0);
+    if (ret != 0)
+        pa_log_error("SBC reinitialization failed: %d", ret);
+
+    set_bitpool(sbc_info, sbc_info->max_bitpool);
+}
+
+static void fill_write_blocksize(struct sbc_info *sbc_info, size_t write_link_mtu, size_t *write_block_size) {
+    *write_block_size =
+        (write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+        / sbc_info->frame_length * sbc_info->codesize;
+}
+
+static void fill_blocksize(void *codec_info, size_t read_link_mtu, size_t write_link_mtu, size_t *read_block_size, size_t *write_block_size) {
+    struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
+
+    *read_block_size =
+        (read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+        / sbc_info->frame_length * sbc_info->codesize;
+
+    fill_write_blocksize(sbc_info, write_link_mtu, write_block_size);
+}
+
+static bool reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu, size_t *write_block_size) {
+    struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
+    uint8_t bitpool;
+
+    /* Check if bitpool is already at its limit */
+    if (sbc_info->sbc.bitpool <= SBC_BITPOOL_DEC_LIMIT)
+        return false;
+
+    bitpool = sbc_info->sbc.bitpool - SBC_BITPOOL_DEC_STEP;
+
+    if (bitpool < SBC_BITPOOL_DEC_LIMIT)
+        bitpool = SBC_BITPOOL_DEC_LIMIT;
+
+    if (sbc_info->sbc.bitpool == bitpool)
+        return false;
+
+    set_bitpool(sbc_info, bitpool);
+    fill_write_blocksize(sbc_info, write_link_mtu, write_block_size);
+    return true;
+}
+
+static size_t encode_buffer(void *codec_info, uint32_t timestamp, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) {
+    struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
+    struct rtp_header *header;
+    struct rtp_payload *payload;
+    uint8_t *d;
+    const uint8_t *p;
+    size_t to_write, to_encode;
+    unsigned frame_count;
+
+    header = (struct rtp_header*) output_buffer;
+    payload = (struct rtp_payload*) (output_buffer + sizeof(*header));
+
+    frame_count = 0;
+
+    p = input_buffer;
+    to_encode = input_size;
+
+    d = output_buffer + sizeof(*header) + sizeof(*payload);
+    to_write = output_size - sizeof(*header) - sizeof(*payload);
+
+    while (PA_LIKELY(to_encode > 0 && to_write > 0)) {
+        ssize_t written;
+        ssize_t encoded;
+
+        encoded = sbc_encode(&sbc_info->sbc,
+                             p, to_encode,
+                             d, to_write,
+                             &written);
+
+        if (PA_UNLIKELY(encoded <= 0)) {
+            pa_log_error("SBC encoding error (%li)", (long) encoded);
+            *processed = p - input_buffer;
+            return 0;
+        }
+
+        pa_assert_fp((size_t) encoded <= to_encode);
+        pa_assert_fp((size_t) encoded == sbc_info->codesize);
+
+        pa_assert_fp((size_t) written <= to_write);
+        pa_assert_fp((size_t) written == sbc_info->frame_length);
+
+        p += encoded;
+        to_encode -= encoded;
+
+        d += written;
+        to_write -= written;
+
+        frame_count++;
+    }
+
+    /* write it to the fifo */
+    memset(output_buffer, 0, sizeof(*header) + sizeof(*payload));
+    header->v = 2;
+    header->pt = 1;
+    header->sequence_number = htons(sbc_info->seq_num++);
+    header->timestamp = htonl(timestamp);
+    header->ssrc = htonl(1);
+    payload->frame_count = frame_count;
+
+    *processed = p - input_buffer;
+    return d - output_buffer;
+}
+
+static size_t decode_buffer(void *codec_info, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) {
+    struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
+
+    struct rtp_header *header;
+    struct rtp_payload *payload;
+    const uint8_t *p;
+    uint8_t *d;
+    size_t to_write, to_decode;
+
+    header = (struct rtp_header *) input_buffer;
+    payload = (struct rtp_payload*) (input_buffer + sizeof(*header));
+
+    p = input_buffer + sizeof(*header) + sizeof(*payload);
+    to_decode = input_size - sizeof(*header) - sizeof(*payload);
+
+    d = output_buffer;
+    to_write = output_size;
+
+    while (PA_LIKELY(to_decode > 0)) {
+        size_t written;
+        ssize_t decoded;
+
+        decoded = sbc_decode(&sbc_info->sbc,
+                             p, to_decode,
+                             d, to_write,
+                             &written);
+
+        if (PA_UNLIKELY(decoded <= 0)) {
+            pa_log_error("SBC decoding error (%li)", (long) decoded);
+            *processed = p - input_buffer;
+            return 0;
+        }
+
+        /* Reset frame length, it can be changed due to bitpool change */
+        sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
+
+        pa_assert_fp((size_t) decoded <= to_decode);
+        pa_assert_fp((size_t) decoded == sbc_info->frame_length);
+
+        pa_assert_fp((size_t) written == sbc_info->codesize);
+
+        p += decoded;
+        to_decode -= decoded;
+
+        d += written;
+        to_write -= written;
+    }
+
+    *processed = p - input_buffer;
+    return d - output_buffer;
+}
+
+const pa_a2dp_codec pa_a2dp_codec_sbc = {
+    .codec_name = "sbc",
+    .codec_description = "SBC",
+    .codec_id = { A2DP_CODEC_SBC, 0, 0 },
+    .support_backchannel = false,
+    .accept_capabilities = accept_capabilities,
+    .choose_capabilities = choose_capabilities,
+    .fill_capabilities = fill_capabilities,
+    .validate_configuration = validate_configuration,
+    .fill_preferred_configuration = fill_preferred_configuration,
+    .init_codec = init_codec,
+    .finish_codec = finish_codec,
+    .reset_codec = reset_codec,
+    .fill_blocksize = fill_blocksize,
+    .reduce_encoder_bitrate = reduce_encoder_bitrate,
+    .encode_buffer = encode_buffer,
+    .decode_buffer = decode_buffer,
+};
diff --git a/src/modules/bluetooth/a2dp-codec-util.c b/src/modules/bluetooth/a2dp-codec-util.c
new file mode 100644
index 000000000..40e84783c
--- /dev/null
+++ b/src/modules/bluetooth/a2dp-codec-util.c
@@ -0,0 +1,56 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2019 Pali Rohár <pali.rohar at gmail.com>
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as
+  published by the Free Software Foundation; either version 2.1 of the
+  License, or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/core.h>
+#include <pulsecore/core-util.h>
+
+#include "a2dp-codec-util.h"
+
+extern const pa_a2dp_codec pa_a2dp_codec_sbc;
+
+/* This is list of supported codecs. Their order is important.
+ * Codec with higher index has higher priority. */
+const pa_a2dp_codec *pa_a2dp_codecs[] = {
+    &pa_a2dp_codec_sbc,
+};
+
+unsigned int pa_bluetooth_a2dp_codec_count(void) {
+    return (sizeof(pa_a2dp_codecs) / sizeof(*pa_a2dp_codecs));
+}
+
+const pa_a2dp_codec *pa_bluetooth_a2dp_codec_iter(unsigned int i) {
+    pa_assert(i < pa_bluetooth_a2dp_codec_count());
+    return pa_a2dp_codecs[i];
+}
+
+const pa_a2dp_codec *pa_bluetooth_a2dp_codec(const char *name) {
+    unsigned int i;
+    unsigned int count = pa_bluetooth_a2dp_codec_count();
+
+    for (i = 0; i < count; i++) {
+        if (pa_streq(pa_a2dp_codecs[i]->codec_name, name))
+            return pa_a2dp_codecs[i];
+    }
+
+    return NULL;
+}
diff --git a/src/modules/bluetooth/a2dp-codec-util.h b/src/modules/bluetooth/a2dp-codec-util.h
new file mode 100644
index 000000000..84a9d55f5
--- /dev/null
+++ b/src/modules/bluetooth/a2dp-codec-util.h
@@ -0,0 +1,34 @@
+#ifndef fooa2dpcodecutilhfoo
+#define fooa2dpcodecutilhfoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2019 Pali Rohár <pali.rohar at gmail.com>
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as
+  published by the Free Software Foundation; either version 2.1 of the
+  License, or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "a2dp-codec-api.h"
+
+/* Get number of supported A2DP codecs */
+unsigned int pa_bluetooth_a2dp_codec_count(void);
+
+/* Get i-th codec. Codec with higher number has higher priority */
+const pa_a2dp_codec *pa_bluetooth_a2dp_codec_iter(unsigned int i);
+
+/* Get codec by name */
+const pa_a2dp_codec *pa_bluetooth_a2dp_codec(const char *name);
+
+#endif
diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 48b147eed..4849cc4ae 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -2,6 +2,7 @@
   This file is part of PulseAudio.
 
   Copyright 2008-2013 João Paulo Rechi Vita
+  Copyrigth 2018-2019 Pali Rohár <pali.rohar at gmail.com>
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as
@@ -33,6 +34,7 @@
 #include <pulsecore/refcnt.h>
 #include <pulsecore/shared.h>
 
+#include "a2dp-codec-util.h"
 #include "a2dp-codecs.h"
 
 #include "bluez5-util.h"
@@ -169,11 +171,13 @@ static const char *transport_state_to_string(pa_bluetooth_transport_state_t stat
 }
 
 static bool device_supports_profile(pa_bluetooth_device *device, pa_bluetooth_profile_t profile) {
+    const pa_a2dp_codec_capabilities *a2dp_codec_capabilities;
+    const pa_a2dp_codec *a2dp_codec;
+    bool is_a2dp_sink;
+    pa_hashmap *endpoints;
+    void *state;
+
     switch (profile) {
-        case PA_BLUETOOTH_PROFILE_A2DP_SINK:
-            return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_SINK);
-        case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
-            return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_SOURCE);
         case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
             return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_HS)
                 || !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_HS_ALT)
@@ -182,10 +186,33 @@ static bool device_supports_profile(pa_bluetooth_device *device, pa_bluetooth_pr
             return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_AG)
                 || !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HFP_AG);
         case PA_BLUETOOTH_PROFILE_OFF:
-            pa_assert_not_reached();
+            return true;
+        default:
+            break;
+    }
+
+    a2dp_codec = pa_bluetooth_profile_to_a2dp_codec(profile);
+    is_a2dp_sink = pa_bluetooth_profile_is_a2dp_sink(profile);
+
+    if (is_a2dp_sink && !pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_SINK))
+        return false;
+    else if (!is_a2dp_sink && !pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_SOURCE))
+        return false;
+
+    if (is_a2dp_sink)
+        endpoints = pa_hashmap_get(device->a2dp_sink_endpoints, &a2dp_codec->codec_id);
+    else
+        endpoints = pa_hashmap_get(device->a2dp_source_endpoints, &a2dp_codec->codec_id);
+
+    if (!endpoints)
+        return false;
+
+    PA_HASHMAP_FOREACH(a2dp_codec_capabilities, endpoints, state) {
+        if (a2dp_codec->accept_capabilities(a2dp_codec_capabilities->buffer, a2dp_codec_capabilities->size, is_a2dp_sink))
+            return true;
     }
 
-    pa_assert_not_reached();
+    return false;
 }
 
 static bool device_is_profile_connected(pa_bluetooth_device *device, pa_bluetooth_profile_t profile) {
@@ -197,9 +224,11 @@ static bool device_is_profile_connected(pa_bluetooth_device *device, pa_bluetoot
 
 static unsigned device_count_disconnected_profiles(pa_bluetooth_device *device) {
     pa_bluetooth_profile_t profile;
+    unsigned bluetooth_profile_count;
     unsigned count = 0;
 
-    for (profile = 0; profile < PA_BLUETOOTH_PROFILE_COUNT; profile++) {
+    bluetooth_profile_count = pa_bluetooth_profile_count();
+    for (profile = 0; profile < bluetooth_profile_count; profile++) {
         if (!device_supports_profile(device, profile))
             continue;
 
@@ -222,6 +251,7 @@ static void wait_for_profiles_cb(pa_mainloop_api *api, pa_time_event* event, con
     pa_bluetooth_device *device = userdata;
     pa_strbuf *buf;
     pa_bluetooth_profile_t profile;
+    unsigned bluetooth_profile_count;
     bool first = true;
     char *profiles_str;
 
@@ -229,7 +259,8 @@ static void wait_for_profiles_cb(pa_mainloop_api *api, pa_time_event* event, con
 
     buf = pa_strbuf_new();
 
-    for (profile = 0; profile < PA_BLUETOOTH_PROFILE_COUNT; profile++) {
+    bluetooth_profile_count = pa_bluetooth_profile_count();
+    for (profile = 0; profile < bluetooth_profile_count; profile++) {
         if (device_is_profile_connected(device, profile))
             continue;
 
@@ -427,14 +458,15 @@ static void bluez5_transport_release_cb(pa_bluetooth_transport *t) {
 }
 
 bool pa_bluetooth_device_any_transport_connected(const pa_bluetooth_device *d) {
-    unsigned i;
+    unsigned i, bluetooth_profile_count;
 
     pa_assert(d);
 
     if (!d->valid)
         return false;
 
-    for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++)
+    bluetooth_profile_count = pa_bluetooth_profile_count();
+    for (i = 0; i < bluetooth_profile_count; i++)
         if (d->transports[i] && d->transports[i]->state != PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)
             return true;
 
@@ -508,6 +540,42 @@ static int parse_transport_properties(pa_bluetooth_transport *t, DBusMessageIter
     return 0;
 }
 
+static unsigned pa_a2dp_codec_id_hash_func(const void *_p) {
+    unsigned hash;
+    const pa_a2dp_codec_id *p = _p;
+
+    hash = p->codec_id;
+    hash = 31 * hash + ((p->vendor_id >>  0) & 0xFF);
+    hash = 31 * hash + ((p->vendor_id >>  8) & 0xFF);
+    hash = 31 * hash + ((p->vendor_id >> 16) & 0xFF);
+    hash = 31 * hash + ((p->vendor_id >> 24) & 0xFF);
+    hash = 31 * hash + ((p->vendor_codec_id >> 0) & 0xFF);
+    hash = 31 * hash + ((p->vendor_codec_id >> 8) & 0xFF);
+    return hash;
+}
+
+static int pa_a2dp_codec_id_compare_func(const void *_a, const void *_b) {
+    const pa_a2dp_codec_id *a = _a;
+    const pa_a2dp_codec_id *b = _b;
+
+    if (a->codec_id < b->codec_id)
+        return -1;
+    if (a->codec_id > b->codec_id)
+        return 1;
+
+    if (a->vendor_id < b->vendor_id)
+        return -1;
+    if (a->vendor_id > b->vendor_id)
+        return 1;
+
+    if (a->vendor_codec_id < b->vendor_codec_id)
+        return -1;
+    if (a->vendor_codec_id > b->vendor_codec_id)
+        return 1;
+
+    return 0;
+}
+
 static pa_bluetooth_device* device_create(pa_bluetooth_discovery *y, const char *path) {
     pa_bluetooth_device *d;
 
@@ -518,6 +586,11 @@ static pa_bluetooth_device* device_create(pa_bluetooth_discovery *y, const char
     d->discovery = y;
     d->path = pa_xstrdup(path);
     d->uuids = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, pa_xfree);
+    d->a2dp_sink_endpoints = pa_hashmap_new_full(pa_a2dp_codec_id_hash_func, pa_a2dp_codec_id_compare_func, pa_xfree, (pa_free_cb_t)pa_hashmap_free);
+    d->a2dp_source_endpoints = pa_hashmap_new_full(pa_a2dp_codec_id_hash_func, pa_a2dp_codec_id_compare_func, pa_xfree, (pa_free_cb_t)pa_hashmap_free);
+    d->a2dp_sink_codecs = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+    d->a2dp_source_codecs = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+    d->transports = pa_xnew0(pa_bluetooth_transport *, pa_bluetooth_profile_count());
 
     pa_hashmap_put(y->devices, d->path, d);
 
@@ -553,14 +626,53 @@ pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_address(pa_bluetooth_d
     return NULL;
 }
 
+static char *remote_endpoint_path_to_device_path(const char *remote_endpoint_path) {
+    char *endptr;
+
+    endptr = strrchr(remote_endpoint_path, '/');
+    if (!endptr) {
+        pa_log_error("Invalid remote endpoint %s", remote_endpoint_path);
+        return NULL;
+    }
+
+    return pa_xstrndup(remote_endpoint_path, endptr-remote_endpoint_path);
+}
+
+static void remote_endpoint_remove(pa_bluetooth_discovery *y, const char *path) {
+    pa_bluetooth_device *device;
+    pa_hashmap *endpoints;
+    char *device_path;
+    void *state;
+
+    device_path = remote_endpoint_path_to_device_path(path);
+    if (!device_path)
+        return;
+
+    device = pa_hashmap_get(y->devices, device_path);
+    if (!device)
+        pa_log_warn("Remote endpoint %s for unknown device removed %s", path, device_path);
+
+    pa_xfree(device_path);
+
+    if (!device)
+        return;
+
+    PA_HASHMAP_FOREACH(endpoints, device->a2dp_sink_endpoints, state)
+        pa_hashmap_remove_and_free(endpoints, path);
+
+    PA_HASHMAP_FOREACH(endpoints, device->a2dp_source_endpoints, state)
+        pa_hashmap_remove_and_free(endpoints, path);
+}
+
 static void device_free(pa_bluetooth_device *d) {
-    unsigned i;
+    unsigned i, bluetooth_profile_count;
 
     pa_assert(d);
 
     device_stop_waiting_for_profiles(d);
 
-    for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++) {
+    bluetooth_profile_count = pa_bluetooth_profile_count();
+    for (i = 0; i < bluetooth_profile_count; i++) {
         pa_bluetooth_transport *t;
 
         if (!(t = d->transports[i]))
@@ -569,9 +681,11 @@ static void device_free(pa_bluetooth_device *d) {
         pa_bluetooth_transport_free(t);
     }
 
-    if (d->uuids)
-        pa_hashmap_free(d->uuids);
+    pa_hashmap_free(d->uuids);
+    pa_hashmap_free(d->a2dp_sink_endpoints);
+    pa_hashmap_free(d->a2dp_source_endpoints);
 
+    pa_xfree(d->transports);
     pa_xfree(d->path);
     pa_xfree(d->alias);
     pa_xfree(d->address);
@@ -810,6 +924,125 @@ static void parse_device_properties(pa_bluetooth_device *d, DBusMessageIter *i)
     }
 }
 
+static void parse_remote_endpoint_properties(pa_bluetooth_device *d, const char *endpoint, DBusMessageIter *i) {
+    DBusMessageIter element_i;
+    pa_hashmap *codec_endpoints;
+    pa_hashmap *endpoints;
+    pa_a2dp_codec_id *a2dp_codec_id;
+    pa_a2dp_codec_capabilities *a2dp_codec_capabilities;
+    const char *uuid = NULL;
+    uint8_t codec_id = 0;
+    bool have_codec_id = false;
+    const uint8_t *capabilities = NULL;
+    int capabilities_size = 0;
+
+    dbus_message_iter_recurse(i, &element_i);
+
+    while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
+        DBusMessageIter dict_i, variant_i;
+        const char *key;
+
+        dbus_message_iter_recurse(&element_i, &dict_i);
+
+        key = check_variant_property(&dict_i);
+        if (key == NULL) {
+            pa_log_error("Received invalid property for remote endpoint %s", endpoint);
+            return;
+        }
+
+        dbus_message_iter_recurse(&dict_i, &variant_i);
+
+        if (pa_streq(key, "UUID")) {
+            if (dbus_message_iter_get_arg_type(&variant_i) != DBUS_TYPE_STRING) {
+                pa_log_warn("Remote endpoint %s property 'UUID' is not string, ignoring", endpoint);
+                return;
+            }
+
+            dbus_message_iter_get_basic(&variant_i, &uuid);
+        } else if (pa_streq(key, "Codec")) {
+            if (dbus_message_iter_get_arg_type(&variant_i) != DBUS_TYPE_BYTE) {
+                pa_log_warn("Remote endpoint %s property 'Codec' is not byte, ignoring", endpoint);
+                return;
+            }
+
+            dbus_message_iter_get_basic(&variant_i, &codec_id);
+            have_codec_id = true;
+        } else if (pa_streq(key, "Capabilities")) {
+            DBusMessageIter array;
+
+            if (dbus_message_iter_get_arg_type(&variant_i) != DBUS_TYPE_ARRAY) {
+                pa_log_warn("Remote endpoint %s property 'Capabilities' is not array, ignoring", endpoint);
+                return;
+            }
+
+            dbus_message_iter_recurse(&variant_i, &array);
+            if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_BYTE) {
+                pa_log_warn("Remote endpoint %s property 'Capabilities' is not array of bytes, ignoring", endpoint);
+                return;
+            }
+
+            dbus_message_iter_get_fixed_array(&array, &capabilities, &capabilities_size);
+        }
+
+        dbus_message_iter_next(&element_i);
+    }
+
+    if (!uuid) {
+        pa_log_warn("Remove endpoint %s does not have property 'UUID', ignoring", endpoint);
+        return;
+    }
+
+    if (!have_codec_id) {
+        pa_log_warn("Remove endpoint %s does not have property 'Codec', ignoring", endpoint);
+        return;
+    }
+
+    if (!capabilities || !capabilities_size) {
+        pa_log_warn("Remove endpoint %s does not have property 'Capabilities', ignoring", endpoint);
+        return;
+    }
+
+    if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK)) {
+        codec_endpoints = d->a2dp_source_endpoints;
+    } else if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE)) {
+        codec_endpoints = d->a2dp_sink_endpoints;
+    } else {
+        pa_log_warn("Remove endpoint %s does not have valid property 'UUID', ignoring", endpoint);
+        return;
+    }
+
+    if (capabilities_size < 0 || capabilities_size > 254) {
+        pa_log_warn("Remove endpoint %s does not have valid property 'Capabilities', ignoring", endpoint);
+        return;
+    }
+
+    a2dp_codec_id = pa_xmalloc0(sizeof(*a2dp_codec_id));
+    a2dp_codec_id->codec_id = codec_id;
+    if (codec_id == A2DP_CODEC_VENDOR) {
+        if ((size_t)capabilities_size < sizeof(a2dp_vendor_codec_t)) {
+            pa_log_warn("Remove endpoint %s does not have valid property 'Capabilities', ignoring", endpoint);
+            return;
+        }
+        a2dp_codec_id->vendor_id = A2DP_GET_VENDOR_ID(*(a2dp_vendor_codec_t *)capabilities);
+        a2dp_codec_id->vendor_codec_id = A2DP_GET_CODEC_ID(*(a2dp_vendor_codec_t *)capabilities);
+    } else {
+        a2dp_codec_id->vendor_id = 0;
+        a2dp_codec_id->vendor_codec_id = 0;
+    }
+
+    a2dp_codec_capabilities = pa_xmalloc0(sizeof(*a2dp_codec_capabilities) + capabilities_size);
+    a2dp_codec_capabilities->size = capabilities_size;
+    memcpy(a2dp_codec_capabilities->buffer, capabilities, capabilities_size);
+
+    endpoints = pa_hashmap_get(codec_endpoints, a2dp_codec_id);
+    if (!endpoints) {
+        endpoints = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree, pa_xfree);
+        pa_hashmap_put(codec_endpoints, a2dp_codec_id, endpoints);
+    }
+
+    pa_hashmap_put(endpoints, pa_xstrdup(endpoint), a2dp_codec_capabilities);
+}
+
 static void parse_adapter_properties(pa_bluetooth_adapter *a, DBusMessageIter *i, bool is_property_change) {
     DBusMessageIter element_i;
 
@@ -885,13 +1118,19 @@ finish:
     pa_xfree(endpoint);
 }
 
-static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const char *endpoint, const char *uuid) {
+static void register_endpoint(pa_bluetooth_discovery *y, const pa_a2dp_codec *a2dp_codec, const char *path, const char *endpoint, const char *uuid) {
     DBusMessage *m;
     DBusMessageIter i, d;
-    uint8_t codec = 0;
+    uint8_t capabilities[254];
+    size_t capabilities_size;
+    uint8_t codec_id;
 
     pa_log_debug("Registering %s on adapter %s", endpoint, path);
 
+    codec_id = a2dp_codec->codec_id.codec_id;
+    capabilities_size = a2dp_codec->fill_capabilities(capabilities);
+    pa_assert(capabilities_size != 0);
+
     pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, path, BLUEZ_MEDIA_INTERFACE, "RegisterEndpoint"));
 
     dbus_message_iter_init_append(m, &i);
@@ -899,23 +1138,8 @@ static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const
     dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
                                          DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &d);
     pa_dbus_append_basic_variant_dict_entry(&d, "UUID", DBUS_TYPE_STRING, &uuid);
-    pa_dbus_append_basic_variant_dict_entry(&d, "Codec", DBUS_TYPE_BYTE, &codec);
-
-    if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE) || pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK)) {
-        a2dp_sbc_t capabilities;
-
-        capabilities.channel_mode = SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL | SBC_CHANNEL_MODE_STEREO |
-                                    SBC_CHANNEL_MODE_JOINT_STEREO;
-        capabilities.frequency = SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 | SBC_SAMPLING_FREQ_44100 |
-                                 SBC_SAMPLING_FREQ_48000;
-        capabilities.allocation_method = SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS;
-        capabilities.subbands = SBC_SUBBANDS_4 | SBC_SUBBANDS_8;
-        capabilities.block_length = SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16;
-        capabilities.min_bitpool = SBC_MIN_BITPOOL;
-        capabilities.max_bitpool = SBC_MAX_BITPOOL;
-
-        pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capabilities, sizeof(capabilities));
-    }
+    pa_dbus_append_basic_variant_dict_entry(&d, "Codec", DBUS_TYPE_BYTE, &codec_id);
+    pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capabilities, capabilities_size);
 
     dbus_message_iter_close_container(&i, &d);
 
@@ -950,6 +1174,7 @@ static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessa
 
         if (pa_streq(interface, BLUEZ_ADAPTER_INTERFACE)) {
             pa_bluetooth_adapter *a;
+            unsigned a2dp_codec_i, a2dp_codec_count;
 
             if ((a = pa_hashmap_get(y->adapters, path))) {
                 pa_log_error("Found duplicated D-Bus path for adapter %s", path);
@@ -964,8 +1189,21 @@ 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);
+            /* Order is important. bluez prefers endpoints registered earlier.
+             * And codec with higher number has higher priority. So iterate in reverse order. */
+            a2dp_codec_count = pa_bluetooth_a2dp_codec_count();
+            for (a2dp_codec_i = a2dp_codec_count; a2dp_codec_i > 0; a2dp_codec_i--) {
+                const pa_a2dp_codec *a2dp_codec = pa_bluetooth_a2dp_codec_iter(a2dp_codec_i-1);
+                char *endpoint;
+
+                endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, a2dp_codec->codec_name);
+                register_endpoint(y, a2dp_codec, path, endpoint, PA_BLUETOOTH_UUID_A2DP_SINK);
+                pa_xfree(endpoint);
+
+                endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, a2dp_codec->codec_name);
+                register_endpoint(y, a2dp_codec, path, endpoint, PA_BLUETOOTH_UUID_A2DP_SOURCE);
+                pa_xfree(endpoint);
+            }
 
         } else if (pa_streq(interface, BLUEZ_DEVICE_INTERFACE)) {
 
@@ -981,6 +1219,21 @@ static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessa
 
             parse_device_properties(d, &iface_i);
 
+        } else if (pa_streq(interface, BLUEZ_MEDIA_ENDPOINT_INTERFACE)) {
+            char *device_path;
+
+            device_path = remote_endpoint_path_to_device_path(path);
+            if (!device_path)
+                return;
+            d = pa_hashmap_get(y->devices, device_path);
+            pa_xfree(device_path);
+            if (!d) {
+                pa_log_warn("Device for endpoint %s was not found", path);
+                return;
+            }
+
+            parse_remote_endpoint_properties(d, path, &iface_i);
+
         } else
             pa_log_debug("Unknown interface %s found, skipping", interface);
 
@@ -1186,6 +1439,8 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
 
             if (pa_streq(iface, BLUEZ_DEVICE_INTERFACE))
                 device_remove(y, p);
+            else if (pa_streq(iface, BLUEZ_MEDIA_ENDPOINT_INTERFACE))
+                remote_endpoint_remove(y, p);
             else if (pa_streq(iface, BLUEZ_ADAPTER_INTERFACE))
                 adapter_remove(y, p);
 
@@ -1237,6 +1492,23 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 
             parse_device_properties(d, &arg_i);
+        } else if (pa_streq(iface, BLUEZ_MEDIA_ENDPOINT_INTERFACE)) {
+            pa_bluetooth_device *d;
+            char *device_path;
+
+            pa_log_info("Properties changed in remote endpoint %s", dbus_message_get_path(m));
+
+            device_path = remote_endpoint_path_to_device_path(dbus_message_get_path(m));
+            if (!device_path)
+                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+            d = pa_hashmap_get(y->devices, device_path);
+            pa_xfree(device_path);
+            if (!d) {
+                pa_log_warn("Properties changed in unknown device");
+                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+            }
+
+            parse_remote_endpoint_properties(d, dbus_message_get_path(m), &arg_i);
         } else if (pa_streq(iface, BLUEZ_MEDIA_TRANSPORT_INTERFACE)) {
             pa_bluetooth_transport *t;
 
@@ -1257,63 +1529,110 @@ 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;
+unsigned pa_bluetooth_profile_count(void) {
+    return PA_BLUETOOTH_PROFILE_A2DP_START_INDEX + 2 * pa_bluetooth_a2dp_codec_count();
+}
+
+bool pa_bluetooth_profile_is_a2dp_source(pa_bluetooth_profile_t profile) {
+    unsigned source_start_index = PA_BLUETOOTH_PROFILE_A2DP_START_INDEX;
+    unsigned sink_start_index = PA_BLUETOOTH_PROFILE_A2DP_START_INDEX + pa_bluetooth_a2dp_codec_count();
+
+    pa_assert(profile < pa_bluetooth_profile_count());
 
-        case SBC_SAMPLING_FREQ_44100:
+    return profile >= source_start_index && profile < sink_start_index;
+}
 
-            switch (mode) {
-                case SBC_CHANNEL_MODE_MONO:
-                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
-                    return 31;
+bool pa_bluetooth_profile_is_a2dp_sink(pa_bluetooth_profile_t profile) {
+    unsigned sink_start_index = PA_BLUETOOTH_PROFILE_A2DP_START_INDEX + pa_bluetooth_a2dp_codec_count();
 
-                case SBC_CHANNEL_MODE_STEREO:
-                case SBC_CHANNEL_MODE_JOINT_STEREO:
-                    return 53;
-            }
+    pa_assert(profile < pa_bluetooth_profile_count());
 
-            pa_log_warn("Invalid channel mode %u", mode);
-            return 53;
+    return profile >= sink_start_index;
+}
 
-        case SBC_SAMPLING_FREQ_48000:
+const pa_a2dp_codec *pa_bluetooth_profile_to_a2dp_codec(pa_bluetooth_profile_t profile) {
+    unsigned source_start_index = PA_BLUETOOTH_PROFILE_A2DP_START_INDEX;
+    unsigned sink_start_index = PA_BLUETOOTH_PROFILE_A2DP_START_INDEX + pa_bluetooth_a2dp_codec_count();
 
-            switch (mode) {
-                case SBC_CHANNEL_MODE_MONO:
-                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
-                    return 29;
+    pa_assert(profile >= source_start_index && profile < pa_bluetooth_profile_count());
 
-                case SBC_CHANNEL_MODE_STEREO:
-                case SBC_CHANNEL_MODE_JOINT_STEREO:
-                    return 51;
-            }
+    if (profile < sink_start_index)
+        return pa_bluetooth_a2dp_codec_iter(profile - source_start_index);
+    else
+        return pa_bluetooth_a2dp_codec_iter(profile - sink_start_index);
+}
 
-            pa_log_warn("Invalid channel mode %u", mode);
-            return 51;
+pa_bluetooth_profile_t pa_bluetooth_profile_for_a2dp_codec(const char *codec_name, bool is_a2dp_sink) {
+    unsigned source_start_index = PA_BLUETOOTH_PROFILE_A2DP_START_INDEX;
+    unsigned sink_start_index = PA_BLUETOOTH_PROFILE_A2DP_START_INDEX + pa_bluetooth_a2dp_codec_count();
+    unsigned count = pa_bluetooth_a2dp_codec_count();
+    const pa_a2dp_codec *a2dp_codec;
+    unsigned i;
+
+    for (i = 0; i < count; i++) {
+        a2dp_codec = pa_bluetooth_a2dp_codec_iter(i);
+        if (pa_streq(a2dp_codec->codec_name, codec_name))
+            return i + (is_a2dp_sink ? sink_start_index : source_start_index);
     }
 
-    pa_log_warn("Invalid sampling freq %u", freq);
-    return 53;
+    return PA_BLUETOOTH_PROFILE_OFF;
 }
 
 const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile) {
+    static __thread char profile_string[128];
+    const pa_a2dp_codec *a2dp_codec;
+    bool is_a2dp_sink;
+
     switch(profile) {
-        case PA_BLUETOOTH_PROFILE_A2DP_SINK:
-            return "a2dp_sink";
-        case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
-            return "a2dp_source";
         case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
             return "headset_head_unit";
         case PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY:
             return "headset_audio_gateway";
         case PA_BLUETOOTH_PROFILE_OFF:
             return "off";
+        default:
+            break;
     }
 
-    return NULL;
+    a2dp_codec = pa_bluetooth_profile_to_a2dp_codec(profile);
+    is_a2dp_sink = pa_bluetooth_profile_is_a2dp_sink(profile);
+
+    /* backward compatible profile names for SBC codec */
+    if (pa_streq(a2dp_codec->codec_name, "sbc"))
+        return is_a2dp_sink ? "a2dp_sink" : "a2dp_source";
+
+    pa_snprintf(profile_string, sizeof(profile_string), "a2dp_%s_%s", is_a2dp_sink ? "sink" : "source", a2dp_codec->codec_name);
+    return profile_string;
+}
+
+static pa_bluetooth_profile_t a2dp_endpoint_to_bluetooth_profile(const char *endpoint) {
+    const char *codec_name;
+    bool is_a2dp_sink;
+
+    if (pa_startswith(endpoint, A2DP_SINK_ENDPOINT "/")) {
+        codec_name = endpoint + strlen(A2DP_SINK_ENDPOINT "/");
+        is_a2dp_sink = false;
+    } else if (pa_startswith(endpoint, A2DP_SOURCE_ENDPOINT "/")) {
+        codec_name = endpoint + strlen(A2DP_SOURCE_ENDPOINT "/");
+        is_a2dp_sink = true;
+    } else {
+        return PA_BLUETOOTH_PROFILE_OFF;
+    }
+
+    return pa_bluetooth_profile_for_a2dp_codec(codec_name, is_a2dp_sink);
+}
+
+static const pa_a2dp_codec *a2dp_endpoint_to_a2dp_codec(const char *endpoint) {
+    const char *codec_name;
+
+    if (pa_startswith(endpoint, A2DP_SINK_ENDPOINT "/"))
+        codec_name = endpoint + strlen(A2DP_SINK_ENDPOINT "/");
+    else if (pa_startswith(endpoint, A2DP_SOURCE_ENDPOINT "/"))
+        codec_name = endpoint + strlen(A2DP_SOURCE_ENDPOINT "/");
+    else
+        return NULL;
+
+    return pa_bluetooth_a2dp_codec(codec_name);
 }
 
 static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
@@ -1345,6 +1664,8 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
     if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY)
         goto fail;
 
+    endpoint_path = dbus_message_get_path(m);
+
     /* Read transport properties */
     while (dbus_message_iter_get_arg_type(&props) == DBUS_TYPE_DICT_ENTRY) {
         const char *key;
@@ -1367,16 +1688,9 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
 
             dbus_message_iter_get_basic(&value, &uuid);
 
-            endpoint_path = dbus_message_get_path(m);
-            if (pa_streq(endpoint_path, A2DP_SOURCE_ENDPOINT)) {
-                if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE))
-                    p = PA_BLUETOOTH_PROFILE_A2DP_SINK;
-            } else if (pa_streq(endpoint_path, A2DP_SINK_ENDPOINT)) {
-                if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK))
-                    p = PA_BLUETOOTH_PROFILE_A2DP_SOURCE;
-            }
-
-            if (p == PA_BLUETOOTH_PROFILE_OFF) {
+            p = a2dp_endpoint_to_bluetooth_profile(endpoint_path);
+            if ((pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE) && !pa_bluetooth_profile_is_a2dp_sink(p)) ||
+                (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK) && !pa_bluetooth_profile_is_a2dp_source(p))) {
                 pa_log_error("UUID %s of transport %s incompatible with endpoint %s", uuid, path, endpoint_path);
                 goto fail;
             }
@@ -1389,7 +1703,7 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
             dbus_message_iter_get_basic(&value, &dev_path);
         } else if (pa_streq(key, "Configuration")) {
             DBusMessageIter array;
-            a2dp_sbc_t *c;
+            const pa_a2dp_codec *a2dp_codec;
 
             if (var != DBUS_TYPE_ARRAY) {
                 pa_log_error("Property %s of wrong type %c", key, (char)var);
@@ -1404,40 +1718,12 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
             }
 
             dbus_message_iter_get_fixed_array(&array, &config, &size);
-            if (size != sizeof(a2dp_sbc_t)) {
-                pa_log_error("Configuration array of invalid size");
-                goto fail;
-            }
-
-            c = (a2dp_sbc_t *) config;
 
-            if (c->frequency != SBC_SAMPLING_FREQ_16000 && c->frequency != SBC_SAMPLING_FREQ_32000 &&
-                c->frequency != SBC_SAMPLING_FREQ_44100 && c->frequency != SBC_SAMPLING_FREQ_48000) {
-                pa_log_error("Invalid sampling frequency in configuration");
-                goto fail;
-            }
-
-            if (c->channel_mode != SBC_CHANNEL_MODE_MONO && c->channel_mode != SBC_CHANNEL_MODE_DUAL_CHANNEL &&
-                c->channel_mode != SBC_CHANNEL_MODE_STEREO && c->channel_mode != SBC_CHANNEL_MODE_JOINT_STEREO) {
-                pa_log_error("Invalid channel mode in configuration");
-                goto fail;
-            }
-
-            if (c->allocation_method != SBC_ALLOCATION_SNR && c->allocation_method != SBC_ALLOCATION_LOUDNESS) {
-                pa_log_error("Invalid allocation method in configuration");
-                goto fail;
-            }
-
-            if (c->subbands != SBC_SUBBANDS_4 && c->subbands != SBC_SUBBANDS_8) {
-                pa_log_error("Invalid SBC subbands in configuration");
-                goto fail;
-            }
+            a2dp_codec = a2dp_endpoint_to_a2dp_codec(endpoint_path);
+            pa_assert(a2dp_codec);
 
-            if (c->block_length != SBC_BLOCK_LENGTH_4 && c->block_length != SBC_BLOCK_LENGTH_8 &&
-                c->block_length != SBC_BLOCK_LENGTH_12 && c->block_length != SBC_BLOCK_LENGTH_16) {
-                pa_log_error("Invalid block length in configuration");
+            if (!a2dp_codec->validate_configuration(config, size))
                 goto fail;
-            }
         }
 
         dbus_message_iter_next(&props);
@@ -1484,21 +1770,17 @@ fail2:
 
 static DBusMessage *endpoint_select_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
     pa_bluetooth_discovery *y = userdata;
-    a2dp_sbc_t *cap, config;
-    uint8_t *pconf = (uint8_t *) &config;
-    int i, size;
+    const char *endpoint_path;
+    uint8_t *cap;
+    int size;
+    const pa_a2dp_codec *a2dp_codec;
+    uint8_t config[254];
+    uint8_t *config_ptr = config;
+    size_t config_size;
     DBusMessage *r;
     DBusError err;
 
-    static const struct {
-        uint32_t rate;
-        uint8_t cap;
-    } freq_table[] = {
-        { 16000U, SBC_SAMPLING_FREQ_16000 },
-        { 32000U, SBC_SAMPLING_FREQ_32000 },
-        { 44100U, SBC_SAMPLING_FREQ_44100 },
-        { 48000U, SBC_SAMPLING_FREQ_48000 }
-    };
+    endpoint_path = dbus_message_get_path(m);
 
     dbus_error_init(&err);
 
@@ -1508,101 +1790,14 @@ static DBusMessage *endpoint_select_configuration(DBusConnection *conn, DBusMess
         goto fail;
     }
 
-    if (size != sizeof(config)) {
-        pa_log_error("Capabilities array has invalid size");
-        goto fail;
-    }
-
-    pa_zero(config);
-
-    /* Find the lowest freq that is at least as high as the requested sampling rate */
-    for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++)
-        if (freq_table[i].rate >= y->core->default_sample_spec.rate && (cap->frequency & freq_table[i].cap)) {
-            config.frequency = freq_table[i].cap;
-            break;
-        }
-
-    if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {
-        for (--i; i >= 0; i--) {
-            if (cap->frequency & freq_table[i].cap) {
-                config.frequency = freq_table[i].cap;
-                break;
-            }
-        }
-
-        if (i < 0) {
-            pa_log_error("Not suitable sample rate");
-            goto fail;
-        }
-    }
-
-    pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table));
-
-    if (y->core->default_sample_spec.channels <= 1) {
-        if (cap->channel_mode & SBC_CHANNEL_MODE_MONO)
-            config.channel_mode = SBC_CHANNEL_MODE_MONO;
-        else if (cap->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
-            config.channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
-        else if (cap->channel_mode & SBC_CHANNEL_MODE_STEREO)
-            config.channel_mode = SBC_CHANNEL_MODE_STEREO;
-        else if (cap->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
-            config.channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
-        else {
-            pa_log_error("No supported channel modes");
-            goto fail;
-        }
-    }
+    a2dp_codec = a2dp_endpoint_to_a2dp_codec(endpoint_path);
+    pa_assert(a2dp_codec);
 
-    if (y->core->default_sample_spec.channels >= 2) {
-        if (cap->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
-            config.channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
-        else if (cap->channel_mode & SBC_CHANNEL_MODE_STEREO)
-            config.channel_mode = SBC_CHANNEL_MODE_STEREO;
-        else if (cap->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
-            config.channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
-        else if (cap->channel_mode & SBC_CHANNEL_MODE_MONO)
-            config.channel_mode = SBC_CHANNEL_MODE_MONO;
-        else {
-            pa_log_error("No supported channel modes");
-            goto fail;
-        }
-    }
-
-    if (cap->block_length & SBC_BLOCK_LENGTH_16)
-        config.block_length = SBC_BLOCK_LENGTH_16;
-    else if (cap->block_length & SBC_BLOCK_LENGTH_12)
-        config.block_length = SBC_BLOCK_LENGTH_12;
-    else if (cap->block_length & SBC_BLOCK_LENGTH_8)
-        config.block_length = SBC_BLOCK_LENGTH_8;
-    else if (cap->block_length & SBC_BLOCK_LENGTH_4)
-        config.block_length = SBC_BLOCK_LENGTH_4;
-    else {
-        pa_log_error("No supported block lengths");
-        goto fail;
-    }
-
-    if (cap->subbands & SBC_SUBBANDS_8)
-        config.subbands = SBC_SUBBANDS_8;
-    else if (cap->subbands & SBC_SUBBANDS_4)
-        config.subbands = SBC_SUBBANDS_4;
-    else {
-        pa_log_error("No supported subbands");
-        goto fail;
-    }
-
-    if (cap->allocation_method & SBC_ALLOCATION_LOUDNESS)
-        config.allocation_method = SBC_ALLOCATION_LOUDNESS;
-    else if (cap->allocation_method & SBC_ALLOCATION_SNR)
-        config.allocation_method = SBC_ALLOCATION_SNR;
-
-    config.min_bitpool = (uint8_t) PA_MAX(SBC_MIN_BITPOOL, cap->min_bitpool);
-    config.max_bitpool = (uint8_t) PA_MIN(a2dp_default_bitpool(config.frequency, config.channel_mode), cap->max_bitpool);
-
-    if (config.min_bitpool > config.max_bitpool)
-        goto fail;
+    config_size = a2dp_codec->fill_preferred_configuration(&y->core->default_sample_spec, cap, size, config);
+    pa_assert(config_size != 0);
 
     pa_assert_se(r = dbus_message_new_method_return(m));
-    pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pconf, size, DBUS_TYPE_INVALID));
+    pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &config_ptr, config_size, DBUS_TYPE_INVALID));
 
     return r;
 
@@ -1677,7 +1872,7 @@ static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, voi
 
     pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member);
 
-    if (!pa_streq(path, A2DP_SOURCE_ENDPOINT) && !pa_streq(path, A2DP_SINK_ENDPOINT))
+    if (!a2dp_endpoint_to_a2dp_codec(path))
         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 
     if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
@@ -1705,49 +1900,32 @@ static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, voi
     return DBUS_HANDLER_RESULT_HANDLED;
 }
 
-static void endpoint_init(pa_bluetooth_discovery *y, pa_bluetooth_profile_t profile) {
+static void endpoint_init(pa_bluetooth_discovery *y, const char *endpoint) {
     static const DBusObjectPathVTable vtable_endpoint = {
         .message_function = endpoint_handler,
     };
 
     pa_assert(y);
+    pa_assert(endpoint);
 
-    switch(profile) {
-        case PA_BLUETOOTH_PROFILE_A2DP_SINK:
-            pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT,
-                                                              &vtable_endpoint, y));
-            break;
-        case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
-            pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT,
-                                                              &vtable_endpoint, y));
-            break;
-        default:
-            pa_assert_not_reached();
-            break;
-    }
+    pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), endpoint,
+                                                      &vtable_endpoint, y));
 }
 
-static void endpoint_done(pa_bluetooth_discovery *y, pa_bluetooth_profile_t profile) {
+static void endpoint_done(pa_bluetooth_discovery *y, const char *endpoint) {
     pa_assert(y);
+    pa_assert(endpoint);
 
-    switch(profile) {
-        case PA_BLUETOOTH_PROFILE_A2DP_SINK:
-            dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT);
-            break;
-        case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
-            dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT);
-            break;
-        default:
-            pa_assert_not_reached();
-            break;
-    }
+    dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), endpoint);
 }
 
 pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backend) {
     pa_bluetooth_discovery *y;
     DBusError err;
     DBusConnection *conn;
-    unsigned i;
+    unsigned i, count;
+    const pa_a2dp_codec *a2dp_codec;
+    char *endpoint;
 
     y = pa_xnew0(pa_bluetooth_discovery, 1);
     PA_REFCNT_INIT(y);
@@ -1792,6 +1970,8 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backe
             "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
             ",arg0='" BLUEZ_DEVICE_INTERFACE "'",
             "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
+            ",arg0='" BLUEZ_MEDIA_ENDPOINT_INTERFACE "'",
+            "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
             ",arg0='" BLUEZ_MEDIA_TRANSPORT_INTERFACE "'",
             NULL) < 0) {
         pa_log_error("Failed to add D-Bus matches: %s", err.message);
@@ -1799,8 +1979,18 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backe
     }
     y->matches_added = true;
 
-    endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_SINK);
-    endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_SOURCE);
+    count = pa_bluetooth_a2dp_codec_count();
+    for (i = 0; i < count; i++) {
+        a2dp_codec = pa_bluetooth_a2dp_codec_iter(i);
+
+        endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, a2dp_codec->codec_name);
+        endpoint_init(y, endpoint);
+        pa_xfree(endpoint);
+
+        endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, a2dp_codec->codec_name);
+        endpoint_init(y, endpoint);
+        pa_xfree(endpoint);
+    }
 
     get_managed_objects(y);
 
@@ -1823,6 +2013,10 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y) {
 }
 
 void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
+    unsigned i, count;
+    const pa_a2dp_codec *a2dp_codec;
+    char *endpoint;
+
     pa_assert(y);
     pa_assert(PA_REFCNT_VALUE(y) > 0);
 
@@ -1862,14 +2056,26 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
                 "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',"
                 "member='PropertiesChanged',arg0='" BLUEZ_DEVICE_INTERFACE "'",
                 "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',"
+                "member='PropertiesChanged',arg0='" BLUEZ_MEDIA_ENDPOINT_INTERFACE "'",
+                "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',"
                 "member='PropertiesChanged',arg0='" BLUEZ_MEDIA_TRANSPORT_INTERFACE "'",
                 NULL);
 
         if (y->filter_added)
             dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y);
 
-        endpoint_done(y, PA_BLUETOOTH_PROFILE_A2DP_SINK);
-        endpoint_done(y, PA_BLUETOOTH_PROFILE_A2DP_SOURCE);
+        count = pa_bluetooth_a2dp_codec_count();
+        for (i = 0; i < count; i++) {
+            a2dp_codec = pa_bluetooth_a2dp_codec_iter(i);
+
+            endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, a2dp_codec->codec_name);
+            endpoint_done(y, endpoint);
+            pa_xfree(endpoint);
+
+            endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, a2dp_codec->codec_name);
+            endpoint_done(y, endpoint);
+            pa_xfree(endpoint);
+        }
 
         pa_dbus_connection_unref(y->connection);
     }
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index ad30708f0..ccc5ceb87 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -5,6 +5,7 @@
   This file is part of PulseAudio.
 
   Copyright 2008-2013 João Paulo Rechi Vita
+  Copyrigth 2018-2019 Pali Rohár <pali.rohar at gmail.com>
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as
@@ -22,6 +23,8 @@
 
 #include <pulsecore/core.h>
 
+#include "a2dp-codec-util.h"
+
 #define PA_BLUETOOTH_UUID_A2DP_SOURCE "0000110a-0000-1000-8000-00805f9b34fb"
 #define PA_BLUETOOTH_UUID_A2DP_SINK   "0000110b-0000-1000-8000-00805f9b34fb"
 
@@ -50,14 +53,14 @@ typedef enum pa_bluetooth_hook {
     PA_BLUETOOTH_HOOK_MAX
 } pa_bluetooth_hook_t;
 
-typedef enum profile {
-    PA_BLUETOOTH_PROFILE_A2DP_SINK,
-    PA_BLUETOOTH_PROFILE_A2DP_SOURCE,
-    PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT,
-    PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY,
-    PA_BLUETOOTH_PROFILE_OFF
-} pa_bluetooth_profile_t;
-#define PA_BLUETOOTH_PROFILE_COUNT PA_BLUETOOTH_PROFILE_OFF
+/* Profile index is used also for card profile priority. Higher number has higher priority.
+ * All A2DP profiles have higher priority as all non-A2DP profiles.
+ * And all A2DP sink profiles have higher priority as all A2DP source profiles. */
+#define PA_BLUETOOTH_PROFILE_OFF                    0
+#define PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY  1
+#define PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT      2
+#define PA_BLUETOOTH_PROFILE_A2DP_START_INDEX       3
+typedef unsigned pa_bluetooth_profile_t;
 
 typedef enum pa_bluetooth_transport_state {
     PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED,
@@ -111,8 +114,12 @@ struct pa_bluetooth_device {
     char *address;
     uint32_t class_of_device;
     pa_hashmap *uuids; /* char* -> char* (hashmap-as-a-set) */
+    pa_hashmap *a2dp_sink_endpoints; /* pa_a2dp_codec_id* -> pa_hashmap ( char* (remote endpoint) -> pa_a2dp_codec_capabilities* ) */
+    pa_hashmap *a2dp_source_endpoints; /* pa_a2dp_codec_id* -> pa_hashmap ( char* (remote endpoint) -> pa_a2dp_codec_capabilities* ) */
+    pa_hashmap *a2dp_sink_codecs; /* char* (codec) -> char* (remote endpoint) */
+    pa_hashmap *a2dp_source_codecs; /* char* (codec) -> char* (remote endpoint) */
 
-    pa_bluetooth_transport *transports[PA_BLUETOOTH_PROFILE_COUNT];
+    pa_bluetooth_transport **transports;
 
     pa_time_event *wait_for_profiles_timer;
 };
@@ -162,8 +169,21 @@ pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_address(pa_bluetooth_d
 
 pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook);
 
+unsigned pa_bluetooth_profile_count(void);
+bool pa_bluetooth_profile_is_a2dp_sink(pa_bluetooth_profile_t profile);
+bool pa_bluetooth_profile_is_a2dp_source(pa_bluetooth_profile_t profile);
+const pa_a2dp_codec *pa_bluetooth_profile_to_a2dp_codec(pa_bluetooth_profile_t profile);
+pa_bluetooth_profile_t pa_bluetooth_profile_for_a2dp_codec(const char *codec_name, bool is_a2dp_sink);
 const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile);
 
+static inline bool pa_bluetooth_profile_is_a2dp(pa_bluetooth_profile_t profile) {
+    return pa_bluetooth_profile_is_a2dp_sink(profile) || pa_bluetooth_profile_is_a2dp_source(profile);
+}
+
+static inline bool pa_bluetooth_profile_support_a2dp_backchannel(pa_bluetooth_profile_t profile) {
+    return pa_bluetooth_profile_to_a2dp_codec(profile)->support_backchannel;
+}
+
 static inline bool pa_bluetooth_uuid_is_hsp_hs(const char *uuid) {
     return pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_HS) || pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_HS_ALT);
 }
diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index d983efdec..adcfd4ff0 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -3,6 +3,7 @@
 
   Copyright 2008-2013 João Paulo Rechi Vita
   Copyright 2011-2013 BMW Car IT GmbH.
+  Copyright 2018-2019 Pali Rohár <pali.rohar at gmail.com>
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as
@@ -25,7 +26,6 @@
 #include <errno.h>
 
 #include <arpa/inet.h>
-#include <sbc/sbc.h>
 
 #include <pulse/rtclock.h>
 #include <pulse/timeval.h>
@@ -47,8 +47,8 @@
 #include <pulsecore/time-smoother.h>
 
 #include "a2dp-codecs.h"
+#include "a2dp-codec-util.h"
 #include "bluez5-util.h"
-#include "rtp.h"
 
 PA_MODULE_AUTHOR("João Paulo Rechi Vita");
 PA_MODULE_DESCRIPTION("BlueZ 5 Bluetooth audio sink and source");
@@ -62,8 +62,6 @@ PA_MODULE_USAGE("path=<device object path>"
 #define FIXED_LATENCY_RECORD_A2DP   (25 * PA_USEC_PER_MSEC)
 #define FIXED_LATENCY_RECORD_SCO    (25 * PA_USEC_PER_MSEC)
 
-#define BITPOOL_DEC_LIMIT 32
-#define BITPOOL_DEC_STEP 5
 #define HSP_MAX_GAIN 15
 
 static const char* const valid_modargs[] = {
@@ -94,18 +92,6 @@ typedef struct bluetooth_msg {
 PA_DEFINE_PRIVATE_CLASS(bluetooth_msg, pa_msgobject);
 #define BLUETOOTH_MSG(o) (bluetooth_msg_cast(o))
 
-typedef struct sbc_info {
-    sbc_t sbc;                           /* Codec data */
-    bool sbc_initialized;                /* Keep track if the encoder is initialized */
-    size_t codesize, frame_length;       /* SBC Codesize, frame_length. We simply cache those values here */
-    uint16_t seq_num;                    /* Cumulative packet sequence */
-    uint8_t min_bitpool;
-    uint8_t max_bitpool;
-
-    void* buffer;                        /* Codec transfer buffer */
-    size_t buffer_size;                  /* Size of the buffer */
-} sbc_info_t;
-
 struct userdata {
     pa_module *module;
     pa_core *core;
@@ -146,7 +132,12 @@ struct userdata {
     pa_smoother *read_smoother;
     pa_memchunk write_memchunk;
     pa_sample_spec sample_spec;
-    struct sbc_info sbc_info;
+    bool support_a2dp_codec_switch;
+    void *encoder_info;
+    void *decoder_info;
+
+    void* buffer;                        /* Codec transfer buffer */
+    size_t buffer_size;                  /* Size of the buffer */
 };
 
 typedef enum pa_bluetooth_form_factor {
@@ -418,105 +409,22 @@ static void a2dp_prepare_buffer(struct userdata *u) {
 
     pa_assert(u);
 
-    if (u->sbc_info.buffer_size >= min_buffer_size)
+    if (u->buffer_size >= min_buffer_size)
         return;
 
-    u->sbc_info.buffer_size = 2 * min_buffer_size;
-    pa_xfree(u->sbc_info.buffer);
-    u->sbc_info.buffer = pa_xmalloc(u->sbc_info.buffer_size);
+    u->buffer_size = 2 * min_buffer_size;
+    pa_xfree(u->buffer);
+    u->buffer = pa_xmalloc(u->buffer_size);
 }
 
 /* Run from IO thread */
-static int a2dp_process_render(struct userdata *u) {
-    struct sbc_info *sbc_info;
-    struct rtp_header *header;
-    struct rtp_payload *payload;
-    size_t nbytes;
-    void *d;
-    const void *p;
-    size_t to_write, to_encode;
-    unsigned frame_count;
+static int a2dp_write_buffer(struct userdata *u, size_t nbytes) {
     int ret = 0;
 
-    pa_assert(u);
-    pa_assert(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK);
-    pa_assert(u->sink);
-
-    /* First, render some data */
-    if (!u->write_memchunk.memblock)
-        pa_sink_render_full(u->sink, u->write_block_size, &u->write_memchunk);
-
-    pa_assert(u->write_memchunk.length == u->write_block_size);
-
-    a2dp_prepare_buffer(u);
-
-    sbc_info = &u->sbc_info;
-    header = sbc_info->buffer;
-    payload = (struct rtp_payload*) ((uint8_t*) sbc_info->buffer + sizeof(*header));
-
-    frame_count = 0;
-
-    /* Try to create a packet of the full MTU */
-
-    p = (const uint8_t *) pa_memblock_acquire_chunk(&u->write_memchunk);
-    to_encode = u->write_memchunk.length;
-
-    d = (uint8_t*) sbc_info->buffer + sizeof(*header) + sizeof(*payload);
-    to_write = sbc_info->buffer_size - sizeof(*header) - sizeof(*payload);
-
-    while (PA_LIKELY(to_encode > 0 && to_write > 0)) {
-        ssize_t written;
-        ssize_t encoded;
-
-        encoded = sbc_encode(&sbc_info->sbc,
-                             p, to_encode,
-                             d, to_write,
-                             &written);
-
-        if (PA_UNLIKELY(encoded <= 0)) {
-            pa_log_error("SBC encoding error (%li)", (long) encoded);
-            pa_memblock_release(u->write_memchunk.memblock);
-            return -1;
-        }
-
-        pa_assert_fp((size_t) encoded <= to_encode);
-        pa_assert_fp((size_t) encoded == sbc_info->codesize);
-
-        pa_assert_fp((size_t) written <= to_write);
-        pa_assert_fp((size_t) written == sbc_info->frame_length);
-
-        p = (const uint8_t*) p + encoded;
-        to_encode -= encoded;
-
-        d = (uint8_t*) d + written;
-        to_write -= written;
-
-        frame_count++;
-    }
-
-    pa_memblock_release(u->write_memchunk.memblock);
-
-    pa_assert(to_encode == 0);
-
-    PA_ONCE_BEGIN {
-        pa_log_debug("Using SBC encoder implementation: %s", pa_strnull(sbc_get_implementation_info(&sbc_info->sbc)));
-    } PA_ONCE_END;
-
-    /* write it to the fifo */
-    memset(sbc_info->buffer, 0, sizeof(*header) + sizeof(*payload));
-    header->v = 2;
-    header->pt = 1;
-    header->sequence_number = htons(sbc_info->seq_num++);
-    header->timestamp = htonl(u->write_index / pa_frame_size(&u->sample_spec));
-    header->ssrc = htonl(1);
-    payload->frame_count = frame_count;
-
-    nbytes = (uint8_t*) d - (uint8_t*) sbc_info->buffer;
-
     for (;;) {
         ssize_t l;
 
-        l = pa_write(u->stream_fd, sbc_info->buffer, nbytes, &u->stream_write_type);
+        l = pa_write(u->stream_fd, u->buffer, nbytes, &u->stream_write_type);
 
         pa_assert(l != 0);
 
@@ -560,37 +468,66 @@ static int a2dp_process_render(struct userdata *u) {
 }
 
 /* Run from IO thread */
+static int a2dp_process_render(struct userdata *u) {
+    const pa_a2dp_codec *a2dp_codec;
+    const uint8_t *ptr;
+    size_t processed;
+    size_t length;
+
+    pa_assert(u);
+    pa_assert(u->sink);
+
+    a2dp_codec = pa_bluetooth_profile_to_a2dp_codec(u->profile);
+
+    /* First, render some data */
+    if (!u->write_memchunk.memblock)
+        pa_sink_render_full(u->sink, u->write_block_size, &u->write_memchunk);
+
+    pa_assert(u->write_memchunk.length == u->write_block_size);
+
+    a2dp_prepare_buffer(u);
+
+    /* Try to create a packet of the full MTU */
+    ptr = (const uint8_t *) pa_memblock_acquire_chunk(&u->write_memchunk);
+
+    length = a2dp_codec->encode_buffer(u->encoder_info, u->write_index / pa_frame_size(&u->sample_spec), ptr, u->write_memchunk.length, u->buffer, u->buffer_size, &processed);
+
+    pa_memblock_release(u->write_memchunk.memblock);
+
+    if (length == 0)
+        return -1;
+
+    pa_assert(processed == u->write_memchunk.length);
+
+    return a2dp_write_buffer(u, length);
+}
+
+/* Run from IO thread */
 static int a2dp_process_push(struct userdata *u) {
+    const pa_a2dp_codec *a2dp_codec;
     int ret = 0;
     pa_memchunk memchunk;
 
     pa_assert(u);
-    pa_assert(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE);
     pa_assert(u->source);
     pa_assert(u->read_smoother);
 
+    a2dp_codec = pa_bluetooth_profile_to_a2dp_codec(u->profile);
+
     memchunk.memblock = pa_memblock_new(u->core->mempool, u->read_block_size);
     memchunk.index = memchunk.length = 0;
 
     for (;;) {
         bool found_tstamp = false;
         pa_usec_t tstamp;
-        struct sbc_info *sbc_info;
-        struct rtp_header *header;
-        struct rtp_payload *payload;
-        const void *p;
-        void *d;
+        uint8_t *ptr;
         ssize_t l;
-        size_t to_write, to_decode;
+        size_t processed;
         size_t total_written = 0;
 
         a2dp_prepare_buffer(u);
 
-        sbc_info = &u->sbc_info;
-        header = sbc_info->buffer;
-        payload = (struct rtp_payload*) ((uint8_t*) sbc_info->buffer + sizeof(*header));
-
-        l = pa_read(u->stream_fd, sbc_info->buffer, sbc_info->buffer_size, &u->stream_write_type);
+        l = pa_read(u->stream_fd, u->buffer, u->buffer_size, &u->stream_write_type);
 
         if (l <= 0) {
 
@@ -607,7 +544,7 @@ static int a2dp_process_push(struct userdata *u) {
             break;
         }
 
-        pa_assert((size_t) l <= sbc_info->buffer_size);
+        pa_assert((size_t) l <= u->buffer_size);
 
         /* TODO: get timestamp from rtp */
         if (!found_tstamp) {
@@ -615,50 +552,18 @@ static int a2dp_process_push(struct userdata *u) {
             tstamp = pa_rtclock_now();
         }
 
-        p = (uint8_t*) sbc_info->buffer + sizeof(*header) + sizeof(*payload);
-        to_decode = l - sizeof(*header) - sizeof(*payload);
-
-        d = pa_memblock_acquire(memchunk.memblock);
-        to_write = memchunk.length = pa_memblock_get_length(memchunk.memblock);
-
-        while (PA_LIKELY(to_decode > 0)) {
-            size_t written;
-            ssize_t decoded;
-
-            decoded = sbc_decode(&sbc_info->sbc,
-                                 p, to_decode,
-                                 d, to_write,
-                                 &written);
-
-            if (PA_UNLIKELY(decoded <= 0)) {
-                pa_log_error("SBC decoding error (%li)", (long) decoded);
-                pa_memblock_release(memchunk.memblock);
-                pa_memblock_unref(memchunk.memblock);
-                return 0;
-            }
+        ptr = pa_memblock_acquire(memchunk.memblock);
+        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 = a2dp_codec->decode_buffer(u->decoder_info, u->buffer, l, ptr, memchunk.length, &processed);
+        if (total_written == 0)
+            return 0;
 
         u->read_index += (uint64_t) total_written;
         pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec));
         pa_smoother_resume(u->read_smoother, tstamp, true);
 
-        memchunk.length -= to_write;
+        memchunk.length -= l - processed;
 
         pa_memblock_release(memchunk.memblock);
 
@@ -673,7 +578,7 @@ static int a2dp_process_push(struct userdata *u) {
     return ret;
 }
 
-static void update_buffer_size(struct userdata *u) {
+static void update_sink_buffer_size(struct userdata *u) {
     int old_bufsize;
     socklen_t len = sizeof(int);
     int ret;
@@ -705,72 +610,6 @@ static void update_buffer_size(struct userdata *u) {
     }
 }
 
-/* Run from I/O thread */
-static void a2dp_set_bitpool(struct userdata *u, uint8_t bitpool) {
-    struct sbc_info *sbc_info;
-
-    pa_assert(u);
-
-    sbc_info = &u->sbc_info;
-
-    if (sbc_info->sbc.bitpool == bitpool)
-        return;
-
-    if (bitpool > sbc_info->max_bitpool)
-        bitpool = sbc_info->max_bitpool;
-    else if (bitpool < sbc_info->min_bitpool)
-        bitpool = sbc_info->min_bitpool;
-
-    sbc_info->sbc.bitpool = bitpool;
-
-    sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc);
-    sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
-
-    pa_log_debug("Bitpool has changed to %u", sbc_info->sbc.bitpool);
-
-    u->read_block_size =
-        (u->read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
-        / sbc_info->frame_length * sbc_info->codesize;
-
-    u->write_block_size =
-        (u->write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
-        / sbc_info->frame_length * sbc_info->codesize;
-
-    pa_sink_set_max_request_within_thread(u->sink, u->write_block_size);
-    pa_sink_set_fixed_latency_within_thread(u->sink,
-            FIXED_LATENCY_PLAYBACK_A2DP + pa_bytes_to_usec(u->write_block_size, &u->sample_spec));
-
-    /* If there is still data in the memchunk, we have to discard it
-     * because the write_block_size may have changed. */
-    if (u->write_memchunk.memblock) {
-        pa_memblock_unref(u->write_memchunk.memblock);
-        pa_memchunk_reset(&u->write_memchunk);
-    }
-
-    update_buffer_size(u);
-}
-
-/* Run from I/O thread */
-static void a2dp_reduce_bitpool(struct userdata *u) {
-    struct sbc_info *sbc_info;
-    uint8_t bitpool;
-
-    pa_assert(u);
-
-    sbc_info = &u->sbc_info;
-
-    /* Check if bitpool is already at its limit */
-    if (sbc_info->sbc.bitpool <= BITPOOL_DEC_LIMIT)
-        return;
-
-    bitpool = sbc_info->sbc.bitpool - BITPOOL_DEC_STEP;
-
-    if (bitpool < BITPOOL_DEC_LIMIT)
-        bitpool = BITPOOL_DEC_LIMIT;
-
-    a2dp_set_bitpool(u, bitpool);
-}
-
 static void teardown_stream(struct userdata *u) {
     if (u->rtpoll_item) {
         pa_rtpoll_item_free(u->rtpoll_item);
@@ -845,6 +684,24 @@ static void transport_release(struct userdata *u) {
 }
 
 /* Run from I/O thread */
+static void handle_sink_block_size_change(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,
+                                            (pa_bluetooth_profile_is_a2dp_sink(u->profile) ?
+                                             FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_SCO) +
+                                            pa_bytes_to_usec(u->write_block_size, &u->sample_spec));
+
+    /* If there is still data in the memchunk, we have to discard it
+     * because the write_block_size may have changed. */
+    if (u->write_memchunk.memblock) {
+        pa_memblock_unref(u->write_memchunk.memblock);
+        pa_memchunk_reset(&u->write_memchunk);
+    }
+
+    update_sink_buffer_size(u);
+}
+
+/* Run from I/O thread */
 static void transport_config_mtu(struct userdata *u) {
     if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
         u->read_block_size = u->read_link_mtu;
@@ -860,32 +717,25 @@ static void transport_config_mtu(struct userdata *u) {
             u->write_block_size = pa_frame_align(u->write_block_size, &u->sink->sample_spec);
         }
     } else {
-        u->read_block_size =
-            (u->read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
-            / u->sbc_info.frame_length * u->sbc_info.codesize;
-
-        u->write_block_size =
-            (u->write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
-            / u->sbc_info.frame_length * u->sbc_info.codesize;
+        const pa_a2dp_codec *a2dp_codec = pa_bluetooth_profile_to_a2dp_codec(u->profile);
+        a2dp_codec->fill_blocksize(pa_bluetooth_profile_is_a2dp_sink(u->profile) ? u->encoder_info : u->decoder_info, u->read_link_mtu, u->write_link_mtu, &u->read_block_size, &u->write_block_size);
     }
 
-    if (u->sink) {
-        pa_sink_set_max_request_within_thread(u->sink, u->write_block_size);
-        pa_sink_set_fixed_latency_within_thread(u->sink,
-                                                (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK ?
-                                                 FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_SCO) +
-                                                pa_bytes_to_usec(u->write_block_size, &u->sample_spec));
-    }
+    /* exclude a2dp source with backchannel */
+    if (u->sink && !pa_bluetooth_profile_is_a2dp_source(u->profile))
+        handle_sink_block_size_change(u);
 
-    if (u->source)
+    /* exclude a2dp sink with backchannel */
+    if (u->source && !pa_bluetooth_profile_is_a2dp_sink(u->profile))
         pa_source_set_fixed_latency_within_thread(u->source,
-                                                  (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE ?
+                                                  (pa_bluetooth_profile_is_a2dp_source(u->profile) ?
                                                    FIXED_LATENCY_RECORD_A2DP : FIXED_LATENCY_RECORD_SCO) +
                                                   pa_bytes_to_usec(u->read_block_size, &u->sample_spec));
 }
 
 /* Run from I/O thread */
 static void setup_stream(struct userdata *u) {
+    const pa_a2dp_codec *a2dp_codec;
     struct pollfd *pollfd;
     int one;
 
@@ -895,6 +745,11 @@ static void setup_stream(struct userdata *u) {
 
     pa_log_info("Transport %s resuming", u->transport->path);
 
+    if (pa_bluetooth_profile_is_a2dp(u->profile)) {
+        a2dp_codec = pa_bluetooth_profile_to_a2dp_codec(u->profile);
+        a2dp_codec->reset_codec(pa_bluetooth_profile_is_a2dp_sink(u->profile) ? u->encoder_info : u->decoder_info);
+    }
+
     transport_config_mtu(u);
 
     pa_make_fd_nonblock(u->stream_fd);
@@ -906,11 +761,6 @@ 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);
-    }
-
     u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
     pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
     pollfd->fd = u->stream_fd;
@@ -1076,25 +926,20 @@ static int add_source(struct userdata *u) {
 
     connect_ports(u, &data, PA_DIRECTION_INPUT);
 
-    if (!u->transport_acquired)
-        switch (u->profile) {
-            case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
-            case PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY:
+    if (!u->transport_acquired) {
+        if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY || pa_bluetooth_profile_is_a2dp_source(u->profile)) {
+            data.suspend_cause = PA_SUSPEND_USER;
+        } else if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT) {
+            /* u->stream_fd contains the error returned by the last transport_acquire()
+             * EAGAIN means we are waiting for a NewConnection signal */
+            if (u->stream_fd == -EAGAIN)
                 data.suspend_cause = PA_SUSPEND_USER;
-                break;
-            case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
-                /* u->stream_fd contains the error returned by the last transport_acquire()
-                 * EAGAIN means we are waiting for a NewConnection signal */
-                if (u->stream_fd == -EAGAIN)
-                    data.suspend_cause = PA_SUSPEND_USER;
-                else
-                    pa_assert_not_reached();
-                break;
-            case PA_BLUETOOTH_PROFILE_A2DP_SINK:
-            case PA_BLUETOOTH_PROFILE_OFF:
+            else
                 pa_assert_not_reached();
-                break;
+        } else {
+            pa_assert_not_reached();
         }
+    }
 
     u->source = pa_source_new(u->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY);
     pa_source_new_data_done(&data);
@@ -1263,10 +1108,8 @@ static int add_sink(struct userdata *u) {
                 else
                     pa_assert_not_reached();
                 break;
-            case PA_BLUETOOTH_PROFILE_A2DP_SINK:
+            default:
                 /* Profile switch should have failed */
-            case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
-            case PA_BLUETOOTH_PROFILE_OFF:
                 pa_assert_not_reached();
                 break;
         }
@@ -1290,117 +1133,26 @@ static int add_sink(struct userdata *u) {
 }
 
 /* Run from main thread */
-static void transport_config(struct userdata *u) {
+static int transport_config(struct userdata *u) {
     if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
         u->sample_spec.format = PA_SAMPLE_S16LE;
         u->sample_spec.channels = 1;
         u->sample_spec.rate = 8000;
+        return 0;
     } else {
-        sbc_info_t *sbc_info = &u->sbc_info;
-        a2dp_sbc_t *config;
-
+        void *info;
+        const pa_a2dp_codec *a2dp_codec = pa_bluetooth_profile_to_a2dp_codec(u->profile);
+        bool is_a2dp_sink = pa_bluetooth_profile_is_a2dp_sink(u->profile);
         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);
+        info = is_a2dp_sink ? u->encoder_info : u->decoder_info;
+        if (info)
+            a2dp_codec->finish_codec(info);
+        info = a2dp_codec->init_codec(is_a2dp_sink, false, u->transport->config, u->transport->config_size, &u->sample_spec);
+        if (is_a2dp_sink)
+            u->encoder_info = info;
         else
-            sbc_init(&sbc_info->sbc, 0);
-        sbc_info->sbc_initialized = true;
-
-        switch (config->frequency) {
-            case SBC_SAMPLING_FREQ_16000:
-                sbc_info->sbc.frequency = SBC_FREQ_16000;
-                u->sample_spec.rate = 16000U;
-                break;
-            case SBC_SAMPLING_FREQ_32000:
-                sbc_info->sbc.frequency = SBC_FREQ_32000;
-                u->sample_spec.rate = 32000U;
-                break;
-            case SBC_SAMPLING_FREQ_44100:
-                sbc_info->sbc.frequency = SBC_FREQ_44100;
-                u->sample_spec.rate = 44100U;
-                break;
-            case SBC_SAMPLING_FREQ_48000:
-                sbc_info->sbc.frequency = SBC_FREQ_48000;
-                u->sample_spec.rate = 48000U;
-                break;
-            default:
-                pa_assert_not_reached();
-        }
-
-        switch (config->channel_mode) {
-            case SBC_CHANNEL_MODE_MONO:
-                sbc_info->sbc.mode = SBC_MODE_MONO;
-                u->sample_spec.channels = 1;
-                break;
-            case SBC_CHANNEL_MODE_DUAL_CHANNEL:
-                sbc_info->sbc.mode = SBC_MODE_DUAL_CHANNEL;
-                u->sample_spec.channels = 2;
-                break;
-            case SBC_CHANNEL_MODE_STEREO:
-                sbc_info->sbc.mode = SBC_MODE_STEREO;
-                u->sample_spec.channels = 2;
-                break;
-            case SBC_CHANNEL_MODE_JOINT_STEREO:
-                sbc_info->sbc.mode = SBC_MODE_JOINT_STEREO;
-                u->sample_spec.channels = 2;
-                break;
-            default:
-                pa_assert_not_reached();
-        }
-
-        switch (config->allocation_method) {
-            case SBC_ALLOCATION_SNR:
-                sbc_info->sbc.allocation = SBC_AM_SNR;
-                break;
-            case SBC_ALLOCATION_LOUDNESS:
-                sbc_info->sbc.allocation = SBC_AM_LOUDNESS;
-                break;
-            default:
-                pa_assert_not_reached();
-        }
-
-        switch (config->subbands) {
-            case SBC_SUBBANDS_4:
-                sbc_info->sbc.subbands = SBC_SB_4;
-                break;
-            case SBC_SUBBANDS_8:
-                sbc_info->sbc.subbands = SBC_SB_8;
-                break;
-            default:
-                pa_assert_not_reached();
-        }
-
-        switch (config->block_length) {
-            case SBC_BLOCK_LENGTH_4:
-                sbc_info->sbc.blocks = SBC_BLK_4;
-                break;
-            case SBC_BLOCK_LENGTH_8:
-                sbc_info->sbc.blocks = SBC_BLK_8;
-                break;
-            case SBC_BLOCK_LENGTH_12:
-                sbc_info->sbc.blocks = SBC_BLK_12;
-                break;
-            case SBC_BLOCK_LENGTH_16:
-                sbc_info->sbc.blocks = SBC_BLK_16;
-                break;
-            default:
-                pa_assert_not_reached();
-        }
-
-        sbc_info->min_bitpool = config->min_bitpool;
-        sbc_info->max_bitpool = config->max_bitpool;
-
-        /* Set minimum bitpool for source to get the maximum possible block_size */
-        sbc_info->sbc.bitpool = u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK ? sbc_info->max_bitpool : sbc_info->min_bitpool;
-        sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc);
-        sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
-
-        pa_log_info("SBC parameters: allocation=%u, subbands=%u, blocks=%u, bitpool=%u",
-                    sbc_info->sbc.allocation, sbc_info->sbc.subbands ? 8 : 4, sbc_info->sbc.blocks, sbc_info->sbc.bitpool);
+            u->decoder_info = info;
+        return info ? 0 : -1;
     }
 }
 
@@ -1415,13 +1167,18 @@ static int setup_transport(struct userdata *u) {
     /* check if profile has a transport */
     t = u->device->transports[u->profile];
     if (!t || t->state <= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) {
-        pa_log_warn("Profile %s has no transport", pa_bluetooth_profile_to_string(u->profile));
+        if (!pa_bluetooth_profile_is_a2dp(u->profile) || !u->support_a2dp_codec_switch) {
+            pa_log_warn("Profile %s has no transport", pa_bluetooth_profile_to_string(u->profile));
+            return -1;
+        }
+        /* TODO: Changing A2DP codec */
+        pa_log_error("Changing A2DP codec is not implemented yet!");
         return -1;
     }
 
     u->transport = t;
 
-    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY)
+    if (pa_bluetooth_profile_is_a2dp_source(u->profile) || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY)
         transport_acquire(u, true); /* In case of error, the sink/sources will be created suspended */
     else {
         int transport_error;
@@ -1431,22 +1188,27 @@ static int setup_transport(struct userdata *u) {
             return -1; /* We need to fail here until the interactions with module-suspend-on-idle and alike get improved */
     }
 
-    transport_config(u);
-
-    return 0;
+    return transport_config(u);
 }
 
 /* Run from main thread */
 static pa_direction_t get_profile_direction(pa_bluetooth_profile_t p) {
-    static const pa_direction_t profile_direction[] = {
-        [PA_BLUETOOTH_PROFILE_A2DP_SINK] = PA_DIRECTION_OUTPUT,
-        [PA_BLUETOOTH_PROFILE_A2DP_SOURCE] = PA_DIRECTION_INPUT,
-        [PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
-        [PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
-        [PA_BLUETOOTH_PROFILE_OFF] = 0
-    };
-
-    return profile_direction[p];
+    if (p == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || p == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY)
+        return PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT;
+    else if (p == PA_BLUETOOTH_PROFILE_OFF)
+        return 0;
+    else if (pa_bluetooth_profile_is_a2dp_sink(p)) {
+        if (pa_bluetooth_profile_support_a2dp_backchannel(p))
+            return PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT;
+        else
+            return PA_DIRECTION_OUTPUT;
+    } else if (pa_bluetooth_profile_is_a2dp_source(p)) {
+        if (pa_bluetooth_profile_support_a2dp_backchannel(p))
+            return PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT;
+        else
+            return PA_DIRECTION_INPUT;
+    } else
+        pa_assert_not_reached();
 }
 
 /* Run from main thread */
@@ -1477,7 +1239,7 @@ static int write_block(struct userdata *u) {
     if (u->write_index <= 0)
         u->started_at = pa_rtclock_now();
 
-    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) {
+    if (pa_bluetooth_profile_is_a2dp_sink(u->profile)) {
         if ((n_written = a2dp_process_render(u)) < 0)
             return -1;
     } else {
@@ -1551,7 +1313,7 @@ static void thread_func(void *userdata) {
                 if (pollfd->revents & POLLIN) {
                     int n_read;
 
-                    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE)
+                    if (pa_bluetooth_profile_is_a2dp_source(u->profile))
                         n_read = a2dp_process_push(u);
                     else
                         n_read = sco_process_push(u);
@@ -1651,8 +1413,11 @@ 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 && pa_bluetooth_profile_is_a2dp_sink(u->profile)) {
+                                const pa_a2dp_codec *a2dp_codec = pa_bluetooth_profile_to_a2dp_codec(u->profile);
+                                if (a2dp_codec->reduce_encoder_bitrate(u->encoder_info, u->write_link_mtu, &u->write_block_size))
+                                    handle_sink_block_size_change(u);
+                            }
                         }
 
                         blocks_to_write = 1;
@@ -1767,7 +1532,7 @@ static int start_thread(struct userdata *u) {
         /* If we are in the headset role or the device is an a2dp source,
          * the source should not become default unless there is no other
          * sound device available. */
-        if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY || u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE)
+        if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY || pa_bluetooth_profile_is_a2dp_source(u->profile))
             u->source->priority = 1500;
 
         pa_source_put(u->source);
@@ -1830,12 +1595,13 @@ static void stop_thread(struct userdata *u) {
 /* Run from main thread */
 static pa_available_t get_port_availability(struct userdata *u, pa_direction_t direction) {
     pa_available_t result = PA_AVAILABLE_NO;
-    unsigned i;
+    unsigned i, count;
 
     pa_assert(u);
     pa_assert(u->device);
 
-    for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++) {
+    count = pa_bluetooth_profile_count();
+    for (i = 0; i < count; i++) {
         pa_bluetooth_transport *transport;
 
         if (!(get_profile_direction(i) & direction))
@@ -1969,8 +1735,12 @@ static void create_card_ports(struct userdata *u, pa_hashmap *ports) {
 static pa_card_profile *create_card_profile(struct userdata *u, pa_bluetooth_profile_t profile, pa_hashmap *ports) {
     pa_device_port *input_port, *output_port;
     const char *name;
+    char *description;
     pa_card_profile *cp = NULL;
     pa_bluetooth_profile_t *p;
+    const pa_a2dp_codec *a2dp_codec;
+    bool is_a2dp_sink;
+    bool support_backchannel;
 
     pa_assert(u->input_port_name);
     pa_assert(u->output_port_name);
@@ -1979,34 +1749,9 @@ static pa_card_profile *create_card_profile(struct userdata *u, pa_bluetooth_pro
 
     name = pa_bluetooth_profile_to_string(profile);
 
-    switch (profile) {
-    case PA_BLUETOOTH_PROFILE_A2DP_SINK:
-        cp = pa_card_profile_new(name, _("High Fidelity Playback (A2DP Sink)"), sizeof(pa_bluetooth_profile_t));
-        cp->priority = 40;
-        cp->n_sinks = 1;
-        cp->n_sources = 0;
-        cp->max_sink_channels = 2;
-        cp->max_source_channels = 0;
-        pa_hashmap_put(output_port->profiles, cp->name, cp);
-
-        p = PA_CARD_PROFILE_DATA(cp);
-        break;
-
-    case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
-        cp = pa_card_profile_new(name, _("High Fidelity Capture (A2DP Source)"), sizeof(pa_bluetooth_profile_t));
-        cp->priority = 20;
-        cp->n_sinks = 0;
-        cp->n_sources = 1;
-        cp->max_sink_channels = 0;
-        cp->max_source_channels = 2;
-        pa_hashmap_put(input_port->profiles, cp->name, cp);
-
-        p = PA_CARD_PROFILE_DATA(cp);
-        break;
-
-    case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
+    if (profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT) {
         cp = pa_card_profile_new(name, _("Headset Head Unit (HSP/HFP)"), sizeof(pa_bluetooth_profile_t));
-        cp->priority = 30;
+        cp->priority = profile;
         cp->n_sinks = 1;
         cp->n_sources = 1;
         cp->max_sink_channels = 1;
@@ -2015,11 +1760,9 @@ static pa_card_profile *create_card_profile(struct userdata *u, pa_bluetooth_pro
         pa_hashmap_put(output_port->profiles, cp->name, cp);
 
         p = PA_CARD_PROFILE_DATA(cp);
-        break;
-
-    case PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY:
+    } else if (profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
         cp = pa_card_profile_new(name, _("Headset Audio Gateway (HSP/HFP)"), sizeof(pa_bluetooth_profile_t));
-        cp->priority = 10;
+        cp->priority = profile;
         cp->n_sinks = 1;
         cp->n_sources = 1;
         cp->max_sink_channels = 1;
@@ -2028,9 +1771,37 @@ static pa_card_profile *create_card_profile(struct userdata *u, pa_bluetooth_pro
         pa_hashmap_put(output_port->profiles, cp->name, cp);
 
         p = PA_CARD_PROFILE_DATA(cp);
-        break;
+    } else if (pa_bluetooth_profile_is_a2dp(profile)) {
+        a2dp_codec = pa_bluetooth_profile_to_a2dp_codec(profile);
+        is_a2dp_sink = pa_bluetooth_profile_is_a2dp_sink(profile);
+        support_backchannel = pa_bluetooth_profile_support_a2dp_backchannel(profile);
 
-    case PA_BLUETOOTH_PROFILE_OFF:
+        if (is_a2dp_sink)
+            description = pa_sprintf_malloc(_("High Fidelity Playback (A2DP Sink) with codec %s"), a2dp_codec->codec_description);
+        else
+            description = pa_sprintf_malloc(_("High Fidelity Capture (A2DP Source) with codec %s"), a2dp_codec->codec_description);
+
+        cp = pa_card_profile_new(name, description, sizeof(pa_bluetooth_profile_t));
+        pa_xfree(description);
+
+        cp->priority = profile;
+
+        if (is_a2dp_sink) {
+            cp->n_sinks = 1;
+            cp->n_sources = support_backchannel ? 1 : 0;
+            cp->max_sink_channels = 2;
+            cp->max_source_channels = support_backchannel ? 1 : 0;
+        } else {
+            cp->n_sinks = support_backchannel ? 1 : 0;
+            cp->n_sources = 1;
+            cp->max_sink_channels = support_backchannel ? 1 : 0;
+            cp->max_source_channels = 2;
+        }
+
+        pa_hashmap_put(output_port->profiles, cp->name, cp);
+
+        p = PA_CARD_PROFILE_DATA(cp);
+    } else {
         pa_assert_not_reached();
     }
 
@@ -2058,10 +1829,18 @@ static int set_profile_cb(pa_card *c, pa_card_profile *new_profile) {
     if (*p != PA_BLUETOOTH_PROFILE_OFF) {
         const pa_bluetooth_device *d = u->device;
 
-        if (!d->transports[*p] || d->transports[*p]->state <= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) {
+        if ((!d->transports[*p] || d->transports[*p]->state <= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) && (!pa_bluetooth_profile_is_a2dp(*p) || !u->support_a2dp_codec_switch)) {
             pa_log_warn("Refused to switch profile to %s: Not connected", new_profile->name);
             return -PA_ERR_IO;
         }
+
+        if (pa_bluetooth_profile_is_a2dp_sink(u->profile) && pa_bluetooth_profile_is_a2dp_sink(*p)) {
+            /* TODO: Changing A2DP sink codec */
+            pa_log_error("Changing A2DP sink codec is not implemented yet!");
+        } else if (pa_bluetooth_profile_is_a2dp_source(u->profile) && pa_bluetooth_profile_is_a2dp_source(*p)) {
+            /* TODO: Changing A2DP source codec */
+            pa_log_error("Changing A2DP source codec is not implemented yet!");
+        }
     }
 
     stop_thread(u);
@@ -2086,21 +1865,6 @@ off:
     return -PA_ERR_IO;
 }
 
-static int uuid_to_profile(const char *uuid, pa_bluetooth_profile_t *_r) {
-    if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK))
-        *_r = PA_BLUETOOTH_PROFILE_A2DP_SINK;
-    else if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE))
-        *_r = PA_BLUETOOTH_PROFILE_A2DP_SOURCE;
-    else if (pa_bluetooth_uuid_is_hsp_hs(uuid) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_HF))
-        *_r = PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT;
-    else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_AG) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_AG))
-        *_r = PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY;
-    else
-        return -PA_ERR_INVALID;
-
-    return 0;
-}
-
 /* Run from main thread */
 static int add_card(struct userdata *u) {
     const pa_bluetooth_device *d;
@@ -2109,6 +1873,8 @@ static int add_card(struct userdata *u) {
     pa_bluetooth_form_factor_t ff;
     pa_card_profile *cp;
     pa_bluetooth_profile_t *p;
+    bool have_a2dp_sink;
+    bool have_a2dp_source;
     const char *uuid;
     void *state;
 
@@ -2141,11 +1907,23 @@ static int add_card(struct userdata *u) {
 
     create_card_ports(u, data.ports);
 
+    have_a2dp_sink = false;
+    have_a2dp_source = false;
+
     PA_HASHMAP_FOREACH(uuid, d->uuids, state) {
         pa_bluetooth_profile_t profile;
 
-        if (uuid_to_profile(uuid, &profile) < 0)
+        if (pa_bluetooth_uuid_is_hsp_hs(uuid) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_HF))
+            profile = PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT;
+        else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_AG) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_AG))
+            profile = PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY;
+        else {
+            if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK))
+                have_a2dp_sink = true;
+            else if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE))
+                have_a2dp_source = true;
             continue;
+        }
 
         if (pa_hashmap_get(data.profiles, pa_bluetooth_profile_to_string(profile)))
             continue;
@@ -2154,6 +1932,76 @@ static int add_card(struct userdata *u) {
         pa_hashmap_put(data.profiles, cp->name, cp);
     }
 
+    if ((pa_hashmap_isempty(d->a2dp_sink_endpoints) && have_a2dp_sink) || (pa_hashmap_isempty(d->a2dp_source_endpoints) && have_a2dp_source)) {
+        /*
+         * We are running old version of bluez which does not announce supported codecs
+         * by remote device nor does not support codec switching. Create profile for
+         * every supported codec by pulseaudio and bluez or remote device will choose one.
+         * Therefore user will see all also unavilable profiles, but would not be able
+         * to switch codec.
+         */
+        unsigned i, count;
+
+        pa_log_warn("Detected old bluez version, changing A2DP codec is not possible");
+        u->support_a2dp_codec_switch = false;
+
+        count = pa_bluetooth_profile_count();
+        for (i = 0; i < count; i++) {
+            if (have_a2dp_sink && !pa_bluetooth_profile_is_a2dp_sink(i))
+                continue;
+
+            if (have_a2dp_source && !pa_bluetooth_profile_is_a2dp_source(i))
+                continue;
+
+            if (pa_hashmap_get(data.profiles, pa_bluetooth_profile_to_string(i)))
+                continue;
+
+            cp = create_card_profile(u, i, data.ports);
+            pa_hashmap_put(data.profiles, cp->name, cp);
+        }
+    } else {
+        const pa_a2dp_codec *a2dp_codec;
+        pa_bluetooth_profile_t profile;
+        pa_hashmap *endpoints;
+        const char *endpoint;
+        unsigned i, count;
+
+        u->support_a2dp_codec_switch = true;
+
+        count = pa_bluetooth_a2dp_codec_count();
+        for (i = 0; i < count; i++) {
+            a2dp_codec = pa_bluetooth_a2dp_codec_iter(i);
+
+            endpoints = pa_hashmap_get(d->a2dp_sink_endpoints, &a2dp_codec->codec_id);
+            if (endpoints) {
+                endpoint = a2dp_codec->choose_capabilities(endpoints, true);
+                if (endpoint) {
+                    profile = pa_bluetooth_profile_for_a2dp_codec(a2dp_codec->codec_name, true);
+                    if (!pa_hashmap_get(data.profiles, pa_bluetooth_profile_to_string(profile))) {
+                        cp = create_card_profile(u, profile, data.ports);
+                        pa_hashmap_put(data.profiles, cp->name, cp);
+                    }
+                    pa_log_info("Detected codec %s on sink endpoint %s", a2dp_codec->codec_name, endpoint);
+                    pa_hashmap_put(d->a2dp_sink_codecs, (void *)a2dp_codec->codec_name, (void *)endpoint);
+                }
+            }
+
+            endpoints = pa_hashmap_get(d->a2dp_source_endpoints, &a2dp_codec->codec_id);
+            if (endpoints) {
+                endpoint = a2dp_codec->choose_capabilities(endpoints, false);
+                if (endpoint) {
+                    profile = pa_bluetooth_profile_for_a2dp_codec(a2dp_codec->codec_name, false);
+                    if (!pa_hashmap_get(data.profiles, pa_bluetooth_profile_to_string(profile))) {
+                        cp = create_card_profile(u, profile, data.ports);
+                        pa_hashmap_put(data.profiles, cp->name, cp);
+                    }
+                    pa_log_info("Detected codec %s on source endpoint %s", a2dp_codec->codec_name, endpoint);
+                    pa_hashmap_put(d->a2dp_source_codecs, (void *)a2dp_codec->codec_name, (void *)endpoint);
+                }
+            }
+        }
+    }
+
     pa_assert(!pa_hashmap_isempty(data.profiles));
 
     cp = pa_card_profile_new("off", _("Off"), sizeof(pa_bluetooth_profile_t));
@@ -2472,6 +2320,7 @@ fail:
 
 void pa__done(pa_module *m) {
     struct userdata *u;
+    const pa_a2dp_codec *a2dp_codec;
 
     pa_assert(m);
 
@@ -2492,11 +2341,16 @@ void pa__done(pa_module *m) {
     if (u->transport_microphone_gain_changed_slot)
         pa_hook_slot_free(u->transport_microphone_gain_changed_slot);
 
-    if (u->sbc_info.buffer)
-        pa_xfree(u->sbc_info.buffer);
+    if (u->buffer)
+        pa_xfree(u->buffer);
 
-    if (u->sbc_info.sbc_initialized)
-        sbc_finish(&u->sbc_info.sbc);
+    if (pa_bluetooth_profile_is_a2dp(u->profile)) {
+        a2dp_codec = pa_bluetooth_profile_to_a2dp_codec(u->profile);
+        if (u->encoder_info)
+            a2dp_codec->finish_codec(u->encoder_info);
+        if (u->decoder_info)
+            a2dp_codec->finish_codec(u->decoder_info);
+    }
 
     if (u->msg)
         pa_xfree(u->msg);
-- 
2.11.0



More information about the pulseaudio-discuss mailing list