a2dp: add ldac ABR support

Signed-off-by: Huang-Huang Bao <eh5@sokka.cn>
This commit is contained in:
Huang-Huang Bao 2020-12-19 08:21:40 +08:00
parent 7ef15f3721
commit 16f5058af9
No known key found for this signature in database
GPG key ID: 33C3271387A13D1B
7 changed files with 186 additions and 34 deletions

View file

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

View file

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

View file

@ -31,21 +31,30 @@
#include <ldacBT.h>
#ifdef ENABLE_LDAC_ABR
#include <ldacBT_abr.h>
#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,

View file

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

View file

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

View file

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

View file

@ -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' ]