mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
a2dp: add ldac ABR support
Signed-off-by: Huang-Huang Bao <eh5@sokka.cn>
This commit is contained in:
parent
7ef15f3721
commit
16f5058af9
7 changed files with 186 additions and 34 deletions
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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' ]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue