[pulseaudio-commits] [Git][pulseaudio/pulseaudio][master] 6 commits: bluetooth: Free memblock after codec errors
PulseAudio Marge Bot (@pulseaudio-merge-bot)
gitlab at gitlab.freedesktop.org
Tue Oct 18 06:30:13 UTC 2022
PulseAudio Marge Bot pushed to branch master at PulseAudio / pulseaudio
Commits:
48f40352 by Igor V. Kovalenko at 2022-10-17T09:07:09+03:00
bluetooth: Free memblock after codec errors
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/628>
- - - - -
0498e7a3 by Igor V. Kovalenko at 2022-10-17T09:07:09+03:00
bluetooth: Do not use hardware volume control for A2DP backchannel
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/628>
- - - - -
cddb9f14 by Igor V. Kovalenko at 2022-10-17T09:07:09+03:00
bluetooth: Add faststream codec
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/628>
- - - - -
697a1a30 by Igor V. Kovalenko at 2022-10-17T09:07:09+03:00
bluetooth: Use stereo sample spec for faststream backchannel
Faststream backchannel decoder does not know whether incoming stream is mono or
stereo before first packet is decoded, and some devices return stereo stream.
As it is not easy to change source sample spec after source is created, use
stereo sample spec always and perform conversion if mono stream is found.
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/628>
- - - - -
76e01b25 by Igor V. Kovalenko at 2022-10-17T09:07:09+03:00
bluetooth: Handle fragmented faststream frames
Incoming frames can span multiple packets, add support for this.
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/628>
- - - - -
f2b748e8 by Igor V. Kovalenko at 2022-10-17T09:07:09+03:00
bluetooth: Add 20dB boost for faststream source volume
Some devices unexpectedly return stereo backchannel stream with wrong frequency,
and volume of backchannel stream appears too low.
If this happens, boost source volume by 20dB.
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/628>
- - - - -
7 changed files:
- src/modules/bluetooth/a2dp-codec-api.h
- src/modules/bluetooth/a2dp-codec-aptx-gst.c
- src/modules/bluetooth/a2dp-codec-ldac-gst.c
- src/modules/bluetooth/a2dp-codec-sbc.c
- src/modules/bluetooth/a2dp-codec-util.c
- src/modules/bluetooth/bt-codec-api.h
- src/modules/bluetooth/module-bluez5-device.c
Changes:
=====================================
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);
=====================================
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,
=====================================
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,
=====================================
src/modules/bluetooth/a2dp-codec-sbc.c
=====================================
@@ -39,6 +39,8 @@
#define SBC_BITPOOL_DEC_STEP 5
#define SBC_BITPOOL_INC_STEP 1
+#define SBC_SYNCWORD 0x9C
+
struct sbc_info {
sbc_t sbc; /* Codec data */
size_t codesize, frame_length; /* SBC Codesize, frame_length. We simply cache those values here */
@@ -54,6 +56,12 @@ struct sbc_info {
uint8_t nr_blocks;
uint8_t nr_subbands;
+
+ bool boost_source_volume;
+ /* Size of SBC frame fragment left over from previous decoding iteration */
+ size_t frame_fragment_size;
+ /* Maximum SBC frame size is 512 bytes when SBC compression ratio > 1 */
+ uint8_t frame_fragment[512];
};
static bool can_be_supported(bool for_encoding) {
@@ -108,6 +116,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 +162,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 +369,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 +610,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 +846,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 = 2;
+
+ 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;
@@ -709,6 +944,29 @@ static int reset(void *codec_info) {
struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
int ret;
+ /* forget about source volume boost */
+ sbc_info->boost_source_volume = false;
+
+ /* forget last saved frame fragment */
+ sbc_info->frame_fragment_size = 0;
+
+ 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);
+
+ sbc_info->seq_num = 0;
+ 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);
@@ -717,6 +975,8 @@ static int reset(void *codec_info) {
/* 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;
@@ -742,6 +1002,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 +1034,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 +1151,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 +1281,141 @@ 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 = 2,
+ .rate = 16000U
+ };
+ uint8_t decode_buffer[4096];
+ uint8_t frame_buffer[4096];
+
+ to_decode = input_size;
+
+ /* append input buffer to fragment left from previous decode call */
+ if (sbc_info->frame_fragment_size) {
+
+ if (sbc_info->frame_fragment_size + to_decode > sizeof(frame_buffer)) {
+ pa_log_debug("FastStream SBC input (saved + incoming) size %lu larger than buffer size %lu, input truncated to fit",
+ sbc_info->frame_fragment_size + to_decode, sizeof(frame_buffer));
+ to_decode = sizeof(frame_buffer) - sbc_info->frame_fragment_size;
+ }
+
+ memcpy(frame_buffer, sbc_info->frame_fragment, sbc_info->frame_fragment_size);
+ memcpy(frame_buffer + sbc_info->frame_fragment_size, input_buffer, to_decode);
+
+ to_decode += sbc_info->frame_fragment_size;
+ p = frame_buffer;
+
+ /* clear saved fragment */
+ sbc_info->frame_fragment_size = 0;
+ } else
+ p = input_buffer;
+
+ d = output_buffer;
+ to_write = output_size;
+
+ while (PA_LIKELY(to_decode > 0 && to_write > 0)) {
+ size_t written = 0;
+ ssize_t decoded;
+
+ /* skip to SBC sync word before attempting decode */
+ if (*p != SBC_SYNCWORD) {
+ ++p;
+ --to_decode;
+ continue;
+ }
+
+ decoded = sbc_decode(&sbc_info->sbc,
+ p, to_decode,
+ decode_buffer, sizeof(decode_buffer),
+ &written);
+
+ if (PA_UNLIKELY(decoded <= 0)) {
+ /* sbc_decode returns -1 if input too short,
+ * break from loop to save this frame fragment for next decode iteration */
+ if (decoded == -1) {
+ pa_log_debug("FastStream SBC decoding error (%li) input %lu is too short", (long) decoded, to_decode);
+ break;
+ }
+
+ /* otherwise failed to decode frame, skip to next SBC sync word */
+ pa_log_error("FastStream SBC decoding error (%li)", (long) decoded);
+ decoded = 1;
+ } 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->mode != sbc_info->sbc.mode)
+ sbc_info->mode = sbc_info->sbc.mode;
+
+ 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;
+
+ /* volume is too low for known devices with unexpected source SBC frequency */
+ pa_log_debug("FastStream decoder requesting 20dB boost for source volume");
+ sbc_info->boost_source_volume = true;
+ }
+
+ if (sbc_info->sbc.mode == SBC_MODE_MONO) {
+ const void *interleave_buf[2] = {decode_buffer, decode_buffer};
+ /* mono->stereo conversion needs to fit into remaining output space */
+ written = PA_MIN(to_write / 2, written);
+ pa_interleave(interleave_buf, 2, d, pa_sample_size(&decoded_sample_spec), written / pa_sample_size(&decoded_sample_spec));
+ written *= 2;
+ } else
+ memcpy(d, decode_buffer, written);
+ }
+
+ pa_assert_fp((size_t) decoded <= to_decode);
+ pa_assert_fp((size_t) written <= to_write);
+
+ p += decoded;
+ to_decode -= decoded;
+
+ d += written;
+ to_write -= written;
+ }
+
+ if (to_decode) {
+ if (to_decode > sizeof(sbc_info->frame_fragment)) {
+ pa_log_debug("FastStream remaining SBC fragment size %lu larger than buffer size %lu, remainder truncated to fit",
+ to_decode, sizeof(sbc_info->frame_fragment));
+ p += to_decode - sizeof(sbc_info->frame_fragment);
+ to_decode = sizeof(sbc_info->frame_fragment);
+ }
+
+ pa_log_debug("FastStream saving SBC fragment size %lu for next decoding iteration", to_decode);
+ memcpy(sbc_info->frame_fragment, p, to_decode);
+ sbc_info->frame_fragment_size = to_decode;
+ }
+
+ *processed = input_size;
+
+ return d - output_buffer;
+}
+
+/* Boost sink backchannel mic volume by 20dB as it appears too quiet */
+double get_source_output_volume_factor_dB_faststream(void *codec_info) {
+ struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
+
+ if (sbc_info->boost_source_volume)
+ return 20.;
+
+ return 1.0;
+}
+
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 +1453,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 +1477,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 +1501,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 +1522,49 @@ 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,
+ .get_source_output_volume_factor_dB = get_source_output_volume_factor_dB_faststream,
+ },
+};
=====================================
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) {
=====================================
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 */
@@ -64,4 +67,7 @@ typedef struct pa_bt_codec {
* returns size of filled ouput_buffer and set processed to size of
* processed input_buffer */
size_t (*decode_buffer)(void *codec_info, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed);
+
+ /* Get volume factor which needs to be applied to output samples */
+ double (*get_source_output_volume_factor_dB)(void *codec_info);
} pa_bt_codec;
=====================================
src/modules/bluetooth/module-bluez5-device.c
=====================================
@@ -116,6 +116,8 @@ struct userdata {
pa_hook_slot *sink_volume_changed_slot;
pa_hook_slot *source_volume_changed_slot;
+ pa_hook_slot *source_output_new_hook_slot;
+
pa_bluetooth_discovery *discovery;
pa_bluetooth_device *device;
pa_bluetooth_transport *transport;
@@ -392,6 +394,8 @@ static int bt_process_render(struct userdata *u) {
if (processed != u->write_memchunk.length) {
pa_log_error("Encoding error");
+ pa_memblock_unref(u->write_memchunk.memblock);
+ pa_memchunk_reset(&u->write_memchunk);
return -1;
}
@@ -535,6 +539,7 @@ static int bt_process_push(struct userdata *u) {
if (processed != (size_t) received) {
pa_log_error("Decoding error");
+ pa_memblock_unref(memchunk.memblock);
return -1;
}
@@ -961,6 +966,12 @@ static void source_setup_volume_callback(pa_source *s) {
if (pa_bluetooth_profile_is_a2dp(u->profile) && !u->transport->device->avrcp_absolute_volume)
return;
+ /* Do not use hardware volume controls for backchannel of A2DP sink */
+ if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) {
+ pa_assert_fp(u->transport->bt_codec && u->transport->bt_codec->support_backchannel);
+ return;
+ }
+
/* Remote volume control has to be supported for the callback to make sense,
* otherwise this source should continue performing attenuation in software
* without HW_VOLUME_CTL.
@@ -1029,6 +1040,12 @@ static int add_source(struct userdata *u) {
if (!u->transport_acquired)
switch (u->profile) {
+ case PA_BLUETOOTH_PROFILE_A2DP_SINK:
+ if (u->bt_codec && u->bt_codec->support_backchannel)
+ data.suspend_cause = PA_SUSPEND_USER;
+ else
+ pa_assert_not_reached();
+ break;
case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
case PA_BLUETOOTH_PROFILE_HFP_AG:
case PA_BLUETOOTH_PROFILE_HSP_AG:
@@ -1043,7 +1060,6 @@ static int add_source(struct userdata *u) {
else
pa_assert_not_reached();
break;
- case PA_BLUETOOTH_PROFILE_A2DP_SINK:
case PA_BLUETOOTH_PROFILE_OFF:
pa_assert_not_reached();
break;
@@ -1198,6 +1214,12 @@ static void sink_setup_volume_callback(pa_sink *s) {
if (pa_bluetooth_profile_is_a2dp(u->profile) && !u->transport->device->avrcp_absolute_volume)
return;
+ /* Do not use hardware volume controls for backchannel of A2DP source */
+ if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE) {
+ pa_assert_fp(u->transport->bt_codec && u->transport->bt_codec->support_backchannel);
+ return;
+ }
+
/* Remote volume control has to be supported for the callback to make sense,
* otherwise this sink should continue performing attenuation in software
* without HW_VOLUME_CTL.
@@ -1322,6 +1344,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);
@@ -1334,15 +1357,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) {
@@ -1400,11 +1426,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;
@@ -1605,13 +1631,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);
}
}
@@ -1654,7 +1682,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) {
@@ -1886,10 +1914,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) {
@@ -2729,6 +2757,30 @@ static int device_process_msg(pa_msgobject *obj, int code, void *data, int64_t o
return 0;
}
+/* Run from main thread */
+static pa_hook_result_t a2dp_source_output_fixate_hook_callback(pa_core *c, pa_source_output_new_data *new_data, struct userdata *u) {
+ double volume_factor_dB;
+ pa_cvolume cv;
+
+ pa_assert(c);
+ pa_assert(new_data);
+ pa_assert(u);
+
+ /* When transport is released, there is no decoder and no codec */
+ if (!u->bt_codec || !u->decoder_info)
+ return PA_HOOK_OK;
+
+ if (!u->bt_codec->get_source_output_volume_factor_dB)
+ return PA_HOOK_OK;
+
+ volume_factor_dB = u->bt_codec->get_source_output_volume_factor_dB(u->decoder_info);
+
+ pa_cvolume_set(&cv, u->decoder_sample_spec.channels, pa_sw_volume_from_dB(volume_factor_dB));
+ pa_source_output_new_data_apply_volume_factor_source(new_data, &cv);
+
+ return PA_HOOK_OK;
+}
+
int pa__init(pa_module* m) {
struct userdata *u;
const char *path;
@@ -2810,6 +2862,8 @@ int pa__init(pa_module* m) {
u->transport_source_volume_changed_slot =
pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_SOURCE_VOLUME_CHANGED), PA_HOOK_NORMAL, (pa_hook_cb_t) transport_source_volume_changed_cb, u);
+ u->source_output_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) a2dp_source_output_fixate_hook_callback, u);
+
if (add_card(u) < 0)
goto fail;
@@ -2873,6 +2927,9 @@ void pa__done(pa_module *m) {
stop_thread(u);
+ if (u->source_output_new_hook_slot)
+ pa_hook_slot_free(u->source_output_new_hook_slot);
+
if (u->device_connection_changed_slot)
pa_hook_slot_free(u->device_connection_changed_slot);
View it on GitLab: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/compare/45df212d6614ad7f57d9e53616c0faac00195c86...f2b748e851668acc67a8dcc190ff8ba822fa034e
--
View it on GitLab: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/compare/45df212d6614ad7f57d9e53616c0faac00195c86...f2b748e851668acc67a8dcc190ff8ba822fa034e
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/20221018/a368e848/attachment-0001.htm>
More information about the pulseaudio-commits
mailing list