[v6,10/11] bluetooth: Add more variants of SBC codec

Submitted by Pali Rohár on Feb. 3, 2019, 2:15 p.m.

Details

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

Commit Message

Pali Rohár Feb. 3, 2019, 2:15 p.m.
Specify configuration for Low, Middle, High and Ultra High Quality of SBC
codec. SBC codec in Ultra High Quality has higher quality than aptX.

Automatic Quality mode matches configuration of SBC codec which was used
before this change. Which means that it accept configuration between Low
and High quality.

Current SBC code was extended to allow definitions of arbitrary
configuration variants of SBC codec parameters.
---
 src/modules/bluetooth/a2dp-codec-sbc.c  | 654 +++++++++++++++++++++++++-------
 src/modules/bluetooth/a2dp-codec-util.c |  16 +-
 2 files changed, 534 insertions(+), 136 deletions(-)

Patch hide | download patch | download mbox

diff --git a/src/modules/bluetooth/a2dp-codec-sbc.c b/src/modules/bluetooth/a2dp-codec-sbc.c
index 034d57a69..a8229735c 100644
--- a/src/modules/bluetooth/a2dp-codec-sbc.c
+++ b/src/modules/bluetooth/a2dp-codec-sbc.c
@@ -36,8 +36,67 @@ 
 #include "a2dp-codec-api.h"
 #include "rtp.h"
 
-#define SBC_BITPOOL_DEC_LIMIT 32
-#define SBC_BITPOOL_DEC_STEP 5
+/* Below are capabilities tables for different qualities. Order of capabilities in tables are from the most preferred to the least preferred. */
+
+#define FIXED_SBC_CAPS(mode, freq, bitpool) { .channel_mode = (mode), .frequency = (freq), .min_bitpool = (bitpool), .max_bitpool = (bitpool), .allocation_method = (SBC_ALLOCATION_SNR|SBC_ALLOCATION_LOUDNESS), .subbands = SBC_SUBBANDS_8, .block_length = SBC_BLOCK_LENGTH_16 }
+
+/* SBC Low Quality, Joint Stereo is same as FastStream's SBC codec configuration, Mono was calculated to match Joint Stereo */
+a2dp_sbc_t sbc_lq_caps_table[] = {
+    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_44100, 29), /* 195.7 kbps */
+    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_48000, 29), /* 213   kbps */
+    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_44100, 15), /* 104.7 kbps */
+    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_48000, 15), /* 114   kbps */
+};
+
+/* SBC Middle Quality, based on A2DP spec: Recommended sets of SBC parameters */
+a2dp_sbc_t sbc_mq_caps_table[] = {
+    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_44100, SBC_BITPOOL_MQ_JOINT_STEREO_44100), /* bitpool = 35, 228.8 kbps */
+    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_48000, SBC_BITPOOL_MQ_JOINT_STEREO_48000), /* bitpool = 33, 237   kbps */
+    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_44100, SBC_BITPOOL_MQ_MONO_44100),         /* bitpool = 19, 126.8 kbps */
+    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_48000, SBC_BITPOOL_MQ_MONO_48000),         /* bitpool = 18, 132   kbps */
+};
+
+/* SBC High Quality, based on A2DP spec: Recommended sets of SBC parameters */
+a2dp_sbc_t sbc_hq_caps_table[] = {
+    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_44100, SBC_BITPOOL_HQ_JOINT_STEREO_44100), /* bitpool = 53, 328   kbps */
+    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_48000, SBC_BITPOOL_HQ_JOINT_STEREO_48000), /* bitpool = 51, 345   kbps */
+    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_44100, SBC_BITPOOL_HQ_MONO_44100),         /* bitpool = 31, 192.9 kbps */
+    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_48000, SBC_BITPOOL_HQ_MONO_48000),         /* bitpool = 29, 210   kbps */
+};
+
+/* SBC Ultra High Quality, calculated to minimize wasted bytes and to be below max possible 512 kbps */
+a2dp_sbc_t sbc_uhq_caps_table[] = {
+    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_44100, 76), /* 454.8 kbps */
+    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_48000, 76), /* 495   kbps */
+    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_STEREO,       SBC_SAMPLING_FREQ_44100, 76), /* 452   kbps */
+    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_STEREO,       SBC_SAMPLING_FREQ_48000, 76), /* 492   kbps */
+    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_DUAL_CHANNEL, SBC_SAMPLING_FREQ_44100, 38), /* 452   kbps */
+    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_DUAL_CHANNEL, SBC_SAMPLING_FREQ_48000, 38), /* 492   kbps */
+    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_44100, 37), /* 226   kbps */
+    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_48000, 37), /* 246   kbps */
+};
+/* In most cases bluetooth headsets would support only sbc dual channel mode
+ * for 2 channels as they have limited maximal bitpool value to 53. */
+
+#undef FIXED_SBC_CAPS
+
+/* SBC Auto Quality, only one row which allow any possible configuration up to common High Quality */
+/* We need to ensure that bitrate is below max possible 512 kbps, therefore limit configuration to High Quality */
+a2dp_sbc_t sbc_auto_caps_table[] = { {
+    .channel_mode = SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL | SBC_CHANNEL_MODE_STEREO | SBC_CHANNEL_MODE_JOINT_STEREO,
+    .frequency = SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 | SBC_SAMPLING_FREQ_44100 | SBC_SAMPLING_FREQ_48000,
+    .allocation_method = SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS,
+    .subbands = SBC_SUBBANDS_4 | SBC_SUBBANDS_8,
+    .block_length = SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16,
+    .min_bitpool = SBC_MIN_BITPOOL,
+    .max_bitpool = SBC_BITPOOL_HQ_JOINT_STEREO_44100,
+} };
+
+/* Bitpool limits and steps for reducing bitrate in Auto Quality mode */
+#define SBC_SEPARATE_BITPOOL_DEC_LIMIT 10
+#define SBC_COMBINED_BITPOOL_DEC_LIMIT 25
+#define SBC_SEPARATE_BITPOOL_DEC_STEP   2
+#define SBC_COMBINED_BITPOOL_DEC_STEP   4
 
 struct sbc_info {
     sbc_t sbc;                           /* Codec data */
@@ -53,62 +112,222 @@  struct sbc_info {
     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;
+static bool are_capabilities_compatible(const a2dp_sbc_t *capabilities1, const a2dp_sbc_t *capabilities2) {
+    if (!(capabilities1->channel_mode & capabilities2->channel_mode))
+        return false;
 
-    if (capabilities_size != sizeof(*capabilities))
+    if (!(capabilities1->frequency & capabilities2->frequency))
         return false;
 
-    if (!(capabilities->frequency & (SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 | SBC_SAMPLING_FREQ_44100 | SBC_SAMPLING_FREQ_48000)))
+    if (!(capabilities1->allocation_method & capabilities2->allocation_method))
         return false;
 
-    if (!(capabilities->channel_mode & (SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL | SBC_CHANNEL_MODE_STEREO | SBC_CHANNEL_MODE_JOINT_STEREO)))
+    if (!(capabilities1->subbands & capabilities2->subbands))
         return false;
 
-    if (!(capabilities->allocation_method & (SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS)))
+    if (!(capabilities1->block_length & capabilities2->block_length))
         return false;
 
-    if (!(capabilities->subbands & (SBC_SUBBANDS_4 | SBC_SUBBANDS_8)))
+    if (!(capabilities1->min_bitpool <= capabilities2->max_bitpool || capabilities2->min_bitpool <= capabilities1->max_bitpool))
         return false;
 
-    if (!(capabilities->block_length & (SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16)))
+    if (capabilities1->min_bitpool > capabilities1->max_bitpool || capabilities2->min_bitpool > capabilities2->max_bitpool)
         return false;
 
     return true;
 }
 
-static const char *choose_capabilities(const pa_hashmap *capabilities_hashmap, bool for_encoding) {
+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;
+
+    return are_capabilities_compatible(capabilities, &sbc_auto_caps_table[0]);
+}
+
+static bool accept_capabilities_table(const uint8_t *capabilities_buffer, uint8_t capabilities_size, const a2dp_sbc_t capabilities_table[], unsigned capabilities_table_elements) {
+    const a2dp_sbc_t *capabilities = (const a2dp_sbc_t *) capabilities_buffer;
+    unsigned i;
+
+    if (!accept_capabilities(capabilities_buffer, capabilities_size, false))
+        return false;
+
+    for (i = 0; i < capabilities_table_elements; i++) {
+        if (!are_capabilities_compatible(capabilities, &capabilities_table[i]))
+            continue;
+        return true;
+    }
+
+    return false;
+}
+
+static bool accept_capabilities_lq(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
+    return accept_capabilities_table(capabilities_buffer, capabilities_size, sbc_lq_caps_table, PA_ELEMENTSOF(sbc_lq_caps_table));
+}
+
+static bool accept_capabilities_mq(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
+    return accept_capabilities_table(capabilities_buffer, capabilities_size, sbc_mq_caps_table, PA_ELEMENTSOF(sbc_mq_caps_table));
+}
+
+static bool accept_capabilities_hq(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
+    return accept_capabilities_table(capabilities_buffer, capabilities_size, sbc_hq_caps_table, PA_ELEMENTSOF(sbc_hq_caps_table));
+}
+
+static bool accept_capabilities_uhq(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
+    return accept_capabilities_table(capabilities_buffer, capabilities_size, sbc_uhq_caps_table, PA_ELEMENTSOF(sbc_uhq_caps_table));
+}
+
+static const char *choose_capabilities_table(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, const a2dp_sbc_t capabilities_table[], unsigned capabilities_table_elements) {
     const pa_a2dp_codec_capabilities *a2dp_capabilities;
+    const a2dp_sbc_t *capabilities;
     const char *key;
     void *state;
+    unsigned i;
+    uint8_t table_range;
+    uint8_t current_range;
+    const char *best_key = NULL;
+    uint8_t best_range = 0;
+    uint8_t frequency = 0;
+    bool is_mono = (default_sample_spec->channels <= 1);
+
+    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 }
+    };
+
+    for (i = 0; i < PA_ELEMENTSOF(freq_table); i++) {
+        if (freq_table[i].rate == default_sample_spec->rate) {
+            frequency = freq_table[i].rate;
+            break;
+        }
+    }
+
+    if (!frequency)
+        return NULL;
 
-    /* There is no preference, just choose random valid entry */
+    /* choose remote capabilities which are compatible and its bitpool range is nearest to one from capabilities table */
     PA_HASHMAP_FOREACH_KV(key, a2dp_capabilities, capabilities_hashmap, state) {
-        if (accept_capabilities(a2dp_capabilities->buffer, a2dp_capabilities->size, for_encoding))
-            return key;
+        /* skip remote capabilities which are not compatible */
+        if (!accept_capabilities(a2dp_capabilities->buffer, a2dp_capabilities->size, false))
+            continue;
+
+        capabilities = (const a2dp_sbc_t *) a2dp_capabilities->buffer;
+
+        /* choose capabilities from our table which is compatible with sample spec and remote capabilities */
+        for (i = 0; i < capabilities_table_elements; i++) {
+            if (!are_capabilities_compatible(capabilities, &capabilities_table[i]))
+                continue;
+            if (is_mono && !(capabilities->channel_mode & SBC_CHANNEL_MODE_MONO))
+                continue;
+            if (!is_mono && !(capabilities->channel_mode & (SBC_CHANNEL_MODE_JOINT_STEREO | SBC_CHANNEL_MODE_STEREO | SBC_CHANNEL_MODE_DUAL_CHANNEL)))
+                continue;
+            if (!(capabilities->frequency & frequency))
+                continue;
+            break;
+        }
+
+        /* skip if nothing is compatible */
+        if (i == capabilities_table_elements)
+            continue;
+
+        /* calculate current bitpool range compatible with both remote capabilities and capabilities from our table */
+        if (capabilities->min_bitpool > capabilities_table[i].min_bitpool) {
+            if (capabilities->max_bitpool > capabilities_table[i].max_bitpool)
+                current_range = capabilities_table[i].max_bitpool - capabilities->min_bitpool;
+            else
+                current_range = capabilities->max_bitpool - capabilities->min_bitpool;
+        } else {
+            if (capabilities->max_bitpool > capabilities_table[i].max_bitpool)
+                current_range = capabilities_table[i].max_bitpool - capabilities_table[i].min_bitpool;
+            else
+                current_range = capabilities->max_bitpool - capabilities_table[i].min_bitpool;
+        }
+
+        table_range = capabilities_table[i].max_bitpool - capabilities_table[i].min_bitpool;
+
+        /* use current remote capabilities if its bitpool range is closer to bitpool range in table */
+        if (!best_key || abs((int)current_range - (int)(table_range)) < abs((int)best_range - (int)(table_range))) {
+            best_range = current_range;
+            best_key = key;
+        }
     }
 
-    return NULL;
+    return best_key;
 }
 
