[v4,5/7] bluetooth: Modular API for A2DP codecs

Submitted by Pali Rohár on Jan. 14, 2019, 11:23 p.m.

Details

Message ID 20190114232343.3143-6-pali.rohar@gmail.com
State New
Series "New API for Bluetooth A2DP codecs"
Headers show

Commit Message

Pali Rohár Jan. 14, 2019, 11:23 p.m.
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 and support backchannel for microphone
voice. This new A2DP codec API fully supports this feature and
module-bluez5-device was extended to support microphone voice source for
codecs which declares such 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.
---
 src/Makefile.am                              |  12 +-
 src/modules/bluetooth/a2dp-codec-api.h       |  80 +++
 src/modules/bluetooth/a2dp-codec-sbc.c       | 633 ++++++++++++++++++++
 src/modules/bluetooth/a2dp-codec-util.c      |  56 ++
 src/modules/bluetooth/a2dp-codec-util.h      |  34 ++
 src/modules/bluetooth/bluez5-util.c          | 701 ++++++++++++++--------
 src/modules/bluetooth/bluez5-util.h          |  38 +-
 src/modules/bluetooth/module-bluez5-device.c | 857 +++++++++++++--------------
 8 files changed, 1696 insertions(+), 715 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

Patch hide | download patch | download mbox

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..cc678ef2b
--- /dev/null
+++ b/src/modules/bluetooth/a2dp-codec-api.h
@@ -0,0 +1,80 @@ 
+#ifndef fooa2dpcodechfoo
+#define fooa2dpcodechfoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2018-2019 Pali Rohár <pali.rohar@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 *default_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);
+
+    /* Get read block size for codec */
+    size_t (*get_read_block_size)(void *codec_info, size_t read_link_mtu);
+    /* Get write block size for codec */
+    size_t (*get_write_block_size)(void *codec_info, size_t write_link_mtu);
+
+    /* Reduce encoder bitrate for codec, returns new write block size or zero if not changed, called when socket is not accepting encoded data fast enough */
+    size_t (*reduce_encoder_bitrate)(void *codec_info, size_t write_link_mtu);
+
+    /* 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..d84ad3c9f
--- /dev/null
+++ b/src/modules/bluetooth/a2dp-codec-sbc.c
@@ -0,0 +1,633 @@ 
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2018-2019 Pali Rohár <pali.rohar@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 frequency;
+    uint8_t blocks;
+    uint8_t subbands;
+    uint8_t mode;
+    uint8_t allocation;
+    uint8_t bitpool;
+    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 *default_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 >= default_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 (default_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;
+        }
+    } 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 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 set_params(struct sbc_info *sbc_info) {
+    sbc_info->sbc.frequency = sbc_info->frequency;
+    sbc_info->sbc.blocks = sbc_info->blocks;
+    sbc_info->sbc.subbands = sbc_info->subbands;
+    sbc_info->sbc.mode = sbc_info->mode;
+    sbc_info->sbc.allocation = sbc_info->allocation;
+    sbc_info->sbc.bitpool = sbc_info->bitpool;
+
+    sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc);
+    sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
+}
+
+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->frequency = SBC_FREQ_16000;
+            sample_spec->rate = 16000U;
+            break;
+        case SBC_SAMPLING_FREQ_32000:
+            sbc_info->frequency = SBC_FREQ_32000;
+            sample_spec->rate = 32000U;
+            break;
+        case SBC_SAMPLING_FREQ_44100:
+            sbc_info->frequency = SBC_FREQ_44100;
+            sample_spec->rate = 44100U;
+            break;
+        case SBC_SAMPLING_FREQ_48000:
+            sbc_info->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->mode = SBC_MODE_MONO;
+            sample_spec->channels = 1;
+            break;
+        case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+            sbc_info->mode = SBC_MODE_DUAL_CHANNEL;
+            sample_spec->channels = 2;
+            break;
+        case SBC_CHANNEL_MODE_STEREO:
+            sbc_info->mode = SBC_MODE_STEREO;
+            sample_spec->channels = 2;
+            break;
+        case SBC_CHANNEL_MODE_JOINT_STEREO:
+            sbc_info->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->allocation = SBC_AM_SNR;
+            break;
+        case SBC_ALLOCATION_LOUDNESS:
+            sbc_info->allocation = SBC_AM_LOUDNESS;
+            break;
+        default:
+            pa_assert_not_reached();
+    }
+
+    switch (config->subbands) {
+        case SBC_SUBBANDS_4:
+            sbc_info->subbands = SBC_SB_4;
+            break;
+        case SBC_SUBBANDS_8:
+            sbc_info->subbands = SBC_SB_8;
+            break;
+        default:
+            pa_assert_not_reached();
+    }
+
+    switch (config->block_length) {
+        case SBC_BLOCK_LENGTH_4:
+            sbc_info->blocks = SBC_BLK_4;
+            break;
+        case SBC_BLOCK_LENGTH_8:
+            sbc_info->blocks = SBC_BLK_8;
+            break;
+        case SBC_BLOCK_LENGTH_12:
+            sbc_info->blocks = SBC_BLK_12;
+            break;
+        case SBC_BLOCK_LENGTH_16:
+            sbc_info->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->bitpool = for_encoding ? sbc_info->max_bitpool : sbc_info->min_bitpool;
+
+    set_params(sbc_info);
+
+    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);
+        return;
+    }
+
+    /* sbc_reinit() sets also default parameters, so reset them back */
+    set_params(sbc_info);
+
+    sbc_info->seq_num = 0;
+}
+
+static size_t get_block_size(void *codec_info, size_t link_mtu) {
+    struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
+
+    return (link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+           / sbc_info->frame_length * sbc_info->codesize;
+}
+
+static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
+    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 0;
+
+    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 0;
+
+    set_bitpool(sbc_info, bitpool);
+    return get_block_size(codec_info, write_link_mtu);
+}
+
+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,
+    .get_read_block_size = get_block_size,
+    .get_write_block_size = get_block_size,
+    .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..27128d8ae
--- /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@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 PA_ELEMENTSOF(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@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..9885ba461 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@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,15 @@  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)
+    config_size = a2dp_codec->fill_preferred_configuration(&y->core->default_sample_spec, cap, size, config);
+    if (config_size == 0)
         goto fail;
 
     pa_assert_se(r = dbus_message_new_method_return(m));
-    pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pconf, size, DBUS_TYPE_INVALID));
+    pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &config_ptr, config_size, DBUS_TYPE_INVALID));
 
     return r;
 
@@ -1677,7 +1873,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 +1901,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 +1971,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 +1980,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 +2014,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 +2057,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@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..fe4ca0568 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@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;
@@ -145,8 +131,19 @@  struct userdata {
     pa_usec_t started_at;
     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 *encoder_backchannel_info;
+    pa_sample_spec encoder_sample_spec;
+    void *encoder_buffer;                        /* Codec transfer buffer */
+    size_t encoder_buffer_size;                  /* Size of the buffer */
+
+    void *decoder_info;
+    void *decoder_backchannel_info;
+    pa_sample_spec decoder_sample_spec;
+    void *decoder_buffer;                        /* Codec transfer buffer */
+    size_t decoder_buffer_size;                  /* Size of the buffer */
 };
 
 typedef enum pa_bluetooth_form_factor {
@@ -380,7 +377,7 @@  static int sco_process_push(struct userdata *u) {
      * issues in our Bluetooth adapter. In these cases, in order to avoid
      * an assertion failure due to unaligned data, just discard the whole
      * packet */
-    if (!pa_frame_aligned(l, &u->sample_spec)) {
+    if (!pa_frame_aligned(l, &u->decoder_sample_spec)) {
         pa_log_warn("SCO packet received of unaligned size: %zu", l);
         pa_memblock_unref(memchunk.memblock);
         return -1;
@@ -403,7 +400,7 @@  static int sco_process_push(struct userdata *u) {
         tstamp = pa_rtclock_now();
     }
 
-    pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec));
+    pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->decoder_sample_spec));
     pa_smoother_resume(u->read_smoother, tstamp, true);
 
     pa_source_post(u->source, &memchunk);
@@ -413,110 +410,41 @@  static int sco_process_push(struct userdata *u) {
 }
 
 /* Run from IO thread */
