bluetooth: Add faststream codec

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/628>
This commit is contained in:
Igor V. Kovalenko 2021-04-20 19:30:52 +03:00
parent 0498e7a3d0
commit cddb9f144a
7 changed files with 482 additions and 27 deletions

View file

@ -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);

View file

@ -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,

View file

@ -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,

View file

@ -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,
},
};

View file

@ -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) {

View file

@ -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 */

View file

@ -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) {