-static uint8_t fill_capabilities(uint8_t capabilities_buffer[254]) {
+static const char *choose_capabilities_lq(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
+    return choose_capabilities_table(capabilities_hashmap, default_sample_spec, sbc_lq_caps_table, PA_ELEMENTSOF(sbc_lq_caps_table));
+}
+
+static const char *choose_capabilities_mq(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
+    return choose_capabilities_table(capabilities_hashmap, default_sample_spec, sbc_mq_caps_table, PA_ELEMENTSOF(sbc_mq_caps_table));
+}
+
+static const char *choose_capabilities_hq(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
+    return choose_capabilities_table(capabilities_hashmap, default_sample_spec, sbc_hq_caps_table, PA_ELEMENTSOF(sbc_hq_caps_table));
+}
+
+static const char *choose_capabilities_uhq(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
+    return choose_capabilities_table(capabilities_hashmap, default_sample_spec, sbc_uhq_caps_table, PA_ELEMENTSOF(sbc_uhq_caps_table));
+}
+
+static const char *choose_capabilities(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
+    return choose_capabilities_table(capabilities_hashmap, default_sample_spec, sbc_auto_caps_table, PA_ELEMENTSOF(sbc_auto_caps_table));
+}
+
+static uint8_t fill_capabilities_table(uint8_t capabilities_buffer[254], const a2dp_sbc_t capabilities_table[], unsigned capabilities_table_elements) {
     a2dp_sbc_t *capabilities = (a2dp_sbc_t *) capabilities_buffer;
+    unsigned i;
 
     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_BITPOOL_HQ_JOINT_STEREO_44100;
+    capabilities->min_bitpool = 0xFF;
+    capabilities->max_bitpool = 0x00;
+
+    for (i = 0; i < capabilities_table_elements; i++) {
+        capabilities->channel_mode |= capabilities_table[i].channel_mode;
+        capabilities->frequency |= capabilities_table[i].frequency;
+        capabilities->allocation_method |= capabilities_table[i].allocation_method;
+        capabilities->subbands |= capabilities_table[i].subbands;
+        capabilities->block_length |= capabilities_table[i].block_length;
+        if (capabilities->min_bitpool > capabilities_table[i].min_bitpool)
+            capabilities->min_bitpool = capabilities_table[i].min_bitpool;
+        if (capabilities->max_bitpool < capabilities_table[i].max_bitpool)
+            capabilities->max_bitpool = capabilities_table[i].max_bitpool;
+    }
+
+    pa_assert(capabilities->min_bitpool != 0xFF);
+    pa_assert(capabilities->max_bitpool != 0x00);
 
     return sizeof(*capabilities);
 }
 
+static uint8_t fill_capabilities_lq(uint8_t capabilities_buffer[254]) {
+    return fill_capabilities_table(capabilities_buffer, sbc_lq_caps_table, PA_ELEMENTSOF(sbc_lq_caps_table));
+}
+
+static uint8_t fill_capabilities_mq(uint8_t capabilities_buffer[254]) {
+    return fill_capabilities_table(capabilities_buffer, sbc_mq_caps_table, PA_ELEMENTSOF(sbc_mq_caps_table));
+}
+
+static uint8_t fill_capabilities_hq(uint8_t capabilities_buffer[254]) {
+    return fill_capabilities_table(capabilities_buffer, sbc_hq_caps_table, PA_ELEMENTSOF(sbc_hq_caps_table));
+}
+
+static uint8_t fill_capabilities_uhq(uint8_t capabilities_buffer[254]) {
+    return fill_capabilities_table(capabilities_buffer, sbc_uhq_caps_table, PA_ELEMENTSOF(sbc_uhq_caps_table));
+}
+
+static uint8_t fill_capabilities(uint8_t capabilities_buffer[254]) {
+    return fill_capabilities_table(capabilities_buffer, sbc_auto_caps_table, PA_ELEMENTSOF(sbc_auto_caps_table));
+}
+
 static bool validate_configuration(const uint8_t *config_buffer, uint8_t config_size) {
     const a2dp_sbc_t *config = (const a2dp_sbc_t *) config_buffer;
 
@@ -153,49 +372,40 @@  static bool validate_configuration(const uint8_t *config_buffer, uint8_t config_
     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:
-            switch (mode) {
-                case SBC_CHANNEL_MODE_MONO:
-                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
-                case SBC_CHANNEL_MODE_STEREO:
-                case SBC_CHANNEL_MODE_JOINT_STEREO:
-                    return SBC_BITPOOL_HQ_JOINT_STEREO_44100;
-            }
+static bool validate_configuration_table(const uint8_t *config_buffer, uint8_t config_size, const a2dp_sbc_t capabilities_table[], unsigned capabilities_table_elements) {
+    if (!validate_configuration(config_buffer, config_size))
+        return false;
 
-        case SBC_SAMPLING_FREQ_44100:
-            switch (mode) {
-                case SBC_CHANNEL_MODE_MONO:
-                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
-                    return SBC_BITPOOL_HQ_MONO_44100;
+    if (!accept_capabilities_table(config_buffer, config_size, capabilities_table, capabilities_table_elements)) {
+        pa_log_error("Some configuration settings are invalid for current quality");
+        return false;
+    }
 
-                case SBC_CHANNEL_MODE_STEREO:
-                case SBC_CHANNEL_MODE_JOINT_STEREO:
-                    return SBC_BITPOOL_HQ_JOINT_STEREO_44100;
-            }
+    return true;
+}
 
-        case SBC_SAMPLING_FREQ_48000:
-            switch (mode) {
-                case SBC_CHANNEL_MODE_MONO:
-                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
-                    return SBC_BITPOOL_HQ_MONO_48000;
+static bool validate_configuration_lq(const uint8_t *config_buffer, uint8_t config_size) {
+    return validate_configuration_table(config_buffer, config_size, sbc_lq_caps_table, PA_ELEMENTSOF(sbc_lq_caps_table));
+}
 
-                case SBC_CHANNEL_MODE_STEREO:
-                case SBC_CHANNEL_MODE_JOINT_STEREO:
-                    return SBC_BITPOOL_HQ_JOINT_STEREO_48000;
-            }
-    }
+static bool validate_configuration_mq(const uint8_t *config_buffer, uint8_t config_size) {
+    return validate_configuration_table(config_buffer, config_size, sbc_mq_caps_table, PA_ELEMENTSOF(sbc_mq_caps_table));
+}
 
-    pa_assert_not_reached();
+static bool validate_configuration_hq(const uint8_t *config_buffer, uint8_t config_size) {
+    return validate_configuration_table(config_buffer, config_size, sbc_hq_caps_table, PA_ELEMENTSOF(sbc_hq_caps_table));
 }
 
-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]) {
+static bool validate_configuration_uhq(const uint8_t *config_buffer, uint8_t config_size) {
+    return validate_configuration_table(config_buffer, config_size, sbc_uhq_caps_table, PA_ELEMENTSOF(sbc_uhq_caps_table));
+}
+
+static uint8_t fill_preferred_configuration_table(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[254], const a2dp_sbc_t capabilities_table[], unsigned capabilities_table_elements) {
     a2dp_sbc_t *config = (a2dp_sbc_t *) config_buffer;
     const a2dp_sbc_t *capabilities = (const a2dp_sbc_t *) capabilities_buffer;
-    int i;
+    bool is_mono = (default_sample_spec->channels <= 1);
+    unsigned i;
+    int j;
 
     static const struct {
         uint32_t rate;
@@ -215,96 +425,184 @@  static uint8_t fill_preferred_configuration(const pa_sample_spec *default_sample
     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;
+    for (j = 0; (unsigned) j < PA_ELEMENTSOF(freq_table); j++) {
+        if (freq_table[j].rate >= default_sample_spec->rate && (capabilities->frequency & freq_table[j].cap)) {
+            for (i = 0; i < capabilities_table_elements; i++) {
+                if (capabilities_table[i].frequency & freq_table[j].cap) {
+                    config->frequency = freq_table[j].cap;
+                    break;
+                }
+            }
+            if (i != capabilities_table_elements)
+                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 ((unsigned) j == PA_ELEMENTSOF(freq_table)) {
+        for (--j; j >= 0; j--) {
+            if (capabilities->frequency & freq_table[j].cap) {
+                for (i = 0; i < capabilities_table_elements; i++) {
+                    if (capabilities_table[i].frequency & freq_table[j].cap) {
+                        config->frequency = freq_table[j].cap;
+                        break;
+                    }
+                }
+                if (i != capabilities_table_elements)
+                    break;
             }
         }
 
-        if (i < 0) {
-            pa_log_error("Not suitable sample rate");
+        if (j < 0) {
+            pa_log_error("No 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;
+    pa_assert((unsigned) j < PA_ELEMENTSOF(freq_table));
+
+    for (i = 0; i < capabilities_table_elements; i++) {
+        if ((capabilities->block_length & SBC_BLOCK_LENGTH_16) && (capabilities_table[i].block_length & SBC_BLOCK_LENGTH_16))
+            config->block_length = SBC_BLOCK_LENGTH_16;
+        else if ((capabilities->block_length & SBC_BLOCK_LENGTH_12) && (capabilities_table[i].block_length & SBC_BLOCK_LENGTH_12))
+            config->block_length = SBC_BLOCK_LENGTH_12;
+        else if ((capabilities->block_length & SBC_BLOCK_LENGTH_8) && (capabilities_table[i].block_length & SBC_BLOCK_LENGTH_8))
+            config->block_length = SBC_BLOCK_LENGTH_8;
+        else if ((capabilities->block_length & SBC_BLOCK_LENGTH_4) && (capabilities_table[i].block_length & SBC_BLOCK_LENGTH_4))
+            config->block_length = SBC_BLOCK_LENGTH_4;
         else {
-            pa_log_error("No supported channel modes");
-            return 0;
+            pa_log_debug("No supported block lengths in table %u", i);
+            continue;
         }
-    } 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;
+
+        if ((capabilities->subbands & SBC_SUBBANDS_8) && (capabilities_table[i].subbands & SBC_SUBBANDS_8))
+            config->subbands = SBC_SUBBANDS_8;
+        else if ((capabilities->subbands & SBC_SUBBANDS_4) && (capabilities_table[i].subbands & SBC_SUBBANDS_4))
+            config->subbands = SBC_SUBBANDS_4;
         else {
-            pa_log_error("No supported channel modes");
-            return 0;
+            pa_log_debug("No supported subbands in table %u", i);
+            continue;
         }
-    }
 
-    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->allocation_method & SBC_ALLOCATION_LOUDNESS) && (capabilities_table[i].allocation_method & SBC_ALLOCATION_LOUDNESS))
+            config->allocation_method = SBC_ALLOCATION_LOUDNESS;
+        else if ((capabilities->allocation_method & SBC_ALLOCATION_SNR) && (capabilities_table[i].allocation_method & SBC_ALLOCATION_SNR))
+            config->allocation_method = SBC_ALLOCATION_SNR;
+        else {
+            pa_log_debug("No supported allocation method in table %u", i);
+            continue;
+        }
 
-    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 (is_mono) {
+            if ((capabilities->channel_mode & SBC_CHANNEL_MODE_MONO) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_MONO))
+                config->channel_mode = SBC_CHANNEL_MODE_MONO;
+            else if ((capabilities->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO))
+                config->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
+            else if ((capabilities->channel_mode & SBC_CHANNEL_MODE_STEREO) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_STEREO))
+                config->channel_mode = SBC_CHANNEL_MODE_STEREO;
+            else if ((capabilities->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL))
+                config->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
+            else {
+                pa_log_debug("No supported channel mode in table %u", i);
+                continue;
+            }
+        } else {
+            if ((capabilities->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO))
+                config->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
+            else if ((capabilities->channel_mode & SBC_CHANNEL_MODE_STEREO) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_STEREO))
+                config->channel_mode = SBC_CHANNEL_MODE_STEREO;
+            else if ((capabilities->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL))
+                config->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
+            else if ((capabilities->channel_mode & SBC_CHANNEL_MODE_MONO) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_MONO))
+                config->channel_mode = SBC_CHANNEL_MODE_MONO;
+            else {
+                pa_log_debug("No supported channel mode in table %u", i);
+                continue;
+            }
+        }
+
+        config->min_bitpool = PA_MAX(capabilities->min_bitpool, capabilities_table[i].min_bitpool);
+        config->max_bitpool = PA_MIN(capabilities->max_bitpool, capabilities_table[i].max_bitpool);
+
+        if (config->min_bitpool > config->max_bitpool) {
+            pa_log_debug("No supported bitpool in table %u", i);
+            continue;
+        }
+
+        break;
     }
 
-    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");
+    if (i == capabilities_table_elements) {
+        pa_log_error("No supported configuration");
         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);
+    return sizeof(*config);
+}
 
-    if (config->min_bitpool > config->max_bitpool) {
-        pa_log_error("No supported bitpool");
-        return 0;
+static uint8_t fill_preferred_configuration_lq(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[254]) {
+    return fill_preferred_configuration_table(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, sbc_lq_caps_table, PA_ELEMENTSOF(sbc_lq_caps_table));
+}
+
+static uint8_t fill_preferred_configuration_mq(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[254]) {
+    return fill_preferred_configuration_table(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, sbc_mq_caps_table, PA_ELEMENTSOF(sbc_mq_caps_table));
+}
+
+static uint8_t fill_preferred_configuration_hq(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[254]) {
+    return fill_preferred_configuration_table(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, sbc_hq_caps_table, PA_ELEMENTSOF(sbc_hq_caps_table));
+}
+
+static uint8_t fill_preferred_configuration_uhq(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[254]) {
+    return fill_preferred_configuration_table(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, sbc_uhq_caps_table, PA_ELEMENTSOF(sbc_uhq_caps_table));
+}
+
+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:
+            switch (mode) {
+                case SBC_CHANNEL_MODE_MONO:
+                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+                case SBC_CHANNEL_MODE_STEREO:
+                case SBC_CHANNEL_MODE_JOINT_STEREO:
+                    return SBC_BITPOOL_HQ_JOINT_STEREO_44100;
+            }
+
+        case SBC_SAMPLING_FREQ_44100:
+            switch (mode) {
+                case SBC_CHANNEL_MODE_MONO:
+                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+                    return SBC_BITPOOL_HQ_MONO_44100;
+
+                case SBC_CHANNEL_MODE_STEREO:
+                case SBC_CHANNEL_MODE_JOINT_STEREO:
+                    return SBC_BITPOOL_HQ_JOINT_STEREO_44100;
+            }
+
+        case SBC_SAMPLING_FREQ_48000:
+            switch (mode) {
+                case SBC_CHANNEL_MODE_MONO:
+                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+                    return SBC_BITPOOL_HQ_MONO_48000;
+
+                case SBC_CHANNEL_MODE_STEREO:
+                case SBC_CHANNEL_MODE_JOINT_STEREO:
+                    return SBC_BITPOOL_HQ_JOINT_STEREO_48000;
+            }
     }
 
-    return sizeof(*config);
+    pa_assert_not_reached();
+}
+
+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;
+    uint8_t ret;
+
+    ret = fill_preferred_configuration_table(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, sbc_auto_caps_table, PA_ELEMENTSOF(sbc_auto_caps_table));
+    config->max_bitpool = PA_MIN(default_bitpool(config->frequency, config->channel_mode), config->max_bitpool);
+    config->max_bitpool = PA_MAX(config->max_bitpool, config->min_bitpool);
+
+    return ret;
 }
 
 static void set_params(struct sbc_info *sbc_info) {
@@ -486,13 +784,17 @@  static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
     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->mode == SBC_CHANNEL_MODE_MONO || sbc_info->mode == SBC_CHANNEL_MODE_DUAL_CHANNEL) {
+        /* For Mono and Dual Channel modes bitpool value is separete for each channel */
+        bitpool = sbc_info->sbc.bitpool - SBC_SEPARATE_BITPOOL_DEC_STEP;
+        if (bitpool <= SBC_SEPARATE_BITPOOL_DEC_LIMIT)
+            return 0;
+    } else {
+        /* For Stereo modes bitpool value is combined for both channels */
+        bitpool = sbc_info->sbc.bitpool - SBC_COMBINED_BITPOOL_DEC_STEP;
+        if (bitpool <= SBC_COMBINED_BITPOOL_DEC_LIMIT)
+            return 0;
+    }
 
     if (sbc_info->sbc.bitpool == bitpool)
         return 0;
@@ -501,6 +803,10 @@  static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
     return get_block_size(codec_info, write_link_mtu);
 }
 
+static size_t reduce_encoder_bitrate_none(void *codec_info, size_t write_link_mtu) {
+    return 0;
+}
+
 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;
@@ -625,9 +931,69 @@  static size_t decode_buffer(void *codec_info, const uint8_t *input_buffer, size_
     return d - output_buffer;
 }
 