-static void a2dp_prepare_buffer(struct userdata *u) {
+static void a2dp_prepare_encoder_buffer(struct userdata *u) {
     size_t min_buffer_size = PA_MAX(u->read_link_mtu, u->write_link_mtu);
 
     pa_assert(u);
 
-    if (u->sbc_info.buffer_size >= min_buffer_size)
+    if (u->encoder_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->encoder_buffer_size = 2 * min_buffer_size;
+    pa_xfree(u->encoder_buffer);
+    u->encoder_buffer = pa_xmalloc(u->encoder_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;
-    int ret = 0;
+static void a2dp_prepare_decoder_buffer(struct userdata *u) {
+    size_t min_buffer_size = PA_MAX(u->read_link_mtu, u->write_link_mtu);
 
     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;
+    if (u->decoder_buffer_size >= min_buffer_size)
+        return;
 
-    /* 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;
+    u->decoder_buffer_size = 2 * min_buffer_size;
+    pa_xfree(u->decoder_buffer);
+    u->decoder_buffer = pa_xmalloc(u->decoder_buffer_size);
+}
 
-    nbytes = (uint8_t*) d - (uint8_t*) sbc_info->buffer;
+/* Run from IO thread */
+static int a2dp_write_buffer(struct userdata *u, size_t nbytes) {
+    int ret = 0;
 
     for (;;) {
         ssize_t l;
 
-        l = pa_write(u->stream_fd, sbc_info->buffer, nbytes, &u->stream_write_type);
+        l = pa_write(u->stream_fd, u->encoder_buffer, nbytes, &u->stream_write_type);
 
         pa_assert(l != 0);
 
@@ -560,37 +488,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_encoder_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(pa_bluetooth_profile_is_a2dp_sink(u->profile) ? u->encoder_info : u->encoder_backchannel_info, u->write_index / pa_frame_size(&u->encoder_sample_spec), ptr, u->write_memchunk.length, u->encoder_buffer, u->encoder_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);
+        a2dp_prepare_decoder_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->decoder_buffer, u->decoder_buffer_size, &u->stream_write_type);
 
         if (l <= 0) {
 
@@ -607,7 +564,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->decoder_buffer_size);
 
         /* TODO: get timestamp from rtp */
         if (!found_tstamp) {
@@ -615,50 +572,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);
+        ptr = pa_memblock_acquire(memchunk.memblock);
+        memchunk.length = pa_memblock_get_length(memchunk.memblock);
 
-        while (PA_LIKELY(to_decode > 0)) {
-            size_t written;
-            ssize_t decoded;
-
-            decoded = sbc_decode(&sbc_info->sbc,
-                                 p, to_decode,
-                                 d, to_write,
-                                 &written);
-
-            if (PA_UNLIKELY(decoded <= 0)) {
-                pa_log_error("SBC decoding error (%li)", (long) decoded);
-                pa_memblock_release(memchunk.memblock);
-                pa_memblock_unref(memchunk.memblock);
-                return 0;
-            }
-
-            total_written += written;
-
-            /* Reset frame length, it can be changed due to bitpool change */
-            sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
-
-            pa_assert_fp((size_t) decoded <= to_decode);
-            pa_assert_fp((size_t) decoded == sbc_info->frame_length);
-
-            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(pa_bluetooth_profile_is_a2dp_source(u->profile) ? u->decoder_info : u->decoder_backchannel_info, u->decoder_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_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->decoder_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 +598,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 +630,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 +704,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(u->profile) ?
+                                             FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_SCO) +
+                                            pa_bytes_to_usec(u->write_block_size, &u->encoder_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 +737,31 @@  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);
+        if (pa_bluetooth_profile_is_a2dp_sink(u->profile)) {
+            u->write_block_size = a2dp_codec->get_write_block_size(u->encoder_info, u->write_link_mtu);
+            if (u->source)
+                u->read_block_size = a2dp_codec->get_read_block_size(u->decoder_backchannel_info, u->read_link_mtu);
+        } else {
+            u->read_block_size = a2dp_codec->get_read_block_size(u->encoder_info, u->read_link_mtu);
+            if (u->sink)
+                u->write_block_size = a2dp_codec->get_write_block_size(u->encoder_backchannel_info, u->write_link_mtu);
+        }
     }
 
-    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));
-    }
+    if (u->sink)
+        handle_sink_block_size_change(u);
 
     if (u->source)
         pa_source_set_fixed_latency_within_thread(u->source,
-                                                  (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE ?
+                                                  (pa_bluetooth_profile_is_a2dp(u->profile) ?
                                                    FIXED_LATENCY_RECORD_A2DP : FIXED_LATENCY_RECORD_SCO) +
-                                                  pa_bytes_to_usec(u->read_block_size, &u->sample_spec));
+                                                  pa_bytes_to_usec(u->read_block_size, &u->decoder_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 +771,19 @@  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);
+        if (pa_bluetooth_profile_is_a2dp_sink(u->profile)) {
+            a2dp_codec->reset_codec(u->encoder_info);
+            if (u->source)
+                a2dp_codec->reset_codec(u->decoder_backchannel_info);
+        } else {
+            a2dp_codec->reset_codec(u->decoder_info);
+            if (u->sink)
+                a2dp_codec->reset_codec(u->encoder_backchannel_info);
+        }
+    }
+
     transport_config_mtu(u);
 
     pa_make_fd_nonblock(u->stream_fd);
@@ -906,11 +795,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;
@@ -952,7 +836,7 @@  static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off
 
             if (u->read_smoother) {
                 wi = pa_smoother_get(u->read_smoother, pa_rtclock_now());
-                ri = pa_bytes_to_usec(u->read_index, &u->sample_spec);
+                ri = pa_bytes_to_usec(u->read_index, &u->decoder_sample_spec);
 
                 *((int64_t*) data) = u->source->thread_info.fixed_latency + wi - ri;
             } else
@@ -1045,11 +929,11 @@  static void source_set_volume_cb(pa_source *s) {
     if (volume < PA_VOLUME_NORM)
         volume++;
 
-    pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
+    pa_cvolume_set(&s->real_volume, u->decoder_sample_spec.channels, volume);
 
     /* Set soft volume when in headset role */
     if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY)
-        pa_cvolume_set(&s->soft_volume, u->sample_spec.channels, volume);
+        pa_cvolume_set(&s->soft_volume, u->decoder_sample_spec.channels, volume);
 
     /* If we are in the AG role, we send a command to the head set to change
      * the microphone gain. In the HS role, source and sink are swapped, so
@@ -1070,31 +954,26 @@  static int add_source(struct userdata *u) {
     data.name = pa_sprintf_malloc("bluez_source.%s.%s", u->device->address, pa_bluetooth_profile_to_string(u->profile));
     data.namereg_fail = false;
     pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bluetooth_profile_to_string(u->profile));
-    pa_source_new_data_set_sample_spec(&data, &u->sample_spec);
+    pa_source_new_data_set_sample_spec(&data, &u->decoder_sample_spec);
     if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT)
         pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
 
     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(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);
@@ -1128,10 +1007,10 @@  static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
 
             if (u->read_smoother) {
                 ri = pa_smoother_get(u->read_smoother, pa_rtclock_now());
-                wi = pa_bytes_to_usec(u->write_index + u->write_block_size, &u->sample_spec);
+                wi = pa_bytes_to_usec(u->write_index + u->write_block_size, &u->encoder_sample_spec);
             } else if (u->started_at) {
                 ri = pa_rtclock_now() - u->started_at;
-                wi = pa_bytes_to_usec(u->write_index, &u->sample_spec);
+                wi = pa_bytes_to_usec(u->write_index, &u->encoder_sample_spec);
             }
 
             *((int64_t*) data) = u->sink->thread_info.fixed_latency + wi - ri;
@@ -1219,11 +1098,11 @@  static void sink_set_volume_cb(pa_sink *s) {
     if (volume < PA_VOLUME_NORM)
         volume++;
 
-    pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
+    pa_cvolume_set(&s->real_volume, u->encoder_sample_spec.channels, volume);
 
     /* Set soft volume when in headset role */
     if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY)
-        pa_cvolume_set(&s->soft_volume, u->sample_spec.channels, volume);
+        pa_cvolume_set(&s->soft_volume, u->encoder_sample_spec.channels, volume);
 
     /* If we are in the AG role, we send a command to the head set to change
      * the speaker gain. In the HS role, source and sink are swapped, so
@@ -1244,7 +1123,7 @@  static int add_sink(struct userdata *u) {
     data.name = pa_sprintf_malloc("bluez_sink.%s.%s", u->device->address, pa_bluetooth_profile_to_string(u->profile));
     data.namereg_fail = false;
     pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bluetooth_profile_to_string(u->profile));
-    pa_sink_new_data_set_sample_spec(&data, &u->sample_spec);
+    pa_sink_new_data_set_sample_spec(&data, &u->encoder_sample_spec);
     if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT)
         pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
 
@@ -1263,10 +1142,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 +1167,71 @@  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;
+        u->encoder_sample_spec.format = PA_SAMPLE_S16LE;
+        u->encoder_sample_spec.channels = 1;
+        u->encoder_sample_spec.rate = 8000;
+        u->decoder_sample_spec.format = PA_SAMPLE_S16LE;
+        u->decoder_sample_spec.channels = 1;
+        u->decoder_sample_spec.rate = 8000;
+        return 0;
     } else {
-        sbc_info_t *sbc_info = &u->sbc_info;
-        a2dp_sbc_t *config;
+        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);
+        void *info;
 
         pa_assert(u->transport);
 
-        u->sample_spec.format = PA_SAMPLE_S16LE;
-        config = (a2dp_sbc_t *) u->transport->config;
-
-        if (sbc_info->sbc_initialized)
-            sbc_reinit(&sbc_info->sbc, 0);
-        else
-            sbc_init(&sbc_info->sbc, 0);
-        sbc_info->sbc_initialized = true;
-
-        switch (config->frequency) {
-            case SBC_SAMPLING_FREQ_16000:
-                sbc_info->sbc.frequency = SBC_FREQ_16000;
-                u->sample_spec.rate = 16000U;
-                break;
-            case SBC_SAMPLING_FREQ_32000:
-                sbc_info->sbc.frequency = SBC_FREQ_32000;
-                u->sample_spec.rate = 32000U;
-                break;
-            case SBC_SAMPLING_FREQ_44100:
-                sbc_info->sbc.frequency = SBC_FREQ_44100;
-                u->sample_spec.rate = 44100U;
-                break;
-            case SBC_SAMPLING_FREQ_48000:
-                sbc_info->sbc.frequency = SBC_FREQ_48000;
-                u->sample_spec.rate = 48000U;
-                break;
-            default:
-                pa_assert_not_reached();
-        }
-
-        switch (config->channel_mode) {
-            case SBC_CHANNEL_MODE_MONO:
-                sbc_info->sbc.mode = SBC_MODE_MONO;
-                u->sample_spec.channels = 1;
-                break;
-            case SBC_CHANNEL_MODE_DUAL_CHANNEL:
-                sbc_info->sbc.mode = SBC_MODE_DUAL_CHANNEL;
-                u->sample_spec.channels = 2;
-                break;
-            case SBC_CHANNEL_MODE_STEREO:
-                sbc_info->sbc.mode = SBC_MODE_STEREO;
-                u->sample_spec.channels = 2;
-                break;
-            case SBC_CHANNEL_MODE_JOINT_STEREO:
-                sbc_info->sbc.mode = SBC_MODE_JOINT_STEREO;
-                u->sample_spec.channels = 2;
-                break;
-            default:
-                pa_assert_not_reached();
+        info = is_a2dp_sink ? u->encoder_info : u->decoder_info;
+        if (info) {
+            a2dp_codec->finish_codec(info);
+            if (is_a2dp_sink)
+                u->encoder_info = NULL;
+            else
+                u->decoder_info = NULL;
         }
 
-        switch (config->allocation_method) {
-            case SBC_ALLOCATION_SNR:
-                sbc_info->sbc.allocation = SBC_AM_SNR;
-                break;
-            case SBC_ALLOCATION_LOUDNESS:
-                sbc_info->sbc.allocation = SBC_AM_LOUDNESS;
-                break;
-            default:
-                pa_assert_not_reached();
+        if (a2dp_codec->support_backchannel) {
+            info = is_a2dp_sink ? u->decoder_backchannel_info : u->encoder_backchannel_info;
+            if (info) {
+                a2dp_codec->finish_codec(info);
+                if (is_a2dp_sink)
+                    u->decoder_backchannel_info = NULL;
+                else
+                    u->encoder_backchannel_info = NULL;
+            }
         }
 
-        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();
-        }
+        info = a2dp_codec->init_codec(is_a2dp_sink, false, u->transport->config, u->transport->config_size, is_a2dp_sink ? &u->encoder_sample_spec : &u->decoder_sample_spec);
+        if (is_a2dp_sink)
+            u->encoder_info = info;
+        else
+            u->decoder_info = info;
 
-        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();
-        }
+        if (!info)
+            return -1;
 
-        sbc_info->min_bitpool = config->min_bitpool;
-        sbc_info->max_bitpool = config->max_bitpool;
+        if (a2dp_codec->support_backchannel) {
+            info = a2dp_codec->init_codec(!is_a2dp_sink, true, u->transport->config, u->transport->config_size, !is_a2dp_sink ? &u->encoder_sample_spec : &u->decoder_sample_spec);
+            if (is_a2dp_sink)
+                u->decoder_backchannel_info = info;
+            else
+                u->encoder_backchannel_info = info;
 
-        /* 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);
+            if (!info) {
+                if (is_a2dp_sink) {
+                    a2dp_codec->finish_codec(u->encoder_info);
+                    u->encoder_info = NULL;
+                } else {
+                    a2dp_codec->finish_codec(u->decoder_info);
+                    u->decoder_info = NULL;
+                }
+                return -1;
+            }
+        }
 
-        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 0;
     }
 }
 
@@ -1415,13 +1246,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 +1267,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 +1318,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(u->profile)) {
         if ((n_written = a2dp_process_render(u)) < 0)
             return -1;
     } else {
@@ -1551,7 +1392,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(u->profile))
                         n_read = a2dp_process_push(u);
                     else
                         n_read = sco_process_push(u);
@@ -1559,7 +1400,7 @@  static void thread_func(void *userdata) {
                     if (n_read < 0)
                         goto fail;
 
-                    if (n_read > 0) {
+                    if (!pa_bluetooth_profile_is_a2dp(u->profile) && n_read > 0) {
                         /* We just read something, so we are supposed to write something, too */
                         bytes_to_write += n_read;
                         blocks_to_write += bytes_to_write / u->write_block_size;
@@ -1581,7 +1422,7 @@  static void thread_func(void *userdata) {
 
                 /* If we have a source, we let the source determine the timing
                  * for the sink */
-                if (have_source) {
+                if (!pa_bluetooth_profile_is_a2dp(u->profile) && have_source) {
 
                     if (writable && blocks_to_write > 0) {
                         int result;
@@ -1613,12 +1454,12 @@  static void thread_func(void *userdata) {
 
                     if (u->started_at) {
                         time_passed = pa_rtclock_now() - u->started_at;
-                        audio_sent = pa_bytes_to_usec(u->write_index, &u->sample_spec);
+                        audio_sent = pa_bytes_to_usec(u->write_index, &u->encoder_sample_spec);
                     }
 
                     /* A new block needs to be sent. */
                     if (audio_sent <= time_passed) {
-                        size_t bytes_to_send = pa_usec_to_bytes(time_passed - audio_sent, &u->sample_spec);
+                        size_t bytes_to_send = pa_usec_to_bytes(time_passed - audio_sent, &u->encoder_sample_spec);
 
                         /* There are more than two blocks that need to be written. It seems that
                          * the socket has not been accepting data fast enough (could be due to
@@ -1631,7 +1472,7 @@  static void thread_func(void *userdata) {
                             pa_usec_t skip_usec;
 
                             skip_bytes = bytes_to_send - 2 * u->write_block_size;
-                            skip_usec = pa_bytes_to_usec(skip_bytes, &u->sample_spec);
+                            skip_usec = pa_bytes_to_usec(skip_bytes, &u->encoder_sample_spec);
 
                             pa_log_debug("Skipping %llu us (= %llu bytes) in audio stream",
                                         (unsigned long long) skip_usec,
@@ -1651,8 +1492,14 @@  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(u->profile)) {
+                                const pa_a2dp_codec *a2dp_codec = pa_bluetooth_profile_to_a2dp_codec(u->profile);
+                                size_t new_write_block_size = a2dp_codec->reduce_encoder_bitrate(pa_bluetooth_profile_is_a2dp_sink(u->profile) ? u->encoder_info : u->encoder_backchannel_info, u->write_link_mtu);
+                                if (new_write_block_size) {
+                                    u->write_block_size = new_write_block_size;
+                                    handle_sink_block_size_change(u);
+                                }
+                            }
                         }
 
                         blocks_to_write = 1;
@@ -1681,7 +1528,7 @@  static void thread_func(void *userdata) {
                         if (writable) {
                             /* There was no write pending on this iteration of the loop.
                              * Let's estimate when we need to wake up next */
-                            next_write_at = pa_bytes_to_usec(u->write_index, &u->sample_spec);
+                            next_write_at = pa_bytes_to_usec(u->write_index, &u->encoder_sample_spec);
                             sleep_for = time_passed < next_write_at ? next_write_at - time_passed : 0;
                             /* pa_log("Sleeping for %lu; time passed %lu, next write at %lu", (unsigned long) sleep_for, (unsigned long) time_passed, (unsigned long)next_write_at); */
                         } else
@@ -1767,7 +1614,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 +1677,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 +1817,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 +1831,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 +1842,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 +1853,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);
+
+        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);
 
-    case PA_BLUETOOTH_PROFILE_OFF:
+        p = PA_CARD_PROFILE_DATA(cp);
+    } else {
         pa_assert_not_reached();
     }
 
@@ -2058,10 +1911,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 +1947,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 +1955,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 +1989,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 +2014,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));
@@ -2304,7 +2234,7 @@  static pa_hook_result_t transport_speaker_gain_changed_cb(pa_bluetooth_discovery
     if (volume < PA_VOLUME_NORM)
         volume++;
 
-    pa_cvolume_set(&v, u->sample_spec.channels, volume);
+    pa_cvolume_set(&v, u->encoder_sample_spec.channels, volume);
     if (t->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT)
         pa_sink_volume_changed(u->sink, &v);
     else
@@ -2331,7 +2261,7 @@  static pa_hook_result_t transport_microphone_gain_changed_cb(pa_bluetooth_discov
     if (volume < PA_VOLUME_NORM)
         volume++;
 
-    pa_cvolume_set(&v, u->sample_spec.channels, volume);
+    pa_cvolume_set(&v, u->decoder_sample_spec.channels, volume);
 
     if (t->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT)
         pa_source_volume_changed(u->source, &v);
@@ -2472,6 +2402,7 @@  fail:
 
 void pa__done(pa_module *m) {
     struct userdata *u;
+    const pa_a2dp_codec *a2dp_codec;
 
     pa_assert(m);
 
@@ -2492,11 +2423,23 @@  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->sbc_info.sbc_initialized)
-        sbc_finish(&u->sbc_info.sbc);
+    if (u->encoder_buffer)
+        pa_xfree(u->encoder_buffer);
+
+    if (u->decoder_buffer)
+        pa_xfree(u->decoder_buffer);
+
+    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->encoder_backchannel_info)
+            a2dp_codec->finish_codec(u->encoder_backchannel_info);
+        if (u->decoder_backchannel_info)
+            a2dp_codec->finish_codec(u->decoder_backchannel_info);
+    }
 
     if (u->msg)
         pa_xfree(u->msg);

Comments

Luiz Augusto von Dentz Jan. 24, 2019, 2:03 p.m.
:

Hi Pali,

On Tue, Jan 15, 2019 at 1:24 AM Pali Rohár <pali.rohar@gmail.com> wrote:
>
> 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 and support backchannel for microphone
> voice. This new A2DP codec API fully supports this feature and
> module-bluez5-device was extended to support microphone voice source for
> codecs which declares such 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.
> ---
>  src/Makefile.am                              |  12 +-
>  src/modules/bluetooth/a2dp-codec-api.h       |  80 +++
>  src/modules/bluetooth/a2dp-codec-sbc.c       | 633 ++++++++++++++++++++
>  src/modules/bluetooth/a2dp-codec-util.c      |  56 ++
>  src/modules/bluetooth/a2dp-codec-util.h      |  34 ++
>  src/modules/bluetooth/bluez5-util.c          | 701 ++++++++++++++--------
>  src/modules/bluetooth/bluez5-util.h          |  38 +-
>  src/modules/bluetooth/module-bluez5-device.c | 857 +++++++++++++--------------
>  8 files changed, 1696 insertions(+), 715 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..cc678ef2b
> --- /dev/null
> +++ b/src/modules/bluetooth/a2dp-codec-api.h
> @@ -0,0 +1,80 @@
> +#ifndef fooa2dpcodechfoo
> +#define fooa2dpcodechfoo
> +
> +/***
> +  This file is part of PulseAudio.
> +
> +  Copyright 2018-2019 Pali Rohár <pali.rohar@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;

Can we not duplicate terms? The struct already is contain codec on its name.

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

Ditto, using name and description here should be just as informative.

> +    /* A2DP codec id */
> +    pa_a2dp_codec_id codec_id;
> +
> +    /* True if codec is bi-directional and supports backchannel */
> +    bool support_backchannel;

Id prefer using the term bidir here.

> +    /* 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 */

accept perhaps is enough here as there are nothing else to accept.

> +    const char *(*choose_capabilities)(const pa_hashmap *capabilities_hashmap, bool for_encoding);

Id perfer using the same terminology as used in D-Bus, thus select
probably fits better here.

> +    /* 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 *default_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);

init, deinit and reset.

> +    /* Get read block size for codec */
> +    size_t (*get_read_block_size)(void *codec_info, size_t read_link_mtu);
> +    /* Get write block size for codec */
> +    size_t (*get_write_block_size)(void *codec_info, size_t write_link_mtu);
> +
> +    /* Reduce encoder bitrate for codec, returns new write block size or zero if not changed, called when socket is not accepting encoded data fast enough */
> +    size_t (*reduce_encoder_bitrate)(void *codec_info, size_t write_link_mtu);

Perhaps a set_bitrate would be better instead of just calling it
reduce, but then we actually need to provide the current bitrate, the
codec can then check if it attend the current configuration and if not
adjust the parameters. This may change the latency as well but I far I
remember we do adjust it on device module but we may need to
incorporate a call to get the algorithm latency here as well.

> +    /* 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);

encode, decode?

> +} 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..d84ad3c9f
> --- /dev/null
> +++ b/src/modules/bluetooth/a2dp-codec-sbc.c
> @@ -0,0 +1,633 @@
> +/***
> +  This file is part of PulseAudio.
> +
> +  Copyright 2018-2019 Pali Rohár <pali.rohar@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 frequency;
> +    uint8_t blocks;
> +    uint8_t subbands;
> +    uint8_t mode;
> +    uint8_t allocation;
> +    uint8_t bitpool;
> +    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;

SBC_MAX_BITPOOL is 64 but we only select 53, we should probably fix this.

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

Change to return SBC_MAX_BITPOOL.

> +            }
> +
> +            pa_log_warn("Invalid channel mode %u", mode);
> +            return 53;

Ditto.

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

Ditto.

> +            }
> +
> +            pa_log_warn("Invalid channel mode %u", mode);
> +            return 51;

Ditto.

> +    }
> +
> +    pa_log_warn("Invalid sampling freq %u", freq);
> +    return 53;

Ditto.

> +}
> +
> +static uint8_t fill_preferred_configuration(const pa_sample_spec *default_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 >= default_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 (default_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;
> +        }
> +    } 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 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 set_params(struct sbc_info *sbc_info) {
> +    sbc_info->sbc.frequency = sbc_info->frequency;
> +    sbc_info->sbc.blocks = sbc_info->blocks;
> +    sbc_info->sbc.subbands = sbc_info->subbands;
> +    sbc_info->sbc.mode = sbc_info->mode;
> +    sbc_info->sbc.allocation = sbc_info->allocation;
> +    sbc_info->sbc.bitpool = sbc_info->bitpool;
> +
> +    sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc);
> +    sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
> +}
> +
> +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->frequency = SBC_FREQ_16000;
> +            sample_spec->rate = 16000U;
> +            break;
> +        case SBC_SAMPLING_FREQ_32000:
> +            sbc_info->frequency = SBC_FREQ_32000;
> +            sample_spec->rate = 32000U;
> +            break;
> +        case SBC_SAMPLING_FREQ_44100:
> +            sbc_info->frequency = SBC_FREQ_44100;
> +            sample_spec->rate = 44100U;
> +            break;
> +        case SBC_SAMPLING_FREQ_48000:
> +            sbc_info->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->mode = SBC_MODE_MONO;
> +            sample_spec->channels = 1;
> +            break;
> +        case SBC_CHANNEL_MODE_DUAL_CHANNEL:
> +            sbc_info->mode = SBC_MODE_DUAL_CHANNEL;
> +            sample_spec->channels = 2;
> +            break;
> +        case SBC_CHANNEL_MODE_STEREO:
> +            sbc_info->mode = SBC_MODE_STEREO;
> +            sample_spec->channels = 2;
> +            break;
> +        case SBC_CHANNEL_MODE_JOINT_STEREO:
> +            sbc_info->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->allocation = SBC_AM_SNR;
> +            break;
> +        case SBC_ALLOCATION_LOUDNESS:
> +            sbc_info->allocation = SBC_AM_LOUDNESS;
> +            break;
> +        default:
> +            pa_assert_not_reached();
> +    }
> +
> +    switch (config->subbands) {
> +        case SBC_SUBBANDS_4:
> +            sbc_info->subbands = SBC_SB_4;
> +            break;
> +        case SBC_SUBBANDS_8:
> +            sbc_info->subbands = SBC_SB_8;
> +            break;
> +        default:
> +            pa_assert_not_reached();
> +    }
> +
> +    switch (config->block_length) {
> +        case SBC_BLOCK_LENGTH_4:
> +            sbc_info->blocks = SBC_BLK_4;
> +            break;
> +        case SBC_BLOCK_LENGTH_8:
> +            sbc_info->blocks = SBC_BLK_8;
> +            break;
> +        case SBC_BLOCK_LENGTH_12:
> +            sbc_info->blocks = SBC_BLK_12;
> +            break;
> +        case SBC_BLOCK_LENGTH_16:
> +            sbc_info->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->bitpool = for_encoding ? sbc_info->max_bitpool : sbc_info->min_bitpool;
> +
> +    set_params(sbc_info);
> +
> +    PA_ONCE_BEGIN {
> +        pa_log_debug("Using SBC codec implementation: %s", pa_strnull(sbc_get_implementation_info(&sbc_info->sbc)));

This was printing NULL to me which I suspect is the reason why you
have pa_strnull(?), anyway it is not enough to just call sbc_init
since that doesn't call sbc_init_primitives. We could in theory change
that to be part of sbc_init instead of on the first call to
sbc_encode.

> +    } 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);
> +        return;
> +    }
> +
> +    /* sbc_reinit() sets also default parameters, so reset them back */
> +    set_params(sbc_info);
> +
> +    sbc_info->seq_num = 0;
> +}
> +
> +static size_t get_block_size(void *codec_info, size_t link_mtu) {
> +    struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
> +
> +    return (link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
> +           / sbc_info->frame_length * sbc_info->codesize;
> +}
> +
> +static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
> +    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 0;
> +
> +    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 0;
> +
> +    set_bitpool(sbc_info, bitpool);
> +    return get_block_size(codec_info, write_link_mtu);
> +}
> +
> +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 },

