mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
bluez5: add aptx-LL codec
Support the low-latency variant of the aptx codec. The magic mostly seems to be on the device side, since the stream is the same as standard aptx, but latency is smaller even if stream/packet sizes are the same. Sound output latency is noticeably less than with the standard aptx. Tested on Sennheiser HD 250 / Avantree Aria Pro. The codec in principle also supports bidirectional duplex streams, but that is not implemented here.
This commit is contained in:
parent
73994feda9
commit
90b62ba2b3
6 changed files with 143 additions and 21 deletions
|
|
@ -44,6 +44,7 @@ enum spa_bluetooth_audio_codec {
|
|||
SPA_BLUETOOTH_AUDIO_CODEC_APTX,
|
||||
SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD,
|
||||
SPA_BLUETOOTH_AUDIO_CODEC_LDAC,
|
||||
SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL,
|
||||
|
||||
/* HFP */
|
||||
SPA_BLUETOOTH_AUDIO_CODEC_CVSD = 0x100,
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ static const struct spa_type_info spa_type_bluetooth_audio_codec[] = {
|
|||
{ SPA_BLUETOOTH_AUDIO_CODEC_APTX, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "aptx", NULL },
|
||||
{ SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "aptx_hd", NULL },
|
||||
{ SPA_BLUETOOTH_AUDIO_CODEC_LDAC, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "ldac", NULL },
|
||||
{ SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "aptx_ll", NULL },
|
||||
|
||||
{ SPA_BLUETOOTH_AUDIO_CODEC_CVSD, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "cvsd", NULL },
|
||||
{ SPA_BLUETOOTH_AUDIO_CODEC_MSBC, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "msbc", NULL },
|
||||
|
|
|
|||
|
|
@ -35,6 +35,18 @@
|
|||
#include "rtp.h"
|
||||
#include "a2dp-codecs.h"
|
||||
|
||||
#define APTX_LL_LEVEL1(level) (((level) >> 8) & 0xFF)
|
||||
#define APTX_LL_LEVEL2(level) (((level) >> 0) & 0xFF)
|
||||
#define APTX_LL_LEVEL(level1, level2) ((((level1) & 0xFF) << 8) | (((level2) & 0xFF) << 0))
|
||||
|
||||
/*
|
||||
* XXX: Bump requested device buffer levels up by 50% from defaults,
|
||||
* XXX: increasing latency similarly. This seems to be necessary for
|
||||
* XXX: stable output when moving headphones. It might be possible to
|
||||
* XXX: reduce this by changing the scheduling of the socket writes.
|
||||
*/
|
||||
#define LL_LEVEL_ADJUSTMENT 3/2
|
||||
|
||||
struct impl {
|
||||
struct aptx_context *aptx;
|
||||
|
||||
|
|
@ -49,13 +61,25 @@ struct impl {
|
|||
bool hd;
|
||||
};
|
||||
|
||||
static inline bool codec_is_hd(const struct a2dp_codec *codec) {
|
||||
static inline bool codec_is_hd(const struct a2dp_codec *codec)
|
||||
{
|
||||
return codec->vendor.codec_id == APTX_HD_CODEC_ID
|
||||
&& codec->vendor.vendor_id == APTX_HD_VENDOR_ID;
|
||||
}
|
||||
|
||||
static inline size_t codec_get_caps_size(const struct a2dp_codec *codec) {
|
||||
return codec_is_hd(codec) ? sizeof(a2dp_aptx_hd_t) : sizeof(a2dp_aptx_t);
|
||||
static inline bool codec_is_ll(const struct a2dp_codec *codec)
|
||||
{
|
||||
return codec->id == SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL;
|
||||
}
|
||||
|
||||
static inline size_t codec_get_caps_size(const struct a2dp_codec *codec)
|
||||
{
|
||||
if (codec_is_hd(codec))
|
||||
return sizeof(a2dp_aptx_hd_t);
|
||||
else if (codec_is_ll(codec))
|
||||
return sizeof(a2dp_aptx_ll_t);
|
||||
else
|
||||
return sizeof(a2dp_aptx_t);
|
||||
}
|
||||
|
||||
static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags,
|
||||
|
|
@ -72,7 +96,15 @@ static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags,
|
|||
.channel_mode =
|
||||
APTX_CHANNEL_MODE_STEREO,
|
||||
};
|
||||
memcpy(caps, &a2dp_aptx, sizeof(a2dp_aptx));
|
||||
const a2dp_aptx_ll_t a2dp_aptx_ll = {
|
||||
.aptx = a2dp_aptx,
|
||||
.bidirect_link = false,
|
||||
.has_new_caps = false,
|
||||
};
|
||||
if (codec_is_ll(codec))
|
||||
memcpy(caps, &a2dp_aptx_ll, sizeof(a2dp_aptx_ll));
|
||||
else
|
||||
memcpy(caps, &a2dp_aptx, sizeof(a2dp_aptx));
|
||||
return actual_conf_size;
|
||||
}
|
||||
|
||||
|
|
@ -120,6 +152,58 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags,
|
|||
return actual_conf_size;
|
||||
}
|
||||
|
||||
static int codec_select_config_ll(const struct a2dp_codec *codec, uint32_t flags,
|
||||
const void *caps, size_t caps_size,
|
||||
const struct a2dp_codec_audio_info *info,
|
||||
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
|
||||
{
|
||||
a2dp_aptx_ll_ext_t conf = { 0 };
|
||||
size_t actual_conf_size;
|
||||
int res;
|
||||
|
||||
/* caps may contain only conf.base, or also the extended attributes */
|
||||
|
||||
if (caps_size < sizeof(conf.base))
|
||||
return -EINVAL;
|
||||
|
||||
memcpy(&conf, caps, SPA_MIN(caps_size, sizeof(conf)));
|
||||
|
||||
actual_conf_size = conf.base.has_new_caps ? sizeof(conf) : sizeof(conf.base);
|
||||
if (caps_size < actual_conf_size)
|
||||
return -EINVAL;
|
||||
|
||||
if ((res = codec_select_config(codec, flags, caps, caps_size, info, settings, config)) < 0)
|
||||
return res;
|
||||
|
||||
memcpy(&conf.base.aptx, config, sizeof(conf.base.aptx));
|
||||
|
||||
if (conf.base.has_new_caps) {
|
||||
int target_level = APTX_LL_LEVEL(conf.target_level1, conf.target_level2);
|
||||
int initial_level = APTX_LL_LEVEL(conf.initial_level1, conf.initial_level2);
|
||||
int good_working_level = APTX_LL_LEVEL(conf.good_working_level1, conf.good_working_level2);
|
||||
|
||||
target_level = SPA_MAX(target_level, APTX_LL_TARGET_CODEC_LEVEL * LL_LEVEL_ADJUSTMENT);
|
||||
initial_level = SPA_MAX(initial_level, APTX_LL_INITIAL_CODEC_LEVEL * LL_LEVEL_ADJUSTMENT);
|
||||
good_working_level = SPA_MAX(good_working_level, APTX_LL_GOOD_WORKING_LEVEL * LL_LEVEL_ADJUSTMENT);
|
||||
|
||||
conf.target_level1 = APTX_LL_LEVEL1(target_level);
|
||||
conf.target_level2 = APTX_LL_LEVEL2(target_level);
|
||||
conf.initial_level1 = APTX_LL_LEVEL1(initial_level);
|
||||
conf.initial_level2 = APTX_LL_LEVEL2(initial_level);
|
||||
conf.good_working_level1 = APTX_LL_LEVEL1(good_working_level);
|
||||
conf.good_working_level2 = APTX_LL_LEVEL2(good_working_level);
|
||||
|
||||
if (conf.sra_max_rate == 0)
|
||||
conf.sra_max_rate = APTX_LL_SRA_MAX_RATE;
|
||||
if (conf.sra_avg_time == 0)
|
||||
conf.sra_avg_time = APTX_LL_SRA_AVG_TIME;
|
||||
}
|
||||
|
||||
memcpy(config, &conf, actual_conf_size);
|
||||
|
||||
return actual_conf_size;
|
||||
}
|
||||
|
||||
static int codec_enum_config(const struct a2dp_codec *codec,
|
||||
const void *caps, size_t caps_size, uint32_t id, uint32_t idx,
|
||||
struct spa_pod_builder *b, struct spa_pod **param)
|
||||
|
|
@ -396,3 +480,37 @@ const struct a2dp_codec a2dp_codec_aptx_hd = {
|
|||
.reduce_bitpool = codec_reduce_bitpool,
|
||||
.increase_bitpool = codec_increase_bitpool,
|
||||
};
|
||||
|
||||
#define APTX_LL_COMMON_DEFS \
|
||||
.id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL, \
|
||||
.codec_id = A2DP_CODEC_VENDOR, \
|
||||
.name = "aptx_ll", \
|
||||
.description = "aptX-LL", \
|
||||
.fill_caps = codec_fill_caps, \
|
||||
.select_config = codec_select_config_ll, \
|
||||
.enum_config = codec_enum_config, \
|
||||
.init = codec_init, \
|
||||
.deinit = codec_deinit, \
|
||||
.get_block_size = codec_get_block_size, \
|
||||
.abr_process = codec_abr_process, \
|
||||
.start_encode = codec_start_encode, \
|
||||
.encode = codec_encode, \
|
||||
.start_decode = codec_start_decode, \
|
||||
.decode = codec_decode, \
|
||||
.reduce_bitpool = codec_reduce_bitpool, \
|
||||
.increase_bitpool = codec_increase_bitpool
|
||||
|
||||
|
||||
const struct a2dp_codec a2dp_codec_aptx_ll_0 = {
|
||||
APTX_LL_COMMON_DEFS,
|
||||
.vendor = { .vendor_id = APTX_LL_VENDOR_ID,
|
||||
.codec_id = APTX_LL_CODEC_ID },
|
||||
.endpoint_name = "aptx_ll_0",
|
||||
};
|
||||
|
||||
const struct a2dp_codec a2dp_codec_aptx_ll_1 = {
|
||||
APTX_LL_COMMON_DEFS,
|
||||
.vendor = { .vendor_id = APTX_LL_VENDOR_ID2,
|
||||
.codec_id = APTX_LL_CODEC_ID },
|
||||
.endpoint_name = "aptx_ll_1",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -132,6 +132,8 @@ extern struct a2dp_codec a2dp_codec_mpeg;
|
|||
#if ENABLE_APTX
|
||||
extern struct a2dp_codec a2dp_codec_aptx;
|
||||
extern struct a2dp_codec a2dp_codec_aptx_hd;
|
||||
extern struct a2dp_codec a2dp_codec_aptx_ll_0;
|
||||
extern struct a2dp_codec a2dp_codec_aptx_ll_1;
|
||||
#endif
|
||||
|
||||
static const struct a2dp_codec * const a2dp_codec_list[] = {
|
||||
|
|
@ -150,6 +152,10 @@ static const struct a2dp_codec * const a2dp_codec_list[] = {
|
|||
#endif
|
||||
&a2dp_codec_sbc,
|
||||
&a2dp_codec_sbc_xq,
|
||||
#if ENABLE_APTX
|
||||
&a2dp_codec_aptx_ll_0,
|
||||
&a2dp_codec_aptx_ll_1,
|
||||
#endif
|
||||
NULL
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1933,24 +1933,20 @@ int64_t spa_bt_transport_get_delay_nsec(struct spa_bt_transport *t)
|
|||
if (t->a2dp_codec == NULL)
|
||||
return 30 * SPA_NSEC_PER_MSEC;
|
||||
|
||||
switch (t->a2dp_codec->codec_id) {
|
||||
case A2DP_CODEC_SBC:
|
||||
switch (t->a2dp_codec->id) {
|
||||
case SPA_BLUETOOTH_AUDIO_CODEC_SBC:
|
||||
case SPA_BLUETOOTH_AUDIO_CODEC_SBC_XQ:
|
||||
return 200 * SPA_NSEC_PER_MSEC;
|
||||
case A2DP_CODEC_MPEG24:
|
||||
case SPA_BLUETOOTH_AUDIO_CODEC_MPEG:
|
||||
case SPA_BLUETOOTH_AUDIO_CODEC_AAC:
|
||||
return 200 * SPA_NSEC_PER_MSEC;
|
||||
case A2DP_CODEC_VENDOR:
|
||||
{
|
||||
uint32_t vendor_id = t->a2dp_codec->vendor.vendor_id;
|
||||
uint16_t codec_id = t->a2dp_codec->vendor.codec_id;
|
||||
|
||||
if (vendor_id == APTX_VENDOR_ID && codec_id == APTX_CODEC_ID)
|
||||
return 150 * SPA_NSEC_PER_MSEC;
|
||||
if (vendor_id == APTX_HD_VENDOR_ID && codec_id == APTX_HD_CODEC_ID)
|
||||
return 150 * SPA_NSEC_PER_MSEC;
|
||||
if (vendor_id == LDAC_VENDOR_ID && codec_id == LDAC_CODEC_ID)
|
||||
return 175 * SPA_NSEC_PER_MSEC;
|
||||
break;
|
||||
}
|
||||
case SPA_BLUETOOTH_AUDIO_CODEC_APTX:
|
||||
case SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD:
|
||||
return 150 * SPA_NSEC_PER_MSEC;
|
||||
case SPA_BLUETOOTH_AUDIO_CODEC_LDAC:
|
||||
return 175 * SPA_NSEC_PER_MSEC;
|
||||
case SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL:
|
||||
return 40 * SPA_NSEC_PER_MSEC;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ properties = {
|
|||
#bluez5.headset-roles = [ hsp_hs hsp_ag hfp_hf hfp_ag ]
|
||||
|
||||
# Enabled A2DP codecs (default: all).
|
||||
#bluez5.codecs = [ sbc aac ldac aptx aptx_hd sbc_xq ]
|
||||
#bluez5.codecs = [ sbc aac ldac aptx aptx_hd aptx_ll sbc_xq ]
|
||||
|
||||
# Properties for the A2DP codec configuration
|
||||
#bluez5.default.rate = 48000
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue