diff options
author | Igor V. Kovalenko <igor.v.kovalenko@gmail.com> | 2021-04-20 19:30:52 +0300 |
---|---|---|
committer | Igor V. Kovalenko <igor.v.kovalenko@gmail.com> | 2022-10-17 09:07:09 +0300 |
commit | cddb9f144a71cc0f1f690898056737fb4402b2b9 (patch) | |
tree | 7bf8bb0776e93ca431e21d7d2944978dd2718d8e | |
parent | 0498e7a3d05c8ccd4f0389778e04f964f8a0b2f8 (diff) | |
download | pulseaudio-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.h | 3 | ||||
-rw-r--r-- | src/modules/bluetooth/a2dp-codec-aptx-gst.c | 2 | ||||
-rw-r--r-- | src/modules/bluetooth/a2dp-codec-ldac-gst.c | 3 | ||||
-rw-r--r-- | src/modules/bluetooth/a2dp-codec-sbc.c | 460 | ||||
-rw-r--r-- | src/modules/bluetooth/a2dp-codec-util.c | 2 | ||||
-rw-r--r-- | src/modules/bluetooth/bt-codec-api.h | 3 | ||||
-rw-r--r-- | src/modules/bluetooth/module-bluez5-device.c | 36 |
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) { |