From 951d31b98481894feab808ec50cbc22e2fb8c4f5 Mon Sep 17 00:00:00 2001 From: Arkadiusz Bokowy Date: Thu, 19 Dec 2024 23:38:15 +0100 Subject: [PATCH] Parse A2DP capabilities blob for aptX Adaptive --- .github/spellcheck-wordlist.txt | 2 ++ src/a2dp.c | 8 +---- src/shared/a2dp-codecs.c | 13 +++++++- src/shared/a2dp-codecs.h | 39 ++++++++++++++++++++-- utils/a2dpconf.c | 57 ++++++++++++++++++++++++++++----- 5 files changed, 101 insertions(+), 18 deletions(-) diff --git a/.github/spellcheck-wordlist.txt b/.github/spellcheck-wordlist.txt index c20297a9a..d639e0a57 100644 --- a/.github/spellcheck-wordlist.txt +++ b/.github/spellcheck-wordlist.txt @@ -186,6 +186,7 @@ EDE EINTR EINVAL ENODEV +eoc EP EPIPE EPMR @@ -263,6 +264,7 @@ syslogd systemd TIOCOUTQ TNS +ttp TWS ud uint diff --git a/src/a2dp.c b/src/a2dp.c index 372479ab8..5fb31ae92 100644 --- a/src/a2dp.c +++ b/src/a2dp.c @@ -314,15 +314,9 @@ const struct a2dp_sep *a2dp_sep_lookup(enum a2dp_type type, uint32_t codec_id) { * @param size A2DP vendor codec capabilities size. * @return On success this function returns A2DP 32-bit vendor codec ID. */ uint32_t a2dp_get_vendor_codec_id(const void *capabilities, size_t size) { - if (size < sizeof(a2dp_vendor_info_t)) return errno = EINVAL, 0xFFFFFFFF; - - const a2dp_vendor_info_t *info = capabilities; - const uint32_t vendor_id = A2DP_VENDOR_INFO_GET_VENDOR_ID(*info); - const uint16_t codec_id = A2DP_VENDOR_INFO_GET_CODEC_ID(*info); - - return A2DP_CODEC_VENDOR_ID(vendor_id, codec_id); + return a2dp_codecs_vendor_codec_id(capabilities); } /** diff --git a/src/shared/a2dp-codecs.c b/src/shared/a2dp-codecs.c index 910ff539f..8470794fe 100644 --- a/src/shared/a2dp-codecs.c +++ b/src/shared/a2dp-codecs.c @@ -48,7 +48,7 @@ static const struct { * Get BlueALSA A2DP codec ID from string representation. * * @param alias Alias of an A2DP audio codec name. - * @return BlueALSA audio codec ID or 0xFFFFFFFF if there was no match. */ + * @return BlueALSA A2DP codec ID or 0xFFFFFFFF if there was no match. */ uint32_t a2dp_codecs_codec_id_from_string(const char *alias) { for (size_t i = 0; i < ARRAYSIZE(codecs); i++) for (size_t n = 0; n < ARRAYSIZE(codecs[i].aliases); n++) @@ -58,6 +58,17 @@ uint32_t a2dp_codecs_codec_id_from_string(const char *alias) { return 0xFFFFFFFF; } +/** + * Get BlueALSA A2DP codec ID from vendor codec information. + * + * @param info A2DP vendor codec capabilities. + * @return BlueALSA A2DP codec ID. */ +uint32_t a2dp_codecs_vendor_codec_id(const a2dp_vendor_info_t *info) { + const uint32_t vendor_id = A2DP_VENDOR_INFO_GET_VENDOR_ID(*info); + const uint16_t codec_id = A2DP_VENDOR_INFO_GET_CODEC_ID(*info); + return A2DP_CODEC_VENDOR_ID(vendor_id, codec_id); +} + /** * Convert BlueALSA A2DP codec ID into a human-readable string. * diff --git a/src/shared/a2dp-codecs.h b/src/shared/a2dp-codecs.h index ef5f93583..fe9e506f9 100644 --- a/src/shared/a2dp-codecs.h +++ b/src/shared/a2dp-codecs.h @@ -488,6 +488,39 @@ typedef struct a2dp_aptx_hd { #define APTX_AD_VENDOR_ID BT_COMPID_QUALCOMM_TECH #define APTX_AD_CODEC_ID 0x00AD +#define APTX_AD_CHANNEL_MODE_MONO (1 << 0) +#define APTX_AD_CHANNEL_MODE_STEREO (1 << 1) +#define APTX_AD_CHANNEL_MODE_TWS (1 << 2) +#define APTX_AD_CHANNEL_MODE_JOINT_STEREO (1 << 3) +#define APTX_AD_CHANNEL_MODE_TWS_MONO (1 << 4) + +#define APTX_AD_SAMPLING_FREQ_44100 (1 << 0) +#define APTX_AD_SAMPLING_FREQ_48000 (1 << 1) +#define APTX_AD_SAMPLING_FREQ_88000 (1 << 2) +#define APTX_AD_SAMPLING_FREQ_192000 (1 << 3) + +typedef struct { + a2dp_vendor_info_t info; +#if __BYTE_ORDER == __BIG_ENDIAN + uint8_t sampling_freq:5; + uint8_t rfa1:3; + uint8_t rfa2:3; + uint8_t channel_mode:5; +#else + uint8_t rfa1:3; + uint8_t sampling_freq:5; + uint8_t channel_mode:5; + uint8_t rfa2:3; +#endif + uint8_t ttp_ll_low; + uint8_t ttp_ll_high; + uint8_t ttp_hq_low; + uint8_t ttp_hq_high; + uint8_t ttp_tws_low; + uint8_t ttp_tws_high; + uint8_t eoc[3]; +} __attribute__ ((packed)) a2dp_aptx_ad_t; + #define LC3PLUS_VENDOR_ID BT_COMPID_FRAUNHOFER_IIS #define LC3PLUS_CODEC_ID 0x0001 @@ -764,11 +797,12 @@ typedef union a2dp { a2dp_aac_t aac; a2dp_usac_t usac; a2dp_atrac_t atrac; - a2dp_aptx_t aptx; a2dp_faststream_t faststream; + a2dp_aptx_t aptx; + a2dp_aptx_ad_t aptx_ad; + a2dp_aptx_hd_t aptx_hd; a2dp_aptx_ll_t aptx_ll; a2dp_aptx_ll_new_t aptx_ll_new; - a2dp_aptx_hd_t aptx_hd; a2dp_lc3plus_t lc3plus; a2dp_ldac_t ldac; a2dp_lhdc_v1_t lhdc_v1; @@ -780,6 +814,7 @@ typedef union a2dp { } a2dp_t; uint32_t a2dp_codecs_codec_id_from_string(const char *alias); +uint32_t a2dp_codecs_vendor_codec_id(const a2dp_vendor_info_t *info); const char *a2dp_codecs_codec_id_to_string(uint32_t codec_id); const char *a2dp_codecs_get_canonical_name(const char *alias); diff --git a/utils/a2dpconf.c b/utils/a2dpconf.c index 1a80deeca..aea9e7e6d 100644 --- a/utils/a2dpconf.c +++ b/utils/a2dpconf.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -34,6 +35,9 @@ static uint32_t get_codec(const char *s) { if ((tmp = strchr(buffer, ':')) != NULL) tmp[0] = '\0'; + if (strcasecmp(buffer, "vendor") == 0) + return A2DP_CODEC_VENDOR; + return a2dp_codecs_codec_id_from_string(buffer); } @@ -280,15 +284,15 @@ static void dump_vendor(const void *blob, size_t size) { static void printf_aptx(const a2dp_aptx_t *aptx) { printf("" - " channel-mode:4 =%s%s%s\n" - " sample-rate:4 =%s%s%s%s\n", - aptx->channel_mode & APTX_CHANNEL_MODE_STEREO ? " Stereo" : "", - aptx->channel_mode & APTX_CHANNEL_MODE_TWS ? " DualChannel" : "", - aptx->channel_mode & APTX_CHANNEL_MODE_MONO ? " Mono" : "", + " sample-rate:4 =%s%s%s%s\n" + " channel-mode:4 =%s%s%s\n", aptx->sampling_freq & APTX_SAMPLING_FREQ_48000 ? " 48000" : "", aptx->sampling_freq & APTX_SAMPLING_FREQ_44100 ? " 44100" : "", aptx->sampling_freq & APTX_SAMPLING_FREQ_32000 ? " 32000" : "", - aptx->sampling_freq & APTX_SAMPLING_FREQ_16000 ? " 16000" : ""); + aptx->sampling_freq & APTX_SAMPLING_FREQ_16000 ? " 16000" : "", + aptx->channel_mode & APTX_CHANNEL_MODE_STEREO ? " Stereo" : "", + aptx->channel_mode & APTX_CHANNEL_MODE_TWS ? " DualChannel" : "", + aptx->channel_mode & APTX_CHANNEL_MODE_MONO ? " Mono" : ""); } static void dump_aptx(const void *blob, size_t size) { @@ -311,6 +315,39 @@ static void dump_aptx_tws(const void *blob, size_t size) { printf("}\n"); } +static void dump_aptx_ad(const void *blob, size_t size) { + const a2dp_aptx_ad_t *aptx_ad = blob; + if (check_blob_size(sizeof(*aptx_ad), size) == -1) + return; + printf("aptX Adaptive {\n", bintohex(blob, size)); + printf_vendor(&aptx_ad->info); + printf("" + " sample-rate:5 =%s%s%s%s\n" + " :6\n" + " channel-mode:5 =%s%s%s%s%s\n" + " ttp-ll-low:8 = %u\n" + " ttp-ll-high:8 = %u\n" + " ttp-hq-low:8 = %u\n" + " ttp-hq-high:8 = %u\n" + " ttp-tws-low:8 = %u\n" + " ttp-tws-high:8 = %u\n" + " eoc:24 = hex:%02x%02x%02x\n" + "}\n", + aptx_ad->sampling_freq & APTX_AD_SAMPLING_FREQ_192000 ? " 192000" : "", + aptx_ad->sampling_freq & APTX_AD_SAMPLING_FREQ_88000 ? " 88000" : "", + aptx_ad->sampling_freq & APTX_AD_SAMPLING_FREQ_48000 ? " 48000" : "", + aptx_ad->sampling_freq & APTX_AD_SAMPLING_FREQ_44100 ? " 44100" : "", + aptx_ad->channel_mode & APTX_AD_CHANNEL_MODE_TWS_MONO ? " TWS-Mono" : "", + aptx_ad->channel_mode & APTX_AD_CHANNEL_MODE_JOINT_STEREO ? " JointStereo" : "", + aptx_ad->channel_mode & APTX_AD_CHANNEL_MODE_STEREO ? " Stereo" : "", + aptx_ad->channel_mode & APTX_AD_CHANNEL_MODE_TWS ? " DualChannel" : "", + aptx_ad->channel_mode & APTX_AD_CHANNEL_MODE_MONO ? " Mono" : "", + aptx_ad->ttp_ll_low, aptx_ad->ttp_ll_high, + aptx_ad->ttp_hq_low, aptx_ad->ttp_hq_high, + aptx_ad->ttp_tws_low, aptx_ad->ttp_tws_high, + aptx_ad->eoc[0], aptx_ad->eoc[1], aptx_ad->eoc[2]); +} + static void dump_aptx_hd(const void *blob, size_t size) { const a2dp_aptx_hd_t *aptx_hd = blob; if (check_blob_size(sizeof(*aptx_hd), size) == -1) @@ -620,7 +657,7 @@ static const struct { { A2DP_CODEC_VENDOR_ID(APTX_TWS_VENDOR_ID, APTX_TWS_CODEC_ID), sizeof(a2dp_aptx_t), dump_aptx_tws }, { A2DP_CODEC_VENDOR_ID(APTX_AD_VENDOR_ID, APTX_AD_CODEC_ID), - -1, dump_vendor }, + sizeof(a2dp_aptx_ad_t), dump_aptx_ad }, { A2DP_CODEC_VENDOR_ID(APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID), sizeof(a2dp_aptx_hd_t), dump_aptx_hd }, { A2DP_CODEC_VENDOR_ID(APTX_LL_VENDOR_ID, APTX_LL_CODEC_ID), @@ -666,6 +703,10 @@ int dump(const char *config, bool detect) { if (get_codec_blob(config, blob, blob_size) == -1) goto final; + if (codec_id == A2DP_CODEC_VENDOR && + (size_t)blob_size >= sizeof(a2dp_vendor_info_t)) + codec_id = a2dp_codecs_vendor_codec_id(blob); + rv = 0; for (size_t i = 0; i < ARRAYSIZE(dumps); i++) if (dumps[i].codec_id == codec_id) { @@ -715,7 +756,7 @@ int main(int argc, char *argv[]) { " -x, --auto-detect\ttry to auto-detect codec\n" "\nExamples:\n" " %s sbc:ffff0235\n" - " %s aptx:4f0000000100ff\n", + " %s vendor:4f0000000100ff\n", argv[0], argv[0], argv[0]); return EXIT_SUCCESS;