diff --git a/spa/include/spa/param/bluetooth/audio.h b/spa/include/spa/param/bluetooth/audio.h index 35ac27b39..d43588e61 100644 --- a/spa/include/spa/param/bluetooth/audio.h +++ b/spa/include/spa/param/bluetooth/audio.h @@ -45,6 +45,7 @@ enum spa_bluetooth_audio_codec { SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD, SPA_BLUETOOTH_AUDIO_CODEC_LDAC, SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL, + SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX, /* HFP */ SPA_BLUETOOTH_AUDIO_CODEC_CVSD = 0x100, diff --git a/spa/include/spa/param/bluetooth/type-info.h b/spa/include/spa/param/bluetooth/type-info.h index 4dc897fb5..2b339d647 100644 --- a/spa/include/spa/param/bluetooth/type-info.h +++ b/spa/include/spa/param/bluetooth/type-info.h @@ -49,6 +49,7 @@ static const struct spa_type_info spa_type_bluetooth_audio_codec[] = { { 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_APTX_LL_DUPLEX, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "aptx_ll_duplex", 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 }, diff --git a/spa/plugins/bluez5/a2dp-codec-aptx.c b/spa/plugins/bluez5/a2dp-codec-aptx.c index f374c333e..bf64a90d8 100644 --- a/spa/plugins/bluez5/a2dp-codec-aptx.c +++ b/spa/plugins/bluez5/a2dp-codec-aptx.c @@ -28,6 +28,9 @@ #include #include +#include + +#include #include @@ -61,6 +64,10 @@ struct impl { bool hd; }; +struct msbc_impl { + sbc_t msbc; +}; + static inline bool codec_is_hd(const struct a2dp_codec *codec) { return codec->vendor.codec_id == APTX_HD_CODEC_ID @@ -98,7 +105,7 @@ static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, }; const a2dp_aptx_ll_t a2dp_aptx_ll = { .aptx = a2dp_aptx, - .bidirect_link = false, + .bidirect_link = codec->duplex_codec ? true : false, .has_new_caps = false, }; if (codec_is_ll(codec)) @@ -172,6 +179,9 @@ static int codec_select_config_ll(const struct a2dp_codec *codec, uint32_t flags if (caps_size < actual_conf_size) return -EINVAL; + if (codec->duplex_codec && !conf.base.bidirect_link) + return -ENOTSUP; + if ((res = codec_select_config(codec, flags, caps, caps_size, info, settings, config)) < 0) return res; @@ -436,6 +446,150 @@ static int codec_decode(void *data, return res; } +/* + * mSBC duplex codec + * + * When connected as SRC to SNK, aptX-LL sink may send back mSBC data. + */ + +static int msbc_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) +{ + struct spa_audio_info_raw info = { 0, }; + + if (caps_size < sizeof(a2dp_aptx_ll_t)) + return -EINVAL; + + if (idx > 0) + return 0; + + info.format = SPA_AUDIO_FORMAT_S16_LE; + info.channels = 1; + info.position[0] = SPA_AUDIO_CHANNEL_MONO; + info.rate = 16000; + + *param = spa_format_audio_raw_build(b, id, &info); + return *param == NULL ? -EIO : 1; +} + +static int msbc_reduce_bitpool(void *data) +{ + return -ENOTSUP; +} + +static int msbc_increase_bitpool(void *data) +{ + return -ENOTSUP; +} + +static int msbc_get_block_size(void *data) +{ + return MSBC_DECODED_SIZE; +} + +static void *msbc_init(const struct a2dp_codec *codec, uint32_t flags, + void *config, size_t config_len, const struct spa_audio_info *info, + void *props, size_t mtu) +{ + struct msbc_impl *this = NULL; + int res; + + if (info->media_type != SPA_MEDIA_TYPE_audio || + info->media_subtype != SPA_MEDIA_SUBTYPE_raw || + info->info.raw.format != SPA_AUDIO_FORMAT_S16_LE) { + res = -EINVAL; + goto error; + } + + if ((this = calloc(1, sizeof(struct msbc_impl))) == NULL) + goto error_errno; + + if ((res = sbc_init_msbc(&this->msbc, 0)) < 0) + goto error; + + this->msbc.endian = SBC_LE; + + return this; + +error_errno: + res = -errno; + goto error; +error: + free(this); + errno = -res; + return NULL; +} + +static void msbc_deinit(void *data) +{ + struct msbc_impl *this = data; + sbc_finish(&this->msbc); + free(this); +} + +static int msbc_abr_process (void *data, size_t unsent) +{ + return -ENOTSUP; +} + +static int msbc_start_encode (void *data, + void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) +{ + return -ENOTSUP; +} + +static int msbc_encode(void *data, + const void *src, size_t src_size, + void *dst, size_t dst_size, + size_t *dst_out, int *need_flush) +{ + return -ENOTSUP; +} + +static int msbc_start_decode (void *data, + const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) +{ + return 0; +} + +static int msbc_decode(void *data, + const void *src, size_t src_size, + void *dst, size_t dst_size, + size_t *dst_out) +{ + struct msbc_impl *this = data; + const uint8_t sync[3] = { 0xAD, 0x00, 0x00 }; + size_t processed = 0; + int res; + + spa_assert(sizeof(sync) <= MSBC_PAYLOAD_SIZE); + + *dst_out = 0; + + /* Scan for msbc sync sequence. + * We could probably assume fixed (<57-byte payload><1-byte pad>)+ format + * which devices seem to be sending. Don't know if there are variations, + * so we make weaker assumption here. + */ + while (src_size >= MSBC_PAYLOAD_SIZE) { + if (memcmp(src, sync, sizeof(sync)) == 0) + break; + src = (uint8_t*)src + 1; + --src_size; + ++processed; + } + + res = sbc_decode(&this->msbc, src, src_size, + dst, dst_size, dst_out); + if (res <= 0) + res = SPA_MIN((size_t)MSBC_PAYLOAD_SIZE, src_size); /* skip bad payload */ + + processed += res; + return processed; +} + + const struct a2dp_codec a2dp_codec_aptx = { .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX, .codec_id = A2DP_CODEC_VENDOR, @@ -482,9 +636,7 @@ const struct a2dp_codec a2dp_codec_aptx_hd = { }; #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, \ @@ -503,14 +655,58 @@ const struct a2dp_codec a2dp_codec_aptx_hd = { const struct a2dp_codec a2dp_codec_aptx_ll_0 = { APTX_LL_COMMON_DEFS, + .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL, .vendor = { .vendor_id = APTX_LL_VENDOR_ID, .codec_id = APTX_LL_CODEC_ID }, + .name = "aptx_ll", .endpoint_name = "aptx_ll_0", }; const struct a2dp_codec a2dp_codec_aptx_ll_1 = { APTX_LL_COMMON_DEFS, + .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL, .vendor = { .vendor_id = APTX_LL_VENDOR_ID2, .codec_id = APTX_LL_CODEC_ID }, + .name = "aptx_ll", .endpoint_name = "aptx_ll_1", }; + +/* Voice channel mSBC, not a real A2DP codec */ +static const struct a2dp_codec aptx_ll_msbc = { + .codec_id = A2DP_CODEC_VENDOR, + .name = "aptx_ll_msbc", + .description = "aptX-LL mSBC", + .fill_caps = codec_fill_caps, + .select_config = codec_select_config_ll, + .enum_config = msbc_enum_config, + .init = msbc_init, + .deinit = msbc_deinit, + .get_block_size = msbc_get_block_size, + .abr_process = msbc_abr_process, + .start_encode = msbc_start_encode, + .encode = msbc_encode, + .start_decode = msbc_start_decode, + .decode = msbc_decode, + .reduce_bitpool = msbc_reduce_bitpool, + .increase_bitpool = msbc_increase_bitpool, +}; + +const struct a2dp_codec a2dp_codec_aptx_ll_duplex_0 = { + APTX_LL_COMMON_DEFS, + .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX, + .vendor = { .vendor_id = APTX_LL_VENDOR_ID, + .codec_id = APTX_LL_CODEC_ID }, + .name = "aptx_ll_duplex", + .endpoint_name = "aptx_ll_duplex_0", + .duplex_codec = &aptx_ll_msbc, +}; + +const struct a2dp_codec a2dp_codec_aptx_ll_duplex_1 = { + APTX_LL_COMMON_DEFS, + .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX, + .vendor = { .vendor_id = APTX_LL_VENDOR_ID2, + .codec_id = APTX_LL_CODEC_ID }, + .name = "aptx_ll_duplex", + .endpoint_name = "aptx_ll_duplex_1", + .duplex_codec = &aptx_ll_msbc, +}; diff --git a/spa/plugins/bluez5/a2dp-codecs.c b/spa/plugins/bluez5/a2dp-codecs.c index 703799bd5..8ab877148 100644 --- a/spa/plugins/bluez5/a2dp-codecs.c +++ b/spa/plugins/bluez5/a2dp-codecs.c @@ -134,6 +134,8 @@ 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; +extern struct a2dp_codec a2dp_codec_aptx_ll_duplex_0; +extern struct a2dp_codec a2dp_codec_aptx_ll_duplex_1; #endif static const struct a2dp_codec * const a2dp_codec_list[] = { @@ -155,6 +157,8 @@ static const struct a2dp_codec * const a2dp_codec_list[] = { #if ENABLE_APTX &a2dp_codec_aptx_ll_0, &a2dp_codec_aptx_ll_1, + &a2dp_codec_aptx_ll_duplex_0, + &a2dp_codec_aptx_ll_duplex_1, #endif NULL }; diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 9dfcf880a..cdd5a6c76 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -1946,6 +1946,7 @@ int64_t spa_bt_transport_get_delay_nsec(struct spa_bt_transport *t) case SPA_BLUETOOTH_AUDIO_CODEC_LDAC: return 175 * SPA_NSEC_PER_MSEC; case SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL: + case SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX: return 40 * SPA_NSEC_PER_MSEC; default: break; diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index 022f926e0..e6879c6cc 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -173,6 +173,7 @@ extern "C" { * of the input (number of PCM samples) and output is known up front. */ #define MSBC_DECODED_SIZE 240 #define MSBC_ENCODED_SIZE 60 /* 2 bytes header + 57 mSBC payload + 1 byte padding */ +#define MSBC_PAYLOAD_SIZE 57 enum spa_bt_profile { SPA_BT_PROFILE_NULL = 0, diff --git a/src/daemon/media-session.d/bluez-monitor.conf b/src/daemon/media-session.d/bluez-monitor.conf index 7623e4ab8..2a0fb0408 100644 --- a/src/daemon/media-session.d/bluez-monitor.conf +++ b/src/daemon/media-session.d/bluez-monitor.conf @@ -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 aptx_ll sbc_xq ] + #bluez5.codecs = [ sbc aac ldac aptx aptx_hd aptx_ll aptx_ll_duplex sbc_xq ] # Properties for the A2DP codec configuration #bluez5.default.rate = 48000