You can declare without the 0, 0, by default the compiler should fill
the remaining fields as 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,
> +    .get_read_block_size = get_block_size,
> +    .get_write_block_size = get_block_size,
> +    .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..27128d8ae
> --- /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@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 PA_ELEMENTSOF(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@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..9885ba461 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@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);

This sounds a bit weird, you have a hash function which creates a hash
with the codec and vendor details, but the compare function don't use
it? Perhaps it would have been better to just use the endpoint path
instead? Actually I think you must do that in order to able to address
endpoint separately as it is valid to have the same codec in multiple
endpoints but for your hash function they would be identical.

> +    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, '/');

There exists a property called Device exactly to avoid this kind of assumption.

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

You can probably rework these checks with the device once you actually
resolve the device when creating the endpoint object.

> +    PA_HASHMAP_FOREACH(endpoints, device->a2dp_sink_endpoints, state)
> +        pa_hashmap_remove_and_free(endpoints, path);

Now I totally lost, does the hash keys are path or a hash of codec id
and vendor details?

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

Just to make sure Ive checked what pa_hashmap_put do, it does call the
hash_func which in this case is pa_a2dp_codec_id_hash_func which
expects a struct pa_a2dp_codec_id * not a string, you were probably
lucky that the string contents is big enough.

> +}
> +
>  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,15 @@ 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)
> +    config_size = a2dp_codec->fill_preferred_configuration(&y->core->default_sample_spec, cap, size, config);
> +    if (config_size == 0)
>          goto fail;
>
>      pa_assert_se(r = dbus_message_new_method_return(m));
> -    pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pconf, size, DBUS_TYPE_INVALID));
> +    pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &config_ptr, config_size, DBUS_TYPE_INVALID));
>
>      return r;
>
> @@ -1677,7 +1873,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 +1901,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 +1971,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 +1980,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 +2014,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 +2057,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@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..fe4ca0568 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@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;
> @@ -145,8 +131,19 @@ struct userdata {
>      pa_usec_t started_at;
>      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 *encoder_backchannel_info;
> +    pa_sample_spec encoder_sample_spec;
> +    void *encoder_buffer;                        /* Codec transfer buffer */
> +    size_t encoder_buffer_size;                  /* Size of the buffer */
> +
> +    void *decoder_info;
> +    void *decoder_backchannel_info;
> +    pa_sample_spec decoder_sample_spec;
> +    void *decoder_buffer;                        /* Codec transfer buffer */
> +    size_t decoder_buffer_size;                  /* Size of the buffer */
>  };
>
>  typedef enum pa_bluetooth_form_factor {
> @@ -380,7 +377,7 @@ static int sco_process_push(struct userdata *u) {
>       * issues in our Bluetooth adapter. In these cases, in order to avoid
>       * an assertion failure due to unaligned data, just discard the whole
>       * packet */
> -    if (!pa_frame_aligned(l, &u->sample_spec)) {
> +    if (!pa_frame_aligned(l, &u->decoder_sample_spec)) {
>          pa_log_warn("SCO packet received of unaligned size: %zu", l);
>          pa_memblock_unref(memchunk.memblock);
>          return -1;
> @@ -403,7 +400,7 @@ static int sco_process_push(struct userdata *u) {
>          tstamp = pa_rtclock_now();
>      }
>
> -    pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec));
> +    pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->decoder_sample_spec));
>      pa_smoother_resume(u->read_smoother, tstamp, true);
>
>      pa_source_post(u->source, &memchunk);
> @@ -413,110 +410,41 @@ static int sco_process_push(struct userdata *u) {
>  }
>
>  /* Run from IO thread */
> -static void a2dp_prepare_buffer(struct userdata *u) {
> +static void a2dp_prepare_encoder_buffer(struct userdata *u) {
>      size_t min_buffer_size = PA_MAX(u->read_link_mtu, u->write_link_mtu);
>
>      pa_assert(u);
>
> -    if (u->sbc_info.buffer_size >= min_buffer_size)
> +    if (u->encoder_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->encoder_buffer_size = 2 * min_buffer_size;
> +    pa_xfree(u->encoder_buffer);
> +    u->encoder_buffer = pa_xmalloc(u->encoder_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;
> -    int ret = 0;
> +static void a2dp_prepare_decoder_buffer(struct userdata *u) {
> +    size_t min_buffer_size = PA_MAX(u->read_link_mtu, u->write_link_mtu);
>
>      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;
> +    if (u->decoder_buffer_size >= min_buffer_size)
> +        return;
>
> -    /* 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;
> +    u->decoder_buffer_size = 2 * min_buffer_size;
> +    pa_xfree(u->decoder_buffer);
> +    u->decoder_buffer = pa_xmalloc(u->decoder_buffer_size);
> +}
>
> -    nbytes = (uint8_t*) d - (uint8_t*) sbc_info->buffer;
> +/* Run from IO thread */
> +static int a2dp_write_buffer(struct userdata *u, size_t nbytes) {
> +    int ret = 0;
>
>      for (;;) {
>          ssize_t l;
>
> -        l = pa_write(u->stream_fd, sbc_info->buffer, nbytes, &u->stream_write_type);
> +        l = pa_write(u->stream_fd, u->encoder_buffer, nbytes, &u->stream_write_type);
>
>          pa_assert(l != 0);
>
> @@ -560,37 +488,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_encoder_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(pa_bluetooth_profile_is_a2dp_sink(u->profile) ? u->encoder_info : u->encoder_backchannel_info, u->write_index / pa_frame_size(&u->encoder_sample_spec), ptr, u->write_memchunk.length, u->encoder_buffer, u->encoder_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);
> +        a2dp_prepare_decoder_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->decoder_buffer, u->decoder_buffer_size, &u->stream_write_type);
>
>          if (l <= 0) {
>
> @@ -607,7 +564,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->decoder_buffer_size);
>
>          /* TODO: get timestamp from rtp */
>          if (!found_tstamp) {
> @@ -615,50 +572,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);
> +        ptr = pa_memblock_acquire(memchunk.memblock);
> +        memchunk.length = pa_memblock_get_length(memchunk.memblock);
>
> -        while (PA_LIKELY(to_decode > 0)) {
> -            size_t written;
> -            ssize_t decoded;
> -
> -            decoded = sbc_decode(&sbc_info->sbc,
> -                                 p, to_decode,
> -                                 d, to_write,
> -                                 &written);
> -
> -            if (PA_UNLIKELY(decoded <= 0)) {
> -                pa_log_error("SBC decoding error (%li)", (long) decoded);
> -                pa_memblock_release(memchunk.memblock);
> -                pa_memblock_unref(memchunk.memblock);
> -                return 0;
> -            }
> -
> -            total_written += written;
> -
> -            /* Reset frame length, it can be changed due to bitpool change */
> -            sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
> -
> -            pa_assert_fp((size_t) decoded <= to_decode);
> -            pa_assert_fp((size_t) decoded == sbc_info->frame_length);
> -
> -            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(pa_bluetooth_profile_is_a2dp_source(u->profile) ? u->decoder_info : u->decoder_backchannel_info, u->decoder_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_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->decoder_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 +598,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 +630,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 +704,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(u->profile) ?
> +                                             FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_SCO) +
> +                                            pa_bytes_to_usec(u->write_block_size, &u->encoder_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 +737,31 @@ 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);
> +        if (pa_bluetooth_profile_is_a2dp_sink(u->profile)) {
> +            u->write_block_size = a2dp_codec->get_write_block_size(u->encoder_info, u->write_link_mtu);
> +            if (u->source)
> +                u->read_block_size = a2dp_codec->get_read_block_size(u->decoder_backchannel_info, u->read_link_mtu);
> +        } else {
> +            u->read_block_size = a2dp_codec->get_read_block_size(u->encoder_info, u->read_link_mtu);
> +            if (u->sink)
> +                u->write_block_size = a2dp_codec->get_write_block_size(u->encoder_backchannel_info, u->write_link_mtu);
> +        }
>      }
>
> -    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));
> -    }
> +    if (u->sink)
> +        handle_sink_block_size_change(u);
>
>      if (u->source)
>          pa_source_set_fixed_latency_within_thread(u->source,
> -                                                  (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE ?
> +                                                  (pa_bluetooth_profile_is_a2dp(u->profile) ?
>                                                     FIXED_LATENCY_RECORD_A2DP : FIXED_LATENCY_RECORD_SCO) +
> -                                                  pa_bytes_to_usec(u->read_block_size, &u->sample_spec));
> +                                                  pa_bytes_to_usec(u->read_block_size, &u->decoder_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 +771,19 @@ 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);
> +        if (pa_bluetooth_profile_is_a2dp_sink(u->profile)) {
> +            a2dp_codec->reset_codec(u->encoder_info);
> +            if (u->source)
> +                a2dp_codec->reset_codec(u->decoder_backchannel_info);
> +        } else {
> +            a2dp_codec->reset_codec(u->decoder_info);
> +            if (u->sink)
> +                a2dp_codec->reset_codec(u->encoder_backchannel_info);
> +        }
> +    }
> +
>      transport_config_mtu(u);
>
>      pa_make_fd_nonblock(u->stream_fd);
> @@ -906,11 +795,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;
> @@ -952,7 +836,7 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off
>
>              if (u->read_smoother) {
>                  wi = pa_smoother_get(u->read_smoother, pa_rtclock_now());
> -                ri = pa_bytes_to_usec(u->read_index, &u->sample_spec);
> +                ri = pa_bytes_to_usec(u->read_index, &u->decoder_sample_spec);
>
>                  *((int64_t*) data) = u->source->thread_info.fixed_latency + wi - ri;
>              } else
> @@ -1045,11 +929,11 @@ static void source_set_volume_cb(pa_source *s) {
>      if (volume < PA_VOLUME_NORM)
>          volume++;
>
> -    pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
> +    pa_cvolume_set(&s->real_volume, u->decoder_sample_spec.channels, volume);
>
>      /* Set soft volume when in headset role */
>      if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY)
> -        pa_cvolume_set(&s->soft_volume, u->sample_spec.channels, volume);
> +        pa_cvolume_set(&s->soft_volume, u->decoder_sample_spec.channels, volume);
>
>      /* If we are in the AG role, we send a command to the head set to change
>       * the microphone gain. In the HS role, source and sink are swapped, so
> @@ -1070,31 +954,26 @@ static int add_source(struct userdata *u) {
>      data.name = pa_sprintf_malloc("bluez_source.%s.%s", u->device->address, pa_bluetooth_profile_to_string(u->profile));
>      data.namereg_fail = false;
>      pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bluetooth_profile_to_string(u->profile));
> -    pa_source_new_data_set_sample_spec(&data, &u->sample_spec);
> +    pa_source_new_data_set_sample_spec(&data, &u->decoder_sample_spec);
>      if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT)
>          pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
>
>      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(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);
> @@ -1128,10 +1007,10 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
>
>              if (u->read_smoother) {
>                  ri = pa_smoother_get(u->read_smoother, pa_rtclock_now());
> -                wi = pa_bytes_to_usec(u->write_index + u->write_block_size, &u->sample_spec);
> +                wi = pa_bytes_to_usec(u->write_index + u->write_block_size, &u->encoder_sample_spec);
>              } else if (u->started_at) {
>                  ri = pa_rtclock_now() - u->started_at;
> -                wi = pa_bytes_to_usec(u->write_index, &u->sample_spec);
> +                wi = pa_bytes_to_usec(u->write_index, &u->encoder_sample_spec);
>              }
>
>              *((int64_t*) data) = u->sink->thread_info.fixed_latency + wi - ri;
> @@ -1219,11 +1098,11 @@ static void sink_set_volume_cb(pa_sink *s) {
>      if (volume < PA_VOLUME_NORM)
>          volume++;
>
> -    pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
> +    pa_cvolume_set(&s->real_volume, u->encoder_sample_spec.channels, volume);
>
>      /* Set soft volume when in headset role */
>      if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY)
> -        pa_cvolume_set(&s->soft_volume, u->sample_spec.channels, volume);
> +        pa_cvolume_set(&s->soft_volume, u->encoder_sample_spec.channels, volume);
>
>      /* If we are in the AG role, we send a command to the head set to change
>       * the speaker gain. In the HS role, source and sink are swapped, so
> @@ -1244,7 +1123,7 @@ static int add_sink(struct userdata *u) {
>      data.name = pa_sprintf_malloc("bluez_sink.%s.%s", u->device->address, pa_bluetooth_profile_to_string(u->profile));
>      data.namereg_fail = false;
>      pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bluetooth_profile_to_string(u->profile));
> -    pa_sink_new_data_set_sample_spec(&data, &u->sample_spec);
> +    pa_sink_new_data_set_sample_spec(&data, &u->encoder_sample_spec);
>      if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT)
>          pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
>
> @@ -1263,10 +1142,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 +1167,71 @@ 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;
> +        u->encoder_sample_spec.format = PA_SAMPLE_S16LE;
> +        u->encoder_sample_spec.channels = 1;
> +        u->encoder_sample_spec.rate = 8000;
> +        u->decoder_sample_spec.format = PA_SAMPLE_S16LE;
> +        u->decoder_sample_spec.channels = 1;
> +        u->decoder_sample_spec.rate = 8000;
> +        return 0;
>      } else {
> -        sbc_info_t *sbc_info = &u->sbc_info;
> -        a2dp_sbc_t *config;
> +        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);
> +        void *info;
>
>          pa_assert(u->transport);
>
> -        u->sample_spec.format = PA_SAMPLE_S16LE;
> -        config = (a2dp_sbc_t *) u->transport->config;
> -
> -        if (sbc_info->sbc_initialized)
> -            sbc_reinit(&sbc_info->sbc, 0);
> -        else
> -            sbc_init(&sbc_info->sbc, 0);
> -        sbc_info->sbc_initialized = true;
> -
> -        switch (config->frequency) {
> -            case SBC_SAMPLING_FREQ_16000:
> -                sbc_info->sbc.frequency = SBC_FREQ_16000;
> -                u->sample_spec.rate = 16000U;
> -                break;
> -            case SBC_SAMPLING_FREQ_32000:
> -                sbc_info->sbc.frequency = SBC_FREQ_32000;
> -                u->sample_spec.rate = 32000U;
> -                break;
> -            case SBC_SAMPLING_FREQ_44100:
> -                sbc_info->sbc.frequency = SBC_FREQ_44100;
> -                u->sample_spec.rate = 44100U;
> -                break;
> -            case SBC_SAMPLING_FREQ_48000:
> -                sbc_info->sbc.frequency = SBC_FREQ_48000;
> -                u->sample_spec.rate = 48000U;
> -                break;
> -            default:
> -                pa_assert_not_reached();
> -        }
> -
> -        switch (config->channel_mode) {
> -            case SBC_CHANNEL_MODE_MONO:
> -                sbc_info->sbc.mode = SBC_MODE_MONO;
> -                u->sample_spec.channels = 1;
> -                break;
> -            case SBC_CHANNEL_MODE_DUAL_CHANNEL:
> -                sbc_info->sbc.mode = SBC_MODE_DUAL_CHANNEL;
> -                u->sample_spec.channels = 2;
> -                break;
> -            case SBC_CHANNEL_MODE_STEREO:
> -                sbc_info->sbc.mode = SBC_MODE_STEREO;
> -                u->sample_spec.channels = 2;
> -                break;
> -            case SBC_CHANNEL_MODE_JOINT_STEREO:
> -                sbc_info->sbc.mode = SBC_MODE_JOINT_STEREO;
> -                u->sample_spec.channels = 2;
> -                break;
> -            default:
> -                pa_assert_not_reached();
> +        info = is_a2dp_sink ? u->encoder_info : u->decoder_info;
> +        if (info) {
> +            a2dp_codec->finish_codec(info);
> +            if (is_a2dp_sink)
> +                u->encoder_info = NULL;
> +            else
> +                u->decoder_info = NULL;
>          }
>
> -        switch (config->allocation_method) {
> -            case SBC_ALLOCATION_SNR:
> -                sbc_info->sbc.allocation = SBC_AM_SNR;
> -                break;
> -            case SBC_ALLOCATION_LOUDNESS:
> -                sbc_info->sbc.allocation = SBC_AM_LOUDNESS;
> -                break;
> -            default:
> -                pa_assert_not_reached();
> +        if (a2dp_codec->support_backchannel) {
> +            info = is_a2dp_sink ? u->decoder_backchannel_info : u->encoder_backchannel_info;
> +            if (info) {
> +                a2dp_codec->finish_codec(info);
> +                if (is_a2dp_sink)
> +                    u->decoder_backchannel_info = NULL;
> +                else
> +                    u->encoder_backchannel_info = NULL;
> +            }
>          }
>
> -        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();
> -        }
> +        info = a2dp_codec->init_codec(is_a2dp_sink, false, u->transport->config, u->transport->config_size, is_a2dp_sink ? &u->encoder_sample_spec : &u->decoder_sample_spec);
> +        if (is_a2dp_sink)
> +            u->encoder_info = info;
> +        else
> +            u->decoder_info = info;
>
> -        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();
> -        }
> +        if (!info)
> +            return -1;
>
> -        sbc_info->min_bitpool = config->min_bitpool;
> -        sbc_info->max_bitpool = config->max_bitpool;
> +        if (a2dp_codec->support_backchannel) {
> +            info = a2dp_codec->init_codec(!is_a2dp_sink, true, u->transport->config, u->transport->config_size, !is_a2dp_sink ? &u->encoder_sample_spec : &u->decoder_sample_spec);
> +            if (is_a2dp_sink)
> +                u->decoder_backchannel_info = info;
> +            else
> +                u->encoder_backchannel_info = info;
>
> -        /* 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);
> +            if (!info) {
> +                if (is_a2dp_sink) {
> +                    a2dp_codec->finish_codec(u->encoder_info);
> +                    u->encoder_info = NULL;
> +                } else {
> +                    a2dp_codec->finish_codec(u->decoder_info);
> +                    u->decoder_info = NULL;
> +                }
> +                return -1;
> +            }
> +        }
>
> -        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 0;
>      }
>  }
>
> @@ -1415,13 +1246,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 +1267,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 +1318,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(u->profile)) {
>          if ((n_written = a2dp_process_render(u)) < 0)
>              return -1;
>      } else {
> @@ -1551,7 +1392,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(u->profile))
>                          n_read = a2dp_process_push(u);
>                      else
>                          n_read = sco_process_push(u);
> @@ -1559,7 +1400,7 @@ static void thread_func(void *userdata) {
>                      if (n_read < 0)
>                          goto fail;
>
> -                    if (n_read > 0) {
> +                    if (!pa_bluetooth_profile_is_a2dp(u->profile) && n_read > 0) {
>                          /* We just read something, so we are supposed to write something, too */
>                          bytes_to_write += n_read;
>                          blocks_to_write += bytes_to_write / u->write_block_size;
> @@ -1581,7 +1422,7 @@ static void thread_func(void *userdata) {
>
>                  /* If we have a source, we let the source determine the timing
>                   * for the sink */
> -                if (have_source) {
> +                if (!pa_bluetooth_profile_is_a2dp(u->profile) && have_source) {
>
>                      if (writable && blocks_to_write > 0) {
>                          int result;
> @@ -1613,12 +1454,12 @@ static void thread_func(void *userdata) {
>
>                      if (u->started_at) {
>                          time_passed = pa_rtclock_now() - u->started_at;
> -                        audio_sent = pa_bytes_to_usec(u->write_index, &u->sample_spec);
> +                        audio_sent = pa_bytes_to_usec(u->write_index, &u->encoder_sample_spec);
>                      }
>
>                      /* A new block needs to be sent. */
>                      if (audio_sent <= time_passed) {
> -                        size_t bytes_to_send = pa_usec_to_bytes(time_passed - audio_sent, &u->sample_spec);
> +                        size_t bytes_to_send = pa_usec_to_bytes(time_passed - audio_sent, &u->encoder_sample_spec);
>
>                          /* There are more than two blocks that need to be written. It seems that
>                           * the socket has not been accepting data fast enough (could be due to
> @@ -1631,7 +1472,7 @@ static void thread_func(void *userdata) {
>                              pa_usec_t skip_usec;
>
>                              skip_bytes = bytes_to_send - 2 * u->write_block_size;
> -                            skip_usec = pa_bytes_to_usec(skip_bytes, &u->sample_spec);
> +                            skip_usec = pa_bytes_to_usec(skip_bytes, &u->encoder_sample_spec);
>
>                              pa_log_debug("Skipping %llu us (= %llu bytes) in audio stream",
>                                          (unsigned long long) skip_usec,
> @@ -1651,8 +1492,14 @@ 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(u->profile)) {
> +                                const pa_a2dp_codec *a2dp_codec = pa_bluetooth_profile_to_a2dp_codec(u->profile);
> +                                size_t new_write_block_size = a2dp_codec->reduce_encoder_bitrate(pa_bluetooth_profile_is_a2dp_sink(u->profile) ? u->encoder_info : u->encoder_backchannel_info, u->write_link_mtu);
> +                                if (new_write_block_size) {
> +                                    u->write_block_size = new_write_block_size;
> +                                    handle_sink_block_size_change(u);
> +                                }
> +                            }
>                          }
>
>                          blocks_to_write = 1;
> @@ -1681,7 +1528,7 @@ static void thread_func(void *userdata) {
>                          if (writable) {
>                              /* There was no write pending on this iteration of the loop.
>                               * Let's estimate when we need to wake up next */
> -                            next_write_at = pa_bytes_to_usec(u->write_index, &u->sample_spec);
> +                            next_write_at = pa_bytes_to_usec(u->write_index, &u->encoder_sample_spec);
>                              sleep_for = time_passed < next_write_at ? next_write_at - time_passed : 0;
>                              /* pa_log("Sleeping for %lu; time passed %lu, next write at %lu", (unsigned long) sleep_for, (unsigned long) time_passed, (unsigned long)next_write_at); */
>                          } else
> @@ -1767,7 +1614,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 +1677,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 +1817,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 +1831,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 +1842,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 +1853,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);
> +
> +        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);
>
> -    case PA_BLUETOOTH_PROFILE_OFF:
> +        p = PA_CARD_PROFILE_DATA(cp);
> +    } else {
>          pa_assert_not_reached();
>      }
>
> @@ -2058,10 +1911,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 +1947,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 +1955,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 +1989,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 +2014,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));
> @@ -2304,7 +2234,7 @@ static pa_hook_result_t transport_speaker_gain_changed_cb(pa_bluetooth_discovery
>      if (volume < PA_VOLUME_NORM)
>          volume++;
>
> -    pa_cvolume_set(&v, u->sample_spec.channels, volume);
> +    pa_cvolume_set(&v, u->encoder_sample_spec.channels, volume);
>      if (t->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT)
>          pa_sink_volume_changed(u->sink, &v);
>      else
> @@ -2331,7 +2261,7 @@ static pa_hook_result_t transport_microphone_gain_changed_cb(pa_bluetooth_discov
>      if (volume < PA_VOLUME_NORM)
>          volume++;
>
> -    pa_cvolume_set(&v, u->sample_spec.channels, volume);
> +    pa_cvolume_set(&v, u->decoder_sample_spec.channels, volume);
>
>      if (t->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT)
>          pa_source_volume_changed(u->source, &v);
> @@ -2472,6 +2402,7 @@ fail:
>
>  void pa__done(pa_module *m) {
>      struct userdata *u;
> +    const pa_a2dp_codec *a2dp_codec;
>
>      pa_assert(m);
>
> @@ -2492,11 +2423,23 @@ 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->sbc_info.sbc_initialized)
> -        sbc_finish(&u->sbc_info.sbc);
> +    if (u->encoder_buffer)
> +        pa_xfree(u->encoder_buffer);
> +
> +    if (u->decoder_buffer)
> +        pa_xfree(u->decoder_buffer);
> +
> +    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->encoder_backchannel_info)
> +            a2dp_codec->finish_codec(u->encoder_backchannel_info);
> +        if (u->decoder_backchannel_info)
> +            a2dp_codec->finish_codec(u->decoder_backchannel_info);
> +    }
>
>      if (u->msg)
>          pa_xfree(u->msg);
> --
> 2.11.0
>
Pali Rohár Jan. 24, 2019, 4 p.m.
Hi!

On Thursday 24 January 2019 16:03:17 Luiz Augusto von Dentz wrote:
> On Tue, Jan 15, 2019 at 1:24 AM Pali Rohár <pali.rohar@gmail.com> wrote:
> > +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;
> 
> Can we not duplicate terms? The struct already is contain codec on its name.
> 
> > +    uint32_t vendor_id;
> > +    uint16_t vendor_codec_id;
> > +} pa_a2dp_codec_id;

So which names do you suggest? For identification of A2DP codec we need
3 values: A2DP codec id, A2DP vendor id, A2DP codec id of specific to
vendor id.

I thought that (codec_id, vendor_id, vendor_codec_id) is the best what
can be invented.

> > +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;
> 
> Ditto, using name and description here should be just as informative.

So you want to have just "name" and "description" without "codec_"
prefix?

> > +    /* A2DP codec id */
> > +    pa_a2dp_codec_id codec_id;
> > +
> > +    /* True if codec is bi-directional and supports backchannel */
> > +    bool support_backchannel;
> 
> Id prefer using the term bidir here.

"bidir" does not sounds like something obvious nor widely used. And in
these cases I rather used more descriptive names.

> > +    /* 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 */
> 
> accept perhaps is enough here as there are nothing else to accept.

Better name could be "can_accept_capabilites". As it really does not
accept.

> > +    const char *(*choose_capabilities)(const pa_hashmap *capabilities_hashmap, bool for_encoding);
> 
> Id perfer using the same terminology as used in D-Bus, thus select
> probably fits better here.

I thought that names "fill_capabilities" or "validate_configuration"
better describe what functions are doing.

> > +    /* 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 *default_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);
> 
> init, deinit and reset.
> 
> > +    /* Get read block size for codec */
> > +    size_t (*get_read_block_size)(void *codec_info, size_t read_link_mtu);
> > +    /* Get write block size for codec */
> > +    size_t (*get_write_block_size)(void *codec_info, size_t write_link_mtu);
> > +
> > +    /* Reduce encoder bitrate for codec, returns new write block size or zero if not changed, called when socket is not accepting encoded data fast enough */
> > +    size_t (*reduce_encoder_bitrate)(void *codec_info, size_t write_link_mtu);
> 
> Perhaps a set_bitrate would be better instead of just calling it
> reduce,

Usage of name "reduce" was suggested by Tanu in V2.

> but then we actually need to provide the current bitrate, the
> codec can then check if it attend the current configuration and if not
> adjust the parameters. This may change the latency as well but I far I
> remember we do adjust it on device module but we may need to
> incorporate a call to get the algorithm latency here as well.
> 
> > +    /* 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);
> 
> encode, decode?
> 
> > +} 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..d84ad3c9f
> > --- /dev/null
> > +++ b/src/modules/bluetooth/a2dp-codec-sbc.c
> > +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;
> 
> SBC_MAX_BITPOOL is 64 but we only select 53, we should probably fix this.

This code is already in pulseaudio tree. I just moved functions to new
file name.

So I would rather not change existing codec parameters in patch which
just introduce new API.

Improvements to SBC codec can be done later after this patch series.

> > +    return sizeof(*capabilities);
> > +}
...
> > +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;
> 
> Change to return SBC_MAX_BITPOOL.
> 
> > +            }
> > +
> > +            pa_log_warn("Invalid channel mode %u", mode);
> > +            return 53;
> 
> Ditto.
> 
> > +        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;
> 
> Ditto.
> 
> > +            }
> > +
> > +            pa_log_warn("Invalid channel mode %u", mode);
> > +            return 51;
> 
> Ditto.
> 
> > +    }
> > +
> > +    pa_log_warn("Invalid sampling freq %u", freq);
> > +    return 53;
> 
> Ditto.

Same there. This is just moving function from one file to another. I
would not expect that moving code changes also logic of parameters in
codec.

> > +}
...
> > +    set_params(sbc_info);
> > +
> > +    PA_ONCE_BEGIN {
> > +        pa_log_debug("Using SBC codec implementation: %s", pa_strnull(sbc_get_implementation_info(&sbc_info->sbc)));
> 
> This was printing NULL to me which I suspect is the reason why you
> have pa_strnull(?), anyway it is not enough to just call sbc_init
> since that doesn't call sbc_init_primitives. We could in theory change
> that to be part of sbc_init instead of on the first call to
> sbc_encode.

Also this code block is just moving from one file to another.

I do not know why pa_strnull was there before.

> > +    } 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;
> > +}
...
> > +const pa_a2dp_codec pa_a2dp_codec_sbc = {
> > +    .codec_name = "sbc",
> > +    .codec_description = "SBC",
> > +    .codec_id = { A2DP_CODEC_SBC, 0, 0 },
> 
> You can declare without the 0, 0, by default the compiler should fill
> the remaining fields as 0.

I just wanted to be explicit. But I can remove them.

> > +    .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,
> > +    .get_read_block_size = get_block_size,
> > +    .get_write_block_size = get_block_size,
> > +    .reduce_encoder_bitrate = reduce_encoder_bitrate,
> > +    .encode_buffer = encode_buffer,
> > +    .decode_buffer = decode_buffer,
> > +};
...
> > diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
> > index 48b147eed..9885ba461 100644
> > --- a/src/modules/bluetooth/bluez5-util.c
> > +++ b/src/modules/bluetooth/bluez5-util.c
> > @@ -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);
> 
> This sounds a bit weird, you have a hash function which creates a hash
> with the codec and vendor details, but the compare function don't use
> it?

Why you think it does not use it? Look few lines above. Both hash and
compare function use codec and vendor information.

> Perhaps it would have been better to just use the endpoint path
> instead?

I cannot. Endpoint path does not say for which codec it belongs. This is
reason why hash table which has key of codec/vendor information is
needed.

> Actually I think you must do that in order to able to address
> endpoint separately as it is valid to have the same codec in multiple
> endpoints but for your hash function they would be identical.

This is used on second level of structures.

Seems you have not caught complete structure. Look into header file
where is comment for these hash tables. It is two level hash table.

Basically it is hash table with function:

  (codec_id, vendor_id, codec_vendor_id) -> hash_table_2

Where hash_table_2 is with function:

  endpoint_path -> codec_capabilities

This allows me to find all endpoints which belongs to specific A2DP
codec. And then for each endpoint I have A2DP codec capabilities.

When user want to switch to new A2DP profile, I get information which
register pulseaudio codec should be used. From it I receive A2DP codec
information (codec_id, vendor_id, codec_vendor_id) and identify all
endpoints which are compatible. After that I ask pulseuadio codec to
choose from second level hash table which endpoint to use, based on
capabilities (as second level is hash table endpoint --> capability).
Pulseaudio codec tells me endpoint and then I instruct bluez to switch
to that endpoint.

> > +    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, '/');
> 
> There exists a property called Device exactly to avoid this kind of assumption.

Ok, I will look at it.

> > +    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;
> 
> You can probably rework these checks with the device once you actually
> resolve the device when creating the endpoint object.
> 
> > +    PA_HASHMAP_FOREACH(endpoints, device->a2dp_sink_endpoints, state)
> > +        pa_hashmap_remove_and_free(endpoints, path);
> 
> Now I totally lost, does the hash keys are path or a hash of codec id
> and vendor details?

Look information above. Keys in first level hash table are codec/vendor
details and keys in second level hash table are paths.

> > +    PA_HASHMAP_FOREACH(endpoints, device->a2dp_source_endpoints, state)
> > +        pa_hashmap_remove_and_free(endpoints, path);
> > +}
> > +
...
> > +    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);
> 
> Just to make sure Ive checked what pa_hashmap_put do, it does call the
> hash_func which in this case is pa_a2dp_codec_id_hash_func

No. It is pa_idxset_string_hash_func function. See 4 lines above.

> which
> expects a struct pa_a2dp_codec_id * not a string, you were probably
> lucky that the string contents is big enough.
> 
> > +}
Luiz Augusto von Dentz Jan. 24, 2019, 4:54 p.m.
Hi Pali,

On Thu, Jan 24, 2019 at 6:00 PM Pali Rohár <pali.rohar@gmail.com> wrote:
>
> Hi!
>
> On Thursday 24 January 2019 16:03:17 Luiz Augusto von Dentz wrote:
> > On Tue, Jan 15, 2019 at 1:24 AM Pali Rohár <pali.rohar@gmail.com> wrote:
> > > +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;
> >
> > Can we not duplicate terms? The struct already is contain codec on its name.
> >
> > > +    uint32_t vendor_id;
> > > +    uint16_t vendor_codec_id;
> > > +} pa_a2dp_codec_id;
>
> So which names do you suggest? For identification of A2DP codec we need
> 3 values: A2DP codec id, A2DP vendor id, A2DP codec id of specific to
> vendor id.
>
> I thought that (codec_id, vendor_id, vendor_codec_id) is the best what
> can be invented.

You could actually just use vendor, codec, if the vendor is ff then
just use the codec, either way having to construct something like
codec_id->codec_id seems pretty long for no reason, codec_id->id is
not as bad.

> > > +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;
> >
> > Ditto, using name and description here should be just as informative.
>
> So you want to have just "name" and "description" without "codec_"
> prefix?

Yep, or you thin codec->codec_name is not a bad pattern?

> > > +    /* A2DP codec id */
> > > +    pa_a2dp_codec_id codec_id;
> > > +
> > > +    /* True if codec is bi-directional and supports backchannel */
> > > +    bool support_backchannel;
> >
> > Id prefer using the term bidir here.
>
> "bidir" does not sounds like something obvious nor widely used. And in
> these cases I rather used more descriptive names.

Well other codecs may not call it a backchannel, but if you insist at
least drop the support term, this should be obvious for this type of
flag.

> > > +    /* 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 */
> >
> > accept perhaps is enough here as there are nothing else to accept.
>
> Better name could be "can_accept_capabilites". As it really does not
> accept.

You really like long names, Im more for short and consistent name
specially for structs.

> > > +    const char *(*choose_capabilities)(const pa_hashmap *capabilities_hashmap, bool for_encoding);
> >
> > Id perfer using the same terminology as used in D-Bus, thus select
> > probably fits better here.
>
> I thought that names "fill_capabilities" or "validate_configuration"
> better describe what functions are doing.

These are used locally only, anyway there are so many duplicates
terms, like sample_spec, buffer, not sure what is the matter with
that.

> > > +    /* 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 *default_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);
> >
> > init, deinit and reset.
> >
> > > +    /* Get read block size for codec */
> > > +    size_t (*get_read_block_size)(void *codec_info, size_t read_link_mtu);
> > > +    /* Get write block size for codec */
> > > +    size_t (*get_write_block_size)(void *codec_info, size_t write_link_mtu);
> > > +
> > > +    /* Reduce encoder bitrate for codec, returns new write block size or zero if not changed, called when socket is not accepting encoded data fast enough */
> > > +    size_t (*reduce_encoder_bitrate)(void *codec_info, size_t write_link_mtu);
> >
> > Perhaps a set_bitrate would be better instead of just calling it
> > reduce,
>
> Usage of name "reduce" was suggested by Tanu in V2.
>
> > but then we actually need to provide the current bitrate, the
> > codec can then check if it attend the current configuration and if not
> > adjust the parameters. This may change the latency as well but I far I
> > remember we do adjust it on device module but we may need to
> > incorporate a call to get the algorithm latency here as well.
> >
> > > +    /* 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);
> >
> > encode, decode?
> >
> > > +} 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..d84ad3c9f
> > > --- /dev/null
> > > +++ b/src/modules/bluetooth/a2dp-codec-sbc.c
> > > +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;
> >
> > SBC_MAX_BITPOOL is 64 but we only select 53, we should probably fix this.
>
> This code is already in pulseaudio tree. I just moved functions to new
> file name.
>
> So I would rather not change existing codec parameters in patch which
> just introduce new API.
>
> Improvements to SBC codec can be done later after this patch series.

Sure thing, I can send a patch for that as well was just pointing out
it is inconsistent regardless where it came from.

> > > +    return sizeof(*capabilities);
> > > +}
> ...
> > > +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;
> >
> > Change to return SBC_MAX_BITPOOL.
> >
> > > +            }
> > > +
> > > +            pa_log_warn("Invalid channel mode %u", mode);
> > > +            return 53;
> >
> > Ditto.
> >
> > > +        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;
> >
> > Ditto.
> >
> > > +            }
> > > +
> > > +            pa_log_warn("Invalid channel mode %u", mode);
> > > +            return 51;
> >
> > Ditto.
> >
> > > +    }
> > > +
> > > +    pa_log_warn("Invalid sampling freq %u", freq);
> > > +    return 53;
> >
> > Ditto.
>
> Same there. This is just moving function from one file to another. I
> would not expect that moving code changes also logic of parameters in
> codec.
>
> > > +}
> ...
> > > +    set_params(sbc_info);
> > > +
> > > +    PA_ONCE_BEGIN {
> > > +        pa_log_debug("Using SBC codec implementation: %s", pa_strnull(sbc_get_implementation_info(&sbc_info->sbc)));
> >
> > This was printing NULL to me which I suspect is the reason why you
> > have pa_strnull(?), anyway it is not enough to just call sbc_init
> > since that doesn't call sbc_init_primitives. We could in theory change
> > that to be part of sbc_init instead of on the first call to
> > sbc_encode.
>
> Also this code block is just moving from one file to another.
>
> I do not know why pa_strnull was there before.

Seems this has been broken for a while.

> > > +    } 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;
> > > +}
> ...
> > > +const pa_a2dp_codec pa_a2dp_codec_sbc = {
> > > +    .codec_name = "sbc",
> > > +    .codec_description = "SBC",
> > > +    .codec_id = { A2DP_CODEC_SBC, 0, 0 },
> >
> > You can declare without the 0, 0, by default the compiler should fill
> > the remaining fields as 0.
>
> I just wanted to be explicit. But I can remove them.
>
> > > +    .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,
> > > +    .get_read_block_size = get_block_size,
> > > +    .get_write_block_size = get_block_size,
> > > +    .reduce_encoder_bitrate = reduce_encoder_bitrate,
> > > +    .encode_buffer = encode_buffer,
> > > +    .decode_buffer = decode_buffer,
> > > +};
> ...
> > > diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
> > > index 48b147eed..9885ba461 100644
> > > --- a/src/modules/bluetooth/bluez5-util.c
> > > +++ b/src/modules/bluetooth/bluez5-util.c
> > > @@ -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);
> >
> > This sounds a bit weird, you have a hash function which creates a hash
> > with the codec and vendor details, but the compare function don't use
> > it?
>
> Why you think it does not use it? Look few lines above. Both hash and
> compare function use codec and vendor information.
>
> > Perhaps it would have been better to just use the endpoint path
> > instead?
>
> I cannot. Endpoint path does not say for which codec it belongs. This is
> reason why hash table which has key of codec/vendor information is
> needed.

Not sure what do mean that you cannot, the element data do contain
that information perhaps that is just pa_hashmap, anyway it now makes
sense it just much more complex to do this way since you also have to
do lookups when and endpoint is removed, actually many lookups one for
each codec and it doesn't even stop when it find the endpoint, you
should probably fix that.

> > Actually I think you must do that in order to able to address
> > endpoint separately as it is valid to have the same codec in multiple
> > endpoints but for your hash function they would be identical.
>
> This is used on second level of structures.
>
> Seems you have not caught complete structure. Look into header file
> where is comment for these hash tables. It is two level hash table.
>
> Basically it is hash table with function:
>
>   (codec_id, vendor_id, codec_vendor_id) -> hash_table_2
>
> Where hash_table_2 is with function:
>
>   endpoint_path -> codec_capabilities
>
> This allows me to find all endpoints which belongs to specific A2DP
> codec. And then for each endpoint I have A2DP codec capabilities.
>
> When user want to switch to new A2DP profile, I get information which
> register pulseaudio codec should be used. From it I receive A2DP codec
> information (codec_id, vendor_id, codec_vendor_id) and identify all
> endpoints which are compatible. After that I ask pulseuadio codec to
> choose from second level hash table which endpoint to use, based on
> capabilities (as second level is hash table endpoint --> capability).
> Pulseaudio codec tells me endpoint and then I instruct bluez to switch
> to that endpoint.

So you want to switch by codec not by endpoint? How do you know if the
user want to switch to SBC 1 with bitpool 64 or SBC 2 with bitpool 32?
So headsets do that actually and Im not really sure how we would be
able to identify what the user wants to use, having it duplicated in
the list of profiles is also bad but at least the user is able to tell
there are 2 endpoints mapped to the same codec otherwise we would have
to do some guessing on which endpoint shall be used, best match, first
match?

> > > +    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, '/');
> >
> > There exists a property called Device exactly to avoid this kind of assumption.
>
> Ok, I will look at it.
>
> > > +    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;
> >
> > You can probably rework these checks with the device once you actually
> > resolve the device when creating the endpoint object.
> >
> > > +    PA_HASHMAP_FOREACH(endpoints, device->a2dp_sink_endpoints, state)
> > > +        pa_hashmap_remove_and_free(endpoints, path);
> >
> > Now I totally lost, does the hash keys are path or a hash of codec id
> > and vendor details?
>
> Look information above. Keys in first level hash table are codec/vendor
> details and keys in second level hash table are paths.
>
> > > +    PA_HASHMAP_FOREACH(endpoints, device->a2dp_source_endpoints, state)
> > > +        pa_hashmap_remove_and_free(endpoints, path);
> > > +}
> > > +
> ...
> > > +    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);
> >
> > Just to make sure Ive checked what pa_hashmap_put do, it does call the
> > hash_func which in this case is pa_a2dp_codec_id_hash_func
>
> No. It is pa_idxset_string_hash_func function. See 4 lines above.
>
> > which
> > expects a struct pa_a2dp_codec_id * not a string, you were probably
> > lucky that the string contents is big enough.
> >
> > > +}
>
> --
> Pali Rohár
> pali.rohar@gmail.com
Pali Rohár Jan. 24, 2019, 5:17 p.m.
On Thursday 24 January 2019 18:54:18 Luiz Augusto von Dentz wrote:
> Hi Pali,
> 
> On Thu, Jan 24, 2019 at 6:00 PM Pali Rohár <pali.rohar@gmail.com> wrote:
> >
> > Hi!
> >
> > On Thursday 24 January 2019 16:03:17 Luiz Augusto von Dentz wrote:
> > > On Tue, Jan 15, 2019 at 1:24 AM Pali Rohár <pali.rohar@gmail.com> wrote:
> > > > +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;
> > >
> > > Can we not duplicate terms? The struct already is contain codec on its name.
> > >
> > > > +    uint32_t vendor_id;
> > > > +    uint16_t vendor_codec_id;
> > > > +} pa_a2dp_codec_id;
> >
> > So which names do you suggest? For identification of A2DP codec we need
> > 3 values: A2DP codec id, A2DP vendor id, A2DP codec id of specific to
> > vendor id.
> >
> > I thought that (codec_id, vendor_id, vendor_codec_id) is the best what
> > can be invented.
> 
> You could actually just use vendor, codec, if the vendor is ff then
> just use the codec,

Those are different types. A2DP codec_id is 8bit, vendor id is 32bit and
codec id which belongs to vendor id is 16bit.

Also vendor is free to choose whatever he wants in its "codec id which
belongs to vendor".

So I really need 3 values (A2DP codec id, vendor id and vendor specific
codec id).

> either way having to construct something like
> codec_id->codec_id seems pretty long for no reason, codec_id->id is
> not as bad.

Maybe codec_id->id, codec_id->vendor_id, codec_id->vendor_codec_id ?

I really do not have better idea for these names...

> > > > +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;
> > >
> > > Ditto, using name and description here should be just as informative.
> >
> > So you want to have just "name" and "description" without "codec_"
> > prefix?
> 
> Yep, or you thin codec->codec_name is not a bad pattern?

Good point. I just look how is descried it in header file.

> > > > +    /* A2DP codec id */
> > > > +    pa_a2dp_codec_id codec_id;
> > > > +
> > > > +    /* True if codec is bi-directional and supports backchannel */
> > > > +    bool support_backchannel;
> > >
> > > Id prefer using the term bidir here.
> >
> > "bidir" does not sounds like something obvious nor widely used. And in
> > these cases I rather used more descriptive names.
> 
> Well other codecs may not call it a backchannel, but if you insist at
> least drop the support term, this should be obvious for this type of
> flag.

So maybe "bi_directional" or just "bidirectional"? Abbreviation bidir
really does not say anything...

> > > > +    /* 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 */
> > >
> > > accept perhaps is enough here as there are nothing else to accept.
> >
> > Better name could be "can_accept_capabilites". As it really does not
> > accept.
> 
> You really like long names, Im more for short and consistent name
> specially for structs.
> 
> > > > +    const char *(*choose_capabilities)(const pa_hashmap *capabilities_hashmap, bool for_encoding);
> > >
> > > Id perfer using the same terminology as used in D-Bus, thus select
> > > probably fits better here.
> >
> > I thought that names "fill_capabilities" or "validate_configuration"
> > better describe what functions are doing.
> 
> These are used locally only, anyway there are so many duplicates
> terms, like sample_spec, buffer, not sure what is the matter with
> that.

There is difference between "default_sample_spec" used in
fill_preferred_configuration and "sample_spec" used in "init_codec".

Also in other header file is defined structure a2dp_capabilities, so
having name "capabilities_buffer" say that it is raw buffer, not
structure.

> > > > +    /* 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 *default_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);
> > >
> > > init, deinit and reset.
> > >
> > > > +    /* Get read block size for codec */
> > > > +    size_t (*get_read_block_size)(void *codec_info, size_t read_link_mtu);
> > > > +    /* Get write block size for codec */
> > > > +    size_t (*get_write_block_size)(void *codec_info, size_t write_link_mtu);
> > > > +
> > > > +    /* Reduce encoder bitrate for codec, returns new write block size or zero if not changed, called when socket is not accepting encoded data fast enough */
> > > > +    size_t (*reduce_encoder_bitrate)(void *codec_info, size_t write_link_mtu);
> > >
> > > Perhaps a set_bitrate would be better instead of just calling it
> > > reduce,
> >
> > Usage of name "reduce" was suggested by Tanu in V2.
> >
> > > but then we actually need to provide the current bitrate, the
> > > codec can then check if it attend the current configuration and if not
> > > adjust the parameters. This may change the latency as well but I far I
> > > remember we do adjust it on device module but we may need to
> > > incorporate a call to get the algorithm latency here as well.
> > >
> > > > +    /* 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);
> > >
> > > encode, decode?
> > >
> > > > +} 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..d84ad3c9f
> > > > --- /dev/null
> > > > +++ b/src/modules/bluetooth/a2dp-codec-sbc.c
> > > > +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;
> > >
> > > SBC_MAX_BITPOOL is 64 but we only select 53, we should probably fix this.
> >
> > This code is already in pulseaudio tree. I just moved functions to new
> > file name.
> >
> > So I would rather not change existing codec parameters in patch which
> > just introduce new API.
> >
> > Improvements to SBC codec can be done later after this patch series.
> 
> Sure thing, I can send a patch for that as well was just pointing out
> it is inconsistent regardless where it came from.

There are lot of other improvements for SBC. I would let for next step.

> > > > +    return sizeof(*capabilities);
> > > > +}
> > ...
> > > > +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;
> > >
> > > Change to return SBC_MAX_BITPOOL.
> > >
> > > > +            }
> > > > +
> > > > +            pa_log_warn("Invalid channel mode %u", mode);
> > > > +            return 53;
> > >
> > > Ditto.
> > >
> > > > +        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;
> > >
> > > Ditto.
> > >
> > > > +            }
> > > > +
> > > > +            pa_log_warn("Invalid channel mode %u", mode);
> > > > +            return 51;
> > >
> > > Ditto.
> > >
> > > > +    }
> > > > +
> > > > +    pa_log_warn("Invalid sampling freq %u", freq);
> > > > +    return 53;
> > >
> > > Ditto.
> >
> > Same there. This is just moving function from one file to another. I
> > would not expect that moving code changes also logic of parameters in
> > codec.
> >
> > > > +}
> > ...
> > > > +    set_params(sbc_info);
> > > > +
> > > > +    PA_ONCE_BEGIN {
> > > > +        pa_log_debug("Using SBC codec implementation: %s", pa_strnull(sbc_get_implementation_info(&sbc_info->sbc)));
> > >
> > > This was printing NULL to me which I suspect is the reason why you
> > > have pa_strnull(?), anyway it is not enough to just call sbc_init
> > > since that doesn't call sbc_init_primitives. We could in theory change
> > > that to be part of sbc_init instead of on the first call to
> > > sbc_encode.
> >
> > Also this code block is just moving from one file to another.
> >
> > I do not know why pa_strnull was there before.
> 
> Seems this has been broken for a while.

Yes.

> > > > +    } 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;
> > > > +}
> > ...
> > > > +const pa_a2dp_codec pa_a2dp_codec_sbc = {
> > > > +    .codec_name = "sbc",
> > > > +    .codec_description = "SBC",
> > > > +    .codec_id = { A2DP_CODEC_SBC, 0, 0 },
> > >
> > > You can declare without the 0, 0, by default the compiler should fill
> > > the remaining fields as 0.
> >
> > I just wanted to be explicit. But I can remove them.
> >
> > > > +    .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,
> > > > +    .get_read_block_size = get_block_size,
> > > > +    .get_write_block_size = get_block_size,
> > > > +    .reduce_encoder_bitrate = reduce_encoder_bitrate,
> > > > +    .encode_buffer = encode_buffer,
> > > > +    .decode_buffer = decode_buffer,
> > > > +};
> > ...
> > > > diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
> > > > index 48b147eed..9885ba461 100644
> > > > --- a/src/modules/bluetooth/bluez5-util.c
> > > > +++ b/src/modules/bluetooth/bluez5-util.c
> > > > @@ -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);
> > >
> > > This sounds a bit weird, you have a hash function which creates a hash
> > > with the codec and vendor details, but the compare function don't use
> > > it?
> >
> > Why you think it does not use it? Look few lines above. Both hash and
> > compare function use codec and vendor information.
> >
> > > Perhaps it would have been better to just use the endpoint path
> > > instead?
> >
> > I cannot. Endpoint path does not say for which codec it belongs. This is
> > reason why hash table which has key of codec/vendor information is
> > needed.
> 
> Not sure what do mean that you cannot,

You suggested to use endpoint as keys instead. But I cannot, because I
need mapping from codec/vendor_id. If key is endpoint, it is not useful
that hash table for me.

> the element data do contain
> that information perhaps that is just pa_hashmap, anyway it now makes
> sense it just much more complex to do this way since you also have to
> do lookups when and endpoint is removed, actually many lookups one for
> each codec and it doesn't even stop when it find the endpoint, you
> should probably fix that.

It is complex, but if I get on input codec/vendor ids and I need to
retrieve endpoint for it, I need to do such thing (or similar).

> > > Actually I think you must do that in order to able to address
> > > endpoint separately as it is valid to have the same codec in multiple
> > > endpoints but for your hash function they would be identical.
> >
> > This is used on second level of structures.
> >
> > Seems you have not caught complete structure. Look into header file
> > where is comment for these hash tables. It is two level hash table.
> >
> > Basically it is hash table with function:
> >
> >   (codec_id, vendor_id, codec_vendor_id) -> hash_table_2
> >
> > Where hash_table_2 is with function:
> >
> >   endpoint_path -> codec_capabilities
> >
> > This allows me to find all endpoints which belongs to specific A2DP
> > codec. And then for each endpoint I have A2DP codec capabilities.
> >
> > When user want to switch to new A2DP profile, I get information which
> > register pulseaudio codec should be used. From it I receive A2DP codec
> > information (codec_id, vendor_id, codec_vendor_id) and identify all
> > endpoints which are compatible. After that I ask pulseuadio codec to
> > choose from second level hash table which endpoint to use, based on
> > capabilities (as second level is hash table endpoint --> capability).
> > Pulseaudio codec tells me endpoint and then I instruct bluez to switch
> > to that endpoint.
> 
> So you want to switch by codec not by endpoint? How do you know if the
> user want to switch to SBC 1 with bitpool 64 or SBC 2 with bitpool 32?

SBC 1 and SBC 2 would be two different entries in "pa_a2dp_codecs"
table. So basically there are two different pulseaudio codecs. Therefore
for each one is defined own profile. And user would see two profiles SBC
1 and SBC 2. Name depends on what is filled in codec_description.

So user choose if he wants SBC 1 or SBC 2.

I think they would be named "SBC low quality", "SBC high quality", "SBC
auto quality". Also based on accept_capabilities() API function,
pulseaudio will filter unsupported codecs, e.g. when advertised bitpool
value is too low for pulseaudio's "SBC high quality".

> So headsets do that actually and Im not really sure how we would be
> able to identify what the user wants to use, having it duplicated in
> the list of profiles is also bad but at least the user is able to tell
> there are 2 endpoints mapped to the same codec otherwise we would have
> to do some guessing on which endpoint shall be used, best match, first
> match?

This decision is up to the choose_capabilities() API function. Codec
needs to define this function and decide which endpoint wants to use.

For A2DP codecs which provides lot of capabilities information (e.g
MPEG-1,2) codec would decide based on details (e.g. MPEG layer; SBC
bitpool, etc.). Codec with no capabilities (e.g. FastStream) would just
choose first/random match. This is up to codec what can do.

But important is that API is prepared for it and common codecs like SBC
or MPEG can benefit from it.

> > > > +    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, '/');
> > >
> > > There exists a property called Device exactly to avoid this kind of assumption.
> >
> > Ok, I will look at it.
> >
> > > > +    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;
> > >
> > > You can probably rework these checks with the device once you actually
> > > resolve the device when creating the endpoint object.
> > >
> > > > +    PA_HASHMAP_FOREACH(endpoints, device->a2dp_sink_endpoints, state)
> > > > +        pa_hashmap_remove_and_free(endpoints, path);
> > >
> > > Now I totally lost, does the hash keys are path or a hash of codec id
> > > and vendor details?
> >
> > Look information above. Keys in first level hash table are codec/vendor
> > details and keys in second level hash table are paths.
> >
> > > > +    PA_HASHMAP_FOREACH(endpoints, device->a2dp_source_endpoints, state)
> > > > +        pa_hashmap_remove_and_free(endpoints, path);
> > > > +}
> > > > +
> > ...
> > > > +    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);
> > >
> > > Just to make sure Ive checked what pa_hashmap_put do, it does call the
> > > hash_func which in this case is pa_a2dp_codec_id_hash_func
> >
> > No. It is pa_idxset_string_hash_func function. See 4 lines above.
> >
> > > which
> > > expects a struct pa_a2dp_codec_id * not a string, you were probably
> > > lucky that the string contents is big enough.
> > >
> > > > +}
> >
> > --
> > Pali Rohár
> > pali.rohar@gmail.com
> 
> 
>
Pali Rohár Jan. 27, 2019, 9:56 a.m.
On Thursday 24 January 2019 17:00:41 Pali Rohár wrote:
> On Thursday 24 January 2019 16:03:17 Luiz Augusto von Dentz wrote:
> > > @@ -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, '/');
> > 
> > There exists a property called Device exactly to avoid this kind of assumption.
> 
> Ok, I will look at it.

I fixed it locally to use "Device" property. Really there is not need to
do that path mangling. I will include this fix in next patch version.

> > > +    if (!endptr) {
> > > +        pa_log_error("Invalid remote endpoint %s", remote_endpoint_path);
> > > +        return NULL;
> > > +    }
> > > +
> > > +    return pa_xstrndup(remote_endpoint_path, endptr-remote_endpoint_path);
> > > +}
Pali Rohár Jan. 27, 2019, 9:59 a.m.
On Thursday 24 January 2019 18:17:08 Pali Rohár wrote:
> On Thursday 24 January 2019 18:54:18 Luiz Augusto von Dentz wrote:
> > > > > +    set_params(sbc_info);
> > > > > +
> > > > > +    PA_ONCE_BEGIN {
> > > > > +        pa_log_debug("Using SBC codec implementation: %s", pa_strnull(sbc_get_implementation_info(&sbc_info->sbc)));
> > > >
> > > > This was printing NULL to me which I suspect is the reason why you
> > > > have pa_strnull(?), anyway it is not enough to just call sbc_init
> > > > since that doesn't call sbc_init_primitives. We could in theory change
> > > > that to be part of sbc_init instead of on the first call to
> > > > sbc_encode.
> > >
> > > Also this code block is just moving from one file to another.
> > >
> > > I do not know why pa_strnull was there before.
> > 
> > Seems this has been broken for a while.
> 
> Yes.

Apparently not, this is my mistake. I incorrectly moved this block from
encode to init. Fixed locally (moved back to encode function) and it
will be in next patch version.

> > > > > +    } 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;
> > > > > +}
Pali Rohár Feb. 3, 2019, 2:27 p.m.
On Sunday 27 January 2019 10:59:42 Pali Rohár wrote:
> On Thursday 24 January 2019 18:17:08 Pali Rohár wrote:
> > On Thursday 24 January 2019 18:54:18 Luiz Augusto von Dentz wrote:
> > > > > > +    set_params(sbc_info);
> > > > > > +
> > > > > > +    PA_ONCE_BEGIN {
> > > > > > +        pa_log_debug("Using SBC codec implementation: %s", pa_strnull(sbc_get_implementation_info(&sbc_info->sbc)));
> > > > >
> > > > > This was printing NULL to me which I suspect is the reason why you
> > > > > have pa_strnull(?), anyway it is not enough to just call sbc_init
> > > > > since that doesn't call sbc_init_primitives. We could in theory change
> > > > > that to be part of sbc_init instead of on the first call to
> > > > > sbc_encode.
> > > >
> > > > Also this code block is just moving from one file to another.
> > > >
> > > > I do not know why pa_strnull was there before.
> > > 
> > > Seems this has been broken for a while.
> > 
> > Yes.
> 
> Apparently not, this is my mistake. I incorrectly moved this block from
> encode to init. Fixed locally (moved back to encode function) and it
> will be in next patch version.

Fixed in V6.

> > > > > > +    } 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;
> > > > > > +}
>
Pali Rohár Feb. 3, 2019, 2:34 p.m.
Hi! I fixed some problems in V6.

About naming conventions: I have not changed nothing right now. But
because function names and parameters does not change code itself, it
can be done later if more people agree that it is needed to change.

Basically for testing functionality it does not matter if function is
named A or B.

On Thursday 24 January 2019 18:17:08 Pali Rohár wrote:
> > > > > diff --git a/src/modules/bluetooth/a2dp-codec-sbc.c b/src/modules/bluetooth/a2dp-codec-sbc.c
> > > > > new file mode 100644
> > > > > index 000000000..d84ad3c9f
> > > > > --- /dev/null
> > > > > +++ b/src/modules/bluetooth/a2dp-codec-sbc.c
> > > > > +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;
> > > >
> > > > SBC_MAX_BITPOOL is 64 but we only select 53, we should probably fix this.
> > >
> > > This code is already in pulseaudio tree. I just moved functions to new
> > > file name.
> > >
> > > So I would rather not change existing codec parameters in patch which
> > > just introduce new API.
> > >
> > > Improvements to SBC codec can be done later after this patch series.
> > 
> > Sure thing, I can send a patch for that as well was just pointing out
> > it is inconsistent regardless where it came from.
> 
> There are lot of other improvements for SBC. I would let for next step.

Fixed in V6 in separate patch.

> > > > > +    return sizeof(*capabilities);
> > > > > +}
> > > ...
> > > > > +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;
> > > >
> > > > Change to return SBC_MAX_BITPOOL.
> > > >
> > > > > +            }
> > > > > +
> > > > > +            pa_log_warn("Invalid channel mode %u", mode);
> > > > > +            return 53;
> > > >
> > > > Ditto.
> > > >
> > > > > +        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;
> > > >
> > > > Ditto.
> > > >
> > > > > +            }
> > > > > +
> > > > > +            pa_log_warn("Invalid channel mode %u", mode);
> > > > > +            return 51;
> > > >
> > > > Ditto.
> > > >
> > > > > +    }
> > > > > +
> > > > > +    pa_log_warn("Invalid sampling freq %u", freq);
> > > > > +    return 53;
> > > >
> > > > Ditto.
> > >
> > > Same there. This is just moving function from one file to another. I
> > > would not expect that moving code changes also logic of parameters in
> > > codec.

Fixed in v6 too.

> > So you want to switch by codec not by endpoint? How do you know if the
> > user want to switch to SBC 1 with bitpool 64 or SBC 2 with bitpool 32?
> 
> SBC 1 and SBC 2 would be two different entries in "pa_a2dp_codecs"
> table. So basically there are two different pulseaudio codecs. Therefore
> for each one is defined own profile. And user would see two profiles SBC
> 1 and SBC 2. Name depends on what is filled in codec_description.
> 
> So user choose if he wants SBC 1 or SBC 2.
> 
> I think they would be named "SBC low quality", "SBC high quality", "SBC
> auto quality". Also based on accept_capabilities() API function,
> pulseaudio will filter unsupported codecs, e.g. when advertised bitpool
> value is too low for pulseaudio's "SBC high quality".

Something is implemented in V6.

> > > > > +    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, '/');
> > > >
> > > > There exists a property called Device exactly to avoid this kind of assumption.
> > >
> > > Ok, I will look at it.

Fixed in V6.

> > > > > +    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;
> > > >
> > > > You can probably rework these checks with the device once you actually
> > > > resolve the device when creating the endpoint object.

Fixed in V6.