summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIgor V. Kovalenko <igor.v.kovalenko@gmail.com>2021-04-20 19:30:52 +0300
committerIgor V. Kovalenko <igor.v.kovalenko@gmail.com>2022-10-17 09:07:09 +0300
commitcddb9f144a71cc0f1f690898056737fb4402b2b9 (patch)
tree7bf8bb0776e93ca431e21d7d2944978dd2718d8e
parent0498e7a3d05c8ccd4f0389778e04f964f8a0b2f8 (diff)
downloadpulseaudio-cddb9f144a71cc0f1f690898056737fb4402b2b9.tar.gz
bluetooth: Add faststream codec
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/628>
-rw-r--r--src/modules/bluetooth/a2dp-codec-api.h3
-rw-r--r--src/modules/bluetooth/a2dp-codec-aptx-gst.c2
-rw-r--r--src/modules/bluetooth/a2dp-codec-ldac-gst.c3
-rw-r--r--src/modules/bluetooth/a2dp-codec-sbc.c460
-rw-r--r--src/modules/bluetooth/a2dp-codec-util.c2
-rw-r--r--src/modules/bluetooth/bt-codec-api.h3
-rw-r--r--src/modules/bluetooth/module-bluez5-device.c36
7 files changed, 482 insertions, 27 deletions
diff --git a/src/modules/bluetooth/a2dp-codec-api.h b/src/modules/bluetooth/a2dp-codec-api.h
index bdfd3f390..e46981836 100644
--- a/src/modules/bluetooth/a2dp-codec-api.h
+++ b/src/modules/bluetooth/a2dp-codec-api.h
@@ -42,9 +42,6 @@ typedef struct pa_a2dp_endpoint_conf {
/* A2DP codec id */
pa_a2dp_codec_id id;
- /* True if codec is bi-directional and supports backchannel */
- bool support_backchannel;
-
/* Returns true if the codec can be supported on the system */
bool (*can_be_supported)(bool for_encoding);
diff --git a/src/modules/bluetooth/a2dp-codec-aptx-gst.c b/src/modules/bluetooth/a2dp-codec-aptx-gst.c
index 61b995b25..d0573e3b2 100644
--- a/src/modules/bluetooth/a2dp-codec-aptx-gst.c
+++ b/src/modules/bluetooth/a2dp-codec-aptx-gst.c
@@ -556,7 +556,6 @@ static size_t decode_buffer_hd(void *codec_info, const uint8_t *input_buffer, si
const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_aptx = {
.id = { A2DP_CODEC_VENDOR, APTX_VENDOR_ID, APTX_CODEC_ID },
- .support_backchannel = false,
.can_be_supported = can_be_supported,
.can_accept_capabilities = can_accept_capabilities,
.choose_remote_endpoint = choose_remote_endpoint,
@@ -580,7 +579,6 @@ const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_aptx = {
const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_aptx_hd = {
.id = { A2DP_CODEC_VENDOR, APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID },
- .support_backchannel = false,
.can_be_supported = can_be_supported,
.can_accept_capabilities = can_accept_capabilities_hd,
.choose_remote_endpoint = choose_remote_endpoint_hd,
diff --git a/src/modules/bluetooth/a2dp-codec-ldac-gst.c b/src/modules/bluetooth/a2dp-codec-ldac-gst.c
index c0bcc6668..dfefbf186 100644
--- a/src/modules/bluetooth/a2dp-codec-ldac-gst.c
+++ b/src/modules/bluetooth/a2dp-codec-ldac-gst.c
@@ -433,7 +433,6 @@ static size_t encode_buffer(void *codec_info, uint32_t timestamp, const uint8_t
const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_ldac_eqmid_hq = {
.id = { A2DP_CODEC_VENDOR, LDAC_VENDOR_ID, LDAC_CODEC_ID },
- .support_backchannel = false,
.can_be_supported = can_be_supported,
.can_accept_capabilities = can_accept_capabilities,
.choose_remote_endpoint = choose_remote_endpoint,
@@ -456,7 +455,6 @@ const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_ldac_eqmid_hq = {
const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_ldac_eqmid_sq = {
.id = { A2DP_CODEC_VENDOR, LDAC_VENDOR_ID, LDAC_CODEC_ID },
- .support_backchannel = false,
.can_be_supported = can_be_supported,
.can_accept_capabilities = can_accept_capabilities,
.choose_remote_endpoint = choose_remote_endpoint,
@@ -479,7 +477,6 @@ const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_ldac_eqmid_sq = {
const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_ldac_eqmid_mq = {
.id = { A2DP_CODEC_VENDOR, LDAC_VENDOR_ID, LDAC_CODEC_ID },
- .support_backchannel = false,
.can_be_supported = can_be_supported,
.can_accept_capabilities = can_accept_capabilities,
.choose_remote_endpoint = choose_remote_endpoint,
diff --git a/src/modules/bluetooth/a2dp-codec-sbc.c b/src/modules/bluetooth/a2dp-codec-sbc.c
index 54e52ae4c..6c6f99bb8 100644
--- a/src/modules/bluetooth/a2dp-codec-sbc.c
+++ b/src/modules/bluetooth/a2dp-codec-sbc.c
@@ -108,6 +108,24 @@ static bool can_accept_capabilities_xq(const uint8_t *capabilities_buffer, uint8
return true;
}
+static bool can_accept_capabilities_faststream(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
+ const a2dp_faststream_t *capabilities = (const a2dp_faststream_t *) capabilities_buffer;
+
+ if (capabilities_size != sizeof(*capabilities))
+ return false;
+
+ if (!(capabilities->direction & (FASTSTREAM_DIRECTION_SINK | FASTSTREAM_DIRECTION_SOURCE)))
+ return false;
+
+ if (!(capabilities->sink_frequency & (FASTSTREAM_SINK_SAMPLING_FREQ_44100 | FASTSTREAM_SINK_SAMPLING_FREQ_48000)))
+ return false;
+
+ if (!(capabilities->source_frequency & FASTSTREAM_SOURCE_SAMPLING_FREQ_16000))
+ 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;
@@ -136,6 +154,23 @@ static const char *choose_remote_endpoint_xq(const pa_hashmap *capabilities_hash
return NULL;
}
+static const char *choose_remote_endpoint_faststream(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) {
+ pa_log_debug("choose_remote_endpoint_faststream checking peer endpoint '%s'", key);
+ if (can_accept_capabilities_faststream(a2dp_capabilities->buffer, a2dp_capabilities->size, for_encoding))
+ return key;
+ }
+
+ pa_log_debug("choose_remote_endpoint_faststream matched no peer endpoint");
+
+ return NULL;
+}
+
static uint8_t fill_capabilities(uint8_t capabilities_buffer[MAX_A2DP_CAPS_SIZE]) {
a2dp_sbc_t *capabilities = (a2dp_sbc_t *) capabilities_buffer;
@@ -326,6 +361,46 @@ static uint8_t fill_capabilities_xq(uint8_t capabilities_buffer[MAX_A2DP_CAPS_SI
return sizeof(*capabilities);
}
+static uint8_t fill_capabilities_faststream(uint8_t capabilities_buffer[MAX_A2DP_CAPS_SIZE]) {
+ a2dp_faststream_t *capabilities = (a2dp_faststream_t *) capabilities_buffer;
+
+ pa_zero(*capabilities);
+
+ capabilities->info = A2DP_SET_VENDOR_ID_CODEC_ID(FASTSTREAM_VENDOR_ID, FASTSTREAM_CODEC_ID);
+
+ capabilities->direction = FASTSTREAM_DIRECTION_SINK | FASTSTREAM_DIRECTION_SOURCE;
+ capabilities->sink_frequency = FASTSTREAM_SINK_SAMPLING_FREQ_44100 | FASTSTREAM_SINK_SAMPLING_FREQ_48000;
+ capabilities->source_frequency = FASTSTREAM_SOURCE_SAMPLING_FREQ_16000;
+
+ return sizeof(*capabilities);
+}
+
+static bool is_configuration_valid_faststream(const uint8_t *config_buffer, uint8_t config_size) {
+ const a2dp_faststream_t *config = (const a2dp_faststream_t *) config_buffer;
+
+ if (config_size != sizeof(*config)) {
+ pa_log_error("Invalid size of config buffer");
+ return false;
+ }
+
+ if (!(config->direction & (FASTSTREAM_DIRECTION_SINK | FASTSTREAM_DIRECTION_SOURCE))) {
+ pa_log_error("Invalid FastStream direction in configuration");
+ return false;
+ }
+
+ if (config->sink_frequency != FASTSTREAM_SINK_SAMPLING_FREQ_44100 && config->sink_frequency != FASTSTREAM_SINK_SAMPLING_FREQ_48000) {
+ pa_log_error("Invalid FastStream sink sampling frequency in configuration");
+ return false;
+ }
+
+ if (config->source_frequency != FASTSTREAM_SOURCE_SAMPLING_FREQ_16000) {
+ pa_log_error("Invalid FastStream source sampling frequency in configuration");
+ return false;
+ }
+
+ return true;
+}
+
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;
@@ -527,6 +602,85 @@ static uint8_t fill_preferred_configuration(const pa_sample_spec *default_sample
return sizeof(*config);
}
+static uint8_t fill_preferred_configuration_faststream(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE]) {
+ a2dp_faststream_t *config = (a2dp_faststream_t *) config_buffer;
+ const a2dp_faststream_t *capabilities = (const a2dp_faststream_t *) capabilities_buffer;
+ int i;
+
+ static const struct {
+ uint32_t rate;
+ uint8_t cap;
+ } sink_freq_table[] = {
+ { 44100U, FASTSTREAM_SINK_SAMPLING_FREQ_44100 },
+ { 48000U, FASTSTREAM_SINK_SAMPLING_FREQ_48000 }
+ };
+
+ static const struct {
+ uint32_t rate;
+ uint8_t cap;
+ } source_freq_table[] = {
+ { 16000U, FASTSTREAM_SOURCE_SAMPLING_FREQ_16000 }
+ };
+
+ if (capabilities_size != sizeof(*capabilities)) {
+ pa_log_error("Invalid size of FastStream 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(sink_freq_table); i++)
+ if (sink_freq_table[i].rate >= default_sample_spec->rate && (capabilities->sink_frequency & sink_freq_table[i].cap)) {
+ config->sink_frequency = sink_freq_table[i].cap;
+ break;
+ }
+
+ /* Match with endpoint capabilities */
+ if ((unsigned) i == PA_ELEMENTSOF(sink_freq_table)) {
+ for (--i; i >= 0; i--) {
+ if (capabilities->sink_frequency & sink_freq_table[i].cap) {
+ config->sink_frequency = sink_freq_table[i].cap;
+ break;
+ }
+ }
+
+ if (i < 0) {
+ pa_log_error("Not suitable FastStream sink sample rate");
+ return 0;
+ }
+ }
+
+ pa_assert((unsigned) i < PA_ELEMENTSOF(sink_freq_table));
+
+ /* Only single frequency (for now?) */
+ config->source_frequency = FASTSTREAM_SOURCE_SAMPLING_FREQ_16000;
+ i = 0;
+
+ /* Match with endpoint capabilities */
+ if ((unsigned) i == PA_ELEMENTSOF(source_freq_table)) {
+ for (--i; i >= 0; i--) {
+ if (capabilities->source_frequency & source_freq_table[i].cap) {
+ config->source_frequency = source_freq_table[i].cap;
+ break;
+ }
+ }
+
+ if (i < 0) {
+ pa_log_error("Not suitable FastStream source sample rate");
+ return 0;
+ }
+ }
+
+ pa_assert((unsigned) i < PA_ELEMENTSOF(source_freq_table));
+
+ config->direction = FASTSTREAM_DIRECTION_SINK | FASTSTREAM_DIRECTION_SOURCE;
+
+ config->info = A2DP_SET_VENDOR_ID_CODEC_ID(FASTSTREAM_VENDOR_ID, FASTSTREAM_CODEC_ID);
+
+ return sizeof(*config);
+}
+
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;
@@ -684,6 +838,79 @@ static void *init(bool for_encoding, bool for_backchannel, const uint8_t *config
return sbc_info;
}
+static void *init_faststream(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_faststream_t *config = (const a2dp_faststream_t *) config_buffer;
+ int ret;
+
+ pa_assert(config_size == sizeof(*config));
+
+ 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;
+
+ if (for_encoding != for_backchannel) {
+ switch (config->sink_frequency) {
+ case FASTSTREAM_SINK_SAMPLING_FREQ_44100:
+ sbc_info->frequency = SBC_FREQ_44100;
+ sample_spec->rate = 44100U;
+ break;
+ case FASTSTREAM_SINK_SAMPLING_FREQ_48000:
+ sbc_info->frequency = SBC_FREQ_48000;
+ sample_spec->rate = 48000U;
+ break;
+ default:
+ pa_assert_not_reached();
+ }
+
+ sample_spec->channels = 2;
+
+ sbc_info->mode = SBC_MODE_JOINT_STEREO;
+ sbc_info->initial_bitpool = sbc_info->min_bitpool = sbc_info->max_bitpool = 29;
+ } else {
+ switch (config->source_frequency) {
+ case FASTSTREAM_SOURCE_SAMPLING_FREQ_16000:
+ sbc_info->frequency = SBC_FREQ_16000;
+ sample_spec->rate = 16000U;
+ break;
+ default:
+ pa_assert_not_reached();
+ }
+
+ sample_spec->channels = 1;
+
+ sbc_info->mode = SBC_MODE_MONO;
+ sbc_info->initial_bitpool = sbc_info->min_bitpool = sbc_info->max_bitpool = 32;
+ }
+
+ sbc_info->allocation = SBC_AM_LOUDNESS;
+ sbc_info->subbands = SBC_SB_8;
+ sbc_info->nr_subbands = 8;
+ sbc_info->blocks = SBC_BLK_16;
+ sbc_info->nr_blocks = 16;
+
+ set_params(sbc_info);
+ if (sbc_info->frame_length & 1)
+ ++sbc_info->frame_length;
+
+ pa_log_info("FastStream %s SBC parameters: allocation=%s, subbands=%u, blocks=%u, mode=%s bitpool=%u codesize=%u frame_length=%u",
+ for_encoding ? "encoder" : "decoder",
+ sbc_info->sbc.allocation ? "SNR" : "Loudness", sbc_info->sbc.subbands ? 8 : 4,
+ (sbc_info->sbc.blocks+1)*4, sbc_info->sbc.mode == SBC_MODE_MONO ? "Mono" :
+ sbc_info->sbc.mode == SBC_MODE_DUAL_CHANNEL ? "DualChannel" :
+ sbc_info->sbc.mode == SBC_MODE_STEREO ? "Stereo" : "JointStereo",
+ sbc_info->sbc.bitpool, (unsigned)sbc_info->codesize, (unsigned)sbc_info->frame_length);
+
+ return sbc_info;
+}
+
static void deinit(void *codec_info) {
struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
@@ -722,6 +949,25 @@ static int reset(void *codec_info) {
return 0;
}
+static int reset_faststream(void *codec_info) {
+ struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
+ int ret;
+
+ ret = sbc_reinit(&sbc_info->sbc, 0);
+ if (ret != 0) {
+ pa_log_error("SBC reinitialization failed: %d", ret);
+ return -1;
+ }
+
+ /* sbc_reinit() sets also default parameters, so reset them back */
+ set_params(sbc_info);
+ if (sbc_info->frame_length & 1)
+ ++sbc_info->frame_length;
+
+ sbc_info->seq_num = 0;
+ return 0;
+}
+
static size_t get_block_size(void *codec_info, size_t link_mtu) {
struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
size_t rtp_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload);
@@ -742,6 +988,28 @@ static size_t get_block_size(void *codec_info, size_t link_mtu) {
return frame_count * sbc_info->codesize;
}
+static size_t get_write_block_size_faststream(void *codec_info, size_t link_mtu) {
+ struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
+ size_t frame_count = link_mtu / sbc_info->frame_length;
+
+ /* 3 frames seem to work best, with minimal glitches */
+ if (frame_count > 3)
+ frame_count = 3;
+
+ return frame_count * sbc_info->codesize;
+}
+
+static size_t get_read_block_size_faststream(void *codec_info, size_t link_mtu) {
+ /* With SBC bitpool >= 29 and any combination of blocks, subbands
+ * and channels maximum compression ratio 4:1 is achieved with
+ * blocks=16, subbands=8, channels=2, bitpool=29
+ *
+ * Though smaller bitpools can yield higher compression ratio, faststream is
+ * assumed to have fixed bitpool so maximum output size is link_mtu * 4.
+ */
+ return link_mtu * 4;
+}
+
static size_t get_encoded_block_size(void *codec_info, size_t input_size) {
struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
size_t rtp_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload);
@@ -752,6 +1020,15 @@ static size_t get_encoded_block_size(void *codec_info, size_t input_size) {
return (input_size / sbc_info->codesize) * sbc_info->frame_length + rtp_size;
}
+static size_t get_encoded_block_size_faststream(void *codec_info, size_t input_size) {
+ struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
+
+ /* input size should be aligned to codec input block size */
+ pa_assert_fp(input_size % sbc_info->codesize == 0);
+
+ return (input_size / sbc_info->codesize) * sbc_info->frame_length;
+}
+
static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
uint8_t bitpool;
@@ -860,6 +1137,72 @@ static size_t encode_buffer(void *codec_info, uint32_t timestamp, const uint8_t
return d - output_buffer;
}
+static size_t encode_buffer_faststream(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;
+ uint8_t *d;
+ const uint8_t *p;
+ size_t to_write, to_encode;
+ uint8_t frame_count;
+
+ frame_count = 0;
+
+ p = input_buffer;
+ to_encode = input_size;
+
+ d = output_buffer;
+ to_write = output_size;
+
+ /* frame_count is only 4 bit number */
+ while (PA_LIKELY(to_encode > 0 && to_write > 0)) {
+ ssize_t written;
+ ssize_t encoded;
+
+ encoded = sbc_encode(&sbc_info->sbc,
+ p, to_encode,
+ d, to_write,
+ &written);
+
+ if (PA_UNLIKELY(encoded <= 0)) {
+ pa_log_error("SBC encoding error (%li)", (long) encoded);
+ break;
+ }
+
+ if (PA_UNLIKELY(written < 0)) {
+ pa_log_error("SBC encoding error (%li)", (long) written);
+ break;
+ }
+
+ while (written < sbc_info->frame_length && written < to_write)
+ d[written++] = 0;
+
+ pa_assert_fp((size_t) encoded <= to_encode);
+ pa_assert_fp((size_t) encoded == sbc_info->codesize);
+
+ pa_assert_fp((size_t) written <= to_write);
+ pa_assert_fp((size_t) written == sbc_info->frame_length);
+
+ p += encoded;
+ to_encode -= encoded;
+
+ d += written;
+ to_write -= written;
+
+ frame_count++;
+ }
+
+ PA_ONCE_BEGIN {
+ pa_log_debug("Using SBC codec implementation: %s", pa_strnull(sbc_get_implementation_info(&sbc_info->sbc)));
+ } PA_ONCE_END;
+
+ if (PA_UNLIKELY(frame_count == 0)) {
+ *processed = 0;
+ return 0;
+ }
+
+ *processed = p - input_buffer;
+ return d - output_buffer;
+}
+
static size_t decode_buffer(void *codec_info, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) {
struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
@@ -924,9 +1267,76 @@ static size_t decode_buffer(void *codec_info, const uint8_t *input_buffer, size_
return d - output_buffer;
}
+static size_t decode_buffer_faststream(void *codec_info, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) {
+ struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
+
+ const uint8_t *p;
+ uint8_t *d;
+ size_t to_write, to_decode;
+ pa_sample_spec decoded_sample_spec = {
+ .format = PA_SAMPLE_S16LE,
+ .channels = 1,
+ .rate = 16000U
+ };
+
+ p = input_buffer;
+ to_decode = input_size;
+
+ d = output_buffer;
+ to_write = output_size;
+
+ while (PA_LIKELY(to_decode > 0 && to_write > 0)) {
+ size_t written;
+ ssize_t decoded;
+
+ decoded = sbc_decode(&sbc_info->sbc,
+ p, to_decode,
+ d, to_write,
+ &written);
+
+ if (PA_UNLIKELY(decoded <= 0)) {
+ pa_log_error("FastStream SBC decoding error (%li)", (long) decoded);
+ decoded = PA_MIN(sbc_info->frame_length, to_decode);
+ written = PA_MIN(sbc_info->codesize, to_write);
+ pa_silence_memory(d, written, &decoded_sample_spec);
+ } else {
+ /* Reset codesize and frame_length to values found by decoder */
+ sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc);
+ sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
+
+ if (sbc_info->frequency != sbc_info->sbc.frequency) {
+ /* some devices unexpectedly return SBC frequency different from 16000
+ * remember this, and keep incoming sample rate at 16000 */
+ pa_log_debug("FastStream decoder detected SBC frequency %u, expected %u", sbc_info->sbc.frequency, sbc_info->frequency);
+ sbc_info->frequency = sbc_info->sbc.frequency;
+ }
+ }
+
+ if ((sbc_info->frame_length & 1) && decoded < to_decode) {
+ ++decoded;
+ ++sbc_info->frame_length;
+ }
+
+ pa_assert_fp((size_t) decoded <= to_decode);
+ pa_assert_fp((size_t) decoded == PA_MIN(sbc_info->frame_length, to_decode));
+
+ pa_assert_fp((size_t) written <= to_write);
+
+ p += decoded;
+ to_decode -= decoded;
+
+ d += written;
+ to_write -= written;
+ }
+
+ /* XXX eat remainder, may need to fix this if input frames are split across packets */
+ *processed = input_size;
+
+ return d - output_buffer;
+}
+
const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_sbc = {
.id = { A2DP_CODEC_SBC, 0, 0 },
- .support_backchannel = false,
.can_be_supported = can_be_supported,
.can_accept_capabilities = can_accept_capabilities,
.choose_remote_endpoint = choose_remote_endpoint,
@@ -964,7 +1374,6 @@ const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_sbc = {
const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_sbc_xq_453 = {
.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,
@@ -989,7 +1398,6 @@ const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_sbc_xq_453 = {
const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_sbc_xq_512 = {
.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,
@@ -1014,7 +1422,6 @@ const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_sbc_xq_512 = {
const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_sbc_xq_552 = {
.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,
@@ -1036,3 +1443,48 @@ const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_sbc_xq_552 = {
.decode_buffer = decode_buffer,
},
};
+
+/* FastStream codec is just SBC codec with fixed parameters.
+ *
+ * Sink stream parameters:
+ * 48.0kHz or 44.1kHz,
+ * Blocks 16,
+ * Sub-bands 8,
+ * Joint Stereo,
+ * Allocation method Loudness,
+ * Bitpool = 29
+ * (data rate = 212kbps, packet size = (71+1)3 <= DM5 = 220, with 3 SBC frames).
+ * SBC frame size is 71 bytes, but FastStream is zero-padded to the even size (72).
+ *
+ * Source stream parameters:
+ * 16kHz,
+ * Mono,
+ * Blocks 16,
+ * Sub-bands 8,
+ * Allocation method Loudness,
+ * Bitpool = 32
+ * (data rate = 72kbps, packet size = 723 <= DM5 = 220, with 3 SBC frames).
+ */
+
+const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_faststream = {
+ .id = { A2DP_CODEC_VENDOR, FASTSTREAM_VENDOR_ID, FASTSTREAM_CODEC_ID },
+ .can_be_supported = can_be_supported,
+ .can_accept_capabilities = can_accept_capabilities_faststream,
+ .choose_remote_endpoint = choose_remote_endpoint_faststream,
+ .fill_capabilities = fill_capabilities_faststream,
+ .is_configuration_valid = is_configuration_valid_faststream,
+ .fill_preferred_configuration = fill_preferred_configuration_faststream,
+ .bt_codec = {
+ .name = "faststream",
+ .description = "FastStream",
+ .support_backchannel = true,
+ .init = init_faststream,
+ .deinit = deinit,
+ .reset = reset_faststream,
+ .get_read_block_size = get_read_block_size_faststream,
+ .get_write_block_size = get_write_block_size_faststream,
+ .get_encoded_block_size = get_encoded_block_size_faststream,
+ .encode_buffer = encode_buffer_faststream,
+ .decode_buffer = decode_buffer_faststream,
+ },
+};
diff --git a/src/modules/bluetooth/a2dp-codec-util.c b/src/modules/bluetooth/a2dp-codec-util.c
index 7db025164..43cf34b2d 100644
--- a/src/modules/bluetooth/a2dp-codec-util.c
+++ b/src/modules/bluetooth/a2dp-codec-util.c
@@ -52,6 +52,7 @@ extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_ldac_eqmid_hq;
extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_ldac_eqmid_sq;
extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_ldac_eqmid_mq;
#endif
+extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_faststream;
/* This is list of supported codecs. Their order is important.
* Codec with lower index has higher priority. */
@@ -69,6 +70,7 @@ static const pa_a2dp_endpoint_conf *pa_a2dp_endpoint_configurations[] = {
&pa_a2dp_endpoint_conf_sbc_xq_453,
&pa_a2dp_endpoint_conf_sbc_xq_512,
&pa_a2dp_endpoint_conf_sbc_xq_552,
+ &pa_a2dp_endpoint_conf_faststream,
};
unsigned int pa_bluetooth_a2dp_endpoint_conf_count(void) {
diff --git a/src/modules/bluetooth/bt-codec-api.h b/src/modules/bluetooth/bt-codec-api.h
index 900ffe942..3ed47166a 100644
--- a/src/modules/bluetooth/bt-codec-api.h
+++ b/src/modules/bluetooth/bt-codec-api.h
@@ -26,6 +26,9 @@ typedef struct pa_bt_codec {
/* Human readable codec description */
const char *description;
+ /* True if codec is bi-directional and supports backchannel */
+ bool support_backchannel;
+
/* Initialize codec, returns codec info data and set sample_spec,
* for_encoding is true when codec_info is used for encoding,
* for_backchannel is true when codec_info is used for backchannel */
diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index bac8ca4b2..f50521c59 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -1342,6 +1342,7 @@ static pa_direction_t get_profile_direction(pa_bluetooth_profile_t p) {
/* Run from main thread */
static int transport_config(struct userdata *u) {
+ bool reverse_backchannel;
pa_assert(u);
pa_assert(u->transport);
pa_assert(!u->bt_codec);
@@ -1354,15 +1355,18 @@ static int transport_config(struct userdata *u) {
/* reset encoder buffer contents */
u->encoder_buffer_used = 0;
- if (get_profile_direction(u->profile) & PA_DIRECTION_OUTPUT) {
- u->encoder_info = u->bt_codec->init(true, false, u->transport->config, u->transport->config_size, &u->encoder_sample_spec, u->core);
+ /* forward encoding direction */
+ reverse_backchannel = u->bt_codec->support_backchannel && !(get_profile_direction(u->profile) & PA_DIRECTION_OUTPUT);
+
+ if ((get_profile_direction(u->profile) & PA_DIRECTION_OUTPUT) || u->bt_codec->support_backchannel) {
+ u->encoder_info = u->bt_codec->init(true, reverse_backchannel, u->transport->config, u->transport->config_size, &u->encoder_sample_spec, u->core);
if (!u->encoder_info)
return -1;
}
- if (get_profile_direction(u->profile) & PA_DIRECTION_INPUT) {
- u->decoder_info = u->bt_codec->init(false, false, u->transport->config, u->transport->config_size, &u->decoder_sample_spec, u->core);
+ if ((get_profile_direction(u->profile) & PA_DIRECTION_INPUT) || u->bt_codec->support_backchannel) {
+ u->decoder_info = u->bt_codec->init(false, reverse_backchannel, u->transport->config, u->transport->config_size, &u->decoder_sample_spec, u->core);
if (!u->decoder_info) {
if (u->encoder_info) {
@@ -1420,11 +1424,11 @@ static int init_profile(struct userdata *u) {
pa_assert(u->transport);
- if (get_profile_direction (u->profile) & PA_DIRECTION_OUTPUT)
+ if ((get_profile_direction(u->profile) & PA_DIRECTION_OUTPUT) || u->bt_codec->support_backchannel)
if (add_sink(u) < 0)
r = -1;
- if (get_profile_direction (u->profile) & PA_DIRECTION_INPUT)
+ if ((get_profile_direction(u->profile) & PA_DIRECTION_INPUT) || u->bt_codec->support_backchannel)
if (add_source(u) < 0)
r = -1;
@@ -1625,13 +1629,15 @@ static void thread_func(void *userdata) {
skip_bytes -= bytes_to_render;
}
- if (u->write_index > 0 && (get_profile_direction(u->profile) & PA_DIRECTION_OUTPUT)) {
- size_t new_write_block_size = u->bt_codec->reduce_encoder_bitrate(u->encoder_info, u->write_link_mtu);
- if (new_write_block_size) {
- u->write_block_size = new_write_block_size;
- handle_sink_block_size_change(u);
+ if (u->write_index > 0 && (get_profile_direction(u->profile) & PA_DIRECTION_OUTPUT || u->bt_codec->support_backchannel)) {
+ if (u->bt_codec->reduce_encoder_bitrate) {
+ size_t new_write_block_size = u->bt_codec->reduce_encoder_bitrate(u->encoder_info, u->write_link_mtu);
+ if (new_write_block_size) {
+ u->write_block_size = new_write_block_size;
+ handle_sink_block_size_change(u);
+ }
+ pa_gettimeofday(&tv_last_output_rate_change);
}
- pa_gettimeofday(&tv_last_output_rate_change);
}
}
@@ -1674,7 +1680,7 @@ static void thread_func(void *userdata) {
sleep_for = time_passed < next_write_at ? next_write_at - time_passed : 0;
/* pa_log("Sleeping for %lu; time passed %lu, next write at %lu", (unsigned long) sleep_for, (unsigned long) time_passed, (unsigned long)next_write_at); */
- if ((get_profile_direction(u->profile) & PA_DIRECTION_OUTPUT) && u->write_memchunk.memblock == NULL) {
+ if ((get_profile_direction(u->profile) & PA_DIRECTION_OUTPUT || u->bt_codec->support_backchannel) && u->write_memchunk.memblock == NULL) {
/* bt_write_buffer() is keeping up with input, try increasing bitrate */
if (u->bt_codec->increase_encoder_bitrate
&& pa_timeval_age(&tv_last_output_rate_change) >= u->device->output_rate_refresh_interval_ms * PA_USEC_PER_MSEC) {
@@ -1906,10 +1912,10 @@ static pa_available_t get_port_availability(struct userdata *u, pa_direction_t d
for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++) {
pa_bluetooth_transport *transport;
- if (!(get_profile_direction(i) & direction))
+ if (!(transport = u->device->transports[i]))
continue;
- if (!(transport = u->device->transports[i]))
+ if (!(get_profile_direction(i) & direction || (transport->bt_codec && transport->bt_codec->support_backchannel)))
continue;
switch(transport->state) {