+const pa_a2dp_codec pa_a2dp_codec_sbc_lq = {
+    .codec_name = "sbc_lq",
+    .codec_description = "SBC (Low Quality)",
+    .codec_id = { A2DP_CODEC_SBC, 0, 0 },
+    .support_backchannel = false,
+    .accept_capabilities = accept_capabilities_lq,
+    .choose_capabilities = choose_capabilities_lq,
+    .fill_capabilities = fill_capabilities_lq,
+    .validate_configuration = validate_configuration_lq,
+    .fill_preferred_configuration = fill_preferred_configuration_lq,
+    .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_none,
+    .encode_buffer = encode_buffer,
+    .decode_buffer = decode_buffer,
+};
+
+const pa_a2dp_codec pa_a2dp_codec_sbc_mq = {
+    .codec_name = "sbc_mq",
+    .codec_description = "SBC (Middle Quality)",
+    .codec_id = { A2DP_CODEC_SBC, 0, 0 },
+    .support_backchannel = false,
+    .accept_capabilities = accept_capabilities_mq,
+    .choose_capabilities = choose_capabilities_mq,
+    .fill_capabilities = fill_capabilities_mq,
+    .validate_configuration = validate_configuration_mq,
+    .fill_preferred_configuration = fill_preferred_configuration_mq,
+    .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_none,
+    .encode_buffer = encode_buffer,
+    .decode_buffer = decode_buffer,
+};
+
+const pa_a2dp_codec pa_a2dp_codec_sbc_hq = {
+    .codec_name = "sbc_hq",
+    .codec_description = "SBC (High Quality)",
+    .codec_id = { A2DP_CODEC_SBC, 0, 0 },
+    .support_backchannel = false,
+    .accept_capabilities = accept_capabilities_hq,
+    .choose_capabilities = choose_capabilities_hq,
+    .fill_capabilities = fill_capabilities_hq,
+    .validate_configuration = validate_configuration_hq,
+    .fill_preferred_configuration = fill_preferred_configuration_hq,
+    .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_none,
+    .encode_buffer = encode_buffer,
+    .decode_buffer = decode_buffer,
+};
+
 const pa_a2dp_codec pa_a2dp_codec_sbc = {
     .codec_name = "sbc",
-    .codec_description = "SBC",
+    .codec_description = "SBC (Automatic Quality)",
     .codec_id = { A2DP_CODEC_SBC, 0, 0 },
     .support_backchannel = false,
     .accept_capabilities = accept_capabilities,
@@ -644,3 +1010,23 @@  const pa_a2dp_codec pa_a2dp_codec_sbc = {
     .encode_buffer = encode_buffer,
     .decode_buffer = decode_buffer,
 };
+
+const pa_a2dp_codec pa_a2dp_codec_sbc_uhq = {
+    .codec_name = "sbc_uhq",
+    .codec_description = "SBC (Ultra High Quality)",
+    .codec_id = { A2DP_CODEC_SBC, 0, 0 },
+    .support_backchannel = false,
+    .accept_capabilities = accept_capabilities_uhq,
+    .choose_capabilities = choose_capabilities_uhq,
+    .fill_capabilities = fill_capabilities_uhq,
+    .validate_configuration = validate_configuration_uhq,
+    .fill_preferred_configuration = fill_preferred_configuration_uhq,
+    .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_none,
+    .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
index 063077237..1fe857202 100644
--- a/src/modules/bluetooth/a2dp-codec-util.c
+++ b/src/modules/bluetooth/a2dp-codec-util.c
@@ -26,20 +26,32 @@ 
 
 #include "a2dp-codec-util.h"
 
+extern const pa_a2dp_codec pa_a2dp_codec_sbc_lq;
 extern const pa_a2dp_codec pa_a2dp_codec_faststream;
+extern const pa_a2dp_codec pa_a2dp_codec_sbc_mq;
 extern const pa_a2dp_codec pa_a2dp_codec_sbc;
 #ifdef HAVE_OPENAPTX
 extern const pa_a2dp_codec pa_a2dp_codec_aptx;
+#endif
+extern const pa_a2dp_codec pa_a2dp_codec_sbc_hq;
+extern const pa_a2dp_codec pa_a2dp_codec_sbc_uhq;
+#ifdef HAVE_OPENAPTX
 extern const pa_a2dp_codec pa_a2dp_codec_aptx_hd;
 #endif
 
 /* 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_faststream,
-    &pa_a2dp_codec_sbc,
+    &pa_a2dp_codec_sbc_lq,
+    &pa_a2dp_codec_faststream,  /* Uses exactly same parameters as SBC-LQ, but with voice backchannel */
+    &pa_a2dp_codec_sbc_mq,
+    &pa_a2dp_codec_sbc,         /* SBC in automatic mode, from SBC-LQ to SBC-HQ; not SBC-UHQ */
 #ifdef HAVE_OPENAPTX
     &pa_a2dp_codec_aptx,
+#endif
+    &pa_a2dp_codec_sbc_hq,      /* SBC-HQ has same quality as aptX */
+    &pa_a2dp_codec_sbc_uhq,     /* SBC-UHQ has higher quality than aptX, but lower than aptX HD */
+#ifdef HAVE_OPENAPTX
     &pa_a2dp_codec_aptx_hd,
 #endif
 };

Comments

Luiz Augusto von Dentz Feb. 6, 2019, 12:04 p.m.
Hi Pali,
On Sun, Feb 3, 2019 at 4:16 PM Pali Rohár <pali.rohar@gmail.com> wrote:
>
> Specify configuration for Low, Middle, High and Ultra High Quality of SBC
> codec. SBC codec in Ultra High Quality has higher quality than aptX.

Low and Middle quality don't seems to serve any purpose since we do
drop quality automatically when necessary, I do understand the Ultra
HQ (perhaps using HD is a better?) is perhaps something we need to
force, though have you tried just having the default SBC at 76
bitpool? Id avoid having multiple endpoints of the same codec as it
just makes discovery slower which extra round-trips.

> Automatic Quality mode matches configuration of SBC codec which was used
> before this change. Which means that it accept configuration between Low
> and High quality.
>
> Current SBC code was extended to allow definitions of arbitrary
> configuration variants of SBC codec parameters.
> ---
>  src/modules/bluetooth/a2dp-codec-sbc.c  | 654 +++++++++++++++++++++++++-------
>  src/modules/bluetooth/a2dp-codec-util.c |  16 +-
>  2 files changed, 534 insertions(+), 136 deletions(-)
>
> diff --git a/src/modules/bluetooth/a2dp-codec-sbc.c b/src/modules/bluetooth/a2dp-codec-sbc.c
> index 034d57a69..a8229735c 100644
> --- a/src/modules/bluetooth/a2dp-codec-sbc.c
> +++ b/src/modules/bluetooth/a2dp-codec-sbc.c
> @@ -36,8 +36,67 @@
>  #include "a2dp-codec-api.h"
>  #include "rtp.h"
>
> -#define SBC_BITPOOL_DEC_LIMIT 32
> -#define SBC_BITPOOL_DEC_STEP 5
> +/* Below are capabilities tables for different qualities. Order of capabilities in tables are from the most preferred to the least preferred. */
> +
> +#define FIXED_SBC_CAPS(mode, freq, bitpool) { .channel_mode = (mode), .frequency = (freq), .min_bitpool = (bitpool), .max_bitpool = (bitpool), .allocation_method = (SBC_ALLOCATION_SNR|SBC_ALLOCATION_LOUDNESS), .subbands = SBC_SUBBANDS_8, .block_length = SBC_BLOCK_LENGTH_16 }
> +
> +/* SBC Low Quality, Joint Stereo is same as FastStream's SBC codec configuration, Mono was calculated to match Joint Stereo */
> +a2dp_sbc_t sbc_lq_caps_table[] = {
> +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_44100, 29), /* 195.7 kbps */
> +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_48000, 29), /* 213   kbps */
> +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_44100, 15), /* 104.7 kbps */
> +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_48000, 15), /* 114   kbps */
> +};
> +
> +/* SBC Middle Quality, based on A2DP spec: Recommended sets of SBC parameters */
> +a2dp_sbc_t sbc_mq_caps_table[] = {
> +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_44100, SBC_BITPOOL_MQ_JOINT_STEREO_44100), /* bitpool = 35, 228.8 kbps */
> +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_48000, SBC_BITPOOL_MQ_JOINT_STEREO_48000), /* bitpool = 33, 237   kbps */
> +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_44100, SBC_BITPOOL_MQ_MONO_44100),         /* bitpool = 19, 126.8 kbps */
> +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_48000, SBC_BITPOOL_MQ_MONO_48000),         /* bitpool = 18, 132   kbps */
> +};
> +
> +/* SBC High Quality, based on A2DP spec: Recommended sets of SBC parameters */
> +a2dp_sbc_t sbc_hq_caps_table[] = {
> +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_44100, SBC_BITPOOL_HQ_JOINT_STEREO_44100), /* bitpool = 53, 328   kbps */
> +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_48000, SBC_BITPOOL_HQ_JOINT_STEREO_48000), /* bitpool = 51, 345   kbps */
> +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_44100, SBC_BITPOOL_HQ_MONO_44100),         /* bitpool = 31, 192.9 kbps */
> +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_48000, SBC_BITPOOL_HQ_MONO_48000),         /* bitpool = 29, 210   kbps */
> +};
> +
> +/* SBC Ultra High Quality, calculated to minimize wasted bytes and to be below max possible 512 kbps */
> +a2dp_sbc_t sbc_uhq_caps_table[] = {
> +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_44100, 76), /* 454.8 kbps */
> +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_48000, 76), /* 495   kbps */
> +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_STEREO,       SBC_SAMPLING_FREQ_44100, 76), /* 452   kbps */
> +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_STEREO,       SBC_SAMPLING_FREQ_48000, 76), /* 492   kbps */
> +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_DUAL_CHANNEL, SBC_SAMPLING_FREQ_44100, 38), /* 452   kbps */
> +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_DUAL_CHANNEL, SBC_SAMPLING_FREQ_48000, 38), /* 492   kbps */
> +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_44100, 37), /* 226   kbps */
> +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_48000, 37), /* 246   kbps */
> +};
> +/* In most cases bluetooth headsets would support only sbc dual channel mode
> + * for 2 channels as they have limited maximal bitpool value to 53. */
> +
> +#undef FIXED_SBC_CAPS
> +
> +/* SBC Auto Quality, only one row which allow any possible configuration up to common High Quality */
> +/* We need to ensure that bitrate is below max possible 512 kbps, therefore limit configuration to High Quality */
> +a2dp_sbc_t sbc_auto_caps_table[] = { {
> +    .channel_mode = SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL | SBC_CHANNEL_MODE_STEREO | SBC_CHANNEL_MODE_JOINT_STEREO,
> +    .frequency = SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 | SBC_SAMPLING_FREQ_44100 | SBC_SAMPLING_FREQ_48000,
> +    .allocation_method = SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS,
> +    .subbands = SBC_SUBBANDS_4 | SBC_SUBBANDS_8,
> +    .block_length = SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16,
> +    .min_bitpool = SBC_MIN_BITPOOL,
> +    .max_bitpool = SBC_BITPOOL_HQ_JOINT_STEREO_44100,
> +} };
> +
> +/* Bitpool limits and steps for reducing bitrate in Auto Quality mode */
> +#define SBC_SEPARATE_BITPOOL_DEC_LIMIT 10
> +#define SBC_COMBINED_BITPOOL_DEC_LIMIT 25
> +#define SBC_SEPARATE_BITPOOL_DEC_STEP   2
> +#define SBC_COMBINED_BITPOOL_DEC_STEP   4
>
>  struct sbc_info {
>      sbc_t sbc;                           /* Codec data */
> @@ -53,62 +112,222 @@ struct sbc_info {
>      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;
> +static bool are_capabilities_compatible(const a2dp_sbc_t *capabilities1, const a2dp_sbc_t *capabilities2) {
> +    if (!(capabilities1->channel_mode & capabilities2->channel_mode))
> +        return false;
>
> -    if (capabilities_size != sizeof(*capabilities))
> +    if (!(capabilities1->frequency & capabilities2->frequency))
>          return false;
>
> -    if (!(capabilities->frequency & (SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 | SBC_SAMPLING_FREQ_44100 | SBC_SAMPLING_FREQ_48000)))
> +    if (!(capabilities1->allocation_method & capabilities2->allocation_method))
>          return false;
>
> -    if (!(capabilities->channel_mode & (SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL | SBC_CHANNEL_MODE_STEREO | SBC_CHANNEL_MODE_JOINT_STEREO)))
> +    if (!(capabilities1->subbands & capabilities2->subbands))
>          return false;
>
> -    if (!(capabilities->allocation_method & (SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS)))
> +    if (!(capabilities1->block_length & capabilities2->block_length))
>          return false;
>
> -    if (!(capabilities->subbands & (SBC_SUBBANDS_4 | SBC_SUBBANDS_8)))
> +    if (!(capabilities1->min_bitpool <= capabilities2->max_bitpool || capabilities2->min_bitpool <= capabilities1->max_bitpool))
>          return false;
>
> -    if (!(capabilities->block_length & (SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16)))
> +    if (capabilities1->min_bitpool > capabilities1->max_bitpool || capabilities2->min_bitpool > capabilities2->max_bitpool)
>          return false;
>
>      return true;
>  }
>
> -static const char *choose_capabilities(const pa_hashmap *capabilities_hashmap, bool for_encoding) {
> +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;
> +
> +    return are_capabilities_compatible(capabilities, &sbc_auto_caps_table[0]);
> +}
> +
> +static bool accept_capabilities_table(const uint8_t *capabilities_buffer, uint8_t capabilities_size, const a2dp_sbc_t capabilities_table[], unsigned capabilities_table_elements) {
> +    const a2dp_sbc_t *capabilities = (const a2dp_sbc_t *) capabilities_buffer;
> +    unsigned i;
> +
> +    if (!accept_capabilities(capabilities_buffer, capabilities_size, false))
> +        return false;
> +
> +    for (i = 0; i < capabilities_table_elements; i++) {
> +        if (!are_capabilities_compatible(capabilities, &capabilities_table[i]))
> +            continue;
> +        return true;
> +    }
> +
> +    return false;
> +}
> +
> +static bool accept_capabilities_lq(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
> +    return accept_capabilities_table(capabilities_buffer, capabilities_size, sbc_lq_caps_table, PA_ELEMENTSOF(sbc_lq_caps_table));
> +}
> +
> +static bool accept_capabilities_mq(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
> +    return accept_capabilities_table(capabilities_buffer, capabilities_size, sbc_mq_caps_table, PA_ELEMENTSOF(sbc_mq_caps_table));
> +}
> +
> +static bool accept_capabilities_hq(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
> +    return accept_capabilities_table(capabilities_buffer, capabilities_size, sbc_hq_caps_table, PA_ELEMENTSOF(sbc_hq_caps_table));
> +}
> +
> +static bool accept_capabilities_uhq(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
> +    return accept_capabilities_table(capabilities_buffer, capabilities_size, sbc_uhq_caps_table, PA_ELEMENTSOF(sbc_uhq_caps_table));
> +}
> +
> +static const char *choose_capabilities_table(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, const a2dp_sbc_t capabilities_table[], unsigned capabilities_table_elements) {
>      const pa_a2dp_codec_capabilities *a2dp_capabilities;
> +    const a2dp_sbc_t *capabilities;
>      const char *key;
>      void *state;
> +    unsigned i;
> +    uint8_t table_range;
> +    uint8_t current_range;
> +    const char *best_key = NULL;
> +    uint8_t best_range = 0;
> +    uint8_t frequency = 0;
> +    bool is_mono = (default_sample_spec->channels <= 1);
> +
> +    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 }
> +    };
> +
> +    for (i = 0; i < PA_ELEMENTSOF(freq_table); i++) {
> +        if (freq_table[i].rate == default_sample_spec->rate) {
> +            frequency = freq_table[i].rate;
> +            break;
> +        }
> +    }
> +
> +    if (!frequency)
> +        return NULL;
>
> -    /* There is no preference, just choose random valid entry */
> +    /* choose remote capabilities which are compatible and its bitpool range is nearest to one from capabilities table */
>      PA_HASHMAP_FOREACH_KV(key, a2dp_capabilities, capabilities_hashmap, state) {
> -        if (accept_capabilities(a2dp_capabilities->buffer, a2dp_capabilities->size, for_encoding))
> -            return key;
> +        /* skip remote capabilities which are not compatible */
> +        if (!accept_capabilities(a2dp_capabilities->buffer, a2dp_capabilities->size, false))
> +            continue;
> +
> +        capabilities = (const a2dp_sbc_t *) a2dp_capabilities->buffer;
> +
> +        /* choose capabilities from our table which is compatible with sample spec and remote capabilities */
> +        for (i = 0; i < capabilities_table_elements; i++) {
> +            if (!are_capabilities_compatible(capabilities, &capabilities_table[i]))
> +                continue;
> +            if (is_mono && !(capabilities->channel_mode & SBC_CHANNEL_MODE_MONO))
> +                continue;
> +            if (!is_mono && !(capabilities->channel_mode & (SBC_CHANNEL_MODE_JOINT_STEREO | SBC_CHANNEL_MODE_STEREO | SBC_CHANNEL_MODE_DUAL_CHANNEL)))
> +                continue;
> +            if (!(capabilities->frequency & frequency))
> +                continue;
> +            break;
> +        }
> +
> +        /* skip if nothing is compatible */
> +        if (i == capabilities_table_elements)
> +            continue;
> +
> +        /* calculate current bitpool range compatible with both remote capabilities and capabilities from our table */
> +        if (capabilities->min_bitpool > capabilities_table[i].min_bitpool) {
> +            if (capabilities->max_bitpool > capabilities_table[i].max_bitpool)
> +                current_range = capabilities_table[i].max_bitpool - capabilities->min_bitpool;
> +            else
> +                current_range = capabilities->max_bitpool - capabilities->min_bitpool;
> +        } else {
> +            if (capabilities->max_bitpool > capabilities_table[i].max_bitpool)
> +                current_range = capabilities_table[i].max_bitpool - capabilities_table[i].min_bitpool;
> +            else
> +                current_range = capabilities->max_bitpool - capabilities_table[i].min_bitpool;
> +        }
> +
> +        table_range = capabilities_table[i].max_bitpool - capabilities_table[i].min_bitpool;
> +
> +        /* use current remote capabilities if its bitpool range is closer to bitpool range in table */
> +        if (!best_key || abs((int)current_range - (int)(table_range)) < abs((int)best_range - (int)(table_range))) {
> +            best_range = current_range;
> +            best_key = key;
> +        }
>      }
>
> -    return NULL;
> +    return best_key;
>  }
>
> -static uint8_t fill_capabilities(uint8_t capabilities_buffer[254]) {
> +static const char *choose_capabilities_lq(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
> +    return choose_capabilities_table(capabilities_hashmap, default_sample_spec, sbc_lq_caps_table, PA_ELEMENTSOF(sbc_lq_caps_table));
> +}
> +
> +static const char *choose_capabilities_mq(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
> +    return choose_capabilities_table(capabilities_hashmap, default_sample_spec, sbc_mq_caps_table, PA_ELEMENTSOF(sbc_mq_caps_table));
> +}
> +
> +static const char *choose_capabilities_hq(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
> +    return choose_capabilities_table(capabilities_hashmap, default_sample_spec, sbc_hq_caps_table, PA_ELEMENTSOF(sbc_hq_caps_table));
> +}
> +
> +static const char *choose_capabilities_uhq(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
> +    return choose_capabilities_table(capabilities_hashmap, default_sample_spec, sbc_uhq_caps_table, PA_ELEMENTSOF(sbc_uhq_caps_table));
> +}
> +
> +static const char *choose_capabilities(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
> +    return choose_capabilities_table(capabilities_hashmap, default_sample_spec, sbc_auto_caps_table, PA_ELEMENTSOF(sbc_auto_caps_table));
> +}
> +
> +static uint8_t fill_capabilities_table(uint8_t capabilities_buffer[254], const a2dp_sbc_t capabilities_table[], unsigned capabilities_table_elements) {
>      a2dp_sbc_t *capabilities = (a2dp_sbc_t *) capabilities_buffer;
> +    unsigned i;
>
>      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_BITPOOL_HQ_JOINT_STEREO_44100;
> +    capabilities->min_bitpool = 0xFF;
> +    capabilities->max_bitpool = 0x00;
> +
> +    for (i = 0; i < capabilities_table_elements; i++) {
> +        capabilities->channel_mode |= capabilities_table[i].channel_mode;
> +        capabilities->frequency |= capabilities_table[i].frequency;
> +        capabilities->allocation_method |= capabilities_table[i].allocation_method;
> +        capabilities->subbands |= capabilities_table[i].subbands;
> +        capabilities->block_length |= capabilities_table[i].block_length;
> +        if (capabilities->min_bitpool > capabilities_table[i].min_bitpool)
> +            capabilities->min_bitpool = capabilities_table[i].min_bitpool;
> +        if (capabilities->max_bitpool < capabilities_table[i].max_bitpool)
> +            capabilities->max_bitpool = capabilities_table[i].max_bitpool;
> +    }
> +
> +    pa_assert(capabilities->min_bitpool != 0xFF);
> +    pa_assert(capabilities->max_bitpool != 0x00);
>
>      return sizeof(*capabilities);
>  }
>
> +static uint8_t fill_capabilities_lq(uint8_t capabilities_buffer[254]) {
> +    return fill_capabilities_table(capabilities_buffer, sbc_lq_caps_table, PA_ELEMENTSOF(sbc_lq_caps_table));
> +}
> +
> +static uint8_t fill_capabilities_mq(uint8_t capabilities_buffer[254]) {
> +    return fill_capabilities_table(capabilities_buffer, sbc_mq_caps_table, PA_ELEMENTSOF(sbc_mq_caps_table));
> +}
> +
> +static uint8_t fill_capabilities_hq(uint8_t capabilities_buffer[254]) {
> +    return fill_capabilities_table(capabilities_buffer, sbc_hq_caps_table, PA_ELEMENTSOF(sbc_hq_caps_table));
> +}
> +
> +static uint8_t fill_capabilities_uhq(uint8_t capabilities_buffer[254]) {
> +    return fill_capabilities_table(capabilities_buffer, sbc_uhq_caps_table, PA_ELEMENTSOF(sbc_uhq_caps_table));
> +}
> +
> +static uint8_t fill_capabilities(uint8_t capabilities_buffer[254]) {
> +    return fill_capabilities_table(capabilities_buffer, sbc_auto_caps_table, PA_ELEMENTSOF(sbc_auto_caps_table));
> +}
> +
>  static bool validate_configuration(const uint8_t *config_buffer, uint8_t config_size) {
>      const a2dp_sbc_t *config = (const a2dp_sbc_t *) config_buffer;
>
> @@ -153,49 +372,40 @@ static bool validate_configuration(const uint8_t *config_buffer, uint8_t config_
>      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:
> -            switch (mode) {
> -                case SBC_CHANNEL_MODE_MONO:
> -                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
> -                case SBC_CHANNEL_MODE_STEREO:
> -                case SBC_CHANNEL_MODE_JOINT_STEREO:
> -                    return SBC_BITPOOL_HQ_JOINT_STEREO_44100;
> -            }
> +static bool validate_configuration_table(const uint8_t *config_buffer, uint8_t config_size, const a2dp_sbc_t capabilities_table[], unsigned capabilities_table_elements) {
> +    if (!validate_configuration(config_buffer, config_size))
> +        return false;
>
> -        case SBC_SAMPLING_FREQ_44100:
> -            switch (mode) {
> -                case SBC_CHANNEL_MODE_MONO:
> -                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
> -                    return SBC_BITPOOL_HQ_MONO_44100;
> +    if (!accept_capabilities_table(config_buffer, config_size, capabilities_table, capabilities_table_elements)) {
> +        pa_log_error("Some configuration settings are invalid for current quality");
> +        return false;
> +    }
>
> -                case SBC_CHANNEL_MODE_STEREO:
> -                case SBC_CHANNEL_MODE_JOINT_STEREO:
> -                    return SBC_BITPOOL_HQ_JOINT_STEREO_44100;
> -            }
> +    return true;
> +}
>
> -        case SBC_SAMPLING_FREQ_48000:
> -            switch (mode) {
> -                case SBC_CHANNEL_MODE_MONO:
> -                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
> -                    return SBC_BITPOOL_HQ_MONO_48000;
> +static bool validate_configuration_lq(const uint8_t *config_buffer, uint8_t config_size) {
> +    return validate_configuration_table(config_buffer, config_size, sbc_lq_caps_table, PA_ELEMENTSOF(sbc_lq_caps_table));
> +}
>
> -                case SBC_CHANNEL_MODE_STEREO:
> -                case SBC_CHANNEL_MODE_JOINT_STEREO:
> -                    return SBC_BITPOOL_HQ_JOINT_STEREO_48000;
> -            }
> -    }
> +static bool validate_configuration_mq(const uint8_t *config_buffer, uint8_t config_size) {
> +    return validate_configuration_table(config_buffer, config_size, sbc_mq_caps_table, PA_ELEMENTSOF(sbc_mq_caps_table));
> +}
>
> -    pa_assert_not_reached();
> +static bool validate_configuration_hq(const uint8_t *config_buffer, uint8_t config_size) {
> +    return validate_configuration_table(config_buffer, config_size, sbc_hq_caps_table, PA_ELEMENTSOF(sbc_hq_caps_table));
>  }
>
> -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]) {
> +static bool validate_configuration_uhq(const uint8_t *config_buffer, uint8_t config_size) {
> +    return validate_configuration_table(config_buffer, config_size, sbc_uhq_caps_table, PA_ELEMENTSOF(sbc_uhq_caps_table));
> +}
> +
> +static uint8_t fill_preferred_configuration_table(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[254], const a2dp_sbc_t capabilities_table[], unsigned capabilities_table_elements) {
>      a2dp_sbc_t *config = (a2dp_sbc_t *) config_buffer;
>      const a2dp_sbc_t *capabilities = (const a2dp_sbc_t *) capabilities_buffer;
> -    int i;
> +    bool is_mono = (default_sample_spec->channels <= 1);
> +    unsigned i;
> +    int j;
>
>      static const struct {
>          uint32_t rate;
> @@ -215,96 +425,184 @@ static uint8_t fill_preferred_configuration(const pa_sample_spec *default_sample
>      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;
> +    for (j = 0; (unsigned) j < PA_ELEMENTSOF(freq_table); j++) {
> +        if (freq_table[j].rate >= default_sample_spec->rate && (capabilities->frequency & freq_table[j].cap)) {
> +            for (i = 0; i < capabilities_table_elements; i++) {
> +                if (capabilities_table[i].frequency & freq_table[j].cap) {
> +                    config->frequency = freq_table[j].cap;
> +                    break;
> +                }
> +            }
> +            if (i != capabilities_table_elements)
> +                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 ((unsigned) j == PA_ELEMENTSOF(freq_table)) {
> +        for (--j; j >= 0; j--) {
> +            if (capabilities->frequency & freq_table[j].cap) {
> +                for (i = 0; i < capabilities_table_elements; i++) {
> +                    if (capabilities_table[i].frequency & freq_table[j].cap) {
> +                        config->frequency = freq_table[j].cap;
> +                        break;
> +                    }
> +                }
> +                if (i != capabilities_table_elements)
> +                    break;
>              }
>          }
>
> -        if (i < 0) {
> -            pa_log_error("Not suitable sample rate");
> +        if (j < 0) {
> +            pa_log_error("No 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;
> +    pa_assert((unsigned) j < PA_ELEMENTSOF(freq_table));
> +
> +    for (i = 0; i < capabilities_table_elements; i++) {
> +        if ((capabilities->block_length & SBC_BLOCK_LENGTH_16) && (capabilities_table[i].block_length & SBC_BLOCK_LENGTH_16))
> +            config->block_length = SBC_BLOCK_LENGTH_16;
> +        else if ((capabilities->block_length & SBC_BLOCK_LENGTH_12) && (capabilities_table[i].block_length & SBC_BLOCK_LENGTH_12))
> +            config->block_length = SBC_BLOCK_LENGTH_12;
> +        else if ((capabilities->block_length & SBC_BLOCK_LENGTH_8) && (capabilities_table[i].block_length & SBC_BLOCK_LENGTH_8))
> +            config->block_length = SBC_BLOCK_LENGTH_8;
> +        else if ((capabilities->block_length & SBC_BLOCK_LENGTH_4) && (capabilities_table[i].block_length & SBC_BLOCK_LENGTH_4))
> +            config->block_length = SBC_BLOCK_LENGTH_4;
>          else {
> -            pa_log_error("No supported channel modes");
> -            return 0;
> +            pa_log_debug("No supported block lengths in table %u", i);
> +            continue;
>          }
> -    } 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;
> +
> +        if ((capabilities->subbands & SBC_SUBBANDS_8) && (capabilities_table[i].subbands & SBC_SUBBANDS_8))
> +            config->subbands = SBC_SUBBANDS_8;
> +        else if ((capabilities->subbands & SBC_SUBBANDS_4) && (capabilities_table[i].subbands & SBC_SUBBANDS_4))
> +            config->subbands = SBC_SUBBANDS_4;
>          else {
> -            pa_log_error("No supported channel modes");
> -            return 0;
> +            pa_log_debug("No supported subbands in table %u", i);
> +            continue;
>          }
> -    }
>
> -    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->allocation_method & SBC_ALLOCATION_LOUDNESS) && (capabilities_table[i].allocation_method & SBC_ALLOCATION_LOUDNESS))
> +            config->allocation_method = SBC_ALLOCATION_LOUDNESS;
> +        else if ((capabilities->allocation_method & SBC_ALLOCATION_SNR) && (capabilities_table[i].allocation_method & SBC_ALLOCATION_SNR))
> +            config->allocation_method = SBC_ALLOCATION_SNR;
> +        else {
> +            pa_log_debug("No supported allocation method in table %u", i);
> +            continue;
> +        }
>
> -    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 (is_mono) {
> +            if ((capabilities->channel_mode & SBC_CHANNEL_MODE_MONO) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_MONO))
> +                config->channel_mode = SBC_CHANNEL_MODE_MONO;
> +            else if ((capabilities->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO))
> +                config->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
> +            else if ((capabilities->channel_mode & SBC_CHANNEL_MODE_STEREO) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_STEREO))
> +                config->channel_mode = SBC_CHANNEL_MODE_STEREO;
> +            else if ((capabilities->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL))
> +                config->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
> +            else {
> +                pa_log_debug("No supported channel mode in table %u", i);
> +                continue;
> +            }
> +        } else {
> +            if ((capabilities->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO))
> +                config->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
> +            else if ((capabilities->channel_mode & SBC_CHANNEL_MODE_STEREO) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_STEREO))
> +                config->channel_mode = SBC_CHANNEL_MODE_STEREO;
> +            else if ((capabilities->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL))
> +                config->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
> +            else if ((capabilities->channel_mode & SBC_CHANNEL_MODE_MONO) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_MONO))
> +                config->channel_mode = SBC_CHANNEL_MODE_MONO;
> +            else {
> +                pa_log_debug("No supported channel mode in table %u", i);
> +                continue;
> +            }
> +        }
> +
> +        config->min_bitpool = PA_MAX(capabilities->min_bitpool, capabilities_table[i].min_bitpool);
> +        config->max_bitpool = PA_MIN(capabilities->max_bitpool, capabilities_table[i].max_bitpool);
> +
> +        if (config->min_bitpool > config->max_bitpool) {
> +            pa_log_debug("No supported bitpool in table %u", i);
> +            continue;
> +        }
> +
> +        break;
>      }
>
> -    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");
> +    if (i == capabilities_table_elements) {
> +        pa_log_error("No supported configuration");
>          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);
> +    return sizeof(*config);
> +}
>
> -    if (config->min_bitpool > config->max_bitpool) {
> -        pa_log_error("No supported bitpool");
> -        return 0;
> +static uint8_t fill_preferred_configuration_lq(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[254]) {
> +    return fill_preferred_configuration_table(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, sbc_lq_caps_table, PA_ELEMENTSOF(sbc_lq_caps_table));
> +}
> +
> +static uint8_t fill_preferred_configuration_mq(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[254]) {
> +    return fill_preferred_configuration_table(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, sbc_mq_caps_table, PA_ELEMENTSOF(sbc_mq_caps_table));
> +}
> +
> +static uint8_t fill_preferred_configuration_hq(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[254]) {
> +    return fill_preferred_configuration_table(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, sbc_hq_caps_table, PA_ELEMENTSOF(sbc_hq_caps_table));
> +}
> +
> +static uint8_t fill_preferred_configuration_uhq(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[254]) {
> +    return fill_preferred_configuration_table(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, sbc_uhq_caps_table, PA_ELEMENTSOF(sbc_uhq_caps_table));
> +}
> +
> +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:
> +            switch (mode) {
> +                case SBC_CHANNEL_MODE_MONO:
> +                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
> +                case SBC_CHANNEL_MODE_STEREO:
> +                case SBC_CHANNEL_MODE_JOINT_STEREO:
> +                    return SBC_BITPOOL_HQ_JOINT_STEREO_44100;
> +            }
> +
> +        case SBC_SAMPLING_FREQ_44100:
> +            switch (mode) {
> +                case SBC_CHANNEL_MODE_MONO:
> +                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
> +                    return SBC_BITPOOL_HQ_MONO_44100;
> +
> +                case SBC_CHANNEL_MODE_STEREO:
> +                case SBC_CHANNEL_MODE_JOINT_STEREO:
> +                    return SBC_BITPOOL_HQ_JOINT_STEREO_44100;
> +            }
> +
> +        case SBC_SAMPLING_FREQ_48000:
> +            switch (mode) {
> +                case SBC_CHANNEL_MODE_MONO:
> +                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
> +                    return SBC_BITPOOL_HQ_MONO_48000;
> +
> +                case SBC_CHANNEL_MODE_STEREO:
> +                case SBC_CHANNEL_MODE_JOINT_STEREO:
> +                    return SBC_BITPOOL_HQ_JOINT_STEREO_48000;
> +            }
>      }
>
> -    return sizeof(*config);
> +    pa_assert_not_reached();
> +}
> +
> +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;
> +    uint8_t ret;
> +
> +    ret = fill_preferred_configuration_table(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, sbc_auto_caps_table, PA_ELEMENTSOF(sbc_auto_caps_table));
> +    config->max_bitpool = PA_MIN(default_bitpool(config->frequency, config->channel_mode), config->max_bitpool);
> +    config->max_bitpool = PA_MAX(config->max_bitpool, config->min_bitpool);
> +
> +    return ret;
>  }
>
>  static void set_params(struct sbc_info *sbc_info) {
> @@ -486,13 +784,17 @@ static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
>      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->mode == SBC_CHANNEL_MODE_MONO || sbc_info->mode == SBC_CHANNEL_MODE_DUAL_CHANNEL) {
> +        /* For Mono and Dual Channel modes bitpool value is separete for each channel */
> +        bitpool = sbc_info->sbc.bitpool - SBC_SEPARATE_BITPOOL_DEC_STEP;
> +        if (bitpool <= SBC_SEPARATE_BITPOOL_DEC_LIMIT)
> +            return 0;
> +    } else {
> +        /* For Stereo modes bitpool value is combined for both channels */
> +        bitpool = sbc_info->sbc.bitpool - SBC_COMBINED_BITPOOL_DEC_STEP;
> +        if (bitpool <= SBC_COMBINED_BITPOOL_DEC_LIMIT)
> +            return 0;
> +    }
>
>      if (sbc_info->sbc.bitpool == bitpool)
>          return 0;
> @@ -501,6 +803,10 @@ static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
>      return get_block_size(codec_info, write_link_mtu);
>  }
>
> +static size_t reduce_encoder_bitrate_none(void *codec_info, size_t write_link_mtu) {
> +    return 0;
> +}
> +
>  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;
> @@ -625,9 +931,69 @@ static size_t decode_buffer(void *codec_info, const uint8_t *input_buffer, size_
>      return d - output_buffer;
>  }
>
> +const pa_a2dp_codec pa_a2dp_codec_sbc_lq = {
> +    .codec_name = "sbc_lq",
> +    .codec_description = "SBC (Low Quality)",
> +    .codec_id = { A2DP_CODEC_SBC, 0, 0 },
> +    .support_backchannel = false,
> +    .accept_capabilities = accept_capabilities_lq,
> +    .choose_capabilities = choose_capabilities_lq,
> +    .fill_capabilities = fill_capabilities_lq,
> +    .validate_configuration = validate_configuration_lq,
> +    .fill_preferred_configuration = fill_preferred_configuration_lq,
> +    .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_none,
> +    .encode_buffer = encode_buffer,
> +    .decode_buffer = decode_buffer,
> +};
> +
> +const pa_a2dp_codec pa_a2dp_codec_sbc_mq = {
> +    .codec_name = "sbc_mq",
> +    .codec_description = "SBC (Middle Quality)",
> +    .codec_id = { A2DP_CODEC_SBC, 0, 0 },
> +    .support_backchannel = false,
> +    .accept_capabilities = accept_capabilities_mq,
> +    .choose_capabilities = choose_capabilities_mq,
> +    .fill_capabilities = fill_capabilities_mq,
> +    .validate_configuration = validate_configuration_mq,
> +    .fill_preferred_configuration = fill_preferred_configuration_mq,
> +    .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_none,
> +    .encode_buffer = encode_buffer,
> +    .decode_buffer = decode_buffer,
> +};
> +
> +const pa_a2dp_codec pa_a2dp_codec_sbc_hq = {
> +    .codec_name = "sbc_hq",
> +    .codec_description = "SBC (High Quality)",
> +    .codec_id = { A2DP_CODEC_SBC, 0, 0 },
> +    .support_backchannel = false,
> +    .accept_capabilities = accept_capabilities_hq,
> +    .choose_capabilities = choose_capabilities_hq,
> +    .fill_capabilities = fill_capabilities_hq,
> +    .validate_configuration = validate_configuration_hq,
> +    .fill_preferred_configuration = fill_preferred_configuration_hq,
> +    .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_none,
> +    .encode_buffer = encode_buffer,
> +    .decode_buffer = decode_buffer,
> +};
> +
>  const pa_a2dp_codec pa_a2dp_codec_sbc = {
>      .codec_name = "sbc",
> -    .codec_description = "SBC",
> +    .codec_description = "SBC (Automatic Quality)",
>      .codec_id = { A2DP_CODEC_SBC, 0, 0 },
>      .support_backchannel = false,
>      .accept_capabilities = accept_capabilities,
> @@ -644,3 +1010,23 @@ const pa_a2dp_codec pa_a2dp_codec_sbc = {
>      .encode_buffer = encode_buffer,
>      .decode_buffer = decode_buffer,
>  };
> +
> +const pa_a2dp_codec pa_a2dp_codec_sbc_uhq = {
> +    .codec_name = "sbc_uhq",
> +    .codec_description = "SBC (Ultra High Quality)",
> +    .codec_id = { A2DP_CODEC_SBC, 0, 0 },
> +    .support_backchannel = false,
> +    .accept_capabilities = accept_capabilities_uhq,
> +    .choose_capabilities = choose_capabilities_uhq,
> +    .fill_capabilities = fill_capabilities_uhq,
> +    .validate_configuration = validate_configuration_uhq,
> +    .fill_preferred_configuration = fill_preferred_configuration_uhq,
> +    .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_none,
> +    .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
> index 063077237..1fe857202 100644
> --- a/src/modules/bluetooth/a2dp-codec-util.c
> +++ b/src/modules/bluetooth/a2dp-codec-util.c
> @@ -26,20 +26,32 @@
>
>  #include "a2dp-codec-util.h"
>
> +extern const pa_a2dp_codec pa_a2dp_codec_sbc_lq;
>  extern const pa_a2dp_codec pa_a2dp_codec_faststream;
> +extern const pa_a2dp_codec pa_a2dp_codec_sbc_mq;
>  extern const pa_a2dp_codec pa_a2dp_codec_sbc;
>  #ifdef HAVE_OPENAPTX
>  extern const pa_a2dp_codec pa_a2dp_codec_aptx;
> +#endif
> +extern const pa_a2dp_codec pa_a2dp_codec_sbc_hq;
> +extern const pa_a2dp_codec pa_a2dp_codec_sbc_uhq;
> +#ifdef HAVE_OPENAPTX
>  extern const pa_a2dp_codec pa_a2dp_codec_aptx_hd;
>  #endif
>
>  /* 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_faststream,
> -    &pa_a2dp_codec_sbc,
> +    &pa_a2dp_codec_sbc_lq,
> +    &pa_a2dp_codec_faststream,  /* Uses exactly same parameters as SBC-LQ, but with voice backchannel */
> +    &pa_a2dp_codec_sbc_mq,
> +    &pa_a2dp_codec_sbc,         /* SBC in automatic mode, from SBC-LQ to SBC-HQ; not SBC-UHQ */
>  #ifdef HAVE_OPENAPTX
>      &pa_a2dp_codec_aptx,
> +#endif
> +    &pa_a2dp_codec_sbc_hq,      /* SBC-HQ has same quality as aptX */
> +    &pa_a2dp_codec_sbc_uhq,     /* SBC-UHQ has higher quality than aptX, but lower than aptX HD */
> +#ifdef HAVE_OPENAPTX
>      &pa_a2dp_codec_aptx_hd,
>  #endif
>  };
> --
> 2.11.0
>
Pali Rohár Feb. 6, 2019, 12:58 p.m.
On Wednesday 06 February 2019 14:04:28 Luiz Augusto von Dentz wrote:
> Hi Pali,
> On Sun, Feb 3, 2019 at 4:16 PM Pali Rohár <pali.rohar@gmail.com> wrote:
> >
> > Specify configuration for Low, Middle, High and Ultra High Quality of SBC
> > codec. SBC codec in Ultra High Quality has higher quality than aptX.
> 
> Low and Middle quality don't seems to serve any purpose since we do
> drop quality automatically when necessary,

It may be useful in more cases:

1) To know what type of quality is used. Auto means "user does not know
used quality".

