[pulseaudio-commits] [Git][pulseaudio/pulseaudio][master] 2 commits: bluetooth: a2dp dual channel SBC XQ codec configurations

PulseAudio Marge Bot gitlab at gitlab.freedesktop.org
Mon Feb 1 17:18:51 UTC 2021



PulseAudio Marge Bot pushed to branch master at PulseAudio / pulseaudio


Commits:
89082cbf by Igor V. Kovalenko at 2021-02-01T17:10:52+00:00
bluetooth: a2dp dual channel SBC XQ codec configurations

Desired SBC bitpool value is calculated from target bitrate limit.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/476>

- - - - -
bf99b4bd by Igor V. Kovalenko at 2021-02-01T17:10:52+00:00
bluetooth: support increasing bitrate for SBC XQ

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/476>

- - - - -


2 changed files:

- src/modules/bluetooth/a2dp-codec-sbc.c
- src/modules/bluetooth/a2dp-codec-util.c


Changes:

=====================================
src/modules/bluetooth/a2dp-codec-sbc.c
=====================================
@@ -51,6 +51,9 @@ struct sbc_info {
     uint8_t initial_bitpool;
     uint8_t min_bitpool;
     uint8_t max_bitpool;
+
+    uint8_t nr_blocks;
+    uint8_t nr_subbands;
 };
 
 static bool can_be_supported(bool for_encoding) {
@@ -81,6 +84,30 @@ static bool can_accept_capabilities(const uint8_t *capabilities_buffer, uint8_t
     return true;
 }
 
+static bool can_accept_capabilities_xq(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
+    const a2dp_sbc_t *capabilities = (const a2dp_sbc_t *) capabilities_buffer;
+
+    if (capabilities_size != sizeof(*capabilities))
+        return false;
+
+    if (!(capabilities->frequency & (SBC_SAMPLING_FREQ_44100 | SBC_SAMPLING_FREQ_48000)))
+        return false;
+
+    if (!(capabilities->channel_mode & (SBC_CHANNEL_MODE_DUAL_CHANNEL)))
+        return false;
+
+    if (!(capabilities->allocation_method & (SBC_ALLOCATION_LOUDNESS)))
+        return false;
+
+    if (!(capabilities->subbands & (SBC_SUBBANDS_8)))
+        return false;
+
+    if (!(capabilities->block_length & (SBC_BLOCK_LENGTH_16)))
+        return false;
+
+    return true;
+}
+
 static const char *choose_remote_endpoint(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
     const pa_a2dp_codec_capabilities *a2dp_capabilities;
     const char *key;
@@ -95,6 +122,20 @@ static const char *choose_remote_endpoint(const pa_hashmap *capabilities_hashmap
     return NULL;
 }
 
+static const char *choose_remote_endpoint_xq(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
+    const pa_a2dp_codec_capabilities *a2dp_capabilities;
+    const char *key;
+    void *state;
+
+    /* There is no preference, just choose random valid entry */
+    PA_HASHMAP_FOREACH_KV(key, a2dp_capabilities, capabilities_hashmap, state) {
+        if (can_accept_capabilities_xq(a2dp_capabilities->buffer, a2dp_capabilities->size, for_encoding))
+            return key;
+    }
+
+    return NULL;
+}
+
 static uint8_t fill_capabilities(uint8_t capabilities_buffer[MAX_A2DP_CAPS_SIZE]) {
     a2dp_sbc_t *capabilities = (a2dp_sbc_t *) capabilities_buffer;
 
@@ -113,6 +154,29 @@ static uint8_t fill_capabilities(uint8_t capabilities_buffer[MAX_A2DP_CAPS_SIZE]
     return sizeof(*capabilities);
 }
 
+/* SBC XQ
+ *
+ * References:
+ *   https://habr.com/en/post/456476/
+ *   http://soundexpert.org/articles/-/blogs/audio-quality-of-sbc-xq-bluetooth-audio-codec
+ *
+ */
+static uint8_t fill_capabilities_xq(uint8_t capabilities_buffer[MAX_A2DP_CAPS_SIZE]) {
+    a2dp_sbc_t *capabilities = (a2dp_sbc_t *) capabilities_buffer;
+
+    pa_zero(*capabilities);
+
+    capabilities->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
+    capabilities->frequency = SBC_SAMPLING_FREQ_44100 | SBC_SAMPLING_FREQ_48000;
+    capabilities->allocation_method = SBC_ALLOCATION_LOUDNESS;
+    capabilities->subbands = SBC_SUBBANDS_8;
+    capabilities->block_length = SBC_BLOCK_LENGTH_16;
+    capabilities->min_bitpool = SBC_MIN_BITPOOL;
+    capabilities->max_bitpool = SBC_MAX_BITPOOL;
+
+    return sizeof(*capabilities);
+}
+
 static bool is_configuration_valid(const uint8_t *config_buffer, uint8_t config_size) {
     const a2dp_sbc_t *config = (const a2dp_sbc_t *) config_buffer;
 
@@ -314,38 +378,7 @@ static uint8_t fill_preferred_configuration(const pa_sample_spec *default_sample
     return sizeof(*config);
 }
 
-static void set_params(struct sbc_info *sbc_info) {
-    sbc_info->sbc.frequency = sbc_info->frequency;
-    sbc_info->sbc.blocks = sbc_info->blocks;
-    sbc_info->sbc.subbands = sbc_info->subbands;
-    sbc_info->sbc.mode = sbc_info->mode;
-    sbc_info->sbc.allocation = sbc_info->allocation;
-    sbc_info->sbc.bitpool = sbc_info->initial_bitpool;
-    sbc_info->sbc.endian = SBC_LE;
-
-    sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc);
-    sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
-}
-
-static void *init(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core) {
-    struct sbc_info *sbc_info;
-    const a2dp_sbc_t *config = (const a2dp_sbc_t *) config_buffer;
-    int ret;
-
-    pa_assert(config_size == sizeof(*config));
-    pa_assert(!for_backchannel);
-
-    sbc_info = pa_xnew0(struct sbc_info, 1);
-
-    ret = sbc_init(&sbc_info->sbc, 0);
-    if (ret != 0) {
-        pa_xfree(sbc_info);
-        pa_log_error("SBC initialization failed: %d", ret);
-        return NULL;
-    }
-
-    sample_spec->format = PA_SAMPLE_S16LE;
-
+static void set_info_and_sample_spec_from_sbc_config(struct sbc_info *sbc_info, pa_sample_spec *sample_spec, const a2dp_sbc_t *config) {
     switch (config->frequency) {
         case SBC_SAMPLING_FREQ_16000:
             sbc_info->frequency = SBC_FREQ_16000;
@@ -402,9 +435,11 @@ static void *init(bool for_encoding, bool for_backchannel, const uint8_t *config
     switch (config->subbands) {
         case SBC_SUBBANDS_4:
             sbc_info->subbands = SBC_SB_4;
+            sbc_info->nr_subbands = 4;
             break;
         case SBC_SUBBANDS_8:
             sbc_info->subbands = SBC_SB_8;
+            sbc_info->nr_subbands = 8;
             break;
         default:
             pa_assert_not_reached();
@@ -413,15 +448,19 @@ static void *init(bool for_encoding, bool for_backchannel, const uint8_t *config
     switch (config->block_length) {
         case SBC_BLOCK_LENGTH_4:
             sbc_info->blocks = SBC_BLK_4;
+            sbc_info->nr_blocks = 4;
             break;
         case SBC_BLOCK_LENGTH_8:
             sbc_info->blocks = SBC_BLK_8;
+            sbc_info->nr_blocks = 8;
             break;
         case SBC_BLOCK_LENGTH_12:
             sbc_info->blocks = SBC_BLK_12;
+            sbc_info->nr_blocks = 12;
             break;
         case SBC_BLOCK_LENGTH_16:
             sbc_info->blocks = SBC_BLK_16;
+            sbc_info->nr_blocks = 16;
             break;
         default:
             pa_assert_not_reached();
@@ -429,6 +468,182 @@ static void *init(bool for_encoding, bool for_backchannel, const uint8_t *config
 
     sbc_info->min_bitpool = config->min_bitpool;
     sbc_info->max_bitpool = config->max_bitpool;
+}
+
+static void set_params(struct sbc_info *sbc_info) {
+    sbc_info->sbc.frequency = sbc_info->frequency;
+    sbc_info->sbc.blocks = sbc_info->blocks;
+    sbc_info->sbc.subbands = sbc_info->subbands;
+    sbc_info->sbc.mode = sbc_info->mode;
+    sbc_info->sbc.allocation = sbc_info->allocation;
+    sbc_info->sbc.bitpool = sbc_info->initial_bitpool;
+    sbc_info->sbc.endian = SBC_LE;
+
+    sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc);
+    sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
+}
+
+uint8_t sbc_get_max_bitpool_below_rate(a2dp_sbc_t *config, uint8_t lower_bound, uint8_t upper_bound, uint32_t bitrate_cap) {
+    pa_sample_spec sample_spec;
+    struct sbc_info sbc_info;
+    int ret;
+
+    pa_assert(config);
+
+    ret = sbc_init(&sbc_info.sbc, 0);
+    if (ret != 0) {
+        pa_log_error("SBC initialization failed: %d", ret);
+        return lower_bound;
+    }
+
+    set_info_and_sample_spec_from_sbc_config(&sbc_info, &sample_spec, config);
+
+    while (upper_bound - lower_bound > 1) {
+        size_t midpoint = (upper_bound + lower_bound) / 2;
+
+        sbc_info.initial_bitpool = midpoint;
+        set_params(&sbc_info);
+
+        size_t bitrate = sbc_info.frame_length * 8 * sample_spec.rate / (sbc_info.nr_subbands * sbc_info.nr_blocks);
+
+        if (bitrate > bitrate_cap)
+            upper_bound = midpoint;
+        else
+            lower_bound = midpoint;
+    }
+
+    sbc_finish(&sbc_info.sbc);
+
+    pa_log_debug("SBC target bitrate %u bitpool %u sample rate %u", bitrate_cap, lower_bound, sample_spec.rate);
+
+    return lower_bound;
+}
+
+static uint8_t fill_preferred_configuration_xq(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE], uint32_t bitrate_cap) {
+    a2dp_sbc_t *config = (a2dp_sbc_t *) config_buffer;
+    const a2dp_sbc_t *capabilities = (const a2dp_sbc_t *) capabilities_buffer;
+    int i;
+
+    static const struct {
+        uint32_t rate;
+        uint8_t cap;
+    } freq_table[] = {
+        { 16000U, SBC_SAMPLING_FREQ_16000 },
+        { 32000U, SBC_SAMPLING_FREQ_32000 },
+        { 44100U, SBC_SAMPLING_FREQ_44100 },
+        { 48000U, SBC_SAMPLING_FREQ_48000 }
+    };
+
+    if (capabilities_size != sizeof(*capabilities)) {
+        pa_log_error("Invalid size of capabilities buffer");
+        return 0;
+    }
+
+    pa_zero(*config);
+
+    /* Find the lowest freq that is at least as high as the requested sampling rate */
+    for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++)
+        if (freq_table[i].rate >= default_sample_spec->rate && (capabilities->frequency & freq_table[i].cap)) {
+            config->frequency = freq_table[i].cap;
+            break;
+        }
+
+    if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {
+        for (--i; i >= 0; i--) {
+            if (capabilities->frequency & freq_table[i].cap) {
+                config->frequency = freq_table[i].cap;
+                break;
+            }
+        }
+
+        if (i < 0) {
+            pa_log_error("Not suitable sample rate");
+            return 0;
+        }
+    }
+
+    pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table));
+
+    if (default_sample_spec->channels <= 1) {
+        if (capabilities->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
+            config->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
+        else {
+            pa_log_error("No supported channel modes");
+            return 0;
+        }
+    } else {
+        if (capabilities->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
+            config->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
+        else {
+            pa_log_error("No supported channel modes");
+            return 0;
+        }
+    }
+
+    if (capabilities->block_length & SBC_BLOCK_LENGTH_16)
+        config->block_length = SBC_BLOCK_LENGTH_16;
+    else {
+        pa_log_error("No supported block lengths");
+        return 0;
+    }
+
+    if (capabilities->subbands & SBC_SUBBANDS_8)
+        config->subbands = SBC_SUBBANDS_8;
+    else {
+        pa_log_error("No supported subbands");
+        return 0;
+    }
+
+    if (capabilities->allocation_method & SBC_ALLOCATION_LOUDNESS)
+        config->allocation_method = SBC_ALLOCATION_LOUDNESS;
+    else {
+        pa_log_error("No supported allocation method");
+        return 0;
+    }
+
+    config->min_bitpool = (uint8_t) PA_MAX(SBC_MIN_BITPOOL, capabilities->min_bitpool);
+    config->max_bitpool = sbc_get_max_bitpool_below_rate(config, config->min_bitpool, capabilities->max_bitpool, bitrate_cap);
+
+    if (config->min_bitpool > config->max_bitpool) {
+        pa_log_error("No supported bitpool");
+        return 0;
+    }
+
+    return sizeof(*config);
+}
+
+static uint8_t fill_preferred_configuration_xq_453kbps(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE]) {
+    return fill_preferred_configuration_xq(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, 453000);
+}
+
+static uint8_t fill_preferred_configuration_xq_512kbps(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE]) {
+    return fill_preferred_configuration_xq(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, 512000);
+}
+
+static uint8_t fill_preferred_configuration_xq_552kbps(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE]) {
+    return fill_preferred_configuration_xq(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, 552000);
+}
+
+static void *init(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core) {
+    struct sbc_info *sbc_info;
+    const a2dp_sbc_t *config = (const a2dp_sbc_t *) config_buffer;
+    int ret;
+
+    pa_assert(config_size == sizeof(*config));
+    pa_assert(!for_backchannel);
+
+    sbc_info = pa_xnew0(struct sbc_info, 1);
+
+    ret = sbc_init(&sbc_info->sbc, 0);
+    if (ret != 0) {
+        pa_xfree(sbc_info);
+        pa_log_error("SBC initialization failed: %d", ret);
+        return NULL;
+    }
+
+    sample_spec->format = PA_SAMPLE_S16LE;
+
+    set_info_and_sample_spec_from_sbc_config(sbc_info, sample_spec, config);
 
     /* Set minimum bitpool for source to get the maximum possible block_size
      * in get_block_size() function. This block_size is length of buffer used
@@ -692,3 +907,82 @@ const pa_a2dp_codec pa_a2dp_codec_sbc = {
     .encode_buffer = encode_buffer,
     .decode_buffer = decode_buffer,
 };
+
+/* There are multiple definitions of SBC XQ, but in all cases this is
+ * SBC codec in Dual Channel mode, 8 bands, block length 16, allocation method Loudness,
+ * with bitpool adjusted to match target bitrates.
+ *
+ * Most commonly choosen bitrates and reasons are:
+ * 453000 - this yields most efficient packing of frames on Android for bluetooth EDR 2mbps
+ * 512000 - this looks to be old limit stated in bluetooth documents
+ * 552000 - this yields most efficient packing of frames on Android for bluetooth EDR 3mbps
+ *
+ * Efficient packing considerations do not apply on Linux (yet?) but still
+ * we can gain from increased bitrate.
+ */
+
+const pa_a2dp_codec pa_a2dp_codec_sbc_xq_453 = {
+    .name = "sbc_xq_453",
+    .description = "SBC XQ 453kbps",
+    .id = { A2DP_CODEC_SBC, 0, 0 },
+    .support_backchannel = false,
+    .can_be_supported = can_be_supported,
+    .can_accept_capabilities = can_accept_capabilities_xq,
+    .choose_remote_endpoint = choose_remote_endpoint_xq,
+    .fill_capabilities = fill_capabilities_xq,
+    .is_configuration_valid = is_configuration_valid,
+    .fill_preferred_configuration = fill_preferred_configuration_xq_453kbps,
+    .init = init,
+    .deinit = deinit,
+    .reset = reset,
+    .get_read_block_size = get_block_size,
+    .get_write_block_size = get_block_size,
+    .reduce_encoder_bitrate = reduce_encoder_bitrate,
+    .increase_encoder_bitrate = increase_encoder_bitrate,
+    .encode_buffer = encode_buffer,
+    .decode_buffer = decode_buffer,
+};
+
+const pa_a2dp_codec pa_a2dp_codec_sbc_xq_512 = {
+    .name = "sbc_xq_512",
+    .description = "SBC XQ 512kbps",
+    .id = { A2DP_CODEC_SBC, 0, 0 },
+    .support_backchannel = false,
+    .can_be_supported = can_be_supported,
+    .can_accept_capabilities = can_accept_capabilities_xq,
+    .choose_remote_endpoint = choose_remote_endpoint_xq,
+    .fill_capabilities = fill_capabilities_xq,
+    .is_configuration_valid = is_configuration_valid,
+    .fill_preferred_configuration = fill_preferred_configuration_xq_512kbps,
+    .init = init,
+    .deinit = deinit,
+    .reset = reset,
+    .get_read_block_size = get_block_size,
+    .get_write_block_size = get_block_size,
+    .reduce_encoder_bitrate = reduce_encoder_bitrate,
+    .increase_encoder_bitrate = increase_encoder_bitrate,
+    .encode_buffer = encode_buffer,
+    .decode_buffer = decode_buffer,
+};
+
+const pa_a2dp_codec pa_a2dp_codec_sbc_xq_552 = {
+    .name = "sbc_xq_552",
+    .description = "SBC XQ 552kbps",
+    .id = { A2DP_CODEC_SBC, 0, 0 },
+    .support_backchannel = false,
+    .can_be_supported = can_be_supported,
+    .can_accept_capabilities = can_accept_capabilities_xq,
+    .choose_remote_endpoint = choose_remote_endpoint_xq,
+    .fill_capabilities = fill_capabilities_xq,
+    .is_configuration_valid = is_configuration_valid,
+    .fill_preferred_configuration = fill_preferred_configuration_xq_552kbps,
+    .init = init,
+    .deinit = deinit,
+    .reset = reset,
+    .get_read_block_size = get_block_size,
+    .get_write_block_size = get_block_size,
+    .reduce_encoder_bitrate = reduce_encoder_bitrate,
+    .increase_encoder_bitrate = increase_encoder_bitrate,
+    .encode_buffer = encode_buffer,
+    .decode_buffer = decode_buffer,
+};


=====================================
src/modules/bluetooth/a2dp-codec-util.c
=====================================
@@ -30,6 +30,9 @@
 #include "a2dp-codec-util.h"
 
 extern const pa_a2dp_codec pa_a2dp_codec_sbc;
+extern const pa_a2dp_codec pa_a2dp_codec_sbc_xq_453;
+extern const pa_a2dp_codec pa_a2dp_codec_sbc_xq_512;
+extern const pa_a2dp_codec pa_a2dp_codec_sbc_xq_552;
 #ifdef HAVE_GSTAPTX
 extern const pa_a2dp_codec pa_a2dp_codec_aptx;
 extern const pa_a2dp_codec pa_a2dp_codec_aptx_hd;
@@ -53,6 +56,9 @@ static const pa_a2dp_codec *pa_a2dp_codecs[] = {
     &pa_a2dp_codec_aptx,
 #endif
     &pa_a2dp_codec_sbc,
+    &pa_a2dp_codec_sbc_xq_453,
+    &pa_a2dp_codec_sbc_xq_512,
+    &pa_a2dp_codec_sbc_xq_552,
 };
 
 unsigned int pa_bluetooth_a2dp_codec_count(void) {



View it on GitLab: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/compare/ff2f16294f23f2d7dbaef50f83d833fd9e8584da...bf99b4bdfc133ec41b29352ab954a7669ae1c569

-- 
View it on GitLab: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/compare/ff2f16294f23f2d7dbaef50f83d833fd9e8584da...bf99b4bdfc133ec41b29352ab954a7669ae1c569
You're receiving this email because of your account on gitlab.freedesktop.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.freedesktop.org/archives/pulseaudio-commits/attachments/20210201/8711c585/attachment-0001.htm>


More information about the pulseaudio-commits mailing list