diff --git a/spa/meson.build b/spa/meson.build index 46097cf37..31eef86ed 100644 --- a/spa/meson.build +++ b/spa/meson.build @@ -29,6 +29,7 @@ if get_option('spa-plugins') bluez_dep = dependency('bluez', version : '>= 4.101') sbc_dep = dependency('sbc') ldac_dep = dependency('ldacBT-enc', required : false) + ldac_abr_dep = dependency('ldacBT-abr', required : false) aptx_dep = dependency('libopenaptx', required : false) endif if get_option('ffmpeg') diff --git a/spa/plugins/bluez5/a2dp-codec-aptx.c b/spa/plugins/bluez5/a2dp-codec-aptx.c index e366ee60f..a63d79db1 100644 --- a/spa/plugins/bluez5/a2dp-codec-aptx.c +++ b/spa/plugins/bluez5/a2dp-codec-aptx.c @@ -262,6 +262,11 @@ static void codec_deinit(void *data) free(this); } +static int codec_abr_process (void *data, size_t unsent) +{ + return -ENOTSUP; +} + static int codec_start_encode (void *data, void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) { @@ -335,6 +340,8 @@ const struct a2dp_codec a2dp_codec_aptx = { .codec_id = APTX_CODEC_ID }, .name = "aptx", .description = "aptX", + .send_fill_frames = 2, + .recv_fill_frames = 2, .fill_caps = codec_fill_caps, .select_config = codec_select_config, .enum_config = codec_enum_config, @@ -357,6 +364,8 @@ const struct a2dp_codec a2dp_codec_aptx_hd = { .codec_id = APTX_HD_CODEC_ID }, .name = "aptx_hd", .description = "aptX HD", + .send_fill_frames = 2, + .recv_fill_frames = 2, .fill_caps = codec_fill_caps, .select_config = codec_select_config, .enum_config = codec_enum_config, @@ -364,6 +373,7 @@ const struct a2dp_codec a2dp_codec_aptx_hd = { .deinit = codec_deinit, .get_block_size = codec_get_block_size, .get_num_blocks = codec_get_num_blocks, + .abr_process = codec_abr_process, .start_encode = codec_start_encode, .encode = codec_encode, .start_decode = codec_start_decode, diff --git a/spa/plugins/bluez5/a2dp-codec-ldac.c b/spa/plugins/bluez5/a2dp-codec-ldac.c index c809e6be8..6c7c6f152 100644 --- a/spa/plugins/bluez5/a2dp-codec-ldac.c +++ b/spa/plugins/bluez5/a2dp-codec-ldac.c @@ -31,21 +31,30 @@ #include +#ifdef ENABLE_LDAC_ABR +#include +#endif + #include "defs.h" #include "rtp.h" #include "a2dp-codecs.h" -#define MAX_FRAME_COUNT 16 +#define LDAC_ABR_MAX_PACKET_NBYTES 1280 + +#define LDAC_ABR_INTERVAL_MS 5 /* 2 frames * 128 lsu / 48000 */ + +/* decrease ABR thresholds to increase stability */ +#define LDAC_ABR_THRESHOLD_CRITICAL 6 +#define LDAC_ABR_THRESHOLD_DANGEROUSTREND 4 +#define LDAC_ABR_THRESHOLD_SAFETY_FOR_HQSQ 3 -#define LDACBT_EQMID_BITRATE_990000 LDACBT_EQMID_HQ -#define LDACBT_EQMID_BITRATE_660000 LDACBT_EQMID_SQ -#define LDACBT_EQMID_BITRATE_330000 LDACBT_EQMID_MQ -#define LDACBT_EQMID_BITRATE_492000 3 -#define LDACBT_EQMID_BITRATE_396000 4 struct impl { HANDLE_LDAC_BT ldac; - +#ifdef ENABLE_LDAC_ABR + HANDLE_LDAC_ABR ldac_abr; + bool enable_abr; +#endif struct rtp_header *header; struct rtp_payload *payload; @@ -55,6 +64,48 @@ struct impl { int fmt; int codesize; int frame_length; + int frame_count; +}; + +enum { + LDACBT_EQMID_BITRATE_990000 = 0, /* LDACBT_EQMID_HQ */ + LDACBT_EQMID_BITRATE_660000, /* LDACBT_EQMID_SQ */ + LDACBT_EQMID_BITRATE_330000, /* LDACBT_EQMID_MQ */ + + LDACBT_EQMID_BITRATE_492000, + LDACBT_EQMID_BITRATE_396000, + LDACBT_EQMID_BITRATE_282000, + LDACBT_EQMID_BITRATE_246000, + LDACBT_EQMID_BITRATE_216000, + LDACBT_EQMID_BITRATE_198000, + LDACBT_EQMID_BITRATE_180000, + LDACBT_EQMID_BITRATE_162000, + LDACBT_EQMID_BITRATE_150000, + LDACBT_EQMID_BITRATE_138000 +}; + +struct ldac_config +{ + int eqmid; + int frame_count; /* number of ldac frames in packet */ + int frame_length; /* ldac frame length */ + int frame_length_1ch; /* ldac frame length per channel */ +}; + +static const struct ldac_config ldac_config_table[] = { + { LDACBT_EQMID_BITRATE_990000, 2, 330, 165}, + { LDACBT_EQMID_BITRATE_660000, 3, 220, 110}, + { LDACBT_EQMID_BITRATE_330000, 4, 164, 82}, + { LDACBT_EQMID_BITRATE_492000, 5, 132, 66}, + { LDACBT_EQMID_BITRATE_396000, 6, 110, 55}, + { LDACBT_EQMID_BITRATE_282000, 7, 94, 47}, + { LDACBT_EQMID_BITRATE_246000, 8, 82, 41}, + { LDACBT_EQMID_BITRATE_216000, 9, 72, 36}, + { LDACBT_EQMID_BITRATE_198000, 10, 66, 33}, + { LDACBT_EQMID_BITRATE_180000, 11, 60, 30}, + { LDACBT_EQMID_BITRATE_162000, 12, 54, 27}, + { LDACBT_EQMID_BITRATE_150000, 13, 50, 25}, + { LDACBT_EQMID_BITRATE_138000, 14, 46, 23}, }; static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, uint8_t caps[A2DP_MAX_CAPS_SIZE]) @@ -198,52 +249,55 @@ static int codec_enum_config(const struct a2dp_codec *codec, return *param == NULL ? -EIO : 1; } -static int get_frame_length(struct impl *this) +static int update_frame_info(struct impl *this) { + const struct ldac_config *config; this->eqmid = ldacBT_get_eqmid(this->ldac); - switch (this->eqmid) { - case LDACBT_EQMID_BITRATE_990000: - return 330; - case LDACBT_EQMID_BITRATE_660000: - return 220; - case LDACBT_EQMID_BITRATE_492000: - return 164; - case LDACBT_EQMID_BITRATE_396000: - return 132; - case LDACBT_EQMID_BITRATE_330000: - return 110; + for (size_t i = 0; i < SPA_N_ELEMENTS(ldac_config_table); ++i) { + config = &ldac_config_table[i]; + + if (config->eqmid != this->eqmid) + continue; + + this->frame_count = config->frame_count; + this->frame_length = config->frame_length; + return 0; } return -EINVAL; } static int codec_reduce_bitpool(void *data) { +#ifdef ENABLE_LDAC_ABR + return -EINVAL; +#else struct impl *this = data; int res; + if (this->eqmid == LDACBT_EQMID_BITRATE_330000) + return this->eqmid; res = ldacBT_alter_eqmid_priority(this->ldac, LDACBT_EQMID_INC_CONNECTION); - this->frame_length = get_frame_length(this); + update_frame_info(this); return res; +#endif } static int codec_increase_bitpool(void *data) { +#ifdef ENABLE_LDAC_ABR + return -EINVAL; +#else struct impl *this = data; int res; res = ldacBT_alter_eqmid_priority(this->ldac, LDACBT_EQMID_INC_QUALITY); - this->frame_length = get_frame_length(this); + update_frame_info(this); return res; +#endif } static int codec_get_num_blocks(void *data) { struct impl *this = data; - size_t rtp_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload); - size_t frame_count = SPA_MIN(this->mtu - rtp_size, 660u) / this->frame_length; - - /* frame_count is only 4 bit number */ - if (frame_count > 15) - frame_count = 15; - return frame_count; + return this->frame_count; } static int codec_get_block_size(void *data) @@ -267,6 +321,12 @@ static void *codec_init(const struct a2dp_codec *codec, uint32_t flags, if (this->ldac == NULL) goto error_errno; +#ifdef ENABLE_LDAC_ABR + this->ldac_abr = ldac_ABR_get_handle(); + if (this->ldac_abr == NULL) + goto error_errno; +#endif + this->eqmid = LDACBT_EQMID_SQ; this->mtu = mtu; this->frequency = info->info.raw.rate; @@ -317,7 +377,22 @@ static void *codec_init(const struct a2dp_codec *codec, uint32_t flags, if (res < 0) goto error; - this->frame_length = get_frame_length(this); +#ifdef ENABLE_LDAC_ABR + res = ldac_ABR_Init(this->ldac_abr, LDAC_ABR_INTERVAL_MS); + if (res < 0) + goto error; + + res = ldac_ABR_set_thresholds(this->ldac_abr, + LDAC_ABR_THRESHOLD_CRITICAL, + LDAC_ABR_THRESHOLD_DANGEROUSTREND, + LDAC_ABR_THRESHOLD_SAFETY_FOR_HQSQ); + if (res < 0) + goto error; + + this->enable_abr = true; +#endif + + update_frame_info(this); return this; @@ -326,6 +401,10 @@ error_errno: error: if (this->ldac) ldacBT_free_handle(this->ldac); +#ifdef ENABLE_LDAC_ABR + if (this->ldac_abr) + ldac_ABR_free_handle(this->ldac_abr); +#endif free(this); errno = -res; return NULL; @@ -336,9 +415,27 @@ static void codec_deinit(void *data) struct impl *this = data; if (this->ldac) ldacBT_free_handle(this->ldac); +#ifdef ENABLE_LDAC_ABR + if (this->ldac_abr) + ldac_ABR_free_handle(this->ldac_abr); +#endif free(this); } +static int codec_abr_process(void *data, size_t unsent) +{ +#ifdef ENABLE_LDAC_ABR + struct impl *this = data; + int res; + res = ldac_ABR_Proc(this->ldac, this->ldac_abr, + unsent / LDAC_ABR_MAX_PACKET_NBYTES, this->enable_abr); + update_frame_info(this); + return res; +#else + return -EINVAL; +#endif +} + static int codec_start_encode (void *data, void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) { @@ -385,6 +482,7 @@ const struct a2dp_codec a2dp_codec_ldac = { .codec_id = LDAC_CODEC_ID }, .name = "ldac", .description = "LDAC", + .send_fill_frames = 4, .fill_caps = codec_fill_caps, .select_config = codec_select_config, .enum_config = codec_enum_config, @@ -392,6 +490,7 @@ const struct a2dp_codec a2dp_codec_ldac = { .deinit = codec_deinit, .get_block_size = codec_get_block_size, .get_num_blocks = codec_get_num_blocks, + .abr_process = codec_abr_process, .start_encode = codec_start_encode, .encode = codec_encode, .reduce_bitpool = codec_reduce_bitpool, diff --git a/spa/plugins/bluez5/a2dp-codec-sbc.c b/spa/plugins/bluez5/a2dp-codec-sbc.c index 0391952c4..f5b6998ac 100644 --- a/spa/plugins/bluez5/a2dp-codec-sbc.c +++ b/spa/plugins/bluez5/a2dp-codec-sbc.c @@ -415,6 +415,11 @@ static void codec_deinit(void *data) free(this); } +static int codec_abr_process (void *data, size_t unsent) +{ + return -ENOTSUP; +} + static int codec_start_encode (void *data, void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) { @@ -483,6 +488,8 @@ const struct a2dp_codec a2dp_codec_sbc = { .codec_id = A2DP_CODEC_SBC, .name = "sbc", .description = "SBC", + .send_fill_frames = 2, + .recv_fill_frames = 2, .fill_caps = codec_fill_caps, .select_config = codec_select_config, .enum_config = codec_enum_config, @@ -490,6 +497,7 @@ const struct a2dp_codec a2dp_codec_sbc = { .deinit = codec_deinit, .get_block_size = codec_get_block_size, .get_num_blocks = codec_get_num_blocks, + .abr_process = codec_abr_process, .start_encode = codec_start_encode, .encode = codec_encode, .start_decode = codec_start_decode, diff --git a/spa/plugins/bluez5/a2dp-codecs.h b/spa/plugins/bluez5/a2dp-codecs.h index 6c6ddbb69..126a7a271 100644 --- a/spa/plugins/bluez5/a2dp-codecs.h +++ b/spa/plugins/bluez5/a2dp-codecs.h @@ -333,6 +333,9 @@ struct a2dp_codec { const char *name; const char *description; + const int send_fill_frames; + const int recv_fill_frames; + int (*fill_caps) (const struct a2dp_codec *codec, uint32_t flags, uint8_t caps[A2DP_MAX_CAPS_SIZE]); int (*select_config) (const struct a2dp_codec *codec, uint32_t flags, @@ -349,6 +352,8 @@ struct a2dp_codec { int (*get_block_size) (void *data); int (*get_num_blocks) (void *data); + int (*abr_process) (void *data, size_t unsent); + int (*start_encode) (void *data, void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp); int (*encode) (void *data, diff --git a/spa/plugins/bluez5/a2dp-sink.c b/spa/plugins/bluez5/a2dp-sink.c index 8646c8111..fb7468628 100644 --- a/spa/plugins/bluez5/a2dp-sink.c +++ b/spa/plugins/bluez5/a2dp-sink.c @@ -140,6 +140,7 @@ struct impl { uint64_t sample_count; uint8_t tmp_buffer[4096]; uint32_t tmp_buffer_used; + uint32_t fd_buffer_size; }; #define NAME "a2dp-sink" @@ -326,6 +327,11 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, return 0; } +static void update_num_blocks(struct impl *this) +{ + this->num_blocks = this->codec->get_num_blocks(this->codec_data); +} + static int reset_buffer(struct impl *this) { this->frame_count = 0; @@ -336,9 +342,27 @@ static int reset_buffer(struct impl *this) return 0; } +static int get_transport_unused_size(struct impl *this) +{ + int res, value; + res = ioctl(this->flush_source.fd, TIOCOUTQ, &value); + if (res < 0) { + spa_log_debug(this->log, NAME " %p: ioctl fail: %m", this); + return -errno; + } + spa_log_debug(this->log, NAME " %p: fd unused buffer size:%d/%d", this, value, this->fd_buffer_size); + return value; +} + static int send_buffer(struct impl *this) { - int written; + int written, unsent; + unsent = get_transport_unused_size(this); + if (unsent >= 0) { + unsent = this->fd_buffer_size - unsent; + this->codec->abr_process(this->codec_data, unsent); + update_num_blocks(this); + } spa_log_trace(this->log, NAME " %p: send %d %u %u %u", this, this->frame_count, this->seqnum, this->timestamp, this->buffer_used); @@ -524,7 +548,7 @@ again: spa_log_trace(this->log, NAME" %p: delay flush", this); if (now_time - this->last_error > SPA_NSEC_PER_SEC / 2) { this->codec->reduce_bitpool(this->codec_data); - this->num_blocks = this->codec->get_num_blocks(this->codec_data); + update_num_blocks(this); this->last_error = now_time; } enable_flush(this, true); @@ -537,7 +561,7 @@ again: else if (written > 0) { if (now_time - this->last_error > SPA_NSEC_PER_SEC) { this->codec->increase_bitpool(this->codec_data); - this->num_blocks = this->codec->get_num_blocks(this->codec_data); + update_num_blocks(this); this->last_error = now_time; } if (!spa_list_is_empty(&port->ready)) @@ -657,7 +681,7 @@ static int do_start(struct impl *this) spa_log_debug(this->log, NAME " %p: block_size %d num_blocks:%d", this, this->block_size, this->num_blocks); - val = FILL_FRAMES * this->transport->write_mtu; + val = SPA_MAX(this->codec->send_fill_frames, FILL_FRAMES) * this->transport->write_mtu; if (setsockopt(this->transport->fd, SOL_SOCKET, SO_SNDBUF, &val, sizeof(val)) < 0) spa_log_warn(this->log, NAME " %p: SO_SNDBUF %m", this); @@ -668,8 +692,9 @@ static int do_start(struct impl *this) else { spa_log_debug(this->log, NAME " %p: SO_SNDBUF: %d", this, val); } + this->fd_buffer_size = val; - val = FILL_FRAMES * this->transport->read_mtu; + val = SPA_MAX(this->codec->recv_fill_frames, FILL_FRAMES) * this->transport->read_mtu; if (setsockopt(this->transport->fd, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val)) < 0) spa_log_warn(this->log, NAME " %p: SO_RCVBUF %m", this); diff --git a/spa/plugins/bluez5/meson.build b/spa/plugins/bluez5/meson.build index a0aaddbb5..7dccf92d7 100644 --- a/spa/plugins/bluez5/meson.build +++ b/spa/plugins/bluez5/meson.build @@ -17,6 +17,10 @@ if ldac_dep.found() bluez5_sources += [ 'a2dp-codec-ldac.c' ] bluez5_args += [ '-DENABLE_LDAC' ] bluez5_deps += ldac_dep + if ldac_abr_dep.found() + bluez5_args += [ '-DENABLE_LDAC_ABR' ] + bluez5_deps += ldac_abr_dep + endif endif if aptx_dep.found() bluez5_sources += [ 'a2dp-codec-aptx.c' ]