diff --git a/.gitmodules b/.gitmodules index 984e2342bb93..efdc0110017f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,11 +25,11 @@ [submodule "components/bt/controller/lib_esp32"] path = components/bt/controller/lib_esp32 - url = ../../espressif/esp32-bt-lib.git + url = https://github.com/espressif/esp32-bt-lib.git [submodule "components/bootloader/subproject/components/micro-ecc/micro-ecc"] path = components/bootloader/subproject/components/micro-ecc/micro-ecc - url = ../../kmackay/micro-ecc.git + url = https://github.com/kmackay/micro-ecc.git sbom-version = 1.1 sbom-cpe = cpe:2.3:a:micro-ecc_project:micro-ecc:{}:*:*:*:*:*:*:* sbom-supplier = Person: Ken MacKay @@ -39,7 +39,7 @@ [submodule "components/spiffs/spiffs"] path = components/spiffs/spiffs - url = ../../pellepl/spiffs.git + url = https://github.com/pellepl/spiffs.git sbom-version = 0.2-255-g0dbb3f71c5f6 sbom-supplier = Person: Peter Andersson sbom-url = https://github.com/pellepl/spiffs @@ -48,7 +48,7 @@ [submodule "components/json/cJSON"] path = components/json/cJSON - url = ../../DaveGamble/cJSON.git + url = https://github.com/DaveGamble/cJSON.git sbom-version = 1.7.17 sbom-cpe = cpe:2.3:a:cjson_project:cjson:{}:*:*:*:*:*:*:* sbom-supplier = Person: Dave Gamble @@ -58,19 +58,19 @@ [submodule "components/mbedtls/mbedtls"] path = components/mbedtls/mbedtls - url = ../../espressif/mbedtls.git + url = https://github.com/espressif/mbedtls.git [submodule "components/lwip/lwip"] path = components/lwip/lwip - url = ../../espressif/esp-lwip.git + url = https://github.com/espressif/esp-lwip.git [submodule "components/mqtt/esp-mqtt"] path = components/mqtt/esp-mqtt - url = ../../espressif/esp-mqtt.git + url = https://github.com/espressif/esp-mqtt.git [submodule "components/protobuf-c/protobuf-c"] path = components/protobuf-c/protobuf-c - url = ../../protobuf-c/protobuf-c.git + url = https://github.com/protobuf-c/protobuf-c.git sbom-version = 1.4.1 sbom-cpe = cpe:2.3:a:protobuf-c_project:protobuf-c:{}:*:*:*:*:*:*:* sbom-supplier = Organization: protobuf-c community @@ -80,7 +80,7 @@ [submodule "components/unity/unity"] path = components/unity/unity - url = ../../ThrowTheSwitch/Unity.git + url = https://github.com/ThrowTheSwitch/Unity.git sbom-version = v2.4.3-51-g7d2bf62b7e6a sbom-supplier = Organization: ThrowTheSwitch community sbom-url = https://github.com/ThrowTheSwitch/Unity @@ -89,15 +89,15 @@ [submodule "components/bt/host/nimble/nimble"] path = components/bt/host/nimble/nimble - url = ../../espressif/esp-nimble.git + url = https://github.com/espressif/esp-nimble.git [submodule "components/esp_wifi/lib"] path = components/esp_wifi/lib - url = ../../espressif/esp32-wifi-lib.git + url = https://github.com/espressif/esp32-wifi-lib.git [submodule "components/cmock/CMock"] path = components/cmock/CMock - url = ../../ThrowTheSwitch/CMock.git + url = https://github.com/ThrowTheSwitch/CMock.git sbom-version = v2.5.2-2-geeecc49ce8af sbom-supplier = Organization: ThrowTheSwitch community sbom-url = https://github.com/ThrowTheSwitch/CMock @@ -106,44 +106,66 @@ [submodule "components/openthread/openthread"] path = components/openthread/openthread - url = ../../espressif/openthread.git + url = https://github.com/espressif/openthread.git [submodule "components/bt/controller/lib_esp32c3_family"] path = components/bt/controller/lib_esp32c3_family - url = ../../espressif/esp32c3-bt-lib.git + url = https://github.com/espressif/esp32c3-bt-lib.git [submodule "components/esp_phy/lib"] path = components/esp_phy/lib - url = ../../espressif/esp-phy-lib.git + url = https://github.com/espressif/esp-phy-lib.git [submodule "components/openthread/lib"] path = components/openthread/lib - url = ../../espressif/esp-thread-lib.git + url = https://github.com/espressif/esp-thread-lib.git [submodule "components/bt/controller/lib_esp32h2/esp32h2-bt-lib"] path = components/bt/controller/lib_esp32h2/esp32h2-bt-lib - url = ../../espressif/esp32h2-bt-lib.git + url = https://github.com/espressif/esp32h2-bt-lib.git [submodule "components/bt/controller/lib_esp32c2/esp32c2-bt-lib"] path = components/bt/controller/lib_esp32c2/esp32c2-bt-lib - url = ../../espressif/esp32c2-bt-lib.git + url = https://github.com/espressif/esp32c2-bt-lib.git [submodule "components/bt/controller/lib_esp32c6/esp32c6-bt-lib"] path = components/bt/controller/lib_esp32c6/esp32c6-bt-lib - url = ../../espressif/esp32c6-bt-lib.git + url = https://github.com/espressif/esp32c6-bt-lib.git [submodule "components/heap/tlsf"] path = components/heap/tlsf - url = ../../espressif/tlsf.git + url = https://github.com/espressif/tlsf.git [submodule "components/esp_coex/lib"] path = components/esp_coex/lib - url = ../../espressif/esp-coex-lib.git + url = https://github.com/espressif/esp-coex-lib.git [submodule "components/bt/host/bluedroid/external/opus"] path = components/bt/host/bluedroid/external/opus url = https://github.com/xiph/opus.git + branch = main [submodule "components/bt/host/bluedroid/external/liblc3"] path = components/bt/host/bluedroid/external/liblc3 url = https://github.com/cfint/liblc3.git + branch = esp32 + +[submodule "components/bt/host/bluedroid/external/libfreeaptx"] + path = components/bt/host/bluedroid/external/libfreeaptx + url = https://github.com/cfint/libfreeaptx-esp.git + branch = master + +[submodule "components/bt/host/bluedroid/external/arduino-fdk-aac"] + path = components/bt/host/bluedroid/external/arduino-fdk-aac + url = https://github.com/cfint/arduino-fdk-aac.git + branch = idf_component + +[submodule "components/bt/host/bluedroid/external/libldac-dec"] + path = components/bt/host/bluedroid/external/libldac-dec + url = https://github.com/cfint/libldac-dec.git + branch = esp32 + +[submodule "components/bt/host/bluedroid/external/LHDC-V5-Decoder"] + path = components/bt/host/bluedroid/external/LHDC-V5-Decoder + url = https://github.com/WillyBilly06/LHDC-V5-Decoder.git + branch = main diff --git a/.gitmodules.bak b/.gitmodules.bak new file mode 100644 index 000000000000..984e2342bb93 --- /dev/null +++ b/.gitmodules.bak @@ -0,0 +1,149 @@ +# +# All the relative URL paths are intended to be GitHub ones +# For Espressif's public projects please use '../../espressif/proj', not a '../proj' +# +# Submodules SBOM information +# --------------------------- +# Submodules, which are used directly and not forked into espressif namespace should +# contain SBOM information here. Other submodules should have the SBOM manifest file +# included in the root of their project's repository. +# +# The sbom-hash entry records the submodule's checkout SHA as presented in git-tree +# commit object. For example spiffs submodule +# +# $ git ls-tree HEAD components/spiffs/spiffs +# 160000 commit 0dbb3f71c5f6fae3747a9d935372773762baf852 components/spiffs/spiffs +# +# The hash can be also obtained with git submodule command +# +# $ git submodule status components/spiffs/spiffs +# 0dbb3f71c5f6fae3747a9d935372773762baf852 components/spiffs/spiffs (0.2-255-g0dbb3f71c5f6) +# +# The submodule SHA recorded here has to match with SHA, which is presented in git-tree. +# This is checked by CI. Also please don't forget to update the submodule version +# if you are changing the sbom-hash. This is important for SBOM generation. + +[submodule "components/bt/controller/lib_esp32"] + path = components/bt/controller/lib_esp32 + url = ../../espressif/esp32-bt-lib.git + +[submodule "components/bootloader/subproject/components/micro-ecc/micro-ecc"] + path = components/bootloader/subproject/components/micro-ecc/micro-ecc + url = ../../kmackay/micro-ecc.git + sbom-version = 1.1 + sbom-cpe = cpe:2.3:a:micro-ecc_project:micro-ecc:{}:*:*:*:*:*:*:* + sbom-supplier = Person: Ken MacKay + sbom-url = https://github.com/kmackay/micro-ecc + sbom-description = A small and fast ECDH and ECDSA implementation for 8-bit, 32-bit, and 64-bit processors + sbom-hash = 24c60e243580c7868f4334a1ba3123481fe1aa48 + +[submodule "components/spiffs/spiffs"] + path = components/spiffs/spiffs + url = ../../pellepl/spiffs.git + sbom-version = 0.2-255-g0dbb3f71c5f6 + sbom-supplier = Person: Peter Andersson + sbom-url = https://github.com/pellepl/spiffs + sbom-description = Wear-leveled SPI flash file system for embedded devices + sbom-hash = 0dbb3f71c5f6fae3747a9d935372773762baf852 + +[submodule "components/json/cJSON"] + path = components/json/cJSON + url = ../../DaveGamble/cJSON.git + sbom-version = 1.7.17 + sbom-cpe = cpe:2.3:a:cjson_project:cjson:{}:*:*:*:*:*:*:* + sbom-supplier = Person: Dave Gamble + sbom-url = https://github.com/DaveGamble/cJSON + sbom-description = Ultralightweight JSON parser in ANSI C + sbom-hash = 87d8f0961a01bf09bef98ff89bae9fdec42181ee + +[submodule "components/mbedtls/mbedtls"] + path = components/mbedtls/mbedtls + url = ../../espressif/mbedtls.git + +[submodule "components/lwip/lwip"] + path = components/lwip/lwip + url = ../../espressif/esp-lwip.git + +[submodule "components/mqtt/esp-mqtt"] + path = components/mqtt/esp-mqtt + url = ../../espressif/esp-mqtt.git + +[submodule "components/protobuf-c/protobuf-c"] + path = components/protobuf-c/protobuf-c + url = ../../protobuf-c/protobuf-c.git + sbom-version = 1.4.1 + sbom-cpe = cpe:2.3:a:protobuf-c_project:protobuf-c:{}:*:*:*:*:*:*:* + sbom-supplier = Organization: protobuf-c community + sbom-url = https://github.com/protobuf-c/protobuf-c + sbom-description = Protocol Buffers implementation in C + sbom-hash = abc67a11c6db271bedbb9f58be85d6f4e2ea8389 + +[submodule "components/unity/unity"] + path = components/unity/unity + url = ../../ThrowTheSwitch/Unity.git + sbom-version = v2.4.3-51-g7d2bf62b7e6a + sbom-supplier = Organization: ThrowTheSwitch community + sbom-url = https://github.com/ThrowTheSwitch/Unity + sbom-description = Simple Unit Testing for C + sbom-hash = 7d2bf62b7e6afaf38153041a9d53c21aeeca9a25 + +[submodule "components/bt/host/nimble/nimble"] + path = components/bt/host/nimble/nimble + url = ../../espressif/esp-nimble.git + +[submodule "components/esp_wifi/lib"] + path = components/esp_wifi/lib + url = ../../espressif/esp32-wifi-lib.git + +[submodule "components/cmock/CMock"] + path = components/cmock/CMock + url = ../../ThrowTheSwitch/CMock.git + sbom-version = v2.5.2-2-geeecc49ce8af + sbom-supplier = Organization: ThrowTheSwitch community + sbom-url = https://github.com/ThrowTheSwitch/CMock + sbom-description = CMock - Mock/stub generator for C + sbom-hash = eeecc49ce8af123cf8ad40efdb9673e37b56230f + +[submodule "components/openthread/openthread"] + path = components/openthread/openthread + url = ../../espressif/openthread.git + +[submodule "components/bt/controller/lib_esp32c3_family"] + path = components/bt/controller/lib_esp32c3_family + url = ../../espressif/esp32c3-bt-lib.git + +[submodule "components/esp_phy/lib"] + path = components/esp_phy/lib + url = ../../espressif/esp-phy-lib.git + +[submodule "components/openthread/lib"] + path = components/openthread/lib + url = ../../espressif/esp-thread-lib.git + +[submodule "components/bt/controller/lib_esp32h2/esp32h2-bt-lib"] + path = components/bt/controller/lib_esp32h2/esp32h2-bt-lib + url = ../../espressif/esp32h2-bt-lib.git + +[submodule "components/bt/controller/lib_esp32c2/esp32c2-bt-lib"] + path = components/bt/controller/lib_esp32c2/esp32c2-bt-lib + url = ../../espressif/esp32c2-bt-lib.git + +[submodule "components/bt/controller/lib_esp32c6/esp32c6-bt-lib"] + path = components/bt/controller/lib_esp32c6/esp32c6-bt-lib + url = ../../espressif/esp32c6-bt-lib.git + +[submodule "components/heap/tlsf"] + path = components/heap/tlsf + url = ../../espressif/tlsf.git + +[submodule "components/esp_coex/lib"] + path = components/esp_coex/lib + url = ../../espressif/esp-coex-lib.git + +[submodule "components/bt/host/bluedroid/external/opus"] + path = components/bt/host/bluedroid/external/opus + url = https://github.com/xiph/opus.git + +[submodule "components/bt/host/bluedroid/external/liblc3"] + path = components/bt/host/bluedroid/external/liblc3 + url = https://github.com/cfint/liblc3.git diff --git a/components/bt/CMakeLists.txt b/components/bt/CMakeLists.txt index 61f42f2b11a6..139a9ae6f85c 100644 --- a/components/bt/CMakeLists.txt +++ b/components/bt/CMakeLists.txt @@ -843,6 +843,27 @@ if(CONFIG_BT_A2DP_AAC_DECODER) endif() +if(CONFIG_BT_A2DP_LHDCV5_DECODER) + list(APPEND priv_include_dirs host/bluedroid/external/LHDC-V5-Decoder/decoder) + list(APPEND lhdcv5_dec_srcs "host/bluedroid/external/LHDC-V5-Decoder/decoder/lhdc_bit_reader.c" + "host/bluedroid/external/LHDC-V5-Decoder/decoder/lhdc_dec.c" + "host/bluedroid/external/LHDC-V5-Decoder/decoder/lhdc_diag_config.c" + "host/bluedroid/external/LHDC-V5-Decoder/decoder/lhdc_entropy_dec.c" + "host/bluedroid/external/LHDC-V5-Decoder/decoder/lhdc_imdct.c" + "host/bluedroid/external/LHDC-V5-Decoder/decoder/lhdc_sns_synth.c" + "host/bluedroid/external/LHDC-V5-Decoder/decoder/lhdc_tables.c") + list(APPEND srcs ${lhdcv5_dec_srcs}) + + list(APPEND srcs "host/bluedroid/stack/a2dp/a2dp_vendor_lhdcv5.c" + "host/bluedroid/stack/a2dp/a2dp_vendor_lhdcv5_decoder.c") + +endif() + +if(CONFIG_BT_A2DP_MIHC_DECODER) + list(APPEND srcs "host/bluedroid/stack/a2dp/a2dp_vendor_mihc.c" + "host/bluedroid/stack/a2dp/a2dp_vendor_mihc_decoder.c") +endif() + idf_component_register(SRCS "${srcs}" INCLUDE_DIRS "${include_dirs}" PRIV_INCLUDE_DIRS "${priv_include_dirs}" diff --git a/components/bt/host/bluedroid/Kconfig.in b/components/bt/host/bluedroid/Kconfig.in index 7223fea4e6a8..a031840768d5 100644 --- a/components/bt/host/bluedroid/Kconfig.in +++ b/components/bt/host/bluedroid/Kconfig.in @@ -61,6 +61,13 @@ config BT_A2DP_ENABLE help Advanced Audio Distrubution Profile +config BT_A2DP_SBC_DECODER + bool "SBC decoder" + depends on BT_A2DP_ENABLE + default n + help + A2DP SBC decoder + config BT_A2DP_APTX_DECODER bool "aptX decoder" depends on BT_A2DP_ENABLE @@ -96,6 +103,48 @@ config BT_A2DP_AAC_DECODER select SPIRAM help A2DP AAC decoder + +config BT_A2DP_LHDCV5_DECODER + bool "LHDCV5 decoder" + depends on BT_A2DP_ENABLE + default n + help + A2DP LHDCV5 decoder + +config BT_A2DP_LHDCV3_DECODER + bool "LHDCV3 decoder" + depends on BT_A2DP_ENABLE + default n + help + A2DP LHDCV3 decoder + +config BT_A2DP_LHDCV2_DECODER + bool "LHDCV2 decoder" + depends on BT_A2DP_ENABLE + default n + help + A2DP LHDCV2 decoder + +config BT_A2DP_LHDCV1_DECODER + bool "LHDCV1 decoder" + depends on BT_A2DP_ENABLE + default n + help + A2DP LHDCV1 decoder + +config BT_A2DP_LHDC_LL_DECODER + bool "LHDC_LL decoder" + depends on BT_A2DP_ENABLE + default n + help + A2DP LHDC_LL decoder + +config BT_A2DP_MIHC_DECODER + bool "MIHC decoder" + depends on BT_A2DP_ENABLE + default n + help + A2DP MIHC decoder config BT_SPP_ENABLED bool "SPP" diff --git a/components/bt/host/bluedroid/api/include/api/esp_a2dp_api.h b/components/bt/host/bluedroid/api/include/api/esp_a2dp_api.h index f1b844950849..8e0440f2f87d 100644 --- a/components/bt/host/bluedroid/api/include/api/esp_a2dp_api.h +++ b/components/bt/host/bluedroid/api/include/api/esp_a2dp_api.h @@ -32,6 +32,7 @@ typedef uint16_t esp_a2d_psc_t; /** * @brief A2DP media codec capabilities union + * 大部分CIE_LEN = CODEC_LEN - 2 */ typedef struct { esp_a2d_mct_t type; /*!< A2DP media codec type */ @@ -44,7 +45,9 @@ typedef struct { #define ESP_A2D_CIE_LEN_APTX_LL (7) #define ESP_A2D_CIE_LEN_LDAC (8) #define ESP_A2D_CIE_LEN_OPUS (26) -#define ESP_A2D_CIE_LEN_LC3PLUS (12) +#define ESP_A2D_CIE_LEN_LC3PLUS (12) +#define ESP_A2D_CIE_LEN_LHDCV5 (11) +#define ESP_A2D_CIE_LEN_MIHC (11) union { uint8_t sbc[ESP_A2D_CIE_LEN_SBC]; /*!< SBC codec capabilities */ uint8_t m12[ESP_A2D_CIE_LEN_M12]; /*!< MPEG-1,2 audio codec capabilities */ @@ -56,6 +59,8 @@ typedef struct { uint8_t ldac[ESP_A2D_CIE_LEN_LDAC]; /*!< LDAC codec capabilities */ uint8_t opus[ESP_A2D_CIE_LEN_OPUS]; /*!< OPUS codec capabilities */ uint8_t lc3plus[ESP_A2D_CIE_LEN_LC3PLUS]; /*!< LC3 Plus codec capabilities */ + uint8_t lhdcv5[ESP_A2D_CIE_LEN_LHDCV5]; /*!< LHDCV5 codec capabilities */ + uint8_t mihc[ESP_A2D_CIE_LEN_MIHC]; /*!< MIHC codec capabilities */ } cie; /*!< A2DP codec information element */ } __attribute__((packed)) esp_a2d_mcc_t; diff --git a/components/bt/host/bluedroid/bta/av/bta_av_act.c b/components/bt/host/bluedroid/bta/av/bta_av_act.c index 13a575071396..18cd689da960 100644 --- a/components/bt/host/bluedroid/bta/av/bta_av_act.c +++ b/components/bt/host/bluedroid/bta/av/bta_av_act.c @@ -127,9 +127,9 @@ void bta_av_del_rc(tBTA_AV_RCB *p_rcb) } /* else ACP && connected. do not clear the handle yet */ AVRC_Close(rc_handle); - if (rc_handle == bta_av_cb.rc_acp_handle) { - bta_av_cb.rc_acp_handle = BTA_AV_RC_HANDLE_NONE; - } +// if (rc_handle == bta_av_cb.rc_acp_handle) { +// bta_av_cb.rc_acp_handle = BTA_AV_RC_HANDLE_NONE; +// } APPL_TRACE_EVENT("end del_rc handle: %d status=0x%x, rc_acp_handle:%d, lidx:%d", p_rcb->handle, p_rcb->status, bta_av_cb.rc_acp_handle, p_rcb->lidx); } @@ -302,7 +302,7 @@ UINT8 bta_av_rc_create(tBTA_AV_CB *p_cb, UINT8 role, UINT8 shdl, UINT8 lidx) bda = p_scb->peer_addr; status = BTA_AV_RC_ROLE_INT; } else { - if ((p_rcb = bta_av_get_rcb_by_shdl(shdl)) != NULL ) { + if (shdl != 0 && ((p_rcb = bta_av_get_rcb_by_shdl(shdl)) != NULL)) { APPL_TRACE_ERROR("bta_av_rc_create ACP handle exist for shdl:%d", shdl); return p_rcb->handle; } @@ -1636,6 +1636,8 @@ void bta_av_rc_disc_done(tBTA_AV_DATA *p_data) } } else { p_cb->rcb[rc_handle].peer_features = peer_features; + p_cb->rcb[rc_handle].peer_ct_features = peer_ct_features; + p_cb->rcb[rc_handle].peer_tg_features = peer_tg_features; rc_feat.rc_handle = rc_handle; rc_feat.peer_features = peer_features; rc_feat.peer_ct_features = peer_ct_features; @@ -1711,7 +1713,8 @@ void bta_av_rc_closed(tBTA_AV_DATA *p_data) bta_av_del_rc(p_rcb); /* if the AVRCP is no longer listening, create the listening channel */ - if (bta_av_cb.rc_acp_handle == BTA_AV_RC_HANDLE_NONE && bta_av_cb.features & BTA_AV_FEAT_RCTG) { + if (bta_av_cb.rc_acp_handle == p_msg->handle && bta_av_cb.features & BTA_AV_FEAT_RCTG) { + bta_av_cb.rc_acp_handle = BTA_AV_RC_HANDLE_NONE; bta_av_rc_create(&bta_av_cb, AVCT_ACP, 0, BTA_AV_NUM_LINKS + 1); } } diff --git a/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_sink.c b/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_sink.c index 25a0928d6b6e..2ec0f82d6d3c 100644 --- a/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_sink.c +++ b/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_sink.c @@ -32,13 +32,14 @@ #include "oi_status.h" #include "osi/future.h" #include +#include "esp_heap_caps.h" // For PSRAM allocation #if (BTC_AV_SINK_INCLUDED == TRUE) -#if (defined(APTX_DEC_INCLUDED) && APTX_DEC_INCLUDED == TRUE) -#define BT_A2DP_SINK_BUF_SIZE 8192 +#if CONFIG_SPIRAM +#define BT_A2DP_SINK_BUF_SIZE (64 * 1024) #else -#define BT_A2DP_SINK_BUF_SIZE 4096 +#define BT_A2DP_SINK_BUF_SIZE (16 * 1024) #endif /***************************************************************************** @@ -88,7 +89,11 @@ enum { #if CONFIG_SPIRAM #define A2DP_TASK_STACK_SIZE (50 * 1024) #else +#if BTC_TASK_STACK_SIZE #define A2DP_TASK_STACK_SIZE (BTC_TASK_STACK_SIZE) +#else +#define A2DP_TASK_STACK_SIZE (16 * 1024) +#endif #endif #define A2DP_TASK_PRIO (BT_TASK_MAX_PRIORITIES - 6) #define A2DP_TASK_PINNED_TO_CORE (1) @@ -116,7 +121,11 @@ typedef struct { tBTC_A2DP_SINK_CB btc_aa_snk_cb; osi_thread_t *btc_aa_snk_task_hdl; const tA2DP_DECODER_INTERFACE* decoder; +#if CONFIG_SPIRAM + unsigned char *decode_buf; // Allocated from PSRAM +#else unsigned char decode_buf[BT_A2DP_SINK_BUF_SIZE]; +#endif // a2dp_sink_media_pkt_seq_num_t media_pkt_seq_num; } a2dp_sink_local_param_t; @@ -526,7 +535,11 @@ static void btc_a2dp_sink_handle_inc_media(BT_HDR *p_msg) if (a2dp_sink_local_param.decoder->decode_packet) { unsigned char* buf = a2dp_sink_local_param.decode_buf; +#if CONFIG_SPIRAM + size_t buf_len = BT_A2DP_SINK_BUF_SIZE; // Size allocated from PSRAM +#else size_t buf_len = sizeof(a2dp_sink_local_param.decode_buf); +#endif a2dp_sink_local_param.decoder->decode_packet(p_msg, buf, buf_len); } } @@ -651,6 +664,20 @@ static void btc_a2dp_sink_thread_init(UNUSED_ATTR void *context) a2dp_sink_local_param.btc_aa_snk_cb.RxSbcQ = fixed_queue_new(QUEUE_SIZE_MAX); + /* Allocate decode buffer from PSRAM if available */ +#if CONFIG_SPIRAM + a2dp_sink_local_param.decode_buf = (unsigned char *)heap_caps_malloc(BT_A2DP_SINK_BUF_SIZE, MALLOC_CAP_SPIRAM); + if (!a2dp_sink_local_param.decode_buf) { + APPL_TRACE_WARNING("PSRAM alloc failed"); + a2dp_sink_local_param.decode_buf = (unsigned char *)osi_malloc(BT_A2DP_SINK_BUF_SIZE); + } else { + APPL_TRACE_EVENT("Decode buffer from PSRAM"); + } +#else + a2dp_sink_local_param.decode_buf = (unsigned char *)osi_malloc(BT_A2DP_SINK_BUF_SIZE); +#endif + assert(a2dp_sink_local_param.decode_buf != NULL); + btc_a2dp_control_init(); } @@ -674,6 +701,14 @@ static void btc_a2dp_sink_thread_cleanup(UNUSED_ATTR void *context) osi_event_delete(a2dp_sink_local_param.btc_aa_snk_cb.data_ready_event); a2dp_sink_local_param.btc_aa_snk_cb.data_ready_event = NULL; + +#if CONFIG_SPIRAM + /* Free decode buffer */ + if (a2dp_sink_local_param.decode_buf) { + heap_caps_free(a2dp_sink_local_param.decode_buf); + a2dp_sink_local_param.decode_buf = NULL; + } +#endif } #endif /* BTC_AV_SINK_INCLUDED */ diff --git a/components/bt/host/bluedroid/btc/profile/std/a2dp/include/btc_av_co.h b/components/bt/host/bluedroid/btc/profile/std/a2dp/include/btc_av_co.h index 29b4e444e679..6a3de0a4e2af 100644 --- a/components/bt/host/bluedroid/btc/profile/std/a2dp/include/btc_av_co.h +++ b/components/bt/host/bluedroid/btc/profile/std/a2dp/include/btc_av_co.h @@ -28,13 +28,19 @@ enum { #endif /* LDAC_DEC_INCLUDED */ #if (defined(OPUS_DEC_INCLUDED) && OPUS_DEC_INCLUDED == TRUE) BTC_SV_AV_AA_OPUS_INDEX, -#endif /* LDAC_DEC_INCLUDED */ +#endif /* OPUS_DEC_INCLUDED */ #if (defined(LC3PLUS_DEC_INCLUDED) && LC3PLUS_DEC_INCLUDED == TRUE) BTC_SV_AV_AA_LC3PLUS_INDEX, -#endif /* LDAC_DEC_INCLUDED */ +#endif /* LC3PLUS_DEC_INCLUDED */ #if (defined(AAC_DEC_INCLUDED) && AAC_DEC_INCLUDED == TRUE) BTC_SV_AV_AA_AAC_INDEX, #endif /* AAC_DEC_INCLUDED */ +#if (defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) + BTC_SV_AV_AA_LHDCV5_INDEX, +#endif /* LHDCV5_DEC_INCLUDED */ +#if (defined(MIHC_DEC_INCLUDED) && MIHC_DEC_INCLUDED == TRUE) + BTC_SV_AV_AA_MIHC_INDEX, +#endif /* MIHC_DEC_INCLUDED */ BTC_SV_AV_AA_SOURCE_MAX, BTC_SV_AV_AA_SINK_MIN = BTC_SV_AV_AA_SOURCE_MAX, BTC_SV_AV_AA_SBC_SINK_INDEX = BTC_SV_AV_AA_SINK_MIN, @@ -55,6 +61,12 @@ enum { #if (defined(AAC_DEC_INCLUDED) && AAC_DEC_INCLUDED == TRUE) BTC_SV_AV_AA_AAC_SINK_INDEX, #endif /* AAC_DEC_INCLUDED */ +#if (defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) + BTC_SV_AV_AA_LHDCV5_SINK_INDEX, +#endif /* LHDCV5_DEC_INCLUDED */ +#if (defined(MIHC_DEC_INCLUDED) && MIHC_DEC_INCLUDED == TRUE) + BTC_SV_AV_AA_MIHC_SINK_INDEX, +#endif /* MIHC_DEC_INCLUDED */ BTC_SV_AV_AA_SINK_MAX }; diff --git a/components/bt/host/bluedroid/btc/profile/std/avrc/btc_avrc.c b/components/bt/host/bluedroid/btc/profile/std/avrc/btc_avrc.c index 7e7f1b2bb6c6..864348528729 100644 --- a/components/bt/host/bluedroid/btc/profile/std/avrc/btc_avrc.c +++ b/components/bt/host/bluedroid/btc/profile/std/avrc/btc_avrc.c @@ -170,8 +170,7 @@ bool btc_avrc_ct_init_p(void) bool btc_avrc_tg_connected_p(void) { return (s_rc_tg_init == BTC_RC_TG_INIT_MAGIC) && - (btc_rc_cb.rc_connected == TRUE) && - (btc_rc_cb.rc_features & BTA_AV_FEAT_RCCT); + (btc_rc_cb.rc_connected == TRUE); } bool btc_avrc_ct_connected_p(void) @@ -456,7 +455,7 @@ static void handle_rc_connect (tBTA_AV_RC_OPEN *p_rc_open) btc_avrc_ct_cb_to_app(ESP_AVRC_CT_CONNECTION_STATE_EVT, ¶m); } - if (p_rc_open->peer_features & BTA_AV_FEAT_RCCT) { + if (btc_avrc_tg_init_p()) { esp_avrc_tg_cb_param_t param; memset(¶m, 0, sizeof(esp_avrc_tg_cb_param_t)); param.conn_stat.connected = true; @@ -515,7 +514,7 @@ static void handle_rc_disconnect (tBTA_AV_RC_CLOSE *p_rc_close) btc_avrc_ct_cb_to_app(ESP_AVRC_CT_CONNECTION_STATE_EVT, ¶m); } - if (rc_features & BTA_AV_FEAT_RCCT) { + if (btc_avrc_tg_init_p()) { esp_avrc_tg_cb_param_t param; memset(¶m, 0, sizeof(esp_avrc_ct_cb_param_t)); param.conn_stat.connected = false; @@ -941,14 +940,10 @@ void btc_rc_handler(tBTA_AV_EVT event, tBTA_AV *p_data) memcpy(param.conn_stat.remote_bda, btc_rc_cb.rc_addr, sizeof(esp_bd_addr_t)); btc_avrc_ct_cb_to_app(ESP_AVRC_CT_CONNECTION_STATE_EVT, ¶m); } - if ((p_data->rc_feat.peer_features & BTA_AV_FEAT_RCCT) && - !(old_feats & BTA_AV_FEAT_RCCT)) { - esp_avrc_tg_cb_param_t param; - memset(¶m, 0, sizeof(esp_avrc_ct_cb_param_t)); - param.conn_stat.connected = true; - memcpy(param.conn_stat.remote_bda, btc_rc_cb.rc_addr, sizeof(esp_bd_addr_t)); - btc_avrc_tg_cb_to_app(ESP_AVRC_CT_CONNECTION_STATE_EVT, ¶m); - } + /** + * @note ESP_AVRC_TG_CONNECTION_STATE_EVT has been reported on rc connect/disconnect event, + * it doesn't rely on the SDP results. + */ } while (0); btc_rc_cb.rc_features = p_data->rc_feat.peer_features; btc_rc_cb.rc_ct_features = p_data->rc_feat.peer_ct_features; diff --git a/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h b/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h index e191aa7309f3..f3f5a8f010b3 100644 --- a/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h +++ b/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h @@ -69,6 +69,18 @@ #define UC_BT_A2DP_AAC_DECODER_ENABLED FALSE #endif +#ifdef CONFIG_BT_A2DP_LHDCV5_DECODER +#define UC_BT_A2DP_LHDCV5_DECODER_ENABLED CONFIG_BT_A2DP_LHDCV5_DECODER +#else +#define UC_BT_A2DP_LHDCV5_DECODER_ENABLED FALSE +#endif + +#ifdef CONFIG_BT_A2DP_MIHC_DECODER +#define UC_BT_A2DP_MIHC_DECODER_ENABLED CONFIG_BT_A2DP_MIHC_DECODER +#else +#define UC_BT_A2DP_MIHC_DECODER_ENABLED FALSE +#endif + //SPP #ifdef CONFIG_BT_SPP_ENABLED #define UC_BT_SPP_ENABLED CONFIG_BT_SPP_ENABLED diff --git a/components/bt/host/bluedroid/common/include/common/bt_target.h b/components/bt/host/bluedroid/common/include/common/bt_target.h index 992f03778f68..94ec2ff3a8e3 100644 --- a/components/bt/host/bluedroid/common/include/common/bt_target.h +++ b/components/bt/host/bluedroid/common/include/common/bt_target.h @@ -87,6 +87,9 @@ #if (UC_BT_A2DP_AAC_DECODER_ENABLED == TRUE) #define AAC_DEC_INCLUDED TRUE #endif /* (UC_BT_A2DP_AAC_DECODER_ENABLED == TRUE) */ +#if (UC_BT_A2DP_LHDCV5_DECODER_ENABLED == TRUE) +#define LHDCV5_DEC_INCLUDED TRUE +#endif /* (UC_BT_A2DP_LHDCV5_DECODER_ENABLED == TRUE) */ #define BTC_AV_SRC_INCLUDED TRUE #define SBC_ENC_INCLUDED TRUE #endif /* UC_BT_A2DP_ENABLED */ @@ -1924,6 +1927,11 @@ # else # define AVDT_AAC_SEPS (0) # endif /* CONFIG_BT_A2DP_AAC_DECODER */ +# ifdef CONFIG_BT_A2DP_LHDCV5_DECODER +# define AVDT_LHDCV5_SEPS (1) +# else +# define AVDT_LHDCV5_SEPS (0) +# endif /* CONFIG_BT_A2DP_LHDCV5_DECODER */ #define AVDT_NUM_SEPS (3 \ + AVDT_APTX_SEPS \ @@ -1931,6 +1939,7 @@ + AVDT_OPUS_SEPS \ + AVDT_LC3PLUS_SEPS \ + AVDT_AAC_SEPS \ + + AVDT_LHDCV5_SEPS \ ) #endif diff --git a/components/bt/host/bluedroid/external/LHDC-V5-Decoder b/components/bt/host/bluedroid/external/LHDC-V5-Decoder new file mode 160000 index 000000000000..63d564b755d8 --- /dev/null +++ b/components/bt/host/bluedroid/external/LHDC-V5-Decoder @@ -0,0 +1 @@ +Subproject commit 63d564b755d869e03b6b6c492fe6765dd1151412 diff --git a/components/bt/host/bluedroid/external/README.md b/components/bt/host/bluedroid/external/README.md new file mode 100644 index 000000000000..3b7f2d56cba2 --- /dev/null +++ b/components/bt/host/bluedroid/external/README.md @@ -0,0 +1,118 @@ +# external of bluedroid + +这里是Bluedroid存放第三方Codecs的地方,每种Codecs都要在`components/bt/CMakeLists.txt`中包含头文件路径,并包含关键源文件;此外每种Codecs都有对应的`integration`文件,包括a2dp_vendor_xxx.c/.h,它们都分别位于`components/bt/host/bluedroid/stack/a2dp`和`components/bt/host/bluedroid/stack/include/stack`,并且源文件也添加到了`components/bt/CMakeLists.txt`里。它们都受各自的宏定义条件编译。 + +--- + +## 关于a2dp_vendor + +a2dp_vendor是cfint从AOSP移植而来的一种统一接口,可以通过各个解码器的`integration`文件接入解码器,其中`components/bt/host/bluedroid/stack/a2dp`和`components/bt/host/bluedroid/stack/include/stack`就是存放`a2dp_vendor`和各个解码器`integration`文件的地方。 + +--- + +## 关于PSRAM + +SBC和aptX可以不需要PSRAM,但是AAC、LDAC、LHDC V5必须要有PSRAM才能正常运行。 + +--- + +## Configuration + +Key sdkconfig settings for optimal performance: +``` +# Flash and Memory +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +CONFIG_SPIRAM_SPEED_80M=y + +# CPU Performance +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y + +# Bluetooth +CONFIG_BTDM_CTRL_MODE_BTDM=y +CONFIG_BT_A2DP_ENABLE=y +CONFIG_BT_A2DP_LDAC_DECODER=y +CONFIG_BT_A2DP_APTX_DECODER=y +CONFIG_BT_A2DP_AAC_DECODER=y +CONFIG_BT_A2DP_LHDCV5_DECODER=y +``` + +--- + +## Troubleshooting + +### LDAC / LHDC V5 Low/Medium Quality causes buffer overflow + +Edit `components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_sink.c`: + +``` +// Change from: +#define BT_A2DP_SINK_BUF_SIZE 8192 +// To: +#define BT_A2DP_SINK_BUF_SIZE 32768 +``` + +> 实际测试设置16384会比较好,如果设置成32768,那么编译会提示IRAM超容量报错,则需要修改代码改成buffer分配到PSRAM。 + +Then rebuild with `idf.py fullclean && idf.py build`. + +### AAC / LHDC V5 decoder fails to initialize + +Ensure PSRAM is enabled and running at 80MHz. Check that your ESP32 board has PSRAM (WROVER, not WROOM). + +### Bluetooth pairing issues on Linux + +Clear the Bluetooth cache: + +``` +sudo rm -rf /var/lib/bluetooth//cache/ +Then re-pair the device. +``` + +### Where is LHDC V5 ? + +已知 [O2C14](https://github.com/O2C14) 和 [O11111(anonymix007)](https://github.com/anonymix007) 是早期逆向LHDC的开发者,不过它们目前并没有开源;目前唯一彻底开源LHDC V5解码器的开发者是 [WillyBilly06](https://github.com/WillyBilly06),它是基于手机的.so动态库完成了逆向。 + +### Audio stutters at LDAC 96 kHz + +- Confirm that CONFIG_FREERTOS_HZ=1000 is set. +- Reduce BLE meter update rate if needed. +- Check that the source device is not forcing an unstable radio condition. +- Keep the ESP32 close to the source device during testing. + +--- + +## Credits + +This project builds upon the excellent work of the open-source community: + +### ESP-IDF with Codec Interface + +| Project | Tree | Author | Description | +|---------|------|--------|-------------| +| [esp32-a2dp-sink](https://github.com/cfint/esp32-a2dp-sink) | v5.1 | cfint | Example of A2DP sink with codecs | +| [esp-idf](https://github.com/cfint/esp-idf) | v5.1.4-a2dp-codecs | cfint | ESP-IDF fork with codec support | +| [esp32-a2dp-sink-with-LDAC-APTX-AAC](https://github.com/WillyBilly06/esp32-a2dp-sink-with-LDAC-APTX-AAC) | main | cfint/WillyBilly06 | ESP-IDF V5.1.4 fork with codec support and example | +| [ESP32-A2DP-SINK-WITH-CODECS-UPDATED ](https://github.com/WillyBilly06/ESP32-A2DP-SINK-WITH-CODECS-UPDATED/tree/main) | main | cfint/WillyBilly06 | ESP-IDF V5.5.2 fork with codec support and internal-RAM operation and example. No PSRAM is required | +| [ESP32-A2DP-SINK-WITH-CODECS-UPDATED ](https://github.com/WillyBilly06/ESP32-A2DP-SINK-WITH-CODECS-UPDATED/tree/PSRAM-only) | PSRAM-only | cfint/WillyBilly06 | ESP-IDF V5.5.2 fork with codec support and PSRAM operation and example. PSRAM is required | +| [ESP32-S31-A2DP-Codecs](https://github.com/WillyBilly06/ESP32-S31-A2DP-Codecs) | main | WillyBilly06 | ESP-IDF V6.1.0 fork with codec support and example | + +### Library with Codec Config and I2S Config + +| Project | Tree | Author | Description | +|---------|------|--------|-------------| +| [ESP32-A2DP](https://github.com/cfint/ESP32-A2DP/tree/v5.1-a2dp_codecs) | v5.1-a2dp_codecs | cfint/pschatzmann | A2DP library with codec config | +| [arduino-audio-tools](https://github.com/cfint/arduino-audio-tools/tree/v5.1-a2dp_codecs) | v5.1-a2dp_codecs | cfint/pschatzmann | Audio Processing with I2S config | + +### A2DP Audio Codecs + +| Project | Tree | Author | Description | Usage Status | +|---------|------|--------|-------------|--------------| +| [opus](https://github.com/xiph/opus) | main | xiph | Opus Decoder | Used | +| [liblc3](https://github.com/cfint/liblc3) | esp32 | cfint | LC3 Decoder (LE Audio)| Used | +| [libfreeaptx](https://github.com/cfint/libfreeaptx-esp) | master | cfint | aptX decoder for ESP32 | Used | +| [arduino-fdk-aac](https://github.com/cfint/arduino-fdk-aac) | idf_component | cfint | AAC decoder | Used | +| [libldacdec](https://github.com/anonymix007/libldacdec) | master | anonymix007 | LDAC Decoder, not very well | Unused | +| [libldac-dec](https://github.com/cfint/libldac-dec) | esp32 | O2C14/cfint | LDAC Decoder, works well | Used | +| [liblhdcv5dec](https://github.com/sprlightning/liblhdcv5dec) | main | sprlightning | LHDC V5 Decoder, no decoder core | Unused | +| [LHDC-V5-Decoder](https://github.com/WillyBilly06/LHDC-V5-Decoder) | main | WillyBilly06 | LHDC V5 Decoder, works well | Used | diff --git a/components/bt/host/bluedroid/external/arduino-fdk-aac b/components/bt/host/bluedroid/external/arduino-fdk-aac new file mode 160000 index 000000000000..47deb22f778a --- /dev/null +++ b/components/bt/host/bluedroid/external/arduino-fdk-aac @@ -0,0 +1 @@ +Subproject commit 47deb22f778ac7cf1991f29b21e82f741ebd0881 diff --git a/components/bt/host/bluedroid/external/libfreeaptx b/components/bt/host/bluedroid/external/libfreeaptx new file mode 160000 index 000000000000..090418787386 --- /dev/null +++ b/components/bt/host/bluedroid/external/libfreeaptx @@ -0,0 +1 @@ +Subproject commit 090418787386e210e6f7414c386f37753af59b5f diff --git a/components/bt/host/bluedroid/external/libldac-dec b/components/bt/host/bluedroid/external/libldac-dec new file mode 160000 index 000000000000..ce2b8947221e --- /dev/null +++ b/components/bt/host/bluedroid/external/libldac-dec @@ -0,0 +1 @@ +Subproject commit ce2b8947221ec05f37a1f80525ebff637ad83820 diff --git a/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor.c b/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor.c index 021c858afb9e..f9ee1f8fe32b 100644 --- a/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor.c +++ b/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor.c @@ -30,6 +30,7 @@ #include "stack/a2dp_vendor_ldac.h" #include "stack/a2dp_vendor_opus.h" #include "stack/a2dp_vendor_lc3plus.h" +#include "stack/a2dp_vendor_lhdcv5.h" tA2D_STATUS A2DP_VendorParseInfo(uint8_t* p_ie, const uint8_t* p_codec_info, bool is_capability) { @@ -78,6 +79,14 @@ tA2D_STATUS A2DP_VendorParseInfo(uint8_t* p_ie, const uint8_t* p_codec_info, } #endif /* defined(LC3PLUS_DEC_INCLUDED) && LC3PLUS_DEC_INCLUDED == TRUE) */ +#if (defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) + // Check for LHDCV5 + if (vendor_id == A2DP_LHDCV5_VENDOR_ID && + codec_id == A2DP_LHDCV5_CODEC_ID) { + return A2DP_ParseInfoLhdcV5((tA2DP_LHDCV5_CIE*)p_ie, p_codec_info, is_capability); + } +#endif /* defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) */ + // Add checks based on (void)vendor_id; (void)codec_id; @@ -131,6 +140,14 @@ bool A2DP_IsVendorPeerSinkCodecValid(const uint8_t* p_codec_info) { } #endif /* defined(LC3PLUS_DEC_INCLUDED) && LC3PLUS_DEC_INCLUDED == TRUE) */ +#if (defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) + // Check for LHDCV5 + if (vendor_id == A2DP_LHDCV5_VENDOR_ID && + codec_id == A2DP_LHDCV5_CODEC_ID) { + return A2DP_IsVendorPeerSinkCodecValidLhdcV5(p_codec_info); + } +#endif /* defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) */ + // Add checks based on (void)vendor_id; (void)codec_id; @@ -197,6 +214,14 @@ tA2D_STATUS A2DP_IsVendorPeerSourceCodecSupported(const uint8_t* p_codec_info) { } #endif /* defined(LC3PLUS_DEC_INCLUDED) && LC3PLUS_DEC_INCLUDED == TRUE) */ +#if (defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) + // Check for LHDCV5 + if (vendor_id == A2DP_LHDCV5_VENDOR_ID && + codec_id == A2DP_LHDCV5_CODEC_ID) { + return A2DP_IsVendorPeerSourceCodecValidLhdcV5(p_codec_info); + } +#endif /* defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) */ + // Add checks based on and peer codec capabilities // NOTE: Should be done only for local Sink codecs. (void)vendor_id; @@ -271,6 +296,14 @@ btav_a2dp_codec_index_t A2DP_VendorSinkCodecIndex( } #endif /* defined(LC3PLUS_DEC_INCLUDED) && LC3PLUS_DEC_INCLUDED == TRUE) */ +#if (defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) + // Check for LHDCV5 + if (vendor_id == A2DP_LHDCV5_VENDOR_ID && + codec_id == A2DP_LHDCV5_CODEC_ID) { + return A2DP_VendorSinkCodecIndexLhdcV5(p_codec_info); + } +#endif /* defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) */ + // Add checks based on (void)vendor_id; (void)codec_id; @@ -325,6 +358,14 @@ btav_a2dp_codec_index_t A2DP_VendorSourceCodecIndex( } #endif /* defined(LC3PLUS_DEC_INCLUDED) && LC3PLUS_DEC_INCLUDED == TRUE) */ +#if (defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) + // Check for LHDCV5 + if (vendor_id == A2DP_LHDCV5_VENDOR_ID && + codec_id == A2DP_LHDCV5_CODEC_ID) { + return A2DP_VendorSourceCodecIndexLhdcV5(p_codec_info); + } +#endif /* defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) */ + // Add checks based on (void)vendor_id; (void)codec_id; @@ -370,6 +411,13 @@ bool A2DP_VendorInitCodecConfig(btav_a2dp_codec_index_t codec_index, UINT8 *p_re } #endif /* defined(LC3PLUS_DEC_INCLUDED) && LC3PLUS_DEC_INCLUDED == TRUE) */ +#if (defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) + // Check for LHDCV5 + if (A2DP_VendorInitCodecConfigLhdcV5(codec_index, p_result)) { + return true; + } +#endif /* defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) */ + (void)codec_index; (void)p_result; @@ -423,6 +471,14 @@ bool A2DP_VendorBuildCodecConfig(UINT8 *p_src_cap, UINT8 *p_result) { } #endif /* defined(LC3PLUS_DEC_INCLUDED) && LC3PLUS_DEC_INCLUDED == TRUE) */ +#if (defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) + // Check for LHDCV5 + if (vendor_id == A2DP_LHDCV5_VENDOR_ID && + codec_id == A2DP_LHDCV5_CODEC_ID) { + return A2DP_VendorBuildCodecConfigLhdcV5(p_src_cap, p_result); + } +#endif /* defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) */ + // Add checks based on (void)vendor_id; (void)codec_id; @@ -476,6 +532,14 @@ const char* A2DP_VendorCodecName(const uint8_t* p_codec_info) { } #endif /* defined(LC3PLUS_DEC_INCLUDED) && LC3PLUS_DEC_INCLUDED == TRUE) */ +#if (defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) + // Check for LHDCV5 + if (vendor_id == A2DP_LHDCV5_VENDOR_ID && + codec_id == A2DP_LHDCV5_CODEC_ID) { + return A2DP_VendorCodecNameLhdcV5(p_codec_info); + } +#endif /* defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) */ + // Add checks based on (void)vendor_id; (void)codec_id; @@ -542,6 +606,14 @@ bool A2DP_VendorCodecTypeEquals(const uint8_t* p_codec_info_a, } #endif /* defined(LC3PLUS_DEC_INCLUDED) && LC3PLUS_DEC_INCLUDED == TRUE) */ +#if (defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) + // Check for LHDCV5 + if (vendor_id_a == A2DP_LHDCV5_VENDOR_ID && + codec_id_a == A2DP_LHDCV5_CODEC_ID) { + return A2DP_VendorCodecTypeEqualsLhdcV5(p_codec_info_a, p_codec_info_b); + } +#endif /* defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) */ + // OPTIONAL: Add extra vendor-specific checks based on the // vendor-specific data stored in "p_codec_info_a" and "p_codec_info_b". (void)vendor_id_a; @@ -596,6 +668,14 @@ const tA2DP_DECODER_INTERFACE* A2DP_GetVendorDecoderInterface( } #endif /* defined(LC3PLUS_DEC_INCLUDED) && LC3PLUS_DEC_INCLUDED == TRUE) */ +#if (defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) + // Check for LHDCV5 + if (vendor_id == A2DP_LHDCV5_VENDOR_ID && + codec_id == A2DP_LHDCV5_CODEC_ID) { + return A2DP_GetVendorDecoderInterfaceLhdcV5(p_codec_info); + } +#endif /* defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) */ + (void)vendor_id; (void)codec_id; diff --git a/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor_ldac.c b/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor_ldac.c index d00cfa2395af..93da86ef27c5 100644 --- a/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor_ldac.c +++ b/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor_ldac.c @@ -1,7 +1,13 @@ -/* +/** * SPDX-FileCopyrightText: 2016 The Android Open Source Project * * SPDX-License-Identifier: Apache-2.0 + * + * a2dp_vendor_ldac.c + * + * a2dp_vendor.c <-> a2dp_vendor_ldac.c <-> a2dp_vendor_ldacbt_decoder.c <- ldacBT.h (OK) + * + * a2dp_vendor.c <-> a2dp_vendor_ldac.c <-> a2dp_vendor_ldac_decoder.c <- ldacdec.h (abandoned) */ #include @@ -55,8 +61,8 @@ static const tA2DP_DECODER_INTERFACE a2dp_decoder_interface_ldac = { NULL, // decoder_reset, a2dp_ldac_decoder_decode_packet_header, a2dp_ldac_decoder_decode_packet, - NULL, // decoder_start - NULL, // decoder_suspend + a2dp_ldac_decoder_start, // decoder_start + a2dp_ldac_decoder_suspend, // decoder_suspend a2dp_ldac_decoder_configure, }; @@ -265,16 +271,23 @@ bool A2DP_VendorBuildCodecConfigLdac(UINT8 *p_src_cap, UINT8 *p_result) { return false; } + // 优先匹配双方支持的最高采样率(44.1到96kHz都支持;推荐用48kHz,稳定性最好,事实上漫步者他们也是优先考虑48kHz) + // 测试发现使用96k时,QQ音乐会导致ldac buffer full的情况,其余播放器未遇到此问题 if (src_cap.sampleRate & A2DP_LDAC_SAMPLING_FREQ_48000) { pref_cap.sampleRate = A2DP_LDAC_SAMPLING_FREQ_48000; - } else if (src_cap.sampleRate & A2DP_LDAC_SAMPLING_FREQ_44100) { + }else if (src_cap.sampleRate & A2DP_LDAC_SAMPLING_FREQ_44100) { pref_cap.sampleRate = A2DP_LDAC_SAMPLING_FREQ_44100; + }else if (src_cap.sampleRate & A2DP_LDAC_SAMPLING_FREQ_96000) { + pref_cap.sampleRate = A2DP_LDAC_SAMPLING_FREQ_96000; + } else if (src_cap.sampleRate & A2DP_LDAC_SAMPLING_FREQ_88200) { + pref_cap.sampleRate = A2DP_LDAC_SAMPLING_FREQ_88200; } else { APPL_TRACE_ERROR("%s: Unsupported sample rate 0x%x", __func__, src_cap.sampleRate); return false; } + // 优先匹配Stereo模式 if (src_cap.channelMode & A2DP_LDAC_CHANNEL_MODE_STEREO) { pref_cap.channelMode = A2DP_LDAC_CHANNEL_MODE_STEREO; } else if (src_cap.channelMode & A2DP_LDAC_CHANNEL_MODE_DUAL) { diff --git a/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor_ldac_decoder.c b/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor_ldac_decoder.c deleted file mode 100644 index 911d3e9b3980..000000000000 --- a/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor_ldac_decoder.c +++ /dev/null @@ -1,127 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "common/bt_trace.h" -#include "stack/a2dp_vendor_ldac.h" -#include "stack/a2dp_vendor_ldac_constants.h" -#include "stack/a2dp_vendor_ldac_decoder.h" -#include "ldacdec.h" - - -#if (defined(LDAC_DEC_INCLUDED) && LDAC_DEC_INCLUDED == TRUE) - -typedef struct { - ldacdec_t decoder; - decoded_data_callback_t decode_callback; -} tA2DP_LDAC_DECODER_CB; - -static tA2DP_LDAC_DECODER_CB a2dp_ldac_decoder_cb; - - -bool a2dp_ldac_decoder_init(decoded_data_callback_t decode_callback) { - int res; - - res = ldacdecInit(&a2dp_ldac_decoder_cb.decoder); - if (res) { - APPL_TRACE_ERROR("%s: decoder init failed %d", __func__, res); - return false; - } - a2dp_ldac_decoder_cb.decode_callback = decode_callback; - return true; -} - -ssize_t a2dp_ldac_decoder_decode_packet_header(BT_HDR* p_buf) { - size_t header_len = sizeof(struct media_packet_header) + - A2DP_LDAC_MPL_HDR_LEN; - p_buf->offset += header_len; - p_buf->len -= header_len; - return 0; -} - -static bool find_sync_word(unsigned char** buf, int *size) { - while((*size) > 0 && **buf != 0xAA) { - (*buf)++; - (*size)--; - } - - return (*size) > 0; -} - - -bool a2dp_ldac_decoder_decode_packet(BT_HDR* p_buf, unsigned char* buf, size_t buf_len) { - ldacdec_t* decoder = &a2dp_ldac_decoder_cb.decoder; - int ret; - - unsigned char* src = ((unsigned char *)(p_buf + 1) + p_buf->offset); - int src_size = p_buf->len; - unsigned char* dst = buf; - int32_t avail = buf_len; - - while (src_size > 0 && avail > 0) { - int bytes_used; - int out_size; - - if (!find_sync_word(&src, &src_size)) { - break; - } - - ret = ldacDecode(decoder, src, (void*)dst, &bytes_used); - if (ret != 0) { - APPL_TRACE_ERROR("%s: decoder error %d", __func__, ret); - return false; - } - src += bytes_used; - src_size -= bytes_used; - - frame_t *frame = &decoder->frame; - out_size = frame->frameSamples * frame->channelCount * sizeof(int16_t); - dst += out_size; - avail -= out_size; - } - - if (src_size > 0 && avail <= 0) { - LOG_ERROR("%s: Insufficient output buffer size. %d bytes remain.", __func__, src_size); - } - - size_t len = buf_len - avail; - len = len <= buf_len ? len : buf_len; - a2dp_ldac_decoder_cb.decode_callback((uint8_t*)buf, len); - return true; -} - -void a2dp_ldac_decoder_configure(const uint8_t* p_codec_info) { - tA2DP_LDAC_CIE cie; - tA2D_STATUS status; - status = A2DP_ParseInfoLdac(&cie, (uint8_t*)p_codec_info, false); - if (status != A2D_SUCCESS) { - LOG_ERROR("%s: failed parsing codec info. %d", __func__, status); - return; - } - - uint32_t sr = 0; - if (cie.sampleRate == A2DP_LDAC_SAMPLING_FREQ_44100) { - sr = 44100; - } else if (cie.sampleRate == A2DP_LDAC_SAMPLING_FREQ_48000) { - sr = 48000; - } else if (cie.sampleRate == A2DP_LDAC_SAMPLING_FREQ_88200) { - sr = 88200; - } else if (cie.sampleRate == A2DP_LDAC_SAMPLING_FREQ_96000) { - sr = 96000; - } else if (cie.sampleRate == A2DP_LDAC_SAMPLING_FREQ_176400) { - sr = 176400; - } else if (cie.sampleRate == A2DP_LDAC_SAMPLING_FREQ_192000) { - sr = 192000; - } - LOG_INFO("%s: LDAC Sampling frequency = %lu", __func__, sr); - - if (cie.channelMode == A2DP_LDAC_CHANNEL_MODE_MONO) { - LOG_INFO("%s: LDAC Channel mode: Mono", __func__); - } else if (cie.channelMode == A2DP_LDAC_CHANNEL_MODE_DUAL) { - LOG_INFO("%s: LDAC Channel mode: Dual", __func__); - } else if (cie.channelMode == A2DP_LDAC_CHANNEL_MODE_STEREO) { - LOG_INFO("%s: LDAC Channel mode: Stereo", __func__); - } -} - -#endif /* defined(LDAC_DEC_INCLUDED) && LDAC_DEC_INCLUDED == TRUE) */ diff --git a/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor_ldacbt_decoder.c b/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor_ldacbt_decoder.c index d638c51182ac..e2c5e0fdd9d3 100644 --- a/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor_ldacbt_decoder.c +++ b/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor_ldacbt_decoder.c @@ -1,5 +1,12 @@ -/* +/** + * SPDX-FileCopyrightText: 2016 The Android Open Source Project + * * SPDX-License-Identifier: Apache-2.0 + * + * a2dp_vendor_ldacbt_decoder.c + * + * 本文件依赖由O2C14开发的外部库libldac-dec中的ldacBT.h + * 与a2dp_vendor_ldac_decoder.c相比,本文件性能更好(a2dp_vendor_ldac_decoder.c was abandoned) */ #include "common/bt_trace.h" @@ -7,7 +14,7 @@ #include "stack/a2dp_vendor_ldac_constants.h" #include "stack/a2dp_vendor_ldac_decoder.h" -#include +#include // O2C14开发的ldac解码器 #if (defined(LDAC_DEC_INCLUDED) && LDAC_DEC_INCLUDED == TRUE) @@ -109,6 +116,14 @@ bool a2dp_ldac_decoder_decode_packet(BT_HDR* p_buf, unsigned char* buf, size_t b return true; } +void a2dp_ldac_decoder_start() { + LOG_INFO("Starting LDAC decoder"); +} + +void a2dp_ldac_decoder_suspend() { + LOG_INFO("Suspending LDAC decoder"); +} + void a2dp_ldac_decoder_configure(const uint8_t* p_codec_info) { tA2DP_LDAC_CIE cie; tA2D_STATUS status; diff --git a/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor_lhdcv5.c b/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor_lhdcv5.c new file mode 100644 index 000000000000..328d59dbc465 --- /dev/null +++ b/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor_lhdcv5.c @@ -0,0 +1,293 @@ +#include + +#include "a2d_int.h" +#include "common/bt_defs.h" +#include "common/bt_target.h" +#include "stack/a2d_api.h" +#include "stack/a2d_sbc.h" +#include "stack/a2dp_vendor_lhdcv5.h" +#include "stack/a2dp_vendor_lhdcv5_decoder.h" + +#if (defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) + +static const tA2DP_LHDCV5_CIE a2dp_lhdcv5_sink_caps = { + A2DP_LHDCV5_VENDOR_ID, + A2DP_LHDCV5_CODEC_ID, + /* Advertise all four rates. NOTE: this is a bitmask (bitwise OR), so the order + * below does not change the advertised value or any priority — 192k listed + * first here is purely cosmetic. The source (phone) picks the rate. */ + A2DP_LHDCV5_SAMPLING_FREQ_192000 | /* 192k: fast IMDCT-1920 */ + A2DP_LHDCV5_SAMPLING_FREQ_96000 | /* 96k: fast IMDCT-960 */ + A2DP_LHDCV5_SAMPLING_FREQ_48000 | + A2DP_LHDCV5_SAMPLING_FREQ_44100, + A2DP_LHDCV5_BITS_PER_SAMPLE_24 | + A2DP_LHDCV5_BITS_PER_SAMPLE_16, + A2DP_LHDCV5_CHANNEL_MODE_STEREO, + A2DP_LHDCV5_VER_1, + A2DP_LHDCV5_FRAME_LEN_5MS, + A2DP_LHDCV5_MAX_BIT_RATE_1000K, + A2DP_LHDCV5_MIN_BIT_RATE_64K, + false, + false, + false, + true, + false, + false, + false, + false, +}; + +static const tA2DP_LHDCV5_CIE a2dp_lhdcv5_default_config = { + A2DP_LHDCV5_VENDOR_ID, + A2DP_LHDCV5_CODEC_ID, + A2DP_LHDCV5_SAMPLING_FREQ_192000, + A2DP_LHDCV5_BITS_PER_SAMPLE_24, + A2DP_LHDCV5_CHANNEL_MODE_STEREO, + A2DP_LHDCV5_VER_1, + A2DP_LHDCV5_FRAME_LEN_5MS, + A2DP_LHDCV5_MAX_BIT_RATE_1000K, + A2DP_LHDCV5_MIN_BIT_RATE_64K, + false, + false, + false, + true, + false, + false, + false, + false, +}; + +static const tA2DP_DECODER_INTERFACE a2dp_decoder_interface_lhdcv5 = { + a2dp_lhdcv5_decoder_init, + a2dp_lhdcv5_decoder_cleanup, + NULL, + a2dp_lhdcv5_decoder_decode_packet_header, + a2dp_lhdcv5_decoder_decode_packet, + NULL, + NULL, + a2dp_lhdcv5_decoder_configure, +}; + +tA2D_STATUS A2DP_BuildInfoLhdcV5(uint8_t media_type, + const tA2DP_LHDCV5_CIE* p_ie, + uint8_t* p_result) { + if (p_ie == NULL || p_result == NULL) return A2D_INVALID_PARAMS; + + *p_result++ = A2DP_LHDCV5_CODEC_LEN; + *p_result++ = (media_type << 4); + *p_result++ = A2D_MEDIA_CT_NON_A2DP; + *p_result++ = (uint8_t)(p_ie->vendorId & 0xff); + *p_result++ = (uint8_t)((p_ie->vendorId >> 8) & 0xff); + *p_result++ = (uint8_t)((p_ie->vendorId >> 16) & 0xff); + *p_result++ = (uint8_t)((p_ie->vendorId >> 24) & 0xff); + *p_result++ = (uint8_t)(p_ie->codecId & 0xff); + *p_result++ = (uint8_t)((p_ie->codecId >> 8) & 0xff); + *p_result++ = p_ie->sampleRate & A2DP_LHDCV5_SAMPLING_FREQ_MASK; + *p_result++ = (p_ie->bitsPerSample & A2DP_LHDCV5_BIT_FMT_MASK) | + (p_ie->maxTargetBitrate & A2DP_LHDCV5_MAX_BIT_RATE_MASK) | + (p_ie->minTargetBitrate & A2DP_LHDCV5_MIN_BIT_RATE_MASK); + *p_result++ = (p_ie->version & A2DP_LHDCV5_VERSION_MASK) | + (p_ie->frameLenType & A2DP_LHDCV5_FRAME_LEN_MASK); + *p_result++ = (p_ie->hasFeatureAR ? A2DP_LHDCV5_FEATURE_AR : 0) | + (p_ie->hasFeatureJAS ? A2DP_LHDCV5_FEATURE_JAS : 0) | + (p_ie->hasFeatureMETA ? A2DP_LHDCV5_FEATURE_META : 0) | + (p_ie->hasFeatureLL ? A2DP_LHDCV5_FEATURE_LL : 0) | + (p_ie->hasFeatureLLESS48K ? A2DP_LHDCV5_FEATURE_LLESS48K : 0) | + (p_ie->hasFeatureLLESS24Bit ? A2DP_LHDCV5_FEATURE_LLESS24BIT : 0) | + (p_ie->hasFeatureLLESS96K ? A2DP_LHDCV5_FEATURE_LLESS96K : 0); + *p_result++ = p_ie->hasFeatureLLESSRaw ? A2DP_LHDCV5_FEATURE_LLESS_RAW : 0; + + return A2D_SUCCESS; +} + +tA2D_STATUS A2DP_ParseInfoLhdcV5(tA2DP_LHDCV5_CIE* p_ie, + const uint8_t* p_codec_info, + bool is_capability) { + if (p_ie == NULL || p_codec_info == NULL) return A2D_INVALID_PARAMS; + memset(p_ie, 0, sizeof(*p_ie)); + + uint8_t losc = *p_codec_info++; + if (losc != A2DP_LHDCV5_CODEC_LEN) return A2D_WRONG_CODEC; + + uint8_t media_type = (*p_codec_info++) >> 4; + tA2D_CODEC_TYPE codec_type = *p_codec_info++; + if (media_type != A2D_MEDIA_TYPE_AUDIO || + codec_type != A2D_MEDIA_CT_NON_A2DP) { + return A2D_WRONG_CODEC; + } + + p_ie->vendorId = (uint32_t)p_codec_info[0] | + ((uint32_t)p_codec_info[1] << 8) | + ((uint32_t)p_codec_info[2] << 16) | + ((uint32_t)p_codec_info[3] << 24); + p_codec_info += 4; + p_ie->codecId = (uint16_t)p_codec_info[0] | + ((uint16_t)p_codec_info[1] << 8); + p_codec_info += 2; + + if (p_ie->vendorId != A2DP_LHDCV5_VENDOR_ID || + p_ie->codecId != A2DP_LHDCV5_CODEC_ID) { + return A2D_WRONG_CODEC; + } + + p_ie->sampleRate = *p_codec_info++ & A2DP_LHDCV5_SAMPLING_FREQ_MASK; + uint8_t bitrate_depth = *p_codec_info++; + p_ie->bitsPerSample = bitrate_depth & A2DP_LHDCV5_BIT_FMT_MASK; + p_ie->maxTargetBitrate = bitrate_depth & A2DP_LHDCV5_MAX_BIT_RATE_MASK; + p_ie->minTargetBitrate = bitrate_depth & A2DP_LHDCV5_MIN_BIT_RATE_MASK; + + uint8_t version_frame = *p_codec_info++; + p_ie->version = version_frame & A2DP_LHDCV5_VERSION_MASK; + p_ie->frameLenType = version_frame & A2DP_LHDCV5_FRAME_LEN_MASK; + p_ie->channelMode = A2DP_LHDCV5_CHANNEL_MODE_STEREO; + + uint8_t features = *p_codec_info++ & A2DP_LHDCV5_FEATURE_MASK; + p_ie->hasFeatureAR = (features & A2DP_LHDCV5_FEATURE_AR) != 0; + p_ie->hasFeatureJAS = (features & A2DP_LHDCV5_FEATURE_JAS) != 0; + p_ie->hasFeatureMETA = (features & A2DP_LHDCV5_FEATURE_META) != 0; + p_ie->hasFeatureLL = (features & A2DP_LHDCV5_FEATURE_LL) != 0; + p_ie->hasFeatureLLESS48K = (features & A2DP_LHDCV5_FEATURE_LLESS48K) != 0; + p_ie->hasFeatureLLESS24Bit = + (features & A2DP_LHDCV5_FEATURE_LLESS24BIT) != 0; + p_ie->hasFeatureLLESS96K = (features & A2DP_LHDCV5_FEATURE_LLESS96K) != 0; + p_ie->hasFeatureLLESSRaw = + ((*p_codec_info++ & A2DP_LHDCV5_FEATURE_LLESS_RAW) != 0); + + if (is_capability) { + if (A2D_BitsSet(p_ie->sampleRate) == A2D_SET_ZERO_BIT) + return A2D_BAD_SAMP_FREQ; + if (A2D_BitsSet(p_ie->bitsPerSample) == A2D_SET_ZERO_BIT) + return A2D_FAIL; + if ((p_ie->version & A2DP_LHDCV5_VER_1) == 0) + return A2D_WRONG_CODEC; + if (A2D_BitsSet(p_ie->frameLenType) == A2D_SET_ZERO_BIT) + return A2D_FAIL; + return A2D_SUCCESS; + } + + return A2D_SUCCESS; +} + +tA2D_STATUS A2DP_IsVendorPeerSourceCodecValidLhdcV5( + const uint8_t* p_codec_info) { + tA2DP_LHDCV5_CIE cfg_cie; + tA2D_STATUS status = A2DP_ParseInfoLhdcV5(&cfg_cie, p_codec_info, true); + if (status == A2D_SUCCESS) return status; + return A2DP_ParseInfoLhdcV5(&cfg_cie, p_codec_info, false); +} + +bool A2DP_IsVendorPeerSinkCodecValidLhdcV5(const uint8_t* p_codec_info) { + tA2DP_LHDCV5_CIE cfg_cie; + return (A2DP_ParseInfoLhdcV5(&cfg_cie, p_codec_info, false) == A2D_SUCCESS) || + (A2DP_ParseInfoLhdcV5(&cfg_cie, p_codec_info, true) == A2D_SUCCESS); +} + +btav_a2dp_codec_index_t A2DP_VendorSinkCodecIndexLhdcV5( + const uint8_t* p_codec_info) { + UNUSED(p_codec_info); + return BTAV_A2DP_CODEC_INDEX_SINK_LHDCV5; +} + +btav_a2dp_codec_index_t A2DP_VendorSourceCodecIndexLhdcV5( + const uint8_t* p_codec_info) { + UNUSED(p_codec_info); + return BTAV_A2DP_CODEC_INDEX_SOURCE_LHDCV5; +} + +const char* A2DP_VendorCodecNameLhdcV5(const uint8_t* p_codec_info) { + UNUSED(p_codec_info); + return "LHDC V5"; +} + +bool A2DP_VendorCodecTypeEqualsLhdcV5(const uint8_t* p_codec_info_a, + const uint8_t* p_codec_info_b) { + tA2DP_LHDCV5_CIE cie_a; + tA2DP_LHDCV5_CIE cie_b; + + if (A2DP_ParseInfoLhdcV5(&cie_a, p_codec_info_a, true) != A2D_SUCCESS) + return false; + if (A2DP_ParseInfoLhdcV5(&cie_b, p_codec_info_b, true) != A2D_SUCCESS) + return false; + + return true; +} + +bool A2DP_VendorInitCodecConfigLhdcV5(btav_a2dp_codec_index_t codec_index, + uint8_t* p_result) { + switch (codec_index) { + case BTAV_A2DP_CODEC_INDEX_SINK_LHDCV5: + return A2DP_VendorInitCodecConfigLhdcV5Sink(p_result); + default: + break; + } + return false; +} + +bool A2DP_VendorInitCodecConfigLhdcV5Sink(uint8_t* p_codec_info) { + return A2DP_BuildInfoLhdcV5(A2D_MEDIA_TYPE_AUDIO, &a2dp_lhdcv5_sink_caps, + p_codec_info) == A2D_SUCCESS; +} + +bool A2DP_VendorBuildCodecConfigLhdcV5(uint8_t* p_src_cap, uint8_t* p_result) { + tA2DP_LHDCV5_CIE src_cap; + tA2DP_LHDCV5_CIE pref_cap = a2dp_lhdcv5_default_config; + + if (A2DP_ParseInfoLhdcV5(&src_cap, p_src_cap, true) != A2D_SUCCESS) { + return false; + } + + /* Prefer the highest hi-res rate the phone offers (192k default), falling back + * down the chain. The decoder handles every rate via the validated fast IMDCT + * (480/960/1920), the scale-factor/gain reconstruction is exact at all rates, + * and the output ring uses rate-proportional prefetch so 192k stays fed. */ + if (src_cap.sampleRate & A2DP_LHDCV5_SAMPLING_FREQ_192000) { + pref_cap.sampleRate = A2DP_LHDCV5_SAMPLING_FREQ_192000; + } else if (src_cap.sampleRate & A2DP_LHDCV5_SAMPLING_FREQ_96000) { + pref_cap.sampleRate = A2DP_LHDCV5_SAMPLING_FREQ_96000; + } else if (src_cap.sampleRate & A2DP_LHDCV5_SAMPLING_FREQ_48000) { + pref_cap.sampleRate = A2DP_LHDCV5_SAMPLING_FREQ_48000; + } else if (src_cap.sampleRate & A2DP_LHDCV5_SAMPLING_FREQ_44100) { + pref_cap.sampleRate = A2DP_LHDCV5_SAMPLING_FREQ_44100; + } else { + /* Phone offers only rates we don't support -> fail config; phone picks + * another codec (LDAC/AAC/SBC) rather than a broken LHDC link. */ + return false; + } + + if (src_cap.bitsPerSample & A2DP_LHDCV5_BITS_PER_SAMPLE_24) { + pref_cap.bitsPerSample = A2DP_LHDCV5_BITS_PER_SAMPLE_24; + } else if (src_cap.bitsPerSample & A2DP_LHDCV5_BITS_PER_SAMPLE_16) { + pref_cap.bitsPerSample = A2DP_LHDCV5_BITS_PER_SAMPLE_16; + } + + /* Echo the source's negotiated target-bitrate range. Previously pref_cap kept + * the default (min=64K / max=1000K), so the SET_CONFIGURATION response did NOT + * match the bitrate the phone requested. When the user fixes a low target + * bitrate (256 kbps sets MIN_BIT_RATE=256K=0x80), the phone saw the sink echo + * min=64K, treated it as a config mismatch, and refused to start the stream -> + * NO AUDIO at 256 kbps (higher fixed rates use a lower min and slipped through). + * The sink decodes whatever frames arrive and advertises the full 64K..1000K + * range, so echoing the phone's selected min/max is always valid. */ + pref_cap.maxTargetBitrate = src_cap.maxTargetBitrate; + pref_cap.minTargetBitrate = src_cap.minTargetBitrate; + + pref_cap.hasFeatureAR = src_cap.hasFeatureAR; + pref_cap.hasFeatureJAS = src_cap.hasFeatureJAS; + pref_cap.hasFeatureMETA = src_cap.hasFeatureMETA; + pref_cap.hasFeatureLL = src_cap.hasFeatureLL; + pref_cap.hasFeatureLLESS48K = src_cap.hasFeatureLLESS48K; + pref_cap.hasFeatureLLESS24Bit = src_cap.hasFeatureLLESS24Bit; + pref_cap.hasFeatureLLESS96K = src_cap.hasFeatureLLESS96K; + pref_cap.hasFeatureLLESSRaw = src_cap.hasFeatureLLESSRaw; + + return A2DP_BuildInfoLhdcV5(A2D_MEDIA_TYPE_AUDIO, &pref_cap, p_result) == + A2D_SUCCESS; +} + +const tA2DP_DECODER_INTERFACE* A2DP_GetVendorDecoderInterfaceLhdcV5( + const uint8_t* p_codec_info) { + if (!A2DP_IsVendorPeerSinkCodecValidLhdcV5(p_codec_info)) return NULL; + return &a2dp_decoder_interface_lhdcv5; +} + +#endif /* defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) */ diff --git a/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor_lhdcv5_decoder.c b/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor_lhdcv5_decoder.c new file mode 100644 index 000000000000..7a9098c81797 --- /dev/null +++ b/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor_lhdcv5_decoder.c @@ -0,0 +1,389 @@ +#include +#include +#include +#include + +#include "common/bt_trace.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "esp_timer.h" +#include "lhdc_dec.h" +#include "lhdc_entropy_dec.h" +#include "lhdc_imdct.h" +#include "osi/allocator.h" +#include "stack/a2dp_vendor_lhdcv5.h" +#include "stack/a2dp_vendor_lhdcv5_decoder.h" + +#if (defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) + +/* Bluedroid's LOG_INFO/APPL_TRACE_* are compiled out (CONFIG_BT_STACK_NO_LOG), + * so use ESP-IDF logging directly to verify the decode path on the monitor. */ +#define LHDCV5_TAG "LHDCV5_DEC" +#define LHDCV5_LOGI(...) ESP_LOGI(LHDCV5_TAG, __VA_ARGS__) +#define LHDCV5_LOGW(...) ESP_LOGW(LHDCV5_TAG, __VA_ARGS__) +#define LHDCV5_LOGE(...) ESP_LOGE(LHDCV5_TAG, __VA_ARGS__) + +/* Per-packet decode logging. OFF: each ~100-char ESP_LOGI blocks the decode + * task ~9 ms on the 115200-baud UART and audibly stutters playback. Set to 1 + * only for bring-up. */ +#define LHDCV5_VERBOSE 0 +/* Dump a few real over-the-air payloads as hex (for host replay/sweep). */ +#define LHDCV5_FRAMEDUMP 0 +static uint32_t s_dump_sr = 0; +static uint32_t s_fd_count = 0; /* reset per stream in configure */ + +/* LHDC frames are small (a 96k/5ms HR frame is <~1.3 KB); even a fragmented + * frame fits in 3 KB. 8 KB was wasteful static .bss that starved the heap. */ +#define LHDCV5_FRAGMENT_BUF_SIZE (3 * 1024) +/* Decode output buffer. Must match BT_A2DP_SINK_BUF_LHDCV5 in btc_a2dp_sink.c. + * 4 KB so 24-bit content emitted in 32-bit containers fits up to 192k + * (960*2*4 = 7680 B). */ +#define LHDCV5_MAX_PCM_BYTES (4 * 2000) + +/* + * The decoder workspace (~39 KB) is allocated from the MAIN internal heap via + * heap_caps_malloc(MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT), NOT osi_malloc. The + * bluedroid/osi heap fragments down to a ~15 KB largest free block during a + * call, so osi_malloc(39 KB) fails there ("cannot allocate" -> NULL decoder -> + * silent packet drop). The main internal heap keeps a large contiguous region. + * Allocated ONCE at init, reused across reconfigurations, freed on cleanup. A + * permanent static .bss array would instead starve the runtime heap. + */ +#define LHDCV5_WS_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) +typedef struct { + void *workspace; /* heap block holding the lhdc_decoder_t + rate-sized tail */ + size_t workspace_size; /* bytes currently allocated (rate-sized) */ + lhdc_decoder_t *decoder; + int fragment_size; + int fragment_count; + uint8_t fragment[LHDCV5_FRAGMENT_BUF_SIZE]; + decoded_data_callback_t decode_callback; +} tA2DP_LHDCV5_DECODER_CB; + +/* Allocated lazily on init, freed on cleanup. The control block embeds a 3 KB + * fragment-reassembly buffer; keeping it off .bss frees that RAM whenever LHDC + * is not the active codec (only one A2DP codec runs at a time). The big decoder + * workspace (cb->workspace) is separately rate-sized in decoder_configure. */ +static tA2DP_LHDCV5_DECODER_CB *s_lhdc_cb = NULL; + +bool a2dp_lhdcv5_decoder_init(decoded_data_callback_t decode_callback) { + if (!s_lhdc_cb) { + s_lhdc_cb = (tA2DP_LHDCV5_DECODER_CB *)calloc(1, sizeof(*s_lhdc_cb)); + if (!s_lhdc_cb) { + LHDCV5_LOGE("cannot allocate LHDC decoder cb"); + return false; + } + } + tA2DP_LHDCV5_DECODER_CB *cb = s_lhdc_cb; + void *ws = cb->workspace; /* keep an already-allocated block across re-init */ + size_t wss = cb->workspace_size; + memset(cb, 0, sizeof(*cb)); + cb->decode_callback = decode_callback; + /* Workspace is RATE-SIZED, so it is (re)allocated in decoder_configure once + * the negotiated sample rate is known. Keep any existing block here. */ + cb->workspace = ws; + cb->workspace_size = wss; + return true; +} + +void a2dp_lhdcv5_decoder_cleanup(void) { + /* Release the lazily-allocated fast-IMDCT tables (480 ~4.2 KB, 960 ~15 KB) + * and the entropy FAC models (~6 KB) since LHDC is going away — so none of + * the decoder's scratch lingers in DRAM while LDAC/SBC is the active codec. */ + lhdc_imdct_free_960(); + lhdc_imdct_free_480(); + lhdc_imdct_free_1920(); /* 192k tables (missed before -> leaked at 192 kHz) */ + lhdc_imdct_free_cos(); /* reference-IMDCT cosine table (~30 KB, was never freed) */ + lhdc_dec_free_window(); /* KBD window (was only freed on rate change) */ + lhdc_entropy_free(); + tA2DP_LHDCV5_DECODER_CB *cb = s_lhdc_cb; + if (!cb) return; + if (cb->workspace) { + heap_caps_free(cb->workspace); + } + free(cb); + s_lhdc_cb = NULL; +} + +void a2dp_lhdcv5_decoder_configure(const uint8_t* p_codec_info) { + tA2DP_LHDCV5_DECODER_CB *cb = s_lhdc_cb; + if (!cb) return; + tA2DP_LHDCV5_CIE cie; + if (A2DP_ParseInfoLhdcV5(&cie, p_codec_info, false) != A2D_SUCCESS) { + LHDCV5_LOGE("failed to parse LHDC V5 CIE"); + return; + } + + cb->decoder = NULL; /* re-init below */ + + lhdc_dec_config_t config = { + .sample_rate = LHDC_DEC_SR_48000, + .bit_depth = LHDC_DEC_BITDEPTH_16, + .frame_duration = LHDC_DEC_FRAME_5MS, /* LHDC V5 encoder uses 5ms */ + .channels = 2, + .max_frame_bytes = LHDCV5_FRAGMENT_BUF_SIZE, + .lossless_enable = 0, + }; + + /* Sample rate from the CIE (44.1 / 48 / 96 / 192 kHz all supported). */ + if (cie.sampleRate & A2DP_LHDCV5_SAMPLING_FREQ_192000) { + config.sample_rate = LHDC_DEC_SR_192000; + } else if (cie.sampleRate & A2DP_LHDCV5_SAMPLING_FREQ_96000) { + config.sample_rate = LHDC_DEC_SR_96000; + } else if (cie.sampleRate & A2DP_LHDCV5_SAMPLING_FREQ_48000) { + config.sample_rate = LHDC_DEC_SR_48000; + } else if (cie.sampleRate & A2DP_LHDCV5_SAMPLING_FREQ_44100) { + config.sample_rate = LHDC_DEC_SR_44100; + } + + /* Bit depth: prefer 24-bit if offered, else 16-bit. */ + if (cie.bitsPerSample & A2DP_LHDCV5_BITS_PER_SAMPLE_24) { + config.bit_depth = LHDC_DEC_BITDEPTH_24; + } else { + config.bit_depth = LHDC_DEC_BITDEPTH_16; + } + + config.channels = 2; + + /* GROW-ONLY workspace, MAX-SIZED for 192k (mdct 1920 = 32,544 B) on the FIRST + * LHDC config, then REUSED for every rate with zero realloc. + * + * Why: rate-sized grow-only can't get 192k to allocate -- the phone connects + * 48k first (10 KB ws), then the 48k->192k switch must grow to 32.5 KB, but + * streaming has fragmented the heap (largest hole seen as low as 18 KB) so the + * 32.5 KB malloc fails -> dec=NULL -> no 192k. Grabbing the full 32.5 KB on the + * first config (heap still pristine, ~70+ KB largest) and never realloc'ing + * GUARANTEES 192k fits AND keeps the heap stable across LHDC rate switches. + * The earlier max-size attempt was reverted ONLY because it had no headroom + * (starved 48k/96k to near-OOM); it is now paired with RAM reclaim (lazy/ + * smaller overlay-mixer ring + smaller audio jitter ring, ~12 KB) so 48k/96k + * keep workable free heap. The block frees when the active codec leaves LHDC. */ + { + size_t need = lhdc_dec_get_workspace_size(LHDC_DEC_SR_192000, config.frame_duration); + if (!cb->workspace || cb->workspace_size < need) { + if (cb->workspace) heap_caps_free(cb->workspace); + cb->workspace = heap_caps_malloc(need, LHDCV5_WS_CAPS); + if (!cb->workspace) { + cb->workspace_size = 0; + LHDCV5_LOGE("configure: cannot allocate %u-byte workspace (largest free %u)", + (unsigned)need, + (unsigned)heap_caps_get_largest_free_block(LHDCV5_WS_CAPS)); + return; + } + cb->workspace_size = need; + LHDCV5_LOGI("configure: workspace %u bytes (max-sized for 192k; rate=%u)", + (unsigned)need, (unsigned)config.sample_rate); + } + } + + cb->decoder = lhdc_dec_init(cb->workspace, &config); + cb->fragment_size = 0; + cb->fragment_count = 0; +#if LHDCV5_FRAMEDUMP + s_dump_sr = config.sample_rate; s_fd_count = 0; /* fresh capture per stream */ +#endif + + if (!cb->decoder) { + LHDCV5_LOGE("lhdc_dec_init failed"); + return; + } + + LHDCV5_LOGI("decoder configured: sr=%d depth=%d ch=%d dur=%d", + (int)config.sample_rate, (int)config.bit_depth, + (int)config.channels, (int)config.frame_duration); + g_lhdc_trace = 0; /* verbose per-frame decode trace off (was 6 for bring-up) */ +} + +ssize_t a2dp_lhdcv5_decoder_decode_packet_header(BT_HDR* p_buf) { + if (!p_buf) return -EINVAL; + + const size_t header_len = sizeof(struct media_packet_header) + + sizeof(struct media_payload_header); + if (p_buf->len < header_len) { + APPL_TRACE_ERROR("%s: packet too short", __func__); + return -EINVAL; + } + + tA2DP_LHDCV5_DECODER_CB *cb = s_lhdc_cb; + if (!cb) return -EINVAL; + uint8_t* src = ((uint8_t *)(p_buf + 1)) + p_buf->offset; + struct media_payload_header* payload = + (struct media_payload_header*)(src + sizeof(struct media_packet_header)); + +#if LHDCV5_VERBOSE + /* Periodic proof that A2DP media packets are reaching the LHDC decoder. */ + static uint32_t s_hdrs = 0; + if ((s_hdrs++ % 200) == 0) { + LHDCV5_LOGI("pkt hdr#%u: len=%u frag=%d first=%d last=%d frame_count=%u", + s_hdrs, (unsigned)p_buf->len, payload->is_fragmented, + payload->is_first_fragment, payload->is_last_fragment, + payload->frame_count); + } +#endif + + + if (payload->is_fragmented) { + if (payload->is_first_fragment) { + cb->fragment_size = 0; + } else if (payload->frame_count + 1 != cb->fragment_count || + (payload->frame_count == 1 && !payload->is_last_fragment)) { + cb->fragment_count = 0; + cb->fragment_size = 0; + return -EINVAL; + } + cb->fragment_count = payload->frame_count; + } else { + /* Non-fragmented packet: decode_packet parses the self-delimiting LHDC + * frame stream directly (it loops on each frame's [u16 len] header and + * does NOT use frame_count), so any count is fine -- do NOT reject 0. + * The LHDC payload's leading byte is a seq/marker byte, not an SBC frame + * counter, so this 4-bit field is meaningless here. At 256 kbps the phone + * packs several small frames per packet and the field reads 0; the old + * `frame_count==0 -> EINVAL` rejected EVERY such packet -> NO AUDIO at + * 256k (higher bitrates happened to read non-zero and slipped through). + * If a payload truly has no decodable frame, decode_packet returns false + * (frames==0) and the packet is dropped there. */ + cb->fragment_count = 0; + cb->fragment_size = 0; + } + + p_buf->offset += header_len; + p_buf->len -= header_len; + return 0; +} + +bool a2dp_lhdcv5_decoder_decode_packet(BT_HDR* p_buf, unsigned char* buf, + size_t buf_len) { + tA2DP_LHDCV5_DECODER_CB *cb = s_lhdc_cb; + if (!cb) return false; +#if LHDCV5_VERBOSE + { + static uint32_t s_enter = 0; + if ((s_enter++ % 200) == 0) { + LHDCV5_LOGI("decode_packet entered #%u: p_buf=%p buf=%p dec=%p cb=%p buf_len=%u", + s_enter, (void*)p_buf, (void*)buf, (void*)cb->decoder, + (void*)cb->decode_callback, (unsigned)buf_len); + } + } +#endif + if (!p_buf || !buf || !cb->decoder || !cb->decode_callback) { + static uint32_t s_e = 0; + if ((s_e++ % 200) == 0) LHDCV5_LOGW("decode_packet null guard (dec=%p)", (void*)cb->decoder); + return false; + } + if (buf_len < LHDCV5_MAX_PCM_BYTES) { + static uint32_t s_b = 0; + if ((s_b++ % 200) == 0) LHDCV5_LOGW("decode buf too small: %u < %u", + (unsigned)buf_len, (unsigned)LHDCV5_MAX_PCM_BYTES); + return false; + } + + uint8_t* src = ((uint8_t *)(p_buf + 1)) + p_buf->offset; + size_t src_size = p_buf->len; + +#if LHDCV5_FRAMEDUMP + /* Capture a few REAL over-the-air payloads as hex so the exact frames the + * phone sends can be replayed/swept on host. Dumps packets 40..47 (past + * startup) once per stream, tagged FDUMP for easy grep. */ + { + uint32_t i = s_fd_count++; + if (i >= 60 && i < 140) { /* one long consecutive burst (80 frames) */ + char line[1600]; int p = 0; + p += snprintf(line+p, sizeof(line)-p, "FDUMP#%u len=%u sr=%u:", i, + (unsigned)src_size, (unsigned)s_dump_sr); + for (size_t b = 0; b < src_size && p < (int)sizeof(line)-4; b++) + p += snprintf(line+p, sizeof(line)-p, "%02x", src[b]); + LHDCV5_LOGI("%s", line); + } + } +#endif + + if (cb->fragment_count > 0) { + if (src_size > (sizeof(cb->fragment) - (size_t)cb->fragment_size)) { + cb->fragment_count = 0; + cb->fragment_size = 0; + APPL_TRACE_ERROR("%s: fragmented LHDC frame too large", __func__); + return false; + } + + memcpy(cb->fragment + cb->fragment_size, src, src_size); + cb->fragment_size += (int)src_size; + + if (cb->fragment_count > 1) return true; + + src = cb->fragment; + src_size = (size_t)cb->fragment_size; + cb->fragment_count = 0; + cb->fragment_size = 0; + } + + /* + * One A2DP payload is a concatenation of LHDC V5 frames, each + * [u16 LE header][payload]. Decode them in a loop until the input is + * exhausted, accumulating the PCM and emitting it to the sink callback. + */ + /* + * Decode each LHDC frame in the payload and emit its PCM to the sink right + * away (no large accumulation buffer — DRAM on ESP32 is scarce). `buf` is + * the caller-provided PCM scratch, big enough for one frame (1920 samples). + */ + /* + * The over-the-air LHDC V5 payload begins with ONE leading byte before the + * frame stream (an LHDC seq/marker byte not present in the raw encoder + * vectors the parser was validated against). The real frames start at + * offset 1 as [u16 LE length][payload]; verified on-device by descrambling + * each candidate start and checking the leading flag bit == 0 (offset 1, LE + * was the unique hit: hdr=0x047d len=250 flag=0). Skip that byte. + */ + size_t off = (src_size >= 1) ? 1 : 0; + int frames = 0; + size_t pcm_emitted = 0; + uint8_t out_channels = 2, out_depth = 16; + (void)out_channels; (void)out_depth; + + while (off + 2 <= src_size) { + size_t consumed = 0; + uint32_t generated = 0; + lhdc_dec_frame_info_t info; + lhdc_dec_ret_t ret = lhdc_dec_decode_frame(cb->decoder, src + off, + src_size - off, buf, 1920, + &consumed, &generated, &info); + if (ret != LHDC_DEC_OK || consumed == 0) { + static uint32_t s_errs = 0; + if (frames == 0 && (s_errs++ % 100) == 0) { + LHDCV5_LOGW("decode ret=%d off=%u/%u (err#%u)", + ret, (unsigned)off, (unsigned)src_size, s_errs); + } + break; + } + out_channels = info.channels; + out_depth = info.bit_depth; + size_t pcm_bytes = (size_t)generated * info.channels * (info.bit_depth / 8); + cb->decode_callback((uint8_t*)buf, pcm_bytes); + pcm_emitted += pcm_bytes; + off += consumed; + frames++; + } + + if (frames == 0) { + return false; + } + +#if LHDCV5_VERBOSE + /* Periodic stats so we can confirm audio is flowing on the serial monitor. + * OFF by default: the ESP_LOGI blocks the decode task ~9 ms on the UART and + * stutters audio. The decode-time profiling timers live behind this too. */ + { + static uint32_t s_packets = 0, s_frames = 0, s_pcm = 0; + s_packets++; s_frames += frames; s_pcm += pcm_emitted; + if ((s_packets % 100) == 1) { + LHDCV5_LOGI("RX pkt#%u frames=%d pcm=%u (ch=%d depth=%d) totals: %u frames, %u KiB", + s_packets, frames, (unsigned)pcm_emitted, + out_channels, out_depth, s_frames, s_pcm / 1024); + } + } +#endif + return true; +} + +#endif /* defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) */ diff --git a/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor_mihc.c b/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor_mihc.c new file mode 100644 index 000000000000..23160348c633 --- /dev/null +++ b/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor_mihc.c @@ -0,0 +1,22 @@ +/** + * SPDX-FileCopyrightText: 2025 The Android Open Source Project + * + * SPDX-License-Identifier: Apache-2.0 + * + * a2dp_vendor_mihc.c + */ + +#include +#include "a2d_int.h" +#include "common/bt_defs.h" +#include "common/bt_target.h" +#include "stack/a2d_api.h" +#include "stack/a2d_sbc.h" +#include "stack/a2dp_vendor_mihc.h" +#include "stack/a2dp_vendor_mihc_decoder.h" + +#if (defined(MIHC_DEC_INCLUDED) && MIHC_DEC_INCLUDED == TRUE) + +// MIHC Functions + +#endif /* defined(MIHC_DEC_INCLUDED) && MIHC_DEC_INCLUDED == TRUE) */ \ No newline at end of file diff --git a/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor_mihc_decoder.c b/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor_mihc_decoder.c new file mode 100644 index 000000000000..086fec717ad9 --- /dev/null +++ b/components/bt/host/bluedroid/stack/a2dp/a2dp_vendor_mihc_decoder.c @@ -0,0 +1,18 @@ +/** + * SPDX-FileCopyrightText: 2025 The Android Open Source Project + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "common/bt_trace.h" +#include "stack/a2dp_vendor_mihc.h" +#include "stack/a2dp_vendor_mihc_constants.h" +#include "stack/a2dp_vendor_mihc_decoder.h" +// #include "mihcdec.h" // perhap + + +#if (defined(MIHC_DEC_INCLUDED) && MIHC_DEC_INCLUDED == TRUE) + +// MIHC Decoder Functions + +#endif /* defined(MIHC_DEC_INCLUDED) && MIHC_DEC_INCLUDED == TRUE) */ diff --git a/components/bt/host/bluedroid/stack/a2dp/include/bt_av.h b/components/bt/host/bluedroid/stack/a2dp/include/bt_av.h index d5c20811b15f..0e13b3898994 100644 --- a/components/bt/host/bluedroid/stack/a2dp/include/bt_av.h +++ b/components/bt/host/bluedroid/stack/a2dp/include/bt_av.h @@ -36,6 +36,12 @@ typedef enum { #if (defined(LC3PLUS_DEC_INCLUDED) && LC3PLUS_DEC_INCLUDED == TRUE) BTAV_A2DP_CODEC_INDEX_SOURCE_LC3PLUS, #endif /* LC3PLUS_DEC_INCLUDED */ +#if (defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) + BTAV_A2DP_CODEC_INDEX_SOURCE_LHDCV5, +#endif /* LHDCV5_DEC_INCLUDED */ +#if (defined(MIHC_DEC_INCLUDED) && MIHC_DEC_INCLUDED == TRUE) + BTAV_A2DP_CODEC_INDEX_SOURCE_MIHC, +#endif /* MIHC_DEC_INCLUDED */ BTAV_A2DP_CODEC_INDEX_SOURCE_MAX, @@ -60,6 +66,12 @@ typedef enum { #if (defined(LC3PLUS_DEC_INCLUDED) && LC3PLUS_DEC_INCLUDED == TRUE) BTAV_A2DP_CODEC_INDEX_SINK_LC3PLUS, #endif /* LC3PLUS_DEC_INCLUDED */ +#if (defined(LHDCV5_DEC_INCLUDED) && LHDCV5_DEC_INCLUDED == TRUE) + BTAV_A2DP_CODEC_INDEX_SINK_LHDCV5, +#endif /* LHDCV5_DEC_INCLUDED */ +#if (defined(MIHC_DEC_INCLUDED) && MIHC_DEC_INCLUDED == TRUE) + BTAV_A2DP_CODEC_INDEX_SINK_MIHC, +#endif /* MIHC_DEC_INCLUDED */ BTAV_A2DP_CODEC_INDEX_SINK_MAX, diff --git a/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor.h b/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor.h index 60e613f797b5..9e7d9132f376 100644 --- a/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor.h +++ b/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor.h @@ -24,7 +24,7 @@ #include #include #include - +#include "a2dp_codec_api.h" #include "bt_av.h" /***************************************************************************** diff --git a/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_ldac.h b/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_ldac.h index 2f31e226ca25..f399fe71154a 100644 --- a/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_ldac.h +++ b/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_ldac.h @@ -2,6 +2,8 @@ * SPDX-FileCopyrightText: 2016 The Android Open Source Project * * SPDX-License-Identifier: Apache-2.0 + * + * a2dp_vendor_ldac.h */ // diff --git a/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_ldac_decoder.h b/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_ldac_decoder.h index d508105306ad..4bceba093dc5 100644 --- a/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_ldac_decoder.h +++ b/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_ldac_decoder.h @@ -1,5 +1,9 @@ -/* +/** + * SPDX-FileCopyrightText: 2016 The Android Open Source Project + * * SPDX-License-Identifier: Apache-2.0 + * + * a2dp_vendor_ldac_decoder.h */ // @@ -34,7 +38,13 @@ extern "C" ******************************************************************************/ bool a2dp_ldac_decoder_init(decoded_data_callback_t decode_callback); - +/****************************************************************************** +** +** Function a2dp_ldac_decoder_cleanup +** +** Description Cleanup the A2DP LDAC decoder |need ldacBT.h +** +******************************************************************************/ void a2dp_ldac_decoder_cleanup(); /****************************************************************************** @@ -62,6 +72,24 @@ ssize_t a2dp_ldac_decoder_decode_packet_header(BT_HDR* p_data); ******************************************************************************/ bool a2dp_ldac_decoder_decode_packet(BT_HDR* p_buf, unsigned char* buf, size_t buf_len); +/****************************************************************************** +** +** Function a2dp_ldac_decoder_start +** +** Description Start the A2DP LDAC decoder. +** +******************************************************************************/ +void a2dp_ldac_decoder_start(); + +/****************************************************************************** +** +** Function a2dp_ldac_decoder_suspend +** +** Description Suspend the A2DP LDAC decoder. +** +******************************************************************************/ +void a2dp_ldac_decoder_suspend(); + /****************************************************************************** ** ** Function a2dp_ldac_decoder_configure diff --git a/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_lhdcv5.h b/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_lhdcv5.h new file mode 100644 index 000000000000..c1c40d5c3ada --- /dev/null +++ b/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_lhdcv5.h @@ -0,0 +1,60 @@ +#ifndef A2DP_VENDOR_LHDCV5_H +#define A2DP_VENDOR_LHDCV5_H + +#include +#include + +#include "a2dp_codec_api.h" +#include "a2dp_vendor_lhdcv5_constants.h" +#include "bt_av.h" +#include "stack/a2d_api.h" + +typedef struct { + uint32_t vendorId; + uint16_t codecId; + uint8_t sampleRate; + uint8_t bitsPerSample; + uint8_t channelMode; + uint8_t version; + uint8_t frameLenType; + uint8_t maxTargetBitrate; + uint8_t minTargetBitrate; + bool hasFeatureAR; + bool hasFeatureJAS; + bool hasFeatureMETA; + bool hasFeatureLL; + bool hasFeatureLLESS48K; + bool hasFeatureLLESS24Bit; + bool hasFeatureLLESS96K; + bool hasFeatureLLESSRaw; +} tA2DP_LHDCV5_CIE; + +#ifdef __cplusplus +extern "C" { +#endif + +tA2D_STATUS A2DP_BuildInfoLhdcV5(uint8_t media_type, + const tA2DP_LHDCV5_CIE* p_ie, + uint8_t* p_result); +tA2D_STATUS A2DP_ParseInfoLhdcV5(tA2DP_LHDCV5_CIE* p_ie, + const uint8_t* p_codec_info, + bool is_capability); +tA2D_STATUS A2DP_IsVendorPeerSourceCodecValidLhdcV5(const uint8_t* p_codec_info); +bool A2DP_IsVendorPeerSinkCodecValidLhdcV5(const uint8_t* p_codec_info); +btav_a2dp_codec_index_t A2DP_VendorSinkCodecIndexLhdcV5(const uint8_t* p_codec_info); +btav_a2dp_codec_index_t A2DP_VendorSourceCodecIndexLhdcV5(const uint8_t* p_codec_info); +const char* A2DP_VendorCodecNameLhdcV5(const uint8_t* p_codec_info); +bool A2DP_VendorCodecTypeEqualsLhdcV5(const uint8_t* p_codec_info_a, + const uint8_t* p_codec_info_b); +bool A2DP_VendorInitCodecConfigLhdcV5(btav_a2dp_codec_index_t codec_index, + uint8_t* p_result); +bool A2DP_VendorInitCodecConfigLhdcV5Sink(uint8_t* p_codec_info); +bool A2DP_VendorBuildCodecConfigLhdcV5(uint8_t* p_src_cap, uint8_t* p_result); +const tA2DP_DECODER_INTERFACE* A2DP_GetVendorDecoderInterfaceLhdcV5( + const uint8_t* p_codec_info); + +#ifdef __cplusplus +} +#endif + +#endif // A2DP_VENDOR_LHDCV5_H diff --git a/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_lhdcv5_constants.h b/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_lhdcv5_constants.h new file mode 100644 index 000000000000..9b41c04251fb --- /dev/null +++ b/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_lhdcv5_constants.h @@ -0,0 +1,64 @@ +#ifndef A2DP_VENDOR_LHDCV5_CONSTANTS_H +#define A2DP_VENDOR_LHDCV5_CONSTANTS_H + +/* Android BluetoothCodecType.CODEC_ID_LHDCV5 = 0x0000004c35053aff. + * A2DP vendor codec info carries the company ID and vendor codec ID after the + * NON_A2DP media codec type byte, so that maps to company 0x053a and codec + * 0x4c35. + */ +#define A2DP_LHDCV5_VENDOR_ID 0x0000053a +#define A2DP_LHDCV5_CODEC_ID 0x4c35 + +#define A2DP_LHDCV5_CODEC_LEN 13 + +#define A2DP_LHDCV5_SAMPLING_FREQ_MASK 0x35 +#define A2DP_LHDCV5_SAMPLING_FREQ_192000 0x01 +#define A2DP_LHDCV5_SAMPLING_FREQ_96000 0x04 +#define A2DP_LHDCV5_SAMPLING_FREQ_48000 0x10 +#define A2DP_LHDCV5_SAMPLING_FREQ_44100 0x20 + +#define A2DP_LHDCV5_CHANNEL_MODE_MONO 0x01 +#define A2DP_LHDCV5_CHANNEL_MODE_STEREO 0x02 + +#define A2DP_LHDCV5_BIT_FMT_MASK 0x07 +#define A2DP_LHDCV5_BIT_FMT_32 0x01 +#define A2DP_LHDCV5_BIT_FMT_24 0x02 +#define A2DP_LHDCV5_BIT_FMT_16 0x04 + +#define A2DP_LHDCV5_MAX_BIT_RATE_MASK 0x30 +#define A2DP_LHDCV5_MAX_BIT_RATE_900K 0x30 +#define A2DP_LHDCV5_MAX_BIT_RATE_500K 0x20 +#define A2DP_LHDCV5_MAX_BIT_RATE_400K 0x10 +#define A2DP_LHDCV5_MAX_BIT_RATE_1000K 0x00 + +#define A2DP_LHDCV5_MIN_BIT_RATE_MASK 0xc0 +#define A2DP_LHDCV5_MIN_BIT_RATE_400K 0xc0 +#define A2DP_LHDCV5_MIN_BIT_RATE_256K 0x80 +#define A2DP_LHDCV5_MIN_BIT_RATE_160K 0x40 +#define A2DP_LHDCV5_MIN_BIT_RATE_64K 0x00 + +#define A2DP_LHDCV5_VERSION_MASK 0x0f +#define A2DP_LHDCV5_VER_1 0x01 + +#define A2DP_LHDCV5_FRAME_LEN_MASK 0x10 +#define A2DP_LHDCV5_FRAME_LEN_5MS 0x10 + +#define A2DP_LHDCV5_FEATURE_MASK 0xc7 +#define A2DP_LHDCV5_FEATURE_LLESS48K 0x80 +#define A2DP_LHDCV5_FEATURE_LL 0x40 +#define A2DP_LHDCV5_FEATURE_LLESS24BIT 0x20 +#define A2DP_LHDCV5_FEATURE_LLESS96K 0x10 +#define A2DP_LHDCV5_FEATURE_META 0x04 +#define A2DP_LHDCV5_FEATURE_JAS 0x02 +#define A2DP_LHDCV5_FEATURE_AR 0x01 + +#define A2DP_LHDCV5_FEATURE_LLESS_RAW 0x80 + +/* Backward-compatible aliases for the experimental decoder wrapper. */ +#define A2DP_LHDCV5_BITS_PER_SAMPLE_32 A2DP_LHDCV5_BIT_FMT_32 +#define A2DP_LHDCV5_BITS_PER_SAMPLE_24 A2DP_LHDCV5_BIT_FMT_24 +#define A2DP_LHDCV5_BITS_PER_SAMPLE_16 A2DP_LHDCV5_BIT_FMT_16 +#define A2DP_LHDCV5_FRAME_5MS A2DP_LHDCV5_FRAME_LEN_5MS +#define A2DP_LHDCV5_FRAME_10MS 0x00 + +#endif // A2DP_VENDOR_LHDCV5_CONSTANTS_H diff --git a/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_lhdcv5_decoder.h b/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_lhdcv5_decoder.h new file mode 100644 index 000000000000..ec942b1a6059 --- /dev/null +++ b/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_lhdcv5_decoder.h @@ -0,0 +1,23 @@ +#ifndef A2DP_VENDOR_LHDCV5_DECODER_H +#define A2DP_VENDOR_LHDCV5_DECODER_H + +#include "a2dp_codec_api.h" +#include "a2dp_decoder.h" +#include "stack/bt_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +bool a2dp_lhdcv5_decoder_init(decoded_data_callback_t decode_callback); +void a2dp_lhdcv5_decoder_cleanup(void); +void a2dp_lhdcv5_decoder_configure(const uint8_t* p_codec_info); +ssize_t a2dp_lhdcv5_decoder_decode_packet_header(BT_HDR* p_data); +bool a2dp_lhdcv5_decoder_decode_packet(BT_HDR* p_buf, unsigned char* buf, + size_t buf_len); + +#ifdef __cplusplus +} +#endif + +#endif // A2DP_VENDOR_LHDCV5_DECODER_H diff --git a/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_mihc.h b/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_mihc.h new file mode 100644 index 000000000000..53f8514e0986 --- /dev/null +++ b/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_mihc.h @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: 2025 The Android Open Source Project + * + * SPDX-License-Identifier: Apache-2.0 + * + * a2dp_vendor_ldac.h + */ + +// +// A2DP Codec API for MIHC +// + +#ifndef A2DP_VENDOR_MIHC_H +#define A2DP_VENDOR_MIHC_H + +#include "a2d_api.h" +#include "a2dp_codec_api.h" +#include "a2dp_vendor_mihc_constants.h" +#include "a2dp_shim.h" +#include "avdt_api.h" +#include "bt_av.h" + +/***************************************************************************** +** Type Definitions +*****************************************************************************/ +// data type for the MIHC Codec Information Element */ +// NOTE: bits_per_sample is needed only for MIHC encoder initialization. +typedef struct { + uint32_t vendorId; + uint16_t codecId; /* Codec ID for MIHC */ + uint8_t sampleRate; /* Sampling Frequency */ + uint8_t channelMode; /* STEREO/DUAL/MONO */ + btav_a2dp_codec_bits_per_sample_t bits_per_sample; +} tA2DP_MIHC_CIE; + +/***************************************************************************** +** External Function Declarations +*****************************************************************************/ +#ifdef __cplusplus +extern "C" +{ +#endif + +// MIHC Functions + +#ifdef __cplusplus +} +#endif + +#endif // A2DP_VENDOR_MIHC_H diff --git a/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_mihc_constants.h b/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_mihc_constants.h new file mode 100644 index 000000000000..d1a56342f240 --- /dev/null +++ b/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_mihc_constants.h @@ -0,0 +1,39 @@ +#ifndef A2DP_VENDOR_MIHC_CONSTANTS_H +#define A2DP_VENDOR_MIHC_CONSTANTS_H + +// MIHC codec specific settings +#define A2DP_MIHC_CODEC_LEN 13 // perhap +// [Octet 0-3] Vendor ID +#define A2DP_MIHC_VENDOR_ID 0x0000038F +// [Octet 4-5] Vendor Specific Codec ID +#define A2DP_MIHC_CODEC_ID 0x4D31 +// [Octet 6] Version +#define A2DP_MIHC_VERSION_1 1 +// [Octet 7] Sampling Frequency +#define A2DP_MIHC_SAMPLING_FREQ_RTA_0x80 0x10 // perhap +#define A2DP_MIHC_SAMPLING_FREQ_16000 0x40 +#define A2DP_MIHC_SAMPLING_FREQ_24000 0x20 +#define A2DP_MIHC_SAMPLING_FREQ_RTA_0x10 0x10 // perhap +#define A2DP_MIHC_SAMPLING_FREQ_44100 0x08 +#define A2DP_MIHC_SAMPLING_FREQ_48000 0x04 +#define A2DP_MIHC_SAMPLING_FREQ_88200 0x02 // perhap +#define A2DP_MIHC_SAMPLING_FREQ_96000 0x01 // perhap +// [Octet 8], [Bits 0] Is CBR +#define A2DP_MIHC_CBR_ON 0x01 +// [Octet 8], [Bits 2-3] Frame Length +#define A2DP_MIHC_DEFAULT_FRLEN 0x04 +#define A2DP_MIHC_5MS_FRLEN 0x08 +// [Octet 8], [Bits 5-7] Bit dipth +#define A2DP_MIHC_BIT_FMT_32 0x20 +#define A2DP_MIHC_BIT_FMT_24 0x40 +#define A2DP_MIHC_BIT_FMT_16 0x80 +// [Octet 9], [Bits 0-1] Channle Mode +#define A2DP_MIHC_CHANNEL_MODE_STEREO 0x01 +#define A2DP_MIHC_CHANNEL_MODE_MONO 0x02 +// [Octet 9], [Bits 4-7] Audio Mode +// [Octet 10] Bit Rate +#define A2DP_MIHC_MIN_BIT_RATE_32K 0x10 +#define A2DP_MIHC_MIN_BIT_RATE_64K 0x20 +#define A2DP_MIHC_MIN_BIT_RATE_96K 0x40 +#define A2DP_MIHC_MIN_BIT_RATE_128K 0x80 +#endif \ No newline at end of file diff --git a/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_mihc_decoder.h b/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_mihc_decoder.h new file mode 100644 index 000000000000..bf0b6b5cf853 --- /dev/null +++ b/components/bt/host/bluedroid/stack/include/stack/a2dp_vendor_mihc_decoder.h @@ -0,0 +1,34 @@ +/** + * SPDX-FileCopyrightText: 2025 The Android Open Source Project + * + * SPDX-License-Identifier: Apache-2.0 + * + * a2dp_vendor_ldac_decoder.h + */ + +// +// Interface to the A2DP MIHC Decoder +// + +#ifndef A2DP_VENDOR_MIHC_DECODER_H +#define A2DP_VENDOR_MIHC_DECODER_H + +#include "a2dp_codec_api.h" +#include "a2dp_decoder.h" +#include "stack/bt_types.h" + +/***************************************************************************** +** External Function Declarations +*****************************************************************************/ +#ifdef __cplusplus +extern "C" +{ +#endif + +// MIHC Decoder Functions + +#ifdef __cplusplus +} +#endif + +#endif // A2DP_VENDOR_LDAC_DECODER_H