2) To force lower bitrate if higher one is causing problems (and
decreasing is not working correctly) in bluetooth transport. E.g. you
need temporary lower bitrate and later increase it back. Whole auto
quality mode can only lower it, so once it go into low mode it never
return back.

E.g. you connect phone with laptop, listening music and you want to
transfer big file...

3) To do quality comparison. In some cases other side can have problems
with higher bitrate. So with this profile you can lower it.

> I do understand the Ultra HQ (perhaps using HD is a better?)

HD is definition, not quality. So I think it is better to have quality
in names.

> is perhaps something we need to
> force, though have you tried just having the default SBC at 76
> bitpool?

No, I do not have headset which support bitpool 76. But you can use
dual channel mode with bitpool 38 which is very similar to stereo mode
with bitpool 76. And dual_channel/38 I tested and is working. See that
Ultra High Quality mode prefer joint_stereo/76, but allow to use also
dual_channel/38.

In dual channel mode, channels have separate bitpools; in stereo mode
channels share one bitpool.

> Id avoid having multiple endpoints of the same codec as it
> just makes discovery slower which extra round-trips.

It is problem? With multiple endpoints it is possible to "enforce"
quality. IIRC this is reason why e.g. aptX codec is used -- it is
enforced in specific high quality.

> > Automatic Quality mode matches configuration of SBC codec which was used
> > before this change. Which means that it accept configuration between Low
> > and High quality.
> >
> > Current SBC code was extended to allow definitions of arbitrary
> > configuration variants of SBC codec parameters.
> > ---
> >  src/modules/bluetooth/a2dp-codec-sbc.c  | 654 +++++++++++++++++++++++++-------
> >  src/modules/bluetooth/a2dp-codec-util.c |  16 +-
> >  2 files changed, 534 insertions(+), 136 deletions(-)
> >
> > diff --git a/src/modules/bluetooth/a2dp-codec-sbc.c b/src/modules/bluetooth/a2dp-codec-sbc.c
> > index 034d57a69..a8229735c 100644
> > --- a/src/modules/bluetooth/a2dp-codec-sbc.c
> > +++ b/src/modules/bluetooth/a2dp-codec-sbc.c
> > @@ -36,8 +36,67 @@
> >  #include "a2dp-codec-api.h"
> >  #include "rtp.h"
> >
> > -#define SBC_BITPOOL_DEC_LIMIT 32
> > -#define SBC_BITPOOL_DEC_STEP 5
> > +/* Below are capabilities tables for different qualities. Order of capabilities in tables are from the most preferred to the least preferred. */
> > +
> > +#define FIXED_SBC_CAPS(mode, freq, bitpool) { .channel_mode = (mode), .frequency = (freq), .min_bitpool = (bitpool), .max_bitpool = (bitpool), .allocation_method = (SBC_ALLOCATION_SNR|SBC_ALLOCATION_LOUDNESS), .subbands = SBC_SUBBANDS_8, .block_length = SBC_BLOCK_LENGTH_16 }
> > +
> > +/* SBC Low Quality, Joint Stereo is same as FastStream's SBC codec configuration, Mono was calculated to match Joint Stereo */
> > +a2dp_sbc_t sbc_lq_caps_table[] = {
> > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_44100, 29), /* 195.7 kbps */
> > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_48000, 29), /* 213   kbps */
> > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_44100, 15), /* 104.7 kbps */
> > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_48000, 15), /* 114   kbps */
> > +};
> > +
> > +/* SBC Middle Quality, based on A2DP spec: Recommended sets of SBC parameters */
> > +a2dp_sbc_t sbc_mq_caps_table[] = {
> > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_44100, SBC_BITPOOL_MQ_JOINT_STEREO_44100), /* bitpool = 35, 228.8 kbps */
> > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_48000, SBC_BITPOOL_MQ_JOINT_STEREO_48000), /* bitpool = 33, 237   kbps */
> > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_44100, SBC_BITPOOL_MQ_MONO_44100),         /* bitpool = 19, 126.8 kbps */
> > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_48000, SBC_BITPOOL_MQ_MONO_48000),         /* bitpool = 18, 132   kbps */
> > +};
> > +
> > +/* SBC High Quality, based on A2DP spec: Recommended sets of SBC parameters */
> > +a2dp_sbc_t sbc_hq_caps_table[] = {
> > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_44100, SBC_BITPOOL_HQ_JOINT_STEREO_44100), /* bitpool = 53, 328   kbps */
> > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_48000, SBC_BITPOOL_HQ_JOINT_STEREO_48000), /* bitpool = 51, 345   kbps */
> > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_44100, SBC_BITPOOL_HQ_MONO_44100),         /* bitpool = 31, 192.9 kbps */
> > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_48000, SBC_BITPOOL_HQ_MONO_48000),         /* bitpool = 29, 210   kbps */
> > +};
> > +
> > +/* SBC Ultra High Quality, calculated to minimize wasted bytes and to be below max possible 512 kbps */
> > +a2dp_sbc_t sbc_uhq_caps_table[] = {
> > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_44100, 76), /* 454.8 kbps */
> > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_48000, 76), /* 495   kbps */
> > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_STEREO,       SBC_SAMPLING_FREQ_44100, 76), /* 452   kbps */
> > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_STEREO,       SBC_SAMPLING_FREQ_48000, 76), /* 492   kbps */
> > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_DUAL_CHANNEL, SBC_SAMPLING_FREQ_44100, 38), /* 452   kbps */
> > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_DUAL_CHANNEL, SBC_SAMPLING_FREQ_48000, 38), /* 492   kbps */
> > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_44100, 37), /* 226   kbps */
> > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_48000, 37), /* 246   kbps */
> > +};
> > +/* In most cases bluetooth headsets would support only sbc dual channel mode
> > + * for 2 channels as they have limited maximal bitpool value to 53. */
> > +
> > +#undef FIXED_SBC_CAPS
> > +
> > +/* SBC Auto Quality, only one row which allow any possible configuration up to common High Quality */
> > +/* We need to ensure that bitrate is below max possible 512 kbps, therefore limit configuration to High Quality */
> > +a2dp_sbc_t sbc_auto_caps_table[] = { {
> > +    .channel_mode = SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL | SBC_CHANNEL_MODE_STEREO | SBC_CHANNEL_MODE_JOINT_STEREO,
> > +    .frequency = SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 | SBC_SAMPLING_FREQ_44100 | SBC_SAMPLING_FREQ_48000,
> > +    .allocation_method = SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS,
> > +    .subbands = SBC_SUBBANDS_4 | SBC_SUBBANDS_8,
> > +    .block_length = SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16,
> > +    .min_bitpool = SBC_MIN_BITPOOL,
> > +    .max_bitpool = SBC_BITPOOL_HQ_JOINT_STEREO_44100,
> > +} };
> > +
> > +/* Bitpool limits and steps for reducing bitrate in Auto Quality mode */
> > +#define SBC_SEPARATE_BITPOOL_DEC_LIMIT 10
> > +#define SBC_COMBINED_BITPOOL_DEC_LIMIT 25
> > +#define SBC_SEPARATE_BITPOOL_DEC_STEP   2
> > +#define SBC_COMBINED_BITPOOL_DEC_STEP   4
> >
> >  struct sbc_info {
> >      sbc_t sbc;                           /* Codec data */
> > @@ -53,62 +112,222 @@ struct sbc_info {
> >      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;
> > +static bool are_capabilities_compatible(const a2dp_sbc_t *capabilities1, const a2dp_sbc_t *capabilities2) {
> > +    if (!(capabilities1->channel_mode & capabilities2->channel_mode))
> > +        return false;
> >
> > -    if (capabilities_size != sizeof(*capabilities))
> > +    if (!(capabilities1->frequency & capabilities2->frequency))
> >          return false;
> >
> > -    if (!(capabilities->frequency & (SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 | SBC_SAMPLING_FREQ_44100 | SBC_SAMPLING_FREQ_48000)))
> > +    if (!(capabilities1->allocation_method & capabilities2->allocation_method))
> >          return false;
> >
> > -    if (!(capabilities->channel_mode & (SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL | SBC_CHANNEL_MODE_STEREO | SBC_CHANNEL_MODE_JOINT_STEREO)))
> > +    if (!(capabilities1->subbands & capabilities2->subbands))
> >          return false;
> >
> > -    if (!(capabilities->allocation_method & (SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS)))
> > +    if (!(capabilities1->block_length & capabilities2->block_length))
> >          return false;
> >
> > -    if (!(capabilities->subbands & (SBC_SUBBANDS_4 | SBC_SUBBANDS_8)))
> > +    if (!(capabilities1->min_bitpool <= capabilities2->max_bitpool || capabilities2->min_bitpool <= capabilities1->max_bitpool))
> >          return false;
> >
> > -    if (!(capabilities->block_length & (SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16)))
> > +    if (capabilities1->min_bitpool > capabilities1->max_bitpool || capabilities2->min_bitpool > capabilities2->max_bitpool)
> >          return false;
> >
> >      return true;
> >  }
> >
> > -static const char *choose_capabilities(const pa_hashmap *capabilities_hashmap, bool for_encoding) {
> > +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;
> > +
> > +    return are_capabilities_compatible(capabilities, &sbc_auto_caps_table[0]);
> > +}
> > +
> > +static bool accept_capabilities_table(const uint8_t *capabilities_buffer, uint8_t capabilities_size, const a2dp_sbc_t capabilities_table[], unsigned capabilities_table_elements) {
> > +    const a2dp_sbc_t *capabilities = (const a2dp_sbc_t *) capabilities_buffer;
> > +    unsigned i;
> > +
> > +    if (!accept_capabilities(capabilities_buffer, capabilities_size, false))
> > +        return false;
> > +
> > +    for (i = 0; i < capabilities_table_elements; i++) {
> > +        if (!are_capabilities_compatible(capabilities, &capabilities_table[i]))
> > +            continue;
> > +        return true;
> > +    }
> > +
> > +    return false;
> > +}
> > +
> > +static bool accept_capabilities_lq(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
> > +    return accept_capabilities_table(capabilities_buffer, capabilities_size, sbc_lq_caps_table, PA_ELEMENTSOF(sbc_lq_caps_table));
> > +}
> > +
> > +static bool accept_capabilities_mq(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
> > +    return accept_capabilities_table(capabilities_buffer, capabilities_size, sbc_mq_caps_table, PA_ELEMENTSOF(sbc_mq_caps_table));
> > +}
> > +
> > +static bool accept_capabilities_hq(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
> > +    return accept_capabilities_table(capabilities_buffer, capabilities_size, sbc_hq_caps_table, PA_ELEMENTSOF(sbc_hq_caps_table));
> > +}
> > +
> > +static bool accept_capabilities_uhq(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
> > +    return accept_capabilities_table(capabilities_buffer, capabilities_size, sbc_uhq_caps_table, PA_ELEMENTSOF(sbc_uhq_caps_table));
> > +}
> > +
> > +static const char *choose_capabilities_table(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, const a2dp_sbc_t capabilities_table[], unsigned capabilities_table_elements) {
> >      const pa_a2dp_codec_capabilities *a2dp_capabilities;
> > +    const a2dp_sbc_t *capabilities;
> >      const char *key;
> >      void *state;
> > +    unsigned i;
> > +    uint8_t table_range;
> > +    uint8_t current_range;
> > +    const char *best_key = NULL;
> > +    uint8_t best_range = 0;
> > +    uint8_t frequency = 0;
> > +    bool is_mono = (default_sample_spec->channels <= 1);
> > +
> > +    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 }
> > +    };
> > +
> > +    for (i = 0; i < PA_ELEMENTSOF(freq_table); i++) {
> > +        if (freq_table[i].rate == default_sample_spec->rate) {
> > +            frequency = freq_table[i].rate;
> > +            break;
> > +        }
> > +    }
> > +
> > +    if (!frequency)
> > +        return NULL;
> >
> > -    /* There is no preference, just choose random valid entry */
> > +    /* choose remote capabilities which are compatible and its bitpool range is nearest to one from capabilities table */
> >      PA_HASHMAP_FOREACH_KV(key, a2dp_capabilities, capabilities_hashmap, state) {
> > -        if (accept_capabilities(a2dp_capabilities->buffer, a2dp_capabilities->size, for_encoding))
> > -            return key;
> > +        /* skip remote capabilities which are not compatible */
> > +        if (!accept_capabilities(a2dp_capabilities->buffer, a2dp_capabilities->size, false))
> > +            continue;
> > +
> > +        capabilities = (const a2dp_sbc_t *) a2dp_capabilities->buffer;
> > +
> > +        /* choose capabilities from our table which is compatible with sample spec and remote capabilities */
> > +        for (i = 0; i < capabilities_table_elements; i++) {
> > +            if (!are_capabilities_compatible(capabilities, &capabilities_table[i]))
> > +                continue;
> > +            if (is_mono && !(capabilities->channel_mode & SBC_CHANNEL_MODE_MONO))
> > +                continue;
> > +            if (!is_mono && !(capabilities->channel_mode & (SBC_CHANNEL_MODE_JOINT_STEREO | SBC_CHANNEL_MODE_STEREO | SBC_CHANNEL_MODE_DUAL_CHANNEL)))
> > +                continue;
> > +            if (!(capabilities->frequency & frequency))
> > +                continue;
> > +            break;
> > +        }
> > +
> > +        /* skip if nothing is compatible */
> > +        if (i == capabilities_table_elements)
> > +            continue;
> > +
> > +        /* calculate current bitpool range compatible with both remote capabilities and capabilities from our table */
> > +        if (capabilities->min_bitpool > capabilities_table[i].min_bitpool) {
> > +            if (capabilities->max_bitpool > capabilities_table[i].max_bitpool)
> > +                current_range = capabilities_table[i].max_bitpool - capabilities->min_bitpool;
> > +            else
> > +                current_range = capabilities->max_bitpool - capabilities->min_bitpool;
> > +        } else {
> > +            if (capabilities->max_bitpool > capabilities_table[i].max_bitpool)
> > +                current_range = capabilities_table[i].max_bitpool - capabilities_table[i].min_bitpool;
> > +            else
> > +                current_range = capabilities->max_bitpool - capabilities_table[i].min_bitpool;
> > +        }
> > +
> > +        table_range = capabilities_table[i].max_bitpool - capabilities_table[i].min_bitpool;
> > +
> > +        /* use current remote capabilities if its bitpool range is closer to bitpool range in table */
> > +        if (!best_key || abs((int)current_range - (int)(table_range)) < abs((int)best_range - (int)(table_range))) {
> > +            best_range = current_range;
> > +            best_key = key;
> > +        }
> >      }
> >
> > -    return NULL;
> > +    return best_key;
> >  }
> >
> > -static uint8_t fill_capabilities(uint8_t capabilities_buffer[254]) {
> > +static const char *choose_capabilities_lq(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
> > +    return choose_capabilities_table(capabilities_hashmap, default_sample_spec, sbc_lq_caps_table, PA_ELEMENTSOF(sbc_lq_caps_table));
> > +}
> > +
> > +static const char *choose_capabilities_mq(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
> > +    return choose_capabilities_table(capabilities_hashmap, default_sample_spec, sbc_mq_caps_table, PA_ELEMENTSOF(sbc_mq_caps_table));
> > +}
> > +
> > +static const char *choose_capabilities_hq(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
> > +    return choose_capabilities_table(capabilities_hashmap, default_sample_spec, sbc_hq_caps_table, PA_ELEMENTSOF(sbc_hq_caps_table));
> > +}
> > +
> > +static const char *choose_capabilities_uhq(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
> > +    return choose_capabilities_table(capabilities_hashmap, default_sample_spec, sbc_uhq_caps_table, PA_ELEMENTSOF(sbc_uhq_caps_table));
> > +}
> > +
> > +static const char *choose_capabilities(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
> > +    return choose_capabilities_table(capabilities_hashmap, default_sample_spec, sbc_auto_caps_table, PA_ELEMENTSOF(sbc_auto_caps_table));
> > +}
> > +
> > +static uint8_t fill_capabilities_table(uint8_t capabilities_buffer[254], const a2dp_sbc_t capabilities_table[], unsigned capabilities_table_elements) {
> >      a2dp_sbc_t *capabilities = (a2dp_sbc_t *) capabilities_buffer;
> > +    unsigned i;
> >
> >      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_BITPOOL_HQ_JOINT_STEREO_44100;
> > +    capabilities->min_bitpool = 0xFF;
> > +    capabilities->max_bitpool = 0x00;
> > +
> > +    for (i = 0; i < capabilities_table_elements; i++) {
> > +        capabilities->channel_mode |= capabilities_table[i].channel_mode;
> > +        capabilities->frequency |= capabilities_table[i].frequency;
> > +        capabilities->allocation_method |= capabilities_table[i].allocation_method;
> > +        capabilities->subbands |= capabilities_table[i].subbands;
> > +        capabilities->block_length |= capabilities_table[i].block_length;
> > +        if (capabilities->min_bitpool > capabilities_table[i].min_bitpool)
> > +            capabilities->min_bitpool = capabilities_table[i].min_bitpool;
> > +        if (capabilities->max_bitpool < capabilities_table[i].max_bitpool)
> > +            capabilities->max_bitpool = capabilities_table[i].max_bitpool;
> > +    }
> > +
> > +    pa_assert(capabilities->min_bitpool != 0xFF);
> > +    pa_assert(capabilities->max_bitpool != 0x00);
> >
> >      return sizeof(*capabilities);
> >  }
> >
> > +static uint8_t fill_capabilities_lq(uint8_t capabilities_buffer[254]) {
> > +    return fill_capabilities_table(capabilities_buffer, sbc_lq_caps_table, PA_ELEMENTSOF(sbc_lq_caps_table));
> > +}
> > +
> > +static uint8_t fill_capabilities_mq(uint8_t capabilities_buffer[254]) {
> > +    return fill_capabilities_table(capabilities_buffer, sbc_mq_caps_table, PA_ELEMENTSOF(sbc_mq_caps_table));
> > +}
> > +
> > +static uint8_t fill_capabilities_hq(uint8_t capabilities_buffer[254]) {
> > +    return fill_capabilities_table(capabilities_buffer, sbc_hq_caps_table, PA_ELEMENTSOF(sbc_hq_caps_table));
> > +}
> > +
> > +static uint8_t fill_capabilities_uhq(uint8_t capabilities_buffer[254]) {
> > +    return fill_capabilities_table(capabilities_buffer, sbc_uhq_caps_table, PA_ELEMENTSOF(sbc_uhq_caps_table));
> > +}
> > +
> > +static uint8_t fill_capabilities(uint8_t capabilities_buffer[254]) {
> > +    return fill_capabilities_table(capabilities_buffer, sbc_auto_caps_table, PA_ELEMENTSOF(sbc_auto_caps_table));
> > +}
> > +
> >  static bool validate_configuration(const uint8_t *config_buffer, uint8_t config_size) {
> >      const a2dp_sbc_t *config = (const a2dp_sbc_t *) config_buffer;
> >
> > @@ -153,49 +372,40 @@ static bool validate_configuration(const uint8_t *config_buffer, uint8_t config_
> >      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:
> > -            switch (mode) {
> > -                case SBC_CHANNEL_MODE_MONO:
> > -                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
> > -                case SBC_CHANNEL_MODE_STEREO:
> > -                case SBC_CHANNEL_MODE_JOINT_STEREO:
> > -                    return SBC_BITPOOL_HQ_JOINT_STEREO_44100;
> > -            }
> > +static bool validate_configuration_table(const uint8_t *config_buffer, uint8_t config_size, const a2dp_sbc_t capabilities_table[], unsigned capabilities_table_elements) {
> > +    if (!validate_configuration(config_buffer, config_size))
> > +        return false;
> >
> > -        case SBC_SAMPLING_FREQ_44100:
> > -            switch (mode) {
> > -                case SBC_CHANNEL_MODE_MONO:
> > -                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
> > -                    return SBC_BITPOOL_HQ_MONO_44100;
> > +    if (!accept_capabilities_table(config_buffer, config_size, capabilities_table, capabilities_table_elements)) {
> > +        pa_log_error("Some configuration settings are invalid for current quality");
> > +        return false;
> > +    }
> >
> > -                case SBC_CHANNEL_MODE_STEREO:
> > -                case SBC_CHANNEL_MODE_JOINT_STEREO:
> > -                    return SBC_BITPOOL_HQ_JOINT_STEREO_44100;
> > -            }
> > +    return true;
> > +}
> >
> > -        case SBC_SAMPLING_FREQ_48000:
> > -            switch (mode) {
> > -                case SBC_CHANNEL_MODE_MONO:
> > -                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
> > -                    return SBC_BITPOOL_HQ_MONO_48000;
> > +static bool validate_configuration_lq(const uint8_t *config_buffer, uint8_t config_size) {
> > +    return validate_configuration_table(config_buffer, config_size, sbc_lq_caps_table, PA_ELEMENTSOF(sbc_lq_caps_table));
> > +}
> >
> > -                case SBC_CHANNEL_MODE_STEREO:
> > -                case SBC_CHANNEL_MODE_JOINT_STEREO:
> > -                    return SBC_BITPOOL_HQ_JOINT_STEREO_48000;
> > -            }
> > -    }
> > +static bool validate_configuration_mq(const uint8_t *config_buffer, uint8_t config_size) {
> > +    return validate_configuration_table(config_buffer, config_size, sbc_mq_caps_table, PA_ELEMENTSOF(sbc_mq_caps_table));
> > +}
> >
> > -    pa_assert_not_reached();
> > +static bool validate_configuration_hq(const uint8_t *config_buffer, uint8_t config_size) {
> > +    return validate_configuration_table(config_buffer, config_size, sbc_hq_caps_table, PA_ELEMENTSOF(sbc_hq_caps_table));
> >  }
> >
> > -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]) {
> > +static bool validate_configuration_uhq(const uint8_t *config_buffer, uint8_t config_size) {
> > +    return validate_configuration_table(config_buffer, config_size, sbc_uhq_caps_table, PA_ELEMENTSOF(sbc_uhq_caps_table));
> > +}
> > +
> > +static uint8_t fill_preferred_configuration_table(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[254], const a2dp_sbc_t capabilities_table[], unsigned capabilities_table_elements) {
> >      a2dp_sbc_t *config = (a2dp_sbc_t *) config_buffer;
> >      const a2dp_sbc_t *capabilities = (const a2dp_sbc_t *) capabilities_buffer;
> > -    int i;
> > +    bool is_mono = (default_sample_spec->channels <= 1);
> > +    unsigned i;
> > +    int j;
> >
> >      static const struct {
> >          uint32_t rate;
> > @@ -215,96 +425,184 @@ static uint8_t fill_preferred_configuration(const pa_sample_spec *default_sample
> >      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;
> > +    for (j = 0; (unsigned) j < PA_ELEMENTSOF(freq_table); j++) {
> > +        if (freq_table[j].rate >= default_sample_spec->rate && (capabilities->frequency & freq_table[j].cap)) {
> > +            for (i = 0; i < capabilities_table_elements; i++) {
> > +                if (capabilities_table[i].frequency & freq_table[j].cap) {
> > +                    config->frequency = freq_table[j].cap;
> > +                    break;
> > +                }
> > +            }
> > +            if (i != capabilities_table_elements)
> > +                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 ((unsigned) j == PA_ELEMENTSOF(freq_table)) {
> > +        for (--j; j >= 0; j--) {
> > +            if (capabilities->frequency & freq_table[j].cap) {
> > +                for (i = 0; i < capabilities_table_elements; i++) {
> > +                    if (capabilities_table[i].frequency & freq_table[j].cap) {
> > +                        config->frequency = freq_table[j].cap;
> > +                        break;
> > +                    }
> > +                }
> > +                if (i != capabilities_table_elements)
> > +                    break;
> >              }
> >          }
> >
> > -        if (i < 0) {
> > -            pa_log_error("Not suitable sample rate");
> > +        if (j < 0) {
> > +            pa_log_error("No 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;
> > +    pa_assert((unsigned) j < PA_ELEMENTSOF(freq_table));
> > +
> > +    for (i = 0; i < capabilities_table_elements; i++) {
> > +        if ((capabilities->block_length & SBC_BLOCK_LENGTH_16) && (capabilities_table[i].block_length & SBC_BLOCK_LENGTH_16))
> > +            config->block_length = SBC_BLOCK_LENGTH_16;
> > +        else if ((capabilities->block_length & SBC_BLOCK_LENGTH_12) && (capabilities_table[i].block_length & SBC_BLOCK_LENGTH_12))
> > +            config->block_length = SBC_BLOCK_LENGTH_12;
> > +        else if ((capabilities->block_length & SBC_BLOCK_LENGTH_8) && (capabilities_table[i].block_length & SBC_BLOCK_LENGTH_8))
> > +            config->block_length = SBC_BLOCK_LENGTH_8;
> > +        else if ((capabilities->block_length & SBC_BLOCK_LENGTH_4) && (capabilities_table[i].block_length & SBC_BLOCK_LENGTH_4))
> > +            config->block_length = SBC_BLOCK_LENGTH_4;
> >          else {
> > -            pa_log_error("No supported channel modes");
> > -            return 0;
> > +            pa_log_debug("No supported block lengths in table %u", i);
> > +            continue;
> >          }
> > -    } 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;
> > +
> > +        if ((capabilities->subbands & SBC_SUBBANDS_8) && (capabilities_table[i].subbands & SBC_SUBBANDS_8))
> > +            config->subbands = SBC_SUBBANDS_8;
> > +        else if ((capabilities->subbands & SBC_SUBBANDS_4) && (capabilities_table[i].subbands & SBC_SUBBANDS_4))
> > +            config->subbands = SBC_SUBBANDS_4;
> >          else {
> > -            pa_log_error("No supported channel modes");
> > -            return 0;
> > +            pa_log_debug("No supported subbands in table %u", i);
> > +            continue;
> >          }
> > -    }
> >
> > -    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->allocation_method & SBC_ALLOCATION_LOUDNESS) && (capabilities_table[i].allocation_method & SBC_ALLOCATION_LOUDNESS))
> > +            config->allocation_method = SBC_ALLOCATION_LOUDNESS;
> > +        else if ((capabilities->allocation_method & SBC_ALLOCATION_SNR) && (capabilities_table[i].allocation_method & SBC_ALLOCATION_SNR))
> > +            config->allocation_method = SBC_ALLOCATION_SNR;
> > +        else {
> > +            pa_log_debug("No supported allocation method in table %u", i);
> > +            continue;
> > +        }
> >
> > -    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 (is_mono) {
> > +            if ((capabilities->channel_mode & SBC_CHANNEL_MODE_MONO) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_MONO))
> > +                config->channel_mode = SBC_CHANNEL_MODE_MONO;
> > +            else if ((capabilities->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO))
> > +                config->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
> > +            else if ((capabilities->channel_mode & SBC_CHANNEL_MODE_STEREO) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_STEREO))
> > +                config->channel_mode = SBC_CHANNEL_MODE_STEREO;
> > +            else if ((capabilities->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL))
> > +                config->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
> > +            else {
> > +                pa_log_debug("No supported channel mode in table %u", i);
> > +                continue;
> > +            }
> > +        } else {
> > +            if ((capabilities->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO))
> > +                config->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
> > +            else if ((capabilities->channel_mode & SBC_CHANNEL_MODE_STEREO) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_STEREO))
> > +                config->channel_mode = SBC_CHANNEL_MODE_STEREO;
> > +            else if ((capabilities->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL))
> > +                config->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
> > +            else if ((capabilities->channel_mode & SBC_CHANNEL_MODE_MONO) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_MONO))
> > +                config->channel_mode = SBC_CHANNEL_MODE_MONO;
> > +            else {
> > +                pa_log_debug("No supported channel mode in table %u", i);
> > +                continue;
> > +            }
> > +        }
> > +
> > +        config->min_bitpool = PA_MAX(capabilities->min_bitpool, capabilities_table[i].min_bitpool);
> > +        config->max_bitpool = PA_MIN(capabilities->max_bitpool, capabilities_table[i].max_bitpool);
> > +
> > +        if (config->min_bitpool > config->max_bitpool) {
> > +            pa_log_debug("No supported bitpool in table %u", i);
> > +            continue;
> > +        }
> > +
> > +        break;
> >      }
> >
> > -    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");
> > +    if (i == capabilities_table_elements) {
> > +        pa_log_error("No supported configuration");
> >          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);
> > +    return sizeof(*config);
> > +}
> >
> > -    if (config->min_bitpool > config->max_bitpool) {
> > -        pa_log_error("No supported bitpool");
> > -        return 0;
> > +static uint8_t fill_preferred_configuration_lq(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[254]) {
> > +    return fill_preferred_configuration_table(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, sbc_lq_caps_table, PA_ELEMENTSOF(sbc_lq_caps_table));
> > +}
> > +
> > +static uint8_t fill_preferred_configuration_mq(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[254]) {
> > +    return fill_preferred_configuration_table(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, sbc_mq_caps_table, PA_ELEMENTSOF(sbc_mq_caps_table));
> > +}
> > +
> > +static uint8_t fill_preferred_configuration_hq(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[254]) {
> > +    return fill_preferred_configuration_table(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, sbc_hq_caps_table, PA_ELEMENTSOF(sbc_hq_caps_table));
> > +}
> > +
> > +static uint8_t fill_preferred_configuration_uhq(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[254]) {
> > +    return fill_preferred_configuration_table(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, sbc_uhq_caps_table, PA_ELEMENTSOF(sbc_uhq_caps_table));
> > +}
> > +
> > +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:
> > +            switch (mode) {
> > +                case SBC_CHANNEL_MODE_MONO:
> > +                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
> > +                case SBC_CHANNEL_MODE_STEREO:
> > +                case SBC_CHANNEL_MODE_JOINT_STEREO:
> > +                    return SBC_BITPOOL_HQ_JOINT_STEREO_44100;
> > +            }
> > +
> > +        case SBC_SAMPLING_FREQ_44100:
> > +            switch (mode) {
> > +                case SBC_CHANNEL_MODE_MONO:
> > +                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
> > +                    return SBC_BITPOOL_HQ_MONO_44100;
> > +
> > +                case SBC_CHANNEL_MODE_STEREO:
> > +                case SBC_CHANNEL_MODE_JOINT_STEREO:
> > +                    return SBC_BITPOOL_HQ_JOINT_STEREO_44100;
> > +            }
> > +
> > +        case SBC_SAMPLING_FREQ_48000:
> > +            switch (mode) {
> > +                case SBC_CHANNEL_MODE_MONO:
> > +                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
> > +                    return SBC_BITPOOL_HQ_MONO_48000;
> > +
> > +                case SBC_CHANNEL_MODE_STEREO:
> > +                case SBC_CHANNEL_MODE_JOINT_STEREO:
> > +                    return SBC_BITPOOL_HQ_JOINT_STEREO_48000;
> > +            }
> >      }
> >
> > -    return sizeof(*config);
> > +    pa_assert_not_reached();
> > +}
> > +
> > +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;
> > +    uint8_t ret;
> > +
> > +    ret = fill_preferred_configuration_table(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, sbc_auto_caps_table, PA_ELEMENTSOF(sbc_auto_caps_table));
> > +    config->max_bitpool = PA_MIN(default_bitpool(config->frequency, config->channel_mode), config->max_bitpool);
> > +    config->max_bitpool = PA_MAX(config->max_bitpool, config->min_bitpool);
> > +
> > +    return ret;
> >  }
> >
> >  static void set_params(struct sbc_info *sbc_info) {
> > @@ -486,13 +784,17 @@ static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
> >      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->mode == SBC_CHANNEL_MODE_MONO || sbc_info->mode == SBC_CHANNEL_MODE_DUAL_CHANNEL) {
> > +        /* For Mono and Dual Channel modes bitpool value is separete for each channel */
> > +        bitpool = sbc_info->sbc.bitpool - SBC_SEPARATE_BITPOOL_DEC_STEP;
> > +        if (bitpool <= SBC_SEPARATE_BITPOOL_DEC_LIMIT)
> > +            return 0;
> > +    } else {
> > +        /* For Stereo modes bitpool value is combined for both channels */
> > +        bitpool = sbc_info->sbc.bitpool - SBC_COMBINED_BITPOOL_DEC_STEP;
> > +        if (bitpool <= SBC_COMBINED_BITPOOL_DEC_LIMIT)
> > +            return 0;
> > +    }
> >
> >      if (sbc_info->sbc.bitpool == bitpool)
> >          return 0;
> > @@ -501,6 +803,10 @@ static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
> >      return get_block_size(codec_info, write_link_mtu);
> >  }
> >
> > +static size_t reduce_encoder_bitrate_none(void *codec_info, size_t write_link_mtu) {
> > +    return 0;
> > +}
> > +
> >  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;
> > @@ -625,9 +931,69 @@ static size_t decode_buffer(void *codec_info, const uint8_t *input_buffer, size_
> >      return d - output_buffer;
> >  }
> >
> > +const pa_a2dp_codec pa_a2dp_codec_sbc_lq = {
> > +    .codec_name = "sbc_lq",
> > +    .codec_description = "SBC (Low Quality)",
> > +    .codec_id = { A2DP_CODEC_SBC, 0, 0 },
> > +    .support_backchannel = false,
> > +    .accept_capabilities = accept_capabilities_lq,
> > +    .choose_capabilities = choose_capabilities_lq,
> > +    .fill_capabilities = fill_capabilities_lq,
> > +    .validate_configuration = validate_configuration_lq,
> > +    .fill_preferred_configuration = fill_preferred_configuration_lq,
> > +    .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_none,
> > +    .encode_buffer = encode_buffer,
> > +    .decode_buffer = decode_buffer,
> > +};
> > +
> > +const pa_a2dp_codec pa_a2dp_codec_sbc_mq = {
> > +    .codec_name = "sbc_mq",
> > +    .codec_description = "SBC (Middle Quality)",
> > +    .codec_id = { A2DP_CODEC_SBC, 0, 0 },
> > +    .support_backchannel = false,
> > +    .accept_capabilities = accept_capabilities_mq,
> > +    .choose_capabilities = choose_capabilities_mq,
> > +    .fill_capabilities = fill_capabilities_mq,
> > +    .validate_configuration = validate_configuration_mq,
> > +    .fill_preferred_configuration = fill_preferred_configuration_mq,
> > +    .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_none,
> > +    .encode_buffer = encode_buffer,
> > +    .decode_buffer = decode_buffer,
> > +};
> > +
> > +const pa_a2dp_codec pa_a2dp_codec_sbc_hq = {
> > +    .codec_name = "sbc_hq",
> > +    .codec_description = "SBC (High Quality)",
> > +    .codec_id = { A2DP_CODEC_SBC, 0, 0 },
> > +    .support_backchannel = false,
> > +    .accept_capabilities = accept_capabilities_hq,
> > +    .choose_capabilities = choose_capabilities_hq,
> > +    .fill_capabilities = fill_capabilities_hq,
> > +    .validate_configuration = validate_configuration_hq,
> > +    .fill_preferred_configuration = fill_preferred_configuration_hq,
> > +    .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_none,
> > +    .encode_buffer = encode_buffer,
> > +    .decode_buffer = decode_buffer,
> > +};
> > +
> >  const pa_a2dp_codec pa_a2dp_codec_sbc = {
> >      .codec_name = "sbc",
> > -    .codec_description = "SBC",
> > +    .codec_description = "SBC (Automatic Quality)",
> >      .codec_id = { A2DP_CODEC_SBC, 0, 0 },
> >      .support_backchannel = false,
> >      .accept_capabilities = accept_capabilities,
> > @@ -644,3 +1010,23 @@ const pa_a2dp_codec pa_a2dp_codec_sbc = {
> >      .encode_buffer = encode_buffer,
> >      .decode_buffer = decode_buffer,
> >  };
> > +
> > +const pa_a2dp_codec pa_a2dp_codec_sbc_uhq = {
> > +    .codec_name = "sbc_uhq",
> > +    .codec_description = "SBC (Ultra High Quality)",
> > +    .codec_id = { A2DP_CODEC_SBC, 0, 0 },
> > +    .support_backchannel = false,
> > +    .accept_capabilities = accept_capabilities_uhq,
> > +    .choose_capabilities = choose_capabilities_uhq,
> > +    .fill_capabilities = fill_capabilities_uhq,
> > +    .validate_configuration = validate_configuration_uhq,
> > +    .fill_preferred_configuration = fill_preferred_configuration_uhq,
> > +    .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_none,
> > +    .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
> > index 063077237..1fe857202 100644
> > --- a/src/modules/bluetooth/a2dp-codec-util.c
> > +++ b/src/modules/bluetooth/a2dp-codec-util.c
> > @@ -26,20 +26,32 @@
> >
> >  #include "a2dp-codec-util.h"
> >
> > +extern const pa_a2dp_codec pa_a2dp_codec_sbc_lq;
> >  extern const pa_a2dp_codec pa_a2dp_codec_faststream;
> > +extern const pa_a2dp_codec pa_a2dp_codec_sbc_mq;
> >  extern const pa_a2dp_codec pa_a2dp_codec_sbc;
> >  #ifdef HAVE_OPENAPTX
> >  extern const pa_a2dp_codec pa_a2dp_codec_aptx;
> > +#endif
> > +extern const pa_a2dp_codec pa_a2dp_codec_sbc_hq;
> > +extern const pa_a2dp_codec pa_a2dp_codec_sbc_uhq;
> > +#ifdef HAVE_OPENAPTX
> >  extern const pa_a2dp_codec pa_a2dp_codec_aptx_hd;
> >  #endif
> >
> >  /* 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_faststream,
> > -    &pa_a2dp_codec_sbc,
> > +    &pa_a2dp_codec_sbc_lq,
> > +    &pa_a2dp_codec_faststream,  /* Uses exactly same parameters as SBC-LQ, but with voice backchannel */
> > +    &pa_a2dp_codec_sbc_mq,
> > +    &pa_a2dp_codec_sbc,         /* SBC in automatic mode, from SBC-LQ to SBC-HQ; not SBC-UHQ */
> >  #ifdef HAVE_OPENAPTX
> >      &pa_a2dp_codec_aptx,
> > +#endif
> > +    &pa_a2dp_codec_sbc_hq,      /* SBC-HQ has same quality as aptX */
> > +    &pa_a2dp_codec_sbc_uhq,     /* SBC-UHQ has higher quality than aptX, but lower than aptX HD */
> > +#ifdef HAVE_OPENAPTX
> >      &pa_a2dp_codec_aptx_hd,
> >  #endif
> >  };
> > --
> > 2.11.0
> >
> 
>
Luiz Augusto von Dentz Feb. 6, 2019, 1:51 p.m.
Hi Pali,
On Wed, Feb 6, 2019 at 2:58 PM Pali Rohár <pali.rohar@gmail.com> wrote:
>
> On Wednesday 06 February 2019 14:04:28 Luiz Augusto von Dentz wrote:
> > Hi Pali,
> > On Sun, Feb 3, 2019 at 4:16 PM Pali Rohár <pali.rohar@gmail.com> wrote:
> > >
> > > Specify configuration for Low, Middle, High and Ultra High Quality of SBC
> > > codec. SBC codec in Ultra High Quality has higher quality than aptX.
> >
> > Low and Middle quality don't seems to serve any purpose since we do
> > drop quality automatically when necessary,
>
> It may be useful in more cases:
>
> 1) To know what type of quality is used. Auto means "user does not know
> used quality".
>
> 2) To force lower bitrate if higher one is causing problems (and
> decreasing is not working correctly) in bluetooth transport. E.g. you
> need temporary lower bitrate and later increase it back. Whole auto
> quality mode can only lower it, so once it go into low mode it never
> return back.
>
> E.g. you connect phone with laptop, listening music and you want to
> transfer big file...
>
> 3) To do quality comparison. In some cases other side can have problems
> with higher bitrate. So with this profile you can lower it.

It might be too complicated for the user to decide, also some remote
stack may stop at first match if they just have SBC in which case it
may never select the quality the user want. We could however switch
the order so that the last used endpoint appears first, it just means
the bluetoothd needs to remember the last used endpoint. Im still not
convinced that this will be very useful though, so perhaps Id leave
those qualities to be enabled somehow, so distros that want to have
this many endpoits/profiles can just opt for auto-quality which can
scale to ultra.

> > I do understand the Ultra HQ (perhaps using HD is a better?)
>
> HD is definition, not quality. So I think it is better to have quality
> in names.
>
> > is perhaps something we need to
> > force, though have you tried just having the default SBC at 76
> > bitpool?
>
> No, I do not have headset which support bitpool 76. But you can use
> dual channel mode with bitpool 38 which is very similar to stereo mode
> with bitpool 76. And dual_channel/38 I tested and is working. See that
> Ultra High Quality mode prefer joint_stereo/76, but allow to use also
> dual_channel/38.

Im don't think we want to go into user selecting modes as well, that
would make the list of profiles really long.

> In dual channel mode, channels have separate bitpools; in stereo mode
> channels share one bitpool.
>
> > Id avoid having multiple endpoints of the same codec as it
> > just makes discovery slower which extra round-trips.
>
> It is problem? With multiple endpoints it is possible to "enforce"
> quality. IIRC this is reason why e.g. aptX codec is used -- it is
> enforced in specific high quality.

It can be depending on what logic the headset uses to reconnect, if it
is first codec match than we may have a problem. Also each round trip
could take some tens of miliseconds if we have serveral endponts that
could build latency when reconnecting.

> > > Automatic Quality mode matches configuration of SBC codec which was used
> > > before this change. Which means that it accept configuration between Low
> > > and High quality.
> > >
> > > Current SBC code was extended to allow definitions of arbitrary
> > > configuration variants of SBC codec parameters.
> > > ---
> > >  src/modules/bluetooth/a2dp-codec-sbc.c  | 654 +++++++++++++++++++++++++-------
> > >  src/modules/bluetooth/a2dp-codec-util.c |  16 +-
> > >  2 files changed, 534 insertions(+), 136 deletions(-)
> > >
> > > diff --git a/src/modules/bluetooth/a2dp-codec-sbc.c b/src/modules/bluetooth/a2dp-codec-sbc.c
> > > index 034d57a69..a8229735c 100644
> > > --- a/src/modules/bluetooth/a2dp-codec-sbc.c
> > > +++ b/src/modules/bluetooth/a2dp-codec-sbc.c
> > > @@ -36,8 +36,67 @@
> > >  #include "a2dp-codec-api.h"
> > >  #include "rtp.h"
> > >
> > > -#define SBC_BITPOOL_DEC_LIMIT 32
> > > -#define SBC_BITPOOL_DEC_STEP 5
> > > +/* Below are capabilities tables for different qualities. Order of capabilities in tables are from the most preferred to the least preferred. */
> > > +
> > > +#define FIXED_SBC_CAPS(mode, freq, bitpool) { .channel_mode = (mode), .frequency = (freq), .min_bitpool = (bitpool), .max_bitpool = (bitpool), .allocation_method = (SBC_ALLOCATION_SNR|SBC_ALLOCATION_LOUDNESS), .subbands = SBC_SUBBANDS_8, .block_length = SBC_BLOCK_LENGTH_16 }
> > > +
> > > +/* SBC Low Quality, Joint Stereo is same as FastStream's SBC codec configuration, Mono was calculated to match Joint Stereo */
> > > +a2dp_sbc_t sbc_lq_caps_table[] = {
> > > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_44100, 29), /* 195.7 kbps */
> > > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_48000, 29), /* 213   kbps */
> > > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_44100, 15), /* 104.7 kbps */
> > > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_48000, 15), /* 114   kbps */
> > > +};
> > > +
> > > +/* SBC Middle Quality, based on A2DP spec: Recommended sets of SBC parameters */
> > > +a2dp_sbc_t sbc_mq_caps_table[] = {
> > > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_44100, SBC_BITPOOL_MQ_JOINT_STEREO_44100), /* bitpool = 35, 228.8 kbps */
> > > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_48000, SBC_BITPOOL_MQ_JOINT_STEREO_48000), /* bitpool = 33, 237   kbps */
> > > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_44100, SBC_BITPOOL_MQ_MONO_44100),         /* bitpool = 19, 126.8 kbps */
> > > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_48000, SBC_BITPOOL_MQ_MONO_48000),         /* bitpool = 18, 132   kbps */
> > > +};
> > > +
> > > +/* SBC High Quality, based on A2DP spec: Recommended sets of SBC parameters */
> > > +a2dp_sbc_t sbc_hq_caps_table[] = {
> > > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_44100, SBC_BITPOOL_HQ_JOINT_STEREO_44100), /* bitpool = 53, 328   kbps */
> > > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_48000, SBC_BITPOOL_HQ_JOINT_STEREO_48000), /* bitpool = 51, 345   kbps */
> > > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_44100, SBC_BITPOOL_HQ_MONO_44100),         /* bitpool = 31, 192.9 kbps */
> > > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_48000, SBC_BITPOOL_HQ_MONO_48000),         /* bitpool = 29, 210   kbps */
> > > +};
> > > +
> > > +/* SBC Ultra High Quality, calculated to minimize wasted bytes and to be below max possible 512 kbps */
> > > +a2dp_sbc_t sbc_uhq_caps_table[] = {
> > > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_44100, 76), /* 454.8 kbps */
> > > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_JOINT_STEREO, SBC_SAMPLING_FREQ_48000, 76), /* 495   kbps */
> > > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_STEREO,       SBC_SAMPLING_FREQ_44100, 76), /* 452   kbps */
> > > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_STEREO,       SBC_SAMPLING_FREQ_48000, 76), /* 492   kbps */
> > > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_DUAL_CHANNEL, SBC_SAMPLING_FREQ_44100, 38), /* 452   kbps */
> > > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_DUAL_CHANNEL, SBC_SAMPLING_FREQ_48000, 38), /* 492   kbps */
> > > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_44100, 37), /* 226   kbps */
> > > +    FIXED_SBC_CAPS(SBC_CHANNEL_MODE_MONO,         SBC_SAMPLING_FREQ_48000, 37), /* 246   kbps */
> > > +};
> > > +/* In most cases bluetooth headsets would support only sbc dual channel mode
> > > + * for 2 channels as they have limited maximal bitpool value to 53. */
> > > +
> > > +#undef FIXED_SBC_CAPS
> > > +
> > > +/* SBC Auto Quality, only one row which allow any possible configuration up to common High Quality */
> > > +/* We need to ensure that bitrate is below max possible 512 kbps, therefore limit configuration to High Quality */
> > > +a2dp_sbc_t sbc_auto_caps_table[] = { {
> > > +    .channel_mode = SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL | SBC_CHANNEL_MODE_STEREO | SBC_CHANNEL_MODE_JOINT_STEREO,
> > > +    .frequency = SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 | SBC_SAMPLING_FREQ_44100 | SBC_SAMPLING_FREQ_48000,
> > > +    .allocation_method = SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS,
> > > +    .subbands = SBC_SUBBANDS_4 | SBC_SUBBANDS_8,
> > > +    .block_length = SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16,
> > > +    .min_bitpool = SBC_MIN_BITPOOL,
> > > +    .max_bitpool = SBC_BITPOOL_HQ_JOINT_STEREO_44100,
> > > +} };
> > > +
> > > +/* Bitpool limits and steps for reducing bitrate in Auto Quality mode */
> > > +#define SBC_SEPARATE_BITPOOL_DEC_LIMIT 10
> > > +#define SBC_COMBINED_BITPOOL_DEC_LIMIT 25
> > > +#define SBC_SEPARATE_BITPOOL_DEC_STEP   2
> > > +#define SBC_COMBINED_BITPOOL_DEC_STEP   4
> > >
> > >  struct sbc_info {
> > >      sbc_t sbc;                           /* Codec data */
> > > @@ -53,62 +112,222 @@ struct sbc_info {
> > >      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;
> > > +static bool are_capabilities_compatible(const a2dp_sbc_t *capabilities1, const a2dp_sbc_t *capabilities2) {
> > > +    if (!(capabilities1->channel_mode & capabilities2->channel_mode))
> > > +        return false;
> > >
> > > -    if (capabilities_size != sizeof(*capabilities))
> > > +    if (!(capabilities1->frequency & capabilities2->frequency))
> > >          return false;
> > >
> > > -    if (!(capabilities->frequency & (SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 | SBC_SAMPLING_FREQ_44100 | SBC_SAMPLING_FREQ_48000)))
> > > +    if (!(capabilities1->allocation_method & capabilities2->allocation_method))
> > >          return false;
> > >
> > > -    if (!(capabilities->channel_mode & (SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL | SBC_CHANNEL_MODE_STEREO | SBC_CHANNEL_MODE_JOINT_STEREO)))
> > > +    if (!(capabilities1->subbands & capabilities2->subbands))
> > >          return false;
> > >
> > > -    if (!(capabilities->allocation_method & (SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS)))
> > > +    if (!(capabilities1->block_length & capabilities2->block_length))
> > >          return false;
> > >
> > > -    if (!(capabilities->subbands & (SBC_SUBBANDS_4 | SBC_SUBBANDS_8)))
> > > +    if (!(capabilities1->min_bitpool <= capabilities2->max_bitpool || capabilities2->min_bitpool <= capabilities1->max_bitpool))
> > >          return false;
> > >
> > > -    if (!(capabilities->block_length & (SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16)))
> > > +    if (capabilities1->min_bitpool > capabilities1->max_bitpool || capabilities2->min_bitpool > capabilities2->max_bitpool)
> > >          return false;
> > >
> > >      return true;
> > >  }
> > >
> > > -static const char *choose_capabilities(const pa_hashmap *capabilities_hashmap, bool for_encoding) {
> > > +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;
> > > +
> > > +    return are_capabilities_compatible(capabilities, &sbc_auto_caps_table[0]);
> > > +}
> > > +
> > > +static bool accept_capabilities_table(const uint8_t *capabilities_buffer, uint8_t capabilities_size, const a2dp_sbc_t capabilities_table[], unsigned capabilities_table_elements) {
> > > +    const a2dp_sbc_t *capabilities = (const a2dp_sbc_t *) capabilities_buffer;
> > > +    unsigned i;
> > > +
> > > +    if (!accept_capabilities(capabilities_buffer, capabilities_size, false))
> > > +        return false;
> > > +
> > > +    for (i = 0; i < capabilities_table_elements; i++) {
> > > +        if (!are_capabilities_compatible(capabilities, &capabilities_table[i]))
> > > +            continue;
> > > +        return true;
> > > +    }
> > > +
> > > +    return false;
> > > +}
> > > +
> > > +static bool accept_capabilities_lq(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
> > > +    return accept_capabilities_table(capabilities_buffer, capabilities_size, sbc_lq_caps_table, PA_ELEMENTSOF(sbc_lq_caps_table));
> > > +}
> > > +
> > > +static bool accept_capabilities_mq(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
> > > +    return accept_capabilities_table(capabilities_buffer, capabilities_size, sbc_mq_caps_table, PA_ELEMENTSOF(sbc_mq_caps_table));
> > > +}
> > > +
> > > +static bool accept_capabilities_hq(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
> > > +    return accept_capabilities_table(capabilities_buffer, capabilities_size, sbc_hq_caps_table, PA_ELEMENTSOF(sbc_hq_caps_table));
> > > +}
> > > +
> > > +static bool accept_capabilities_uhq(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
> > > +    return accept_capabilities_table(capabilities_buffer, capabilities_size, sbc_uhq_caps_table, PA_ELEMENTSOF(sbc_uhq_caps_table));
> > > +}
> > > +
> > > +static const char *choose_capabilities_table(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, const a2dp_sbc_t capabilities_table[], unsigned capabilities_table_elements) {
> > >      const pa_a2dp_codec_capabilities *a2dp_capabilities;
> > > +    const a2dp_sbc_t *capabilities;
> > >      const char *key;
> > >      void *state;
> > > +    unsigned i;
> > > +    uint8_t table_range;
> > > +    uint8_t current_range;
> > > +    const char *best_key = NULL;
> > > +    uint8_t best_range = 0;
> > > +    uint8_t frequency = 0;
> > > +    bool is_mono = (default_sample_spec->channels <= 1);
> > > +
> > > +    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 }
> > > +    };
> > > +
> > > +    for (i = 0; i < PA_ELEMENTSOF(freq_table); i++) {
> > > +        if (freq_table[i].rate == default_sample_spec->rate) {
> > > +            frequency = freq_table[i].rate;
> > > +            break;
> > > +        }
> > > +    }
> > > +
> > > +    if (!frequency)
> > > +        return NULL;
> > >
> > > -    /* There is no preference, just choose random valid entry */
> > > +    /* choose remote capabilities which are compatible and its bitpool range is nearest to one from capabilities table */
> > >      PA_HASHMAP_FOREACH_KV(key, a2dp_capabilities, capabilities_hashmap, state) {
> > > -        if (accept_capabilities(a2dp_capabilities->buffer, a2dp_capabilities->size, for_encoding))
> > > -            return key;
> > > +        /* skip remote capabilities which are not compatible */
> > > +        if (!accept_capabilities(a2dp_capabilities->buffer, a2dp_capabilities->size, false))
> > > +            continue;
> > > +
> > > +        capabilities = (const a2dp_sbc_t *) a2dp_capabilities->buffer;
> > > +
> > > +        /* choose capabilities from our table which is compatible with sample spec and remote capabilities */
> > > +        for (i = 0; i < capabilities_table_elements; i++) {
> > > +            if (!are_capabilities_compatible(capabilities, &capabilities_table[i]))
> > > +                continue;
> > > +            if (is_mono && !(capabilities->channel_mode & SBC_CHANNEL_MODE_MONO))
> > > +                continue;
> > > +            if (!is_mono && !(capabilities->channel_mode & (SBC_CHANNEL_MODE_JOINT_STEREO | SBC_CHANNEL_MODE_STEREO | SBC_CHANNEL_MODE_DUAL_CHANNEL)))
> > > +                continue;
> > > +            if (!(capabilities->frequency & frequency))
> > > +                continue;
> > > +            break;
> > > +        }
> > > +
> > > +        /* skip if nothing is compatible */
> > > +        if (i == capabilities_table_elements)
> > > +            continue;
> > > +
> > > +        /* calculate current bitpool range compatible with both remote capabilities and capabilities from our table */
> > > +        if (capabilities->min_bitpool > capabilities_table[i].min_bitpool) {
> > > +            if (capabilities->max_bitpool > capabilities_table[i].max_bitpool)
> > > +                current_range = capabilities_table[i].max_bitpool - capabilities->min_bitpool;
> > > +            else
> > > +                current_range = capabilities->max_bitpool - capabilities->min_bitpool;
> > > +        } else {
> > > +            if (capabilities->max_bitpool > capabilities_table[i].max_bitpool)
> > > +                current_range = capabilities_table[i].max_bitpool - capabilities_table[i].min_bitpool;
> > > +            else
> > > +                current_range = capabilities->max_bitpool - capabilities_table[i].min_bitpool;
> > > +        }
> > > +
> > > +        table_range = capabilities_table[i].max_bitpool - capabilities_table[i].min_bitpool;
> > > +
> > > +        /* use current remote capabilities if its bitpool range is closer to bitpool range in table */
> > > +        if (!best_key || abs((int)current_range - (int)(table_range)) < abs((int)best_range - (int)(table_range))) {
> > > +            best_range = current_range;
> > > +            best_key = key;
> > > +        }
> > >      }
> > >
> > > -    return NULL;
> > > +    return best_key;
> > >  }
> > >
> > > -static uint8_t fill_capabilities(uint8_t capabilities_buffer[254]) {
> > > +static const char *choose_capabilities_lq(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
> > > +    return choose_capabilities_table(capabilities_hashmap, default_sample_spec, sbc_lq_caps_table, PA_ELEMENTSOF(sbc_lq_caps_table));
> > > +}
> > > +
> > > +static const char *choose_capabilities_mq(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
> > > +    return choose_capabilities_table(capabilities_hashmap, default_sample_spec, sbc_mq_caps_table, PA_ELEMENTSOF(sbc_mq_caps_table));
> > > +}
> > > +
> > > +static const char *choose_capabilities_hq(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
> > > +    return choose_capabilities_table(capabilities_hashmap, default_sample_spec, sbc_hq_caps_table, PA_ELEMENTSOF(sbc_hq_caps_table));
> > > +}
> > > +
> > > +static const char *choose_capabilities_uhq(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
> > > +    return choose_capabilities_table(capabilities_hashmap, default_sample_spec, sbc_uhq_caps_table, PA_ELEMENTSOF(sbc_uhq_caps_table));
> > > +}
> > > +
> > > +static const char *choose_capabilities(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
> > > +    return choose_capabilities_table(capabilities_hashmap, default_sample_spec, sbc_auto_caps_table, PA_ELEMENTSOF(sbc_auto_caps_table));
> > > +}
> > > +
> > > +static uint8_t fill_capabilities_table(uint8_t capabilities_buffer[254], const a2dp_sbc_t capabilities_table[], unsigned capabilities_table_elements) {
> > >      a2dp_sbc_t *capabilities = (a2dp_sbc_t *) capabilities_buffer;
> > > +    unsigned i;
> > >
> > >      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_BITPOOL_HQ_JOINT_STEREO_44100;
> > > +    capabilities->min_bitpool = 0xFF;
> > > +    capabilities->max_bitpool = 0x00;
> > > +
> > > +    for (i = 0; i < capabilities_table_elements; i++) {
> > > +        capabilities->channel_mode |= capabilities_table[i].channel_mode;
> > > +        capabilities->frequency |= capabilities_table[i].frequency;
> > > +        capabilities->allocation_method |= capabilities_table[i].allocation_method;
> > > +        capabilities->subbands |= capabilities_table[i].subbands;
> > > +        capabilities->block_length |= capabilities_table[i].block_length;
> > > +        if (capabilities->min_bitpool > capabilities_table[i].min_bitpool)
> > > +            capabilities->min_bitpool = capabilities_table[i].min_bitpool;
> > > +        if (capabilities->max_bitpool < capabilities_table[i].max_bitpool)
> > > +            capabilities->max_bitpool = capabilities_table[i].max_bitpool;
> > > +    }
> > > +
> > > +    pa_assert(capabilities->min_bitpool != 0xFF);
> > > +    pa_assert(capabilities->max_bitpool != 0x00);
> > >
> > >      return sizeof(*capabilities);
> > >  }
> > >
> > > +static uint8_t fill_capabilities_lq(uint8_t capabilities_buffer[254]) {
> > > +    return fill_capabilities_table(capabilities_buffer, sbc_lq_caps_table, PA_ELEMENTSOF(sbc_lq_caps_table));
> > > +}
> > > +
> > > +static uint8_t fill_capabilities_mq(uint8_t capabilities_buffer[254]) {
> > > +    return fill_capabilities_table(capabilities_buffer, sbc_mq_caps_table, PA_ELEMENTSOF(sbc_mq_caps_table));
> > > +}
> > > +
> > > +static uint8_t fill_capabilities_hq(uint8_t capabilities_buffer[254]) {
> > > +    return fill_capabilities_table(capabilities_buffer, sbc_hq_caps_table, PA_ELEMENTSOF(sbc_hq_caps_table));
> > > +}
> > > +
> > > +static uint8_t fill_capabilities_uhq(uint8_t capabilities_buffer[254]) {
> > > +    return fill_capabilities_table(capabilities_buffer, sbc_uhq_caps_table, PA_ELEMENTSOF(sbc_uhq_caps_table));
> > > +}
> > > +
> > > +static uint8_t fill_capabilities(uint8_t capabilities_buffer[254]) {
> > > +    return fill_capabilities_table(capabilities_buffer, sbc_auto_caps_table, PA_ELEMENTSOF(sbc_auto_caps_table));
> > > +}
> > > +
> > >  static bool validate_configuration(const uint8_t *config_buffer, uint8_t config_size) {
> > >      const a2dp_sbc_t *config = (const a2dp_sbc_t *) config_buffer;
> > >
> > > @@ -153,49 +372,40 @@ static bool validate_configuration(const uint8_t *config_buffer, uint8_t config_
> > >      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:
> > > -            switch (mode) {
> > > -                case SBC_CHANNEL_MODE_MONO:
> > > -                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
> > > -                case SBC_CHANNEL_MODE_STEREO:
> > > -                case SBC_CHANNEL_MODE_JOINT_STEREO:
> > > -                    return SBC_BITPOOL_HQ_JOINT_STEREO_44100;
> > > -            }
> > > +static bool validate_configuration_table(const uint8_t *config_buffer, uint8_t config_size, const a2dp_sbc_t capabilities_table[], unsigned capabilities_table_elements) {
> > > +    if (!validate_configuration(config_buffer, config_size))
> > > +        return false;
> > >
> > > -        case SBC_SAMPLING_FREQ_44100:
> > > -            switch (mode) {
> > > -                case SBC_CHANNEL_MODE_MONO:
> > > -                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
> > > -                    return SBC_BITPOOL_HQ_MONO_44100;
> > > +    if (!accept_capabilities_table(config_buffer, config_size, capabilities_table, capabilities_table_elements)) {
> > > +        pa_log_error("Some configuration settings are invalid for current quality");
> > > +        return false;
> > > +    }
> > >
> > > -                case SBC_CHANNEL_MODE_STEREO:
> > > -                case SBC_CHANNEL_MODE_JOINT_STEREO:
> > > -                    return SBC_BITPOOL_HQ_JOINT_STEREO_44100;
> > > -            }
> > > +    return true;
> > > +}
> > >
> > > -        case SBC_SAMPLING_FREQ_48000:
> > > -            switch (mode) {
> > > -                case SBC_CHANNEL_MODE_MONO:
> > > -                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
> > > -                    return SBC_BITPOOL_HQ_MONO_48000;
> > > +static bool validate_configuration_lq(const uint8_t *config_buffer, uint8_t config_size) {
> > > +    return validate_configuration_table(config_buffer, config_size, sbc_lq_caps_table, PA_ELEMENTSOF(sbc_lq_caps_table));
> > > +}
> > >
> > > -                case SBC_CHANNEL_MODE_STEREO:
> > > -                case SBC_CHANNEL_MODE_JOINT_STEREO:
> > > -                    return SBC_BITPOOL_HQ_JOINT_STEREO_48000;
> > > -            }
> > > -    }
> > > +static bool validate_configuration_mq(const uint8_t *config_buffer, uint8_t config_size) {
> > > +    return validate_configuration_table(config_buffer, config_size, sbc_mq_caps_table, PA_ELEMENTSOF(sbc_mq_caps_table));
> > > +}
> > >
> > > -    pa_assert_not_reached();
> > > +static bool validate_configuration_hq(const uint8_t *config_buffer, uint8_t config_size) {
> > > +    return validate_configuration_table(config_buffer, config_size, sbc_hq_caps_table, PA_ELEMENTSOF(sbc_hq_caps_table));
> > >  }
> > >
> > > -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]) {
> > > +static bool validate_configuration_uhq(const uint8_t *config_buffer, uint8_t config_size) {
> > > +    return validate_configuration_table(config_buffer, config_size, sbc_uhq_caps_table, PA_ELEMENTSOF(sbc_uhq_caps_table));
> > > +}
> > > +
> > > +static uint8_t fill_preferred_configuration_table(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[254], const a2dp_sbc_t capabilities_table[], unsigned capabilities_table_elements) {
> > >      a2dp_sbc_t *config = (a2dp_sbc_t *) config_buffer;
> > >      const a2dp_sbc_t *capabilities = (const a2dp_sbc_t *) capabilities_buffer;
> > > -    int i;
> > > +    bool is_mono = (default_sample_spec->channels <= 1);
> > > +    unsigned i;
> > > +    int j;
> > >
> > >      static const struct {
> > >          uint32_t rate;
> > > @@ -215,96 +425,184 @@ static uint8_t fill_preferred_configuration(const pa_sample_spec *default_sample
> > >      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;
> > > +    for (j = 0; (unsigned) j < PA_ELEMENTSOF(freq_table); j++) {
> > > +        if (freq_table[j].rate >= default_sample_spec->rate && (capabilities->frequency & freq_table[j].cap)) {
> > > +            for (i = 0; i < capabilities_table_elements; i++) {
> > > +                if (capabilities_table[i].frequency & freq_table[j].cap) {
> > > +                    config->frequency = freq_table[j].cap;
> > > +                    break;
> > > +                }
> > > +            }
> > > +            if (i != capabilities_table_elements)
> > > +                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 ((unsigned) j == PA_ELEMENTSOF(freq_table)) {
> > > +        for (--j; j >= 0; j--) {
> > > +            if (capabilities->frequency & freq_table[j].cap) {
> > > +                for (i = 0; i < capabilities_table_elements; i++) {
> > > +                    if (capabilities_table[i].frequency & freq_table[j].cap) {
> > > +                        config->frequency = freq_table[j].cap;
> > > +                        break;
> > > +                    }
> > > +                }
> > > +                if (i != capabilities_table_elements)
> > > +                    break;
> > >              }
> > >          }
> > >
> > > -        if (i < 0) {
> > > -            pa_log_error("Not suitable sample rate");
> > > +        if (j < 0) {
> > > +            pa_log_error("No 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;
> > > +    pa_assert((unsigned) j < PA_ELEMENTSOF(freq_table));
> > > +
> > > +    for (i = 0; i < capabilities_table_elements; i++) {
> > > +        if ((capabilities->block_length & SBC_BLOCK_LENGTH_16) && (capabilities_table[i].block_length & SBC_BLOCK_LENGTH_16))
> > > +            config->block_length = SBC_BLOCK_LENGTH_16;
> > > +        else if ((capabilities->block_length & SBC_BLOCK_LENGTH_12) && (capabilities_table[i].block_length & SBC_BLOCK_LENGTH_12))
> > > +            config->block_length = SBC_BLOCK_LENGTH_12;
> > > +        else if ((capabilities->block_length & SBC_BLOCK_LENGTH_8) && (capabilities_table[i].block_length & SBC_BLOCK_LENGTH_8))
> > > +            config->block_length = SBC_BLOCK_LENGTH_8;
> > > +        else if ((capabilities->block_length & SBC_BLOCK_LENGTH_4) && (capabilities_table[i].block_length & SBC_BLOCK_LENGTH_4))
> > > +            config->block_length = SBC_BLOCK_LENGTH_4;
> > >          else {
> > > -            pa_log_error("No supported channel modes");
> > > -            return 0;
> > > +            pa_log_debug("No supported block lengths in table %u", i);
> > > +            continue;
> > >          }
> > > -    } 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;
> > > +
> > > +        if ((capabilities->subbands & SBC_SUBBANDS_8) && (capabilities_table[i].subbands & SBC_SUBBANDS_8))
> > > +            config->subbands = SBC_SUBBANDS_8;
> > > +        else if ((capabilities->subbands & SBC_SUBBANDS_4) && (capabilities_table[i].subbands & SBC_SUBBANDS_4))
> > > +            config->subbands = SBC_SUBBANDS_4;
> > >          else {
> > > -            pa_log_error("No supported channel modes");
> > > -            return 0;
> > > +            pa_log_debug("No supported subbands in table %u", i);
> > > +            continue;
> > >          }
> > > -    }
> > >
> > > -    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->allocation_method & SBC_ALLOCATION_LOUDNESS) && (capabilities_table[i].allocation_method & SBC_ALLOCATION_LOUDNESS))
> > > +            config->allocation_method = SBC_ALLOCATION_LOUDNESS;
> > > +        else if ((capabilities->allocation_method & SBC_ALLOCATION_SNR) && (capabilities_table[i].allocation_method & SBC_ALLOCATION_SNR))
> > > +            config->allocation_method = SBC_ALLOCATION_SNR;
> > > +        else {
> > > +            pa_log_debug("No supported allocation method in table %u", i);
> > > +            continue;
> > > +        }
> > >
> > > -    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 (is_mono) {
> > > +            if ((capabilities->channel_mode & SBC_CHANNEL_MODE_MONO) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_MONO))
> > > +                config->channel_mode = SBC_CHANNEL_MODE_MONO;
> > > +            else if ((capabilities->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO))
> > > +                config->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
> > > +            else if ((capabilities->channel_mode & SBC_CHANNEL_MODE_STEREO) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_STEREO))
> > > +                config->channel_mode = SBC_CHANNEL_MODE_STEREO;
> > > +            else if ((capabilities->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL))
> > > +                config->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
> > > +            else {
> > > +                pa_log_debug("No supported channel mode in table %u", i);
> > > +                continue;
> > > +            }
> > > +        } else {
> > > +            if ((capabilities->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO))
> > > +                config->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
> > > +            else if ((capabilities->channel_mode & SBC_CHANNEL_MODE_STEREO) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_STEREO))
> > > +                config->channel_mode = SBC_CHANNEL_MODE_STEREO;
> > > +            else if ((capabilities->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL))
> > > +                config->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
> > > +            else if ((capabilities->channel_mode & SBC_CHANNEL_MODE_MONO) && (capabilities_table[i].channel_mode & SBC_CHANNEL_MODE_MONO))
> > > +                config->channel_mode = SBC_CHANNEL_MODE_MONO;
> > > +            else {
> > > +                pa_log_debug("No supported channel mode in table %u", i);
> > > +                continue;
> > > +            }
> > > +        }
> > > +
> > > +        config->min_bitpool = PA_MAX(capabilities->min_bitpool, capabilities_table[i].min_bitpool);
> > > +        config->max_bitpool = PA_MIN(capabilities->max_bitpool, capabilities_table[i].max_bitpool);
> > > +
> > > +        if (config->min_bitpool > config->max_bitpool) {
> > > +            pa_log_debug("No supported bitpool in table %u", i);
> > > +            continue;
> > > +        }
> > > +
> > > +        break;
> > >      }
> > >
> > > -    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");
> > > +    if (i == capabilities_table_elements) {
> > > +        pa_log_error("No supported configuration");
> > >          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);
> > > +    return sizeof(*config);
> > > +}
> > >
> > > -    if (config->min_bitpool > config->max_bitpool) {
> > > -        pa_log_error("No supported bitpool");
> > > -        return 0;
> > > +static uint8_t fill_preferred_configuration_lq(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[254]) {
> > > +    return fill_preferred_configuration_table(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, sbc_lq_caps_table, PA_ELEMENTSOF(sbc_lq_caps_table));
> > > +}
> > > +
> > > +static uint8_t fill_preferred_configuration_mq(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[254]) {
> > > +    return fill_preferred_configuration_table(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, sbc_mq_caps_table, PA_ELEMENTSOF(sbc_mq_caps_table));
> > > +}
> > > +
> > > +static uint8_t fill_preferred_configuration_hq(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[254]) {
> > > +    return fill_preferred_configuration_table(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, sbc_hq_caps_table, PA_ELEMENTSOF(sbc_hq_caps_table));
> > > +}
> > > +
> > > +static uint8_t fill_preferred_configuration_uhq(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[254]) {
> > > +    return fill_preferred_configuration_table(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, sbc_uhq_caps_table, PA_ELEMENTSOF(sbc_uhq_caps_table));
> > > +}
> > > +
> > > +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:
> > > +            switch (mode) {
> > > +                case SBC_CHANNEL_MODE_MONO:
> > > +                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
> > > +                case SBC_CHANNEL_MODE_STEREO:
> > > +                case SBC_CHANNEL_MODE_JOINT_STEREO:
> > > +                    return SBC_BITPOOL_HQ_JOINT_STEREO_44100;
> > > +            }
> > > +
> > > +        case SBC_SAMPLING_FREQ_44100:
> > > +            switch (mode) {
> > > +                case SBC_CHANNEL_MODE_MONO:
> > > +                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
> > > +                    return SBC_BITPOOL_HQ_MONO_44100;
> > > +
> > > +                case SBC_CHANNEL_MODE_STEREO:
> > > +                case SBC_CHANNEL_MODE_JOINT_STEREO:
> > > +                    return SBC_BITPOOL_HQ_JOINT_STEREO_44100;
> > > +            }
> > > +
> > > +        case SBC_SAMPLING_FREQ_48000:
> > > +            switch (mode) {
> > > +                case SBC_CHANNEL_MODE_MONO:
> > > +                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
> > > +                    return SBC_BITPOOL_HQ_MONO_48000;
> > > +
> > > +                case SBC_CHANNEL_MODE_STEREO:
> > > +                case SBC_CHANNEL_MODE_JOINT_STEREO:
> > > +                    return SBC_BITPOOL_HQ_JOINT_STEREO_48000;
> > > +            }
> > >      }
> > >
> > > -    return sizeof(*config);
> > > +    pa_assert_not_reached();
> > > +}
> > > +
> > > +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;
> > > +    uint8_t ret;
> > > +
> > > +    ret = fill_preferred_configuration_table(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, sbc_auto_caps_table, PA_ELEMENTSOF(sbc_auto_caps_table));
> > > +    config->max_bitpool = PA_MIN(default_bitpool(config->frequency, config->channel_mode), config->max_bitpool);
> > > +    config->max_bitpool = PA_MAX(config->max_bitpool, config->min_bitpool);
> > > +
> > > +    return ret;
> > >  }
> > >
> > >  static void set_params(struct sbc_info *sbc_info) {
> > > @@ -486,13 +784,17 @@ static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
> > >      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->mode == SBC_CHANNEL_MODE_MONO || sbc_info->mode == SBC_CHANNEL_MODE_DUAL_CHANNEL) {
> > > +        /* For Mono and Dual Channel modes bitpool value is separete for each channel */
> > > +        bitpool = sbc_info->sbc.bitpool - SBC_SEPARATE_BITPOOL_DEC_STEP;
> > > +        if (bitpool <= SBC_SEPARATE_BITPOOL_DEC_LIMIT)
> > > +            return 0;
> > > +    } else {
> > > +        /* For Stereo modes bitpool value is combined for both channels */
> > > +        bitpool = sbc_info->sbc.bitpool - SBC_COMBINED_BITPOOL_DEC_STEP;
> > > +        if (bitpool <= SBC_COMBINED_BITPOOL_DEC_LIMIT)
> > > +            return 0;
> > > +    }
> > >
> > >      if (sbc_info->sbc.bitpool == bitpool)
> > >          return 0;
> > > @@ -501,6 +803,10 @@ static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
> > >      return get_block_size(codec_info, write_link_mtu);
> > >  }
> > >
> > > +static size_t reduce_encoder_bitrate_none(void *codec_info, size_t write_link_mtu) {
> > > +    return 0;
> > > +}
> > > +
> > >  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;
> > > @@ -625,9 +931,69 @@ static size_t decode_buffer(void *codec_info, const uint8_t *input_buffer, size_
> > >      return d - output_buffer;
> > >  }
> > >
> > > +const pa_a2dp_codec pa_a2dp_codec_sbc_lq = {
> > > +    .codec_name = "sbc_lq",
> > > +    .codec_description = "SBC (Low Quality)",
> > > +    .codec_id = { A2DP_CODEC_SBC, 0, 0 },
> > > +    .support_backchannel = false,
> > > +    .accept_capabilities = accept_capabilities_lq,
> > > +    .choose_capabilities = choose_capabilities_lq,
> > > +    .fill_capabilities = fill_capabilities_lq,
> > > +    .validate_configuration = validate_configuration_lq,
> > > +    .fill_preferred_configuration = fill_preferred_configuration_lq,
> > > +    .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_none,
> > > +    .encode_buffer = encode_buffer,
> > > +    .decode_buffer = decode_buffer,
> > > +};
> > > +
> > > +const pa_a2dp_codec pa_a2dp_codec_sbc_mq = {
> > > +    .codec_name = "sbc_mq",
> > > +    .codec_description = "SBC (Middle Quality)",
> > > +    .codec_id = { A2DP_CODEC_SBC, 0, 0 },
> > > +    .support_backchannel = false,
> > > +    .accept_capabilities = accept_capabilities_mq,
> > > +    .choose_capabilities = choose_capabilities_mq,
> > > +    .fill_capabilities = fill_capabilities_mq,
> > > +    .validate_configuration = validate_configuration_mq,
> > > +    .fill_preferred_configuration = fill_preferred_configuration_mq,
> > > +    .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_none,
> > > +    .encode_buffer = encode_buffer,
> > > +    .decode_buffer = decode_buffer,
> > > +};
> > > +
> > > +const pa_a2dp_codec pa_a2dp_codec_sbc_hq = {
> > > +    .codec_name = "sbc_hq",
> > > +    .codec_description = "SBC (High Quality)",
> > > +    .codec_id = { A2DP_CODEC_SBC, 0, 0 },
> > > +    .support_backchannel = false,
> > > +    .accept_capabilities = accept_capabilities_hq,
> > > +    .choose_capabilities = choose_capabilities_hq,
> > > +    .fill_capabilities = fill_capabilities_hq,
> > > +    .validate_configuration = validate_configuration_hq,
> > > +    .fill_preferred_configuration = fill_preferred_configuration_hq,
> > > +    .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_none,
> > > +    .encode_buffer = encode_buffer,
> > > +    .decode_buffer = decode_buffer,
> > > +};
> > > +
> > >  const pa_a2dp_codec pa_a2dp_codec_sbc = {
> > >      .codec_name = "sbc",
> > > -    .codec_description = "SBC",
> > > +    .codec_description = "SBC (Automatic Quality)",
> > >      .codec_id = { A2DP_CODEC_SBC, 0, 0 },
> > >      .support_backchannel = false,
> > >      .accept_capabilities = accept_capabilities,
> > > @@ -644,3 +1010,23 @@ const pa_a2dp_codec pa_a2dp_codec_sbc = {
> > >      .encode_buffer = encode_buffer,
> > >      .decode_buffer = decode_buffer,
> > >  };
> > > +
> > > +const pa_a2dp_codec pa_a2dp_codec_sbc_uhq = {
> > > +    .codec_name = "sbc_uhq",
> > > +    .codec_description = "SBC (Ultra High Quality)",
> > > +    .codec_id = { A2DP_CODEC_SBC, 0, 0 },
> > > +    .support_backchannel = false,
> > > +    .accept_capabilities = accept_capabilities_uhq,
> > > +    .choose_capabilities = choose_capabilities_uhq,
> > > +    .fill_capabilities = fill_capabilities_uhq,
> > > +    .validate_configuration = validate_configuration_uhq,
> > > +    .fill_preferred_configuration = fill_preferred_configuration_uhq,
> > > +    .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_none,
> > > +    .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
> > > index 063077237..1fe857202 100644
> > > --- a/src/modules/bluetooth/a2dp-codec-util.c
> > > +++ b/src/modules/bluetooth/a2dp-codec-util.c
> > > @@ -26,20 +26,32 @@
> > >
> > >  #include "a2dp-codec-util.h"
> > >
> > > +extern const pa_a2dp_codec pa_a2dp_codec_sbc_lq;
> > >  extern const pa_a2dp_codec pa_a2dp_codec_faststream;
> > > +extern const pa_a2dp_codec pa_a2dp_codec_sbc_mq;
> > >  extern const pa_a2dp_codec pa_a2dp_codec_sbc;
> > >  #ifdef HAVE_OPENAPTX
> > >  extern const pa_a2dp_codec pa_a2dp_codec_aptx;
> > > +#endif
> > > +extern const pa_a2dp_codec pa_a2dp_codec_sbc_hq;
> > > +extern const pa_a2dp_codec pa_a2dp_codec_sbc_uhq;
> > > +#ifdef HAVE_OPENAPTX
> > >  extern const pa_a2dp_codec pa_a2dp_codec_aptx_hd;
> > >  #endif
> > >
> > >  /* 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_faststream,
> > > -    &pa_a2dp_codec_sbc,
> > > +    &pa_a2dp_codec_sbc_lq,
> > > +    &pa_a2dp_codec_faststream,  /* Uses exactly same parameters as SBC-LQ, but with voice backchannel */
> > > +    &pa_a2dp_codec_sbc_mq,
> > > +    &pa_a2dp_codec_sbc,         /* SBC in automatic mode, from SBC-LQ to SBC-HQ; not SBC-UHQ */
> > >  #ifdef HAVE_OPENAPTX
> > >      &pa_a2dp_codec_aptx,
> > > +#endif
> > > +    &pa_a2dp_codec_sbc_hq,      /* SBC-HQ has same quality as aptX */
> > > +    &pa_a2dp_codec_sbc_uhq,     /* SBC-UHQ has higher quality than aptX, but lower than aptX HD */
> > > +#ifdef HAVE_OPENAPTX
> > >      &pa_a2dp_codec_aptx_hd,
> > >  #endif
> > >  };
> > > --
> > > 2.11.0
> > >
> >
> >
>
> --
> Pali Rohár
> pali.rohar@gmail.com
Pali Rohár Feb. 6, 2019, 1:59 p.m.
On Wednesday 06 February 2019 15:51:00 Luiz Augusto von Dentz wrote:
> Hi Pali,
> On Wed, Feb 6, 2019 at 2:58 PM Pali Rohár <pali.rohar@gmail.com> wrote:
> >
> > On Wednesday 06 February 2019 14:04:28 Luiz Augusto von Dentz wrote:
> > > Hi Pali,
> > > On Sun, Feb 3, 2019 at 4:16 PM Pali Rohár <pali.rohar@gmail.com> wrote:
> > > >
> > > > Specify configuration for Low, Middle, High and Ultra High Quality of SBC
> > > > codec. SBC codec in Ultra High Quality has higher quality than aptX.
> > >
> > > Low and Middle quality don't seems to serve any purpose since we do
> > > drop quality automatically when necessary,
> >
> > It may be useful in more cases:
> >
> > 1) To know what type of quality is used. Auto means "user does not know
> > used quality".
> >
> > 2) To force lower bitrate if higher one is causing problems (and
> > decreasing is not working correctly) in bluetooth transport. E.g. you
> > need temporary lower bitrate and later increase it back. Whole auto
> > quality mode can only lower it, so once it go into low mode it never
> > return back.
> >
> > E.g. you connect phone with laptop, listening music and you want to
> > transfer big file...
> >
> > 3) To do quality comparison. In some cases other side can have problems
> > with higher bitrate. So with this profile you can lower it.
> 
> It might be too complicated for the user to decide, also some remote
> stack may stop at first match if they just have SBC in which case it
> may never select the quality the user want. We could however switch
> the order so that the last used endpoint appears first, it just means
> the bluetoothd needs to remember the last used endpoint. Im still not
> convinced that this will be very useful though, so perhaps Id leave
> those qualities to be enabled somehow, so distros that want to have
> this many endpoits/profiles can just opt for auto-quality which can
> scale to ultra.

Look, for most headsets which have bitpool limited to 53, you cannot use
Ultra High Quality mode until channel mode is switched from stereo to
dual channel. So for this case I separated UHQ mode and not added it
into Auto mode...