From 9f6cf1ac3ca53e69e5374751e34212c0cabfe686 Mon Sep 17 00:00:00 2001 From: hejiaxin Date: Wed, 17 Jun 2026 14:45:35 +0800 Subject: [PATCH 1/2] feat(bluedroid): Update LC3-SWB support in bluedroid - Update LC3 in btc and bta, update AT - Update LC3 in SCO frame transfer and examples - improve lc3 transfer speed - Fix some encoding and decoding bugs - Rename the functions and macros to fit both msbc and lc3 - Add config dependency - Add migration guides for bluetooth-classic - change the statement of "Wide Band Speech" to "Wideband Speech" to fit HFPv1.10 --- components/bt/host/bluedroid/Kconfig.in | 12 +- .../bt/host/bluedroid/api/esp_hf_ag_api.c | 26 + .../bluedroid/api/include/api/esp_hf_ag_api.h | 38 +- .../api/include/api/esp_hf_client_api.h | 6 +- .../bluedroid/api/include/api/esp_hf_defs.h | 43 +- .../bt/host/bluedroid/bta/hf_ag/bta_ag_act.c | 3 +- .../bt/host/bluedroid/bta/hf_ag/bta_ag_api.c | 2 +- .../bt/host/bluedroid/bta/hf_ag/bta_ag_cmd.c | 46 +- .../bt/host/bluedroid/bta/hf_ag/bta_ag_main.c | 5 + .../bt/host/bluedroid/bta/hf_ag/bta_ag_sco.c | 122 +++- .../bt/host/bluedroid/bta/hf_ag/bta_ag_sdp.c | 10 + .../bluedroid/bta/hf_ag/include/bta_ag_int.h | 5 +- .../bta/hf_client/bta_hf_client_api.c | 3 +- .../bta/hf_client/bta_hf_client_at.c | 58 +- .../bta/hf_client/bta_hf_client_main.c | 15 + .../bta/hf_client/bta_hf_client_sco.c | 76 +- .../bta/hf_client/bta_hf_client_sdp.c | 11 + .../bta/hf_client/include/bta_hf_client_int.h | 11 + .../bluedroid/bta/include/bta/bta_ag_api.h | 5 +- .../bta/include/bta/bta_hf_client_api.h | 7 +- .../bluedroid/bta/include/bta/bta_hfp_defs.h | 2 + .../btc/profile/std/hf_ag/bta_ag_co.c | 2 +- .../btc/profile/std/hf_ag/btc_hf_ag.c | 108 ++- .../profile/std/hf_client/bta_hf_client_co.c | 12 +- .../btc/profile/std/hf_client/btc_hf_client.c | 21 +- .../btc/profile/std/include/btc_hf_ag.h | 11 +- .../include/common/bluedroid_user_config.h | 9 +- .../common/include/common/bt_target.h | 14 + .../bluedroid/stack/include/stack/btm_api.h | 1 + .../bluedroid/stack/include/stack/sdpdefs.h | 3 +- docs/conf_common.py | 1 + .../release-6.x/6.2/bluetooth-classic.rst | 65 ++ .../release-6.x/6.2/index.rst | 1 + .../release-6.x/6.2/bluetooth-classic.rst | 66 ++ .../release-6.x/6.2/index.rst | 1 + .../bluedroid/classic_bt/hfp_ag/README.md | 94 ++- .../classic_bt/hfp_ag/main/CMakeLists.txt | 2 +- .../classic_bt/hfp_ag/main/app_hf_msg_set.c | 3 + .../classic_bt/hfp_ag/main/bt_app_hf.c | 653 +++++++++++++++++- .../classic_bt/hfp_ag/main/bt_app_hf.h | 11 +- .../classic_bt/hfp_ag/main/idf_component.yml | 17 + .../bluedroid/classic_bt/hfp_hf/README.md | 12 +- .../classic_bt/hfp_hf/main/bt_app_hf.c | 62 +- 43 files changed, 1490 insertions(+), 185 deletions(-) create mode 100644 docs/en/migration-guides/release-6.x/6.2/bluetooth-classic.rst create mode 100644 docs/zh_CN/migration-guides/release-6.x/6.2/bluetooth-classic.rst create mode 100644 examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/idf_component.yml diff --git a/components/bt/host/bluedroid/Kconfig.in b/components/bt/host/bluedroid/Kconfig.in index 20aad2a51c0..c4857158cc7 100644 --- a/components/bt/host/bluedroid/Kconfig.in +++ b/components/bt/host/bluedroid/Kconfig.in @@ -238,13 +238,21 @@ config BT_HFP_USE_EXTERNAL_CODEC for new design. config BT_HFP_WBS_ENABLE - bool "Wide Band Speech" + bool "Wideband Speech" depends on BT_HFP_ENABLE && BT_HFP_AUDIO_DATA_PATH_HCI default y help - This enables Wide Band Speech. Should disable it when SCO data path is PCM. + This enables Wideband Speech. Should disable it when SCO data path is PCM. Otherwise there will be no data transmitted via GPIOs. +config BT_HFP_LC3_ENABLE + bool "Super Wideband Speech (LC3-SWB)" + depends on BT_HFP_ENABLE && BT_HFP_WBS_ENABLE && BT_HFP_USE_EXTERNAL_CODEC + default n + help + Enable HFP 1.9 LC3-SWB codec negotiation support. + Requires Wideband Speech (codec negotiation) to be enabled. + menuconfig BT_HID_ENABLED bool "Classic BT HID" depends on BT_CLASSIC_ENABLED diff --git a/components/bt/host/bluedroid/api/esp_hf_ag_api.c b/components/bt/host/bluedroid/api/esp_hf_ag_api.c index ff9b4f20581..2bdd139294f 100644 --- a/components/bt/host/bluedroid/api/esp_hf_ag_api.c +++ b/components/bt/host/bluedroid/api/esp_hf_ag_api.c @@ -160,6 +160,32 @@ esp_err_t esp_hf_ag_audio_disconnect(esp_bd_addr_t remote_addr) return (status == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; } +esp_err_t esp_hf_ag_set_codec(esp_bd_addr_t remote_bda, esp_hf_codec_mode_t mode) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + if (remote_bda == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (mode > ESP_HF_CODEC_LC3) { + return ESP_ERR_INVALID_ARG; + } + + btc_msg_t msg; + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_HF; + msg.act = BTC_HF_SET_CODEC_EVT; + + btc_hf_args_t arg; + memset(&arg, 0, sizeof(btc_hf_args_t)); + memcpy(&arg.set_codec.remote_addr, remote_bda, sizeof(esp_bd_addr_t)); + arg.set_codec.mode = mode; + + bt_status_t status = btc_transfer_context(&msg, &arg, sizeof(btc_hf_args_t), NULL, NULL); + return (status == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + esp_err_t esp_hf_ag_vra_control(esp_bd_addr_t remote_addr, esp_hf_vr_state_t value) { if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { diff --git a/components/bt/host/bluedroid/api/include/api/esp_hf_ag_api.h b/components/bt/host/bluedroid/api/include/api/esp_hf_ag_api.h index 65020dfb937..ffcb18394d5 100644 --- a/components/bt/host/bluedroid/api/include/api/esp_hf_ag_api.h +++ b/components/bt/host/bluedroid/api/include/api/esp_hf_ag_api.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -61,10 +61,11 @@ typedef enum ESP_HF_ATA_RESPONSE_EVT, /*!< Answer an Incoming Call */ ESP_HF_CHUP_RESPONSE_EVT, /*!< Reject an Incoming Call */ ESP_HF_DIAL_EVT, /*!< Originate an outgoing call with specific number or dial the last number */ - ESP_HF_WBS_RESPONSE_EVT, /*!< Codec Status */ + ESP_HF_WBS_RESPONSE_EVT, /*!< Result of esp_hf_ag_set_codec() */ ESP_HF_BCS_RESPONSE_EVT, /*!< Final Codec Choice */ ESP_HF_PKT_STAT_NUMS_GET_EVT, /*!< Request number of packet different status */ ESP_HF_PROF_STATE_EVT, /*!< Indicate HF init or deinit complete */ + ESP_HF_BAC_RESPONSE_EVT, /*!< Peer codec capabilities from AT+BAC */ } esp_hf_cb_event_t; /// Dial type of ESP_HF_DIAL_EVT @@ -210,15 +211,24 @@ typedef union */ struct hf_wbs_rep_param { esp_bd_addr_t remote_addr; /*!< Remote bluetooth device address */ - esp_hf_wbs_config_t codec; /*!< codec mode CVSD or mSBC */ + esp_hf_codec_mode_t codec; /*!< Requested codec CVSD, mSBC or LC3 */ + esp_bt_status_t status; /*!< ESP_BT_STATUS_SUCCESS or FAIL */ } wbs_rep; /*!< AG callback param of ESP_HF_WBS_RESPONSE_EVT */ + /** + * @brief ESP_HF_BAC_RESPONSE_EVT + */ + struct hf_bac_rep_param { + esp_bd_addr_t remote_addr; /*!< Remote bluetooth device address */ + uint16_t peer_codecs; /*!< Bitmap: ESP_HF_CODEC_CAP_CVSD | MSBC | LC3 */ + } bac_rep; /*!< AG callback param of ESP_HF_BAC_RESPONSE_EVT */ + /** * @brief ESP_HF_BCS_RESPONSE_EVT */ struct hf_bcs_rep_param { esp_bd_addr_t remote_addr; /*!< Remote bluetooth device address */ - esp_hf_wbs_config_t mode; /*!< codec mode CVSD or mSBC */ + esp_hf_codec_mode_t mode; /*!< Final negotiated codec mode CVSD, mSBC or LC3 */ } bcs_rep; /*!< AG callback param of ESP_HF_BCS_RESPONSE_EVT */ /** @@ -371,6 +381,26 @@ esp_err_t esp_hf_ag_audio_connect(esp_bd_addr_t remote_bda); */ esp_err_t esp_hf_ag_audio_disconnect(esp_bd_addr_t remote_bda); +/** + * + * @brief Set preferred codec for subsequent SCO connections with a remote HF. + * Should be called after receiving ESP_HF_BAC_RESPONSE_EVT to know peer capabilities. + * Result is reported via ESP_HF_WBS_RESPONSE_EVT. + * As a precondition to use this API, Service Level Connection shall exist with HFP client. + * + * @param[in] remote_bda: remote bluetooth HFP client device address + * @param[in] mode: ESP_HF_CODEC_CVSD, ESP_HF_CODEC_MSBC, ESP_HF_CODEC_LC3, + * or ESP_HF_CODEC_NONE to reset to stack default + * + * @return + * - ESP_OK: request sent to lower layer + * - ESP_ERR_INVALID_STATE: if bluetooth stack is not yet enabled or SLC not connected + * - ESP_ERR_INVALID_ARG: invalid codec mode or address + * - ESP_FAIL: others + * + */ +esp_err_t esp_hf_ag_set_codec(esp_bd_addr_t remote_bda, esp_hf_codec_mode_t mode); + /** * * @brief Response of Volume Recognition Command(AT+VRA) from HFP client. diff --git a/components/bt/host/bluedroid/api/include/api/esp_hf_client_api.h b/components/bt/host/bluedroid/api/include/api/esp_hf_client_api.h index 9ae45e33495..f684efa03c2 100644 --- a/components/bt/host/bluedroid/api/include/api/esp_hf_client_api.h +++ b/components/bt/host/bluedroid/api/include/api/esp_hf_client_api.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -35,6 +35,7 @@ typedef enum { ESP_HF_CLIENT_AUDIO_STATE_CONNECTING, /*!< audio connection has been initiated */ ESP_HF_CLIENT_AUDIO_STATE_CONNECTED, /*!< audio connection is established */ ESP_HF_CLIENT_AUDIO_STATE_CONNECTED_MSBC, /*!< mSBC audio connection is established */ + ESP_HF_CLIENT_AUDIO_STATE_CONNECTED_LC3, /*!< LC3-SWB audio connection is established */ } esp_hf_client_audio_state_t; /// in-band ring tone state @@ -67,6 +68,9 @@ typedef struct { #define ESP_HF_CLIENT_PEER_FEAT_HF_IND 0x400 /*!< HF Indicators */ #define ESP_HF_CLIENT_PEER_FEAT_ESCO_S4 0x800 /*!< eSCO S4 Setting Supported */ +/* HFP 1.9 - local HF feature, maps to SDP SupportedFeatures bit 8 */ +#define ESP_HF_CLIENT_FEAT_SWB 0x00010000 /*!< Super Wideband Speech */ + /* CHLD feature masks of AG */ #define ESP_HF_CLIENT_CHLD_FEAT_REL 0x01 /*!< 0 Release waiting call or held calls */ #define ESP_HF_CLIENT_CHLD_FEAT_REL_ACC 0x02 /*!< 1 Release active calls and accept other waiting or held call */ diff --git a/components/bt/host/bluedroid/api/include/api/esp_hf_defs.h b/components/bt/host/bluedroid/api/include/api/esp_hf_defs.h index 5001e81d9d6..4fae397bd1a 100644 --- a/components/bt/host/bluedroid/api/include/api/esp_hf_defs.h +++ b/components/bt/host/bluedroid/api/include/api/esp_hf_defs.h @@ -47,8 +47,9 @@ typedef enum { typedef enum { ESP_HF_AUDIO_STATE_DISCONNECTED = 0, /*!< audio connection released */ ESP_HF_AUDIO_STATE_CONNECTING, /*!< audio connection has been initiated */ - ESP_HF_AUDIO_STATE_CONNECTED, /*!< audio connection is established */ + ESP_HF_AUDIO_STATE_CONNECTED, /*!< CVSD audio connection is established */ ESP_HF_AUDIO_STATE_CONNECTED_MSBC, /*!< mSBC audio connection is established */ + ESP_HF_AUDIO_STATE_CONNECTED_LC3, /*!< LC3-SWB audio connection is established */ } esp_hf_audio_state_t; /// Bluetooth HFP audio volume type @@ -179,13 +180,32 @@ typedef enum { ESP_HF_CALL_WAITING_ACTIVE, /*!< active call waiting */ } esp_hf_call_waiting_status_t; -/* WBS codec setting */ +/* codec setting - values match HFP Codec ID in AT+BAC/+BCS (Appendix B) */ typedef enum { - ESP_HF_WBS_NONE, /*!< No Wideband Speech (WBS) codec support */ - ESP_HF_WBS_NO, /*!< Wideband Speech (WBS) codec is not enabled */ - ESP_HF_WBS_YES /*!< Wideband Speech (WBS) codec is enabled */ -}esp_hf_wbs_config_t; + ESP_HF_CODEC_NONE = 0, /*!< No codec negotiated */ + ESP_HF_CODEC_CVSD = 1, /*!< Narrowband Speech (CVSD) */ + ESP_HF_CODEC_MSBC = 2, /*!< Wideband Speech (mSBC) */ + ESP_HF_CODEC_LC3 = 3 /*!< Super Wideband Speech (LC3-SWB) */ +} esp_hf_codec_mode_t; + +/*!< @deprecated Renamed to ESP_HF_CODEC_NONE */ +#define ESP_HF_WBS_NONE (ESP_HF_CODEC_NONE) /*!< No Wideband Speech (WBS) codec support */ + +/*!< @deprecated Renamed to ESP_HF_CODEC_CVSD */ +#define ESP_HF_WBS_NO (ESP_HF_CODEC_CVSD) /*!< Wideband Speech (WBS) codec is not enabled */ + +/*!< @deprecated Renamed to ESP_HF_CODEC_MSBC */ +#define ESP_HF_WBS_YES (ESP_HF_CODEC_MSBC) /*!< Wideband Speech (WBS) codec is enabled */ + +/*!< @deprecated esp_hf_wbs_config_t is changed to esp_hf_codec_mode_t */ +typedef esp_hf_codec_mode_t esp_hf_wbs_config_t; + + +/** @brief Codec capability bitmap for AT+BAC, matches BTA_AG_CODEC_* flags */ +#define ESP_HF_CODEC_CAP_CVSD (1u << (ESP_HF_CODEC_CVSD - 1)) /*!< 0x01 */ +#define ESP_HF_CODEC_CAP_MSBC (1u << (ESP_HF_CODEC_MSBC - 1)) /*!< 0x02 */ +#define ESP_HF_CODEC_CAP_LC3 (1u << (ESP_HF_CODEC_LC3 - 1)) /*!< 0x04 */ /// Bluetooth HFP RFCOMM connection and service level connection status typedef enum { @@ -261,6 +281,17 @@ typedef enum { #define ESP_HF_MSBC_BITPOOL 26 /*!< mSBC bitpool */ /* frame size after mSBC encoded */ #define ESP_HF_MSBC_ENCODED_FRAME_SIZE 57 /*!< mSBC frame size */ +#define ESP_HF_MSBC_SAMPLING_RATE_HZ 16000 /*!< mSBC PCM sampling rate */ +#define ESP_HF_MSBC_H2_HEADER_LEN 2 /*!< H2 sync header length */ +#define ESP_HF_MSBC_ENCODED_FRAME_SIZE_WITH_H2 (ESP_HF_MSBC_ENCODED_FRAME_SIZE + ESP_HF_MSBC_H2_HEADER_LEN) + +/* Since HFP uses a fixed set of LC3-SWB codec parameters (HFP 1.9 Table 6.15) */ +#define ESP_HF_LC3_SAMPLING_RATE_HZ 32000 /*!< LC3-SWB PCM sampling rate */ +#define ESP_HF_LC3_FRAME_DURATION_US 7500 /*!< LC3-SWB frame duration */ +#define ESP_HF_LC3_ENCODED_FRAME_SIZE 58 /*!< LC3 payload size (excl. H2 header) */ +#define ESP_HF_LC3_H2_HEADER_LEN 2 /*!< H2 sync header length */ +#define ESP_HF_LC3_ENCODED_FRAME_SIZE_WITH_H2 (ESP_HF_LC3_ENCODED_FRAME_SIZE + ESP_HF_LC3_H2_HEADER_LEN) +#define ESP_HF_LC3_BITRATE_BPS 61867 /*!< Resulting bit rate (excl. H2 header) */ /** * @brief HFP audio buffer diff --git a/components/bt/host/bluedroid/bta/hf_ag/bta_ag_act.c b/components/bt/host/bluedroid/bta/hf_ag/bta_ag_act.c index fc385d7c3f5..87617d6839d 100644 --- a/components/bt/host/bluedroid/bta/hf_ag/bta_ag_act.c +++ b/components/bt/host/bluedroid/bta/hf_ag/bta_ag_act.c @@ -805,7 +805,8 @@ void bta_ag_setcodec(tBTA_AG_SCB *p_scb, tBTA_AG_DATA *p_data) /* Check if the requested codec type is valid */ if((codec_type != BTA_AG_CODEC_NONE) && (codec_type != BTA_AG_CODEC_CVSD) && - (codec_type != BTA_AG_CODEC_MSBC)) { + (codec_type != BTA_AG_CODEC_MSBC) && + (codec_type != BTA_AG_CODEC_LC3)) { val.hdr.status = BTA_AG_FAIL_RESOURCES; APPL_TRACE_ERROR("%s error: unsupported codec type %d", __func__, codec_type); if (bta_ag_cb.p_cback) { diff --git a/components/bt/host/bluedroid/bta/hf_ag/bta_ag_api.c b/components/bt/host/bluedroid/bta/hf_ag/bta_ag_api.c index 607a836fdce..7261259dd32 100644 --- a/components/bt/host/bluedroid/bta/hf_ag/bta_ag_api.c +++ b/components/bt/host/bluedroid/bta/hf_ag/bta_ag_api.c @@ -355,7 +355,7 @@ void BTA_AgCiData(UINT16 handle) *******************************************************************************/ void BTA_AgAudioBuffAlloc(UINT16 size, UINT8 **pp_buff, UINT8 **pp_data) { - /* reserve 1 byte at last, when the size is mSBC frame size (57), then we got a buffer that can hold 60 bytes data */ + /* reserve trailing space for H2 header and optional eSCO padding (mSBC: 57+2+1=60, LC3: 58+2=60) */ BT_HDR *p_buf= (BT_HDR *)osi_calloc(sizeof(BT_HDR) + BTA_AG_BUFF_OFFSET_MIN + BTA_AG_H2_HEADER_LEN + size + 1); if (p_buf != NULL) { /* mSBC offset is large than CVSD, so this is also work in CVSD air mode */ diff --git a/components/bt/host/bluedroid/bta/hf_ag/bta_ag_cmd.c b/components/bt/host/bluedroid/bta/hf_ag/bta_ag_cmd.c index 7d6ced5cf8e..63c416d63f4 100644 --- a/components/bt/host/bluedroid/bta/hf_ag/bta_ag_cmd.c +++ b/components/bt/host/bluedroid/bta/hf_ag/bta_ag_cmd.c @@ -694,6 +694,10 @@ static tBTA_AG_PEER_CODEC bta_ag_parse_bac(tBTA_AG_SCB *p_scb, char *p_s) retval |= BTA_AG_CODEC_MSBC; break; + case UUID_CODEC_LC3: + retval |= BTA_AG_CODEC_LC3; + break; + default: APPL_TRACE_ERROR("Unknown Codec UUID(%d) received", uuid_codec); break; @@ -707,6 +711,32 @@ static tBTA_AG_PEER_CODEC bta_ag_parse_bac(tBTA_AG_SCB *p_scb, char *p_s) } return (retval); } + +/******************************************************************************* +** +** Function bta_ag_select_sco_codec_from_peer +** +** Description Select preferred SCO codec from peer capabilities (LC3 > mSBC > CVSD). +** +** Returns void +** +*******************************************************************************/ +static void bta_ag_select_sco_codec_from_peer(tBTA_AG_SCB *p_scb) +{ +#if UC_BT_HFP_LC3_ENABLE + if (p_scb->peer_codecs & BTA_AG_CODEC_LC3) { + p_scb->sco_codec = BTA_AG_CODEC_LC3; + APPL_TRACE_DEBUG("Received AT+BAC, updating sco codec to LC3"); + } else +#endif + if (p_scb->peer_codecs & BTA_AG_CODEC_MSBC) { + p_scb->sco_codec = BTA_AG_CODEC_MSBC; + APPL_TRACE_DEBUG("Received AT+BAC, updating sco codec to MSBC"); + } else { + p_scb->sco_codec = BTA_AG_CODEC_CVSD; + APPL_TRACE_DEBUG("Received AT+BAC, updating sco codec to CVSD"); + } +} #endif /* #if (BTM_WBS_INCLUDED == TRUE ) */ /******************************************************************************* @@ -1167,13 +1197,7 @@ void bta_ag_at_hfp_cback(tBTA_AG_SCB *p_scb, UINT16 cmd, UINT8 arg_type, p_scb->peer_codecs = bta_ag_parse_bac(p_scb, p_arg); p_scb->codec_updated = TRUE; - if (p_scb->peer_codecs & BTA_AG_CODEC_MSBC) { - p_scb->sco_codec = UUID_CODEC_MSBC; - APPL_TRACE_DEBUG("Received AT+BAC, updating sco codec to MSBC"); - } else { - p_scb->sco_codec = UUID_CODEC_CVSD; - APPL_TRACE_DEBUG("Received AT+BAC, updating sco codec to CVSD"); - } + bta_ag_select_sco_codec_from_peer(p_scb); /* The above logic sets the stack preferred codec based on local and peer codec capabilities. This can be overridden by the application depending on its preference using the bta_ag_setcodec API. We send the peer_codecs to the application. */ @@ -1204,6 +1228,10 @@ void bta_ag_at_hfp_cback(tBTA_AG_SCB *p_scb, UINT16 cmd, UINT8 arg_type, codec_type = BTA_AG_CODEC_MSBC; break; + case UUID_CODEC_LC3: + codec_type = BTA_AG_CODEC_LC3; + break; + default: APPL_TRACE_ERROR("Unknown codec_uuid %d", int_arg); codec_type = 0xFFFF; @@ -1675,6 +1703,10 @@ void bta_ag_send_bcs(tBTA_AG_SCB *p_scb, tBTA_AG_DATA *p_data) codec_uuid = UUID_CODEC_MSBC; break; + case BTA_AG_CODEC_LC3: + codec_uuid = UUID_CODEC_LC3; + break; + default: APPL_TRACE_ERROR("bta_ag_send_bcs: unknown codec %d, use CVSD", p_scb->sco_codec); codec_uuid = UUID_CODEC_CVSD; diff --git a/components/bt/host/bluedroid/bta/hf_ag/bta_ag_main.c b/components/bt/host/bluedroid/bta/hf_ag/bta_ag_main.c index aff9410f8d7..6141c6eba80 100644 --- a/components/bt/host/bluedroid/bta/hf_ag/bta_ag_main.c +++ b/components/bt/host/bluedroid/bta/hf_ag/bta_ag_main.c @@ -27,6 +27,7 @@ #include "bta_ag_int.h" #include "bta/bta_api.h" #include "bta/bta_sys.h" +#include "common/bt_target.h" #include "bta/bta_ag_api.h" #include "bta/bta_ag_co.h" #include "bta/utl.h" @@ -286,7 +287,11 @@ const tBTA_AG_ST_TBL bta_ag_st_tbl[] = /***************************************************************************** ** Global data *****************************************************************************/ +#if UC_BT_HFP_LC3_ENABLE +const uint16_t bta_ag_version = HFP_VERSION_1_9; +#else const uint16_t bta_ag_version = HFP_VERSION_1_8; +#endif /* AG control block */ #if BTA_DYNAMIC_MEMORY == FALSE tBTA_AG_CB bta_ag_cb; diff --git a/components/bt/host/bluedroid/bta/hf_ag/bta_ag_sco.c b/components/bt/host/bluedroid/bta/hf_ag/bta_ag_sco.c index aa4cd41033e..bf751792a4d 100644 --- a/components/bt/host/bluedroid/bta/hf_ag/bta_ag_sco.c +++ b/components/bt/host/bluedroid/bta/hf_ag/bta_ag_sco.c @@ -129,6 +129,22 @@ static const tBTM_ESCO_PARAMS bta_ag_esco_params[BTA_AG_NUM_CODECS] = BTM_ESCO_RETRANS_QUALITY /* Retransmission effort */ } }; + +static BOOLEAN bta_ag_is_transparent_codec(tBTM_SCO_CODEC_TYPE codec) +{ + return (codec == BTM_SCO_CODEC_MSBC || codec == BTM_SCO_CODEC_LC3); +} + +static UINT32 bta_ag_sco_get_pcm_rate(tBTM_SCO_CODEC_TYPE codec) +{ + if (codec == BTM_SCO_CODEC_LC3) { + return BTA_HFP_SCO_SAMP_RATE_32K; + } + if (codec == BTM_SCO_CODEC_MSBC) { + return BTA_HFP_SCO_SAMP_RATE_16K; + } + return BTA_HFP_SCO_SAMP_RATE_8K; +} #else #define BTA_AG_NUM_CODECS 2 #define BTA_AG_ESCO_SETTING_IDX_CVSD 0 /* eSCO setting for CVSD S3 */ @@ -282,24 +298,33 @@ static void bta_ag_sco_disc_cback(UINT16 sco_idx) #if (BTM_WBS_INCLUDED == TRUE ) /* Restore settings */ - if(bta_ag_cb.sco.p_curr_scb->inuse_codec == BTA_AG_CODEC_MSBC) + if (bta_ag_is_transparent_codec(bta_ag_cb.sco.p_curr_scb->inuse_codec)) { /* set_sco_codec(BTM_SCO_CODEC_NONE); we should get a close */ BTM_WriteVoiceSettings (BTM_VOICE_SETTING_CVSD); - /* If SCO open was initiated by AG and failed for mSBC, then attempt - mSBC with T1 settings i.e. 'Safe Settings'. If this fails, then switch to CVSD */ + /* If SCO open was initiated by AG and failed for mSBC/LC3, then attempt + transparent codec with T1 settings i.e. 'Safe Settings'. If this fails, + fall back to mSBC (from LC3) or CVSD. */ if (bta_ag_sco_is_opening (bta_ag_cb.sco.p_curr_scb)) { if (bta_ag_cb.sco.p_curr_scb->codec_msbc_settings == BTA_AG_SCO_MSBC_SETTINGS_T2) { - APPL_TRACE_DEBUG("Fallback to mSBC T1 settings"); + APPL_TRACE_DEBUG("Fallback to transparent codec T1 settings"); bta_ag_cb.sco.p_curr_scb->codec_msbc_settings = BTA_AG_SCO_MSBC_SETTINGS_T1; } else { - APPL_TRACE_DEBUG("Fallback to CVSD settings"); - bta_ag_cb.sco.p_curr_scb->codec_fallback = TRUE; + if (bta_ag_cb.sco.p_curr_scb->inuse_codec == BTA_AG_CODEC_LC3 && + (bta_ag_cb.sco.p_curr_scb->peer_codecs & BTA_AG_CODEC_MSBC)) { + APPL_TRACE_DEBUG("Fallback from LC3 to mSBC"); + bta_ag_cb.sco.p_curr_scb->sco_codec = BTA_AG_CODEC_MSBC; + bta_ag_cb.sco.p_curr_scb->codec_msbc_settings = BTA_AG_SCO_MSBC_SETTINGS_T2; + bta_ag_cb.sco.p_curr_scb->codec_updated = TRUE; + } else { + APPL_TRACE_DEBUG("Fallback to CVSD settings"); + bta_ag_cb.sco.p_curr_scb->codec_fallback = TRUE; + } } } } @@ -482,7 +507,11 @@ static UINT16 bta_ag_sco_get_frame_size(tBTA_AG_SCB *p_scb) frame_size = p_scb->out_pkt_len; break; case BTM_SCO_AIR_MODE_TRANSPNT: - frame_size = BTA_AG_MSBC_FRAME_SIZE; + if (p_scb->inuse_codec == BTA_AG_CODEC_LC3) { + frame_size = BTA_AG_LC3_FRAME_PAYLOAD_SIZE; + } else { + frame_size = BTA_AG_MSBC_FRAME_PAYLOAD_SIZE; + } break; default: break; @@ -555,9 +584,9 @@ static void bta_ag_create_sco(tBTA_AG_SCB *p_scb, BOOLEAN is_orig) #if (BTM_WBS_INCLUDED == TRUE) - if ((p_scb->sco_codec == BTM_SCO_CODEC_MSBC) && !p_scb->codec_fallback && !p_scb->retry_with_sco_only) + if (bta_ag_is_transparent_codec(p_scb->sco_codec) && !p_scb->codec_fallback && !p_scb->retry_with_sco_only) { - esco_codec = BTM_SCO_CODEC_MSBC; + esco_codec = p_scb->sco_codec; } if (p_scb->codec_fallback) { @@ -566,8 +595,8 @@ static void bta_ag_create_sco(tBTA_AG_SCB *p_scb, BOOLEAN is_orig) p_scb->codec_updated = TRUE; } /* If WBS included, use CVSD by default, index is 0 for CVSD by initialization */ - /* If eSCO codec is mSBC, index is T2 or T1 */ - if (esco_codec == BTM_SCO_CODEC_MSBC) + /* If eSCO codec is mSBC or LC3, index is T2 or T1 */ + if (bta_ag_is_transparent_codec(esco_codec)) { if (p_scb->codec_msbc_settings == BTA_AG_SCO_MSBC_SETTINGS_T2) { @@ -625,12 +654,12 @@ static void bta_ag_create_sco(tBTA_AG_SCB *p_scb, BOOLEAN is_orig) ||!((params.packet_types & ~(BTM_ESCO_LINK_ONLY_MASK | BTM_SCO_LINK_ONLY_MASK)) ^ BTA_AG_NO_EDR_ESCO)) { #if (BTM_WBS_INCLUDED == TRUE) - if (esco_codec != BTA_AG_CODEC_MSBC) + if (!bta_ag_is_transparent_codec(esco_codec)) { p_scb->retry_with_sco_only = TRUE; APPL_TRACE_API("Setting retry_with_sco_only to TRUE"); } - else /* Do not use SCO when using mSBC */ + else /* Do not use SCO when using mSBC or LC3 */ { p_scb->retry_with_sco_only = FALSE; APPL_TRACE_API("Setting retry_with_sco_only to FALSE"); @@ -661,7 +690,7 @@ static void bta_ag_create_sco(tBTA_AG_SCB *p_scb, BOOLEAN is_orig) /* This setting may not be necessary */ /* To be verified with stable 2049 boards */ - if (esco_codec == BTA_AG_CODEC_MSBC) + if (bta_ag_is_transparent_codec(esco_codec)) BTM_WriteVoiceSettings(BTM_VOICE_SETTING_TRANS); else BTM_WriteVoiceSettings(BTM_VOICE_SETTING_CVSD); @@ -675,13 +704,12 @@ static void bta_ag_create_sco(tBTA_AG_SCB *p_scb, BOOLEAN is_orig) #if (BTM_SCO_HCI_INCLUDED == TRUE) #if (BTM_WBS_INCLUDED == TRUE) - if (esco_codec == BTA_AG_CODEC_MSBC) { - pcm_sample_rate = BTA_HFP_SCO_SAMP_RATE_16K; - } else -#endif + pcm_sample_rate = bta_ag_sco_get_pcm_rate(esco_codec); +#else { pcm_sample_rate = BTA_HFP_SCO_SAMP_RATE_8K; } +#endif sco_route = bta_ag_sco_co_init(pcm_sample_rate, pcm_sample_rate, &codec_info, p_scb->app_id); #endif @@ -730,7 +758,7 @@ static void bta_ag_create_sco(tBTA_AG_SCB *p_scb, BOOLEAN is_orig) *******************************************************************************/ BOOLEAN bta_ag_attempt_msbc_safe_settings(tBTA_AG_SCB *p_scb) { - if (p_scb->svc_conn && p_scb->sco_codec == BTM_SCO_CODEC_MSBC && + if (p_scb->svc_conn && bta_ag_is_transparent_codec(p_scb->sco_codec) && p_scb->codec_msbc_settings == BTA_AG_SCO_MSBC_SETTINGS_T1) return TRUE; else @@ -791,6 +819,7 @@ void bta_ag_codec_negotiate(tBTA_AG_SCB *p_scb) bta_ag_send_bcs(p_scb, NULL); /* Start timer to handle timeout */ + bta_sys_stop_timer(&p_scb->cn_timer); p_scb->cn_timer.p_cback = (TIMER_CBACK*)&bta_ag_cn_timer_cback; p_scb->cn_timer.param = (UINT32)p_scb; bta_sys_start_timer(&p_scb->cn_timer, 0, BTA_AG_CODEC_NEGO_TIMEOUT); @@ -1588,7 +1617,9 @@ void bta_ag_sco_conn_open(tBTA_AG_SCB *p_scb, tBTA_AG_DATA *p_data) #if (BTM_WBS_INCLUDED == TRUE) /* call app callback */ - if (p_scb->sco_codec == BTA_AG_CODEC_MSBC) { + if (p_scb->inuse_codec == BTA_AG_CODEC_LC3) { + bta_ag_cback_sco(p_scb, BTA_AG_AUDIO_LC3_OPEN_EVT); + } else if (p_scb->inuse_codec == BTA_AG_CODEC_MSBC) { bta_ag_cback_sco(p_scb, BTA_AG_AUDIO_MSBC_OPEN_EVT); } else { bta_ag_cback_sco(p_scb, BTA_AG_AUDIO_OPEN_EVT); @@ -1905,24 +1936,43 @@ static void bta_ag_sco_data_send_cvsd(tBTA_AG_SCB *p_scb, BT_HDR *p_buf) /******************************************************************************* ** -** Function bta_ag_sco_data_send_msbc +** Function bta_ag_sco_transparent_air_pad ** -** Description Process SCO data of mSBC air mode +** Description Padding bytes after H2 + codec payload to fill eSCO packet. +** +** Returns Number of padding bytes +** +*******************************************************************************/ +static UINT8 bta_ag_sco_transparent_air_pad(UINT16 frame_payload_size, UINT16 out_pkt_len) +{ + UINT16 air_len = frame_payload_size + BTA_AG_H2_HEADER_LEN; + if (out_pkt_len > air_len) { + return (UINT8)(out_pkt_len - air_len); + } + return 0; +} + +/******************************************************************************* +** +** Function bta_ag_sco_data_send_transparent +** +** Description Process SCO data of transparent air mode (mSBC or LC3-SWB) ** ** ** Returns void ** *******************************************************************************/ -static void bta_ag_sco_data_send_msbc(tBTA_AG_SCB *p_scb, BT_HDR *p_buf) +static void bta_ag_sco_data_send_transparent(tBTA_AG_SCB *p_scb, BT_HDR *p_buf, UINT16 frame_payload_size) { UINT16 out_pkt_len = p_scb->out_pkt_len; - if (p_buf->len == BTA_AG_MSBC_FRAME_SIZE && p_buf->offset >= BTA_AG_BUFF_OFFSET_MIN + BTA_AG_H2_HEADER_LEN) { + UINT8 air_pad = bta_ag_sco_transparent_air_pad(frame_payload_size, out_pkt_len); + + if (p_buf->len == frame_payload_size && p_buf->offset >= BTA_AG_BUFF_OFFSET_MIN + BTA_AG_H2_HEADER_LEN) { /* add H2 header */ p_buf->offset -= BTA_AG_H2_HEADER_LEN; UINT8 *p_data = (UINT8 *)(p_buf + 1) + p_buf->offset; bta_ag_h2_header((UINT16 *)p_data); - /* add header len, add addition one bytes, the len is BTA_AG_SCO_OUT_PKT_LEN_2EV3 now */ - p_buf->len += BTA_AG_H2_HEADER_LEN + 1; + p_buf->len += BTA_AG_H2_HEADER_LEN + air_pad; if (out_pkt_len == BTA_AG_SCO_OUT_PKT_LEN_2EV3) { /* mSBC frame can be send directly */ @@ -1950,8 +2000,8 @@ static void bta_ag_sco_data_send_msbc(tBTA_AG_SCB *p_scb, BT_HDR *p_buf) APPL_TRACE_WARNING("%s, invalid out pkt len: %d", __FUNCTION__, out_pkt_len); } } - else if (p_buf->len != 0 && p_buf->len % BTA_AG_MSBC_FRAME_SIZE == 0) { - /* multiple mSBC frame in the buffer, or just one but offset is too small */ + else if (p_buf->len != 0 && p_buf->len % frame_payload_size == 0) { + /* multiple mSBC or LC3-SWB frame in the buffer, or just one but offset is too small */ UINT8 *p_data = (UINT8 *)(p_buf + 1) + p_buf->offset; UINT16 total_len = p_buf->len; if (out_pkt_len == BTA_AG_SCO_OUT_PKT_LEN_2EV3) { @@ -1966,9 +2016,9 @@ static void bta_ag_sco_data_send_msbc(tBTA_AG_SCB *p_scb, BT_HDR *p_buf) UINT8 *p_data2 = (UINT8 *)(p_buf2 + 1) + p_buf2->offset; bta_ag_h2_header((UINT16 *)p_data2); p_data2 += BTA_AG_H2_HEADER_LEN; - memcpy(p_data2, p_data, BTA_AG_MSBC_FRAME_SIZE); - p_data += BTA_AG_MSBC_FRAME_SIZE; - total_len -= BTA_AG_MSBC_FRAME_SIZE; + memcpy(p_data2, p_data, frame_payload_size); + p_data += frame_payload_size; + total_len -= frame_payload_size; bta_ag_write_sco_data(p_scb, p_buf2, NULL); } } @@ -2001,7 +2051,7 @@ static void bta_ag_sco_data_send_msbc(tBTA_AG_SCB *p_scb, BT_HDR *p_buf) p_buf3->offset = BTA_AG_BUFF_OFFSET_MIN; p_buf3->len = BTA_AG_SCO_OUT_PKT_LEN_EV3; UINT8 *p_data3 = (UINT8 *)(p_buf3 + 1) + p_buf3->offset; - UINT16 rem_payload = BTA_AG_MSBC_FRAME_SIZE - (BTA_AG_SCO_OUT_PKT_LEN_EV3 - BTA_AG_H2_HEADER_LEN); + UINT16 rem_payload = frame_payload_size - (BTA_AG_SCO_OUT_PKT_LEN_EV3 - BTA_AG_H2_HEADER_LEN); memcpy(p_data3, p_data, rem_payload); p_data += rem_payload; total_len -= rem_payload; @@ -2014,7 +2064,7 @@ static void bta_ag_sco_data_send_msbc(tBTA_AG_SCB *p_scb, BT_HDR *p_buf) osi_free(p_buf); } else { - APPL_TRACE_WARNING("%s, unaccepted data len: %d", __FUNCTION__, p_buf->len); + APPL_TRACE_WARNING("%s, unaccepted data len: %d (frame %d)", __FUNCTION__, p_buf->len, frame_payload_size); osi_free(p_buf); } } @@ -2048,7 +2098,11 @@ void bta_ag_sco_data_send(tBTA_AG_SCB *p_scb, tBTA_AG_DATA *p_data) bta_ag_sco_data_send_cvsd(p_scb, p_buf); break; case BTM_SCO_AIR_MODE_TRANSPNT: - bta_ag_sco_data_send_msbc(p_scb, p_buf); + if (p_scb->inuse_codec == BTA_AG_CODEC_LC3) { + bta_ag_sco_data_send_transparent(p_scb, p_buf, BTA_AG_LC3_FRAME_PAYLOAD_SIZE); + } else { + bta_ag_sco_data_send_transparent(p_scb, p_buf, BTA_AG_MSBC_FRAME_PAYLOAD_SIZE); + } break; default: osi_free(p_buf); diff --git a/components/bt/host/bluedroid/bta/hf_ag/bta_ag_sdp.c b/components/bt/host/bluedroid/bta/hf_ag/bta_ag_sdp.c index 1d6aa34b095..3aefe6518aa 100644 --- a/components/bt/host/bluedroid/bta/hf_ag/bta_ag_sdp.c +++ b/components/bt/host/bluedroid/bta/hf_ag/bta_ag_sdp.c @@ -25,6 +25,7 @@ #include #include "bta_ag_int.h" +#include "common/bt_target.h" #include "bta/bta_ag_api.h" #include "bta/bta_sys.h" #include "bta/bta_ag_api.h" @@ -161,7 +162,11 @@ BOOLEAN bta_ag_add_record(UINT16 service_uuid, char *p_service_name, UINT8 scn, /* add profile descriptor list */ if (service_uuid == UUID_SERVCLASS_AG_HANDSFREE) { profile_uuid = UUID_SERVCLASS_HF_HANDSFREE; +#if UC_BT_HFP_LC3_ENABLE + version = HFP_VERSION_1_9; +#else version = HFP_VERSION_1_8; +#endif } else { profile_uuid = UUID_SERVCLASS_HEADSET; version = HSP_VERSION_1_2; @@ -188,6 +193,11 @@ BOOLEAN bta_ag_add_record(UINT16 service_uuid, char *p_service_name, UINT8 scn, if (codec_supported) { features |= 0x0020; } +#if UC_BT_HFP_LC3_ENABLE + if (codec_supported) { + features |= 0x0100; /* SWB supported in SDP */ + } +#endif UINT16_TO_BE_FIELD(buf, features); result &= SDP_AddAttribute(sdp_handle, ATTR_ID_SUPPORTED_FEATURES, UINT_DESC_TYPE, 2, buf); } diff --git a/components/bt/host/bluedroid/bta/hf_ag/include/bta_ag_int.h b/components/bt/host/bluedroid/bta/hf_ag/include/bta_ag_int.h index 505809fabb1..674d420669b 100644 --- a/components/bt/host/bluedroid/bta/hf_ag/include/bta_ag_int.h +++ b/components/bt/host/bluedroid/bta/hf_ag/include/bta_ag_int.h @@ -50,6 +50,7 @@ #define HFP_VERSION_1_6 0x0106 #define HFP_VERSION_1_7 0x0107 #define HFP_VERSION_1_8 0x0108 +#define HFP_VERSION_1_9 0x0109 #define HSP_VERSION_1_0 0x0100 #define HSP_VERSION_1_2 0x0102 @@ -102,7 +103,9 @@ /* mSBC H2 header length */ #define BTA_AG_H2_HEADER_LEN 2 /* mSBC frame size not include H1/H2 header */ -#define BTA_AG_MSBC_FRAME_SIZE 57 +#define BTA_AG_MSBC_FRAME_PAYLOAD_SIZE 57 +#define BTA_AG_LC3_FRAME_PAYLOAD_SIZE 58 +#define BTA_AG_LC3_VOHCI_FRAME_SIZE 60 /* max user data len of sco packet type EV3 */ #define BTA_AG_SCO_OUT_PKT_LEN_EV3 30 /* max user data len of sco packet type 2-EV3 */ diff --git a/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_api.c b/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_api.c index 4737548ca95..1b188ef9cdc 100644 --- a/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_api.c +++ b/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_api.c @@ -63,6 +63,7 @@ static const uint8_t bta_hf_client_cb_data_size[] = { sizeof(tBTA_HF_CLIENT_VAL), // #define BTA_HF_CLIENT_RING_INDICATION 21 0, // #define BTA_HF_CLIENT_DISABLE_EVT 22 sizeof(tBTA_SCO_PKT_STAT_NUMS), // #define BTA_HF_CLIENT_PKT_STAT_NUMS_GET_EVT 23 + sizeof(tBTA_HF_CLIENT_AUDIO_STAT), // #define BTA_HF_CLIENT_AUDIO_LC3_OPEN_EVT 24 }; /***************************************************************************** ** External Function Declarations @@ -368,7 +369,7 @@ void BTA_HfClientAudioBuffAlloc(UINT16 size, UINT8 **pp_buff, UINT8 **pp_data) return; } - /* reserve 1 byte at last, when the size is mSBC frame size (57), then we got a buffer that can hold 60 bytes data */ + /* reserve trailing space for H2 header and optional eSCO padding (mSBC: 57+2+1=60, LC3: 58+2=60) */ p_buf = (BT_HDR *)osi_calloc(sizeof(BT_HDR) + BTA_HF_CLIENT_BUFF_OFFSET_MIN + BTA_HF_CLIENT_H2_HEADER_LEN + size + 1); if (p_buf != NULL) { /* mSBC offset is large than CVSD, so this is work in CVSD air mode */ diff --git a/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_at.c b/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_at.c index 3ab7f5b1cf5..9a594eaa05a 100644 --- a/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_at.c +++ b/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_at.c @@ -22,6 +22,8 @@ #include "bta/bta_hf_client_api.h" #include "bta_hf_client_int.h" +#include "common/bt_target.h" +#include "stack/sdpdefs.h" #include "stack/port_api.h" #include "osi/allocator.h" @@ -488,14 +490,49 @@ static void bta_hf_client_handle_ciev(UINT32 index, UINT32 value) } } +static tBTM_SCO_CODEC_TYPE bta_hf_client_hfp_codec_id_to_btm(UINT32 hfp_codec_id) +{ + switch (hfp_codec_id) { + case UUID_CODEC_MSBC: + return BTM_SCO_CODEC_MSBC; + case UUID_CODEC_LC3: + return BTM_SCO_CODEC_LC3; + case UUID_CODEC_CVSD: + return BTM_SCO_CODEC_CVSD; + default: + /* Accept BTM bitmask values for backward compatibility */ + if (hfp_codec_id == BTM_SCO_CODEC_MSBC || hfp_codec_id == BTM_SCO_CODEC_LC3 || + hfp_codec_id == BTM_SCO_CODEC_CVSD) { + return (tBTM_SCO_CODEC_TYPE)hfp_codec_id; + } + return BTM_SCO_CODEC_CVSD; + } +} + +static UINT32 bta_hf_client_btm_codec_to_hfp_id(tBTM_SCO_CODEC_TYPE btm_codec) +{ + switch (btm_codec) { + case BTM_SCO_CODEC_MSBC: + return UUID_CODEC_MSBC; + case BTM_SCO_CODEC_LC3: + return UUID_CODEC_LC3; + case BTM_SCO_CODEC_CVSD: + default: + return UUID_CODEC_CVSD; + } +} + static void bta_hf_client_handle_bcs(UINT32 codec) { - APPL_TRACE_DEBUG("%s %u", __FUNCTION__, codec); + tBTM_SCO_CODEC_TYPE btm_codec = bta_hf_client_hfp_codec_id_to_btm(codec); - if (codec == BTM_SCO_CODEC_CVSD || - (codec == BTM_SCO_CODEC_MSBC && bta_hf_client_cb.msbc_enabled == TRUE)) { - bta_hf_client_cb.scb.negotiated_codec = codec; - bta_hf_client_send_at_bcs(codec); + APPL_TRACE_DEBUG("%s hfp_id %u btm_codec %u", __FUNCTION__, codec, btm_codec); + + if (btm_codec == BTM_SCO_CODEC_CVSD || + (btm_codec == BTM_SCO_CODEC_MSBC && bta_hf_client_cb.msbc_enabled == TRUE) || + (btm_codec == BTM_SCO_CODEC_LC3 && bta_hf_client_cb.lc3_enabled == TRUE)) { + bta_hf_client_cb.scb.negotiated_codec = btm_codec; + bta_hf_client_send_at_bcs(btm_codec); } else { bta_hf_client_cb.scb.negotiated_codec = BTM_SCO_CODEC_CVSD; bta_hf_client_send_at_bac(); @@ -1427,7 +1464,8 @@ void bta_hf_client_send_at_brsf(void) APPL_TRACE_DEBUG("%s", __FUNCTION__); - at_len = snprintf(buf, BTA_HF_CLIENT_AT_MAX_LEN, "AT+BRSF=%u\r", bta_hf_client_cb.scb.features); + at_len = snprintf(buf, BTA_HF_CLIENT_AT_MAX_LEN, "AT+BRSF=%u\r", + bta_hf_client_cb.scb.features & BTA_HF_CLIENT_BRSF_FEAT_SPEC); at_len = MIN(at_len, BTA_HF_CLIENT_AT_MAX_LEN - 1); if (at_len < 0) { @@ -1446,6 +1484,11 @@ void bta_hf_client_send_at_bac(void) APPL_TRACE_DEBUG("%s", __FUNCTION__); +#if UC_BT_HFP_LC3_ENABLE + if (bta_hf_client_cb.lc3_enabled && bta_hf_client_cb.msbc_enabled) { + buf = "AT+BAC=1,2,3\r"; + } else +#endif if (bta_hf_client_cb.msbc_enabled) { buf = "AT+BAC=1,2\r"; } else { @@ -1465,7 +1508,8 @@ void bta_hf_client_send_at_bcs(UINT32 codec) return; } - at_len = snprintf(buf, BTA_HF_CLIENT_AT_MAX_LEN, "AT+BCS=%u\r", codec); + at_len = snprintf(buf, BTA_HF_CLIENT_AT_MAX_LEN, "AT+BCS=%u\r", + bta_hf_client_btm_codec_to_hfp_id((tBTM_SCO_CODEC_TYPE)codec)); at_len = MIN(at_len, BTA_HF_CLIENT_AT_MAX_LEN - 1); if (at_len < 0) { diff --git a/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_main.c b/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_main.c index 99efa103e33..e582d1f8f0a 100644 --- a/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_main.c +++ b/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_main.c @@ -20,6 +20,7 @@ #include #include #include "common/bt_defs.h" +#include "common/bt_target.h" #include "bta/bta_api.h" #include "bta/bta_sys.h" #include "bta/bta_hf_client_api.h" @@ -251,7 +252,11 @@ const tBTA_HF_CLIENT_ST_TBL bta_hf_client_st_tbl[] = { bta_hf_client_st_closing }; +#if UC_BT_HFP_LC3_ENABLE +const int bta_hf_client_version = HFP_HF_VERSION_1_9; +#else const int bta_hf_client_version = HFP_HF_VERSION_1_7; +#endif /* HF Client control block */ #if BTA_DYNAMIC_MEMORY == FALSE @@ -460,6 +465,16 @@ static void bta_hf_client_api_enable(tBTA_HF_CLIENT_DATA *p_data) bta_hf_client_cb.msbc_enabled = FALSE; } +#if UC_BT_HFP_LC3_ENABLE + if (bta_hf_client_version >= HFP_HF_VERSION_1_9) { + bta_hf_client_cb.lc3_enabled = TRUE; + } else { + bta_hf_client_cb.lc3_enabled = FALSE; + } +#else + bta_hf_client_cb.lc3_enabled = FALSE; +#endif + bta_hf_client_cb.scb.negotiated_codec = BTM_SCO_CODEC_CVSD; /* set same setting as AG does */ diff --git a/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_sco.c b/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_sco.c index 6e51321e0c2..5d17e3880da 100644 --- a/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_sco.c +++ b/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_sco.c @@ -43,6 +43,22 @@ static BOOLEAN s_bta_hf_client_bqb_esco_s1_flag = false; #define BTA_HF_CLIENT_ESCO_PARAM_IDX_CVSD_S3 1 /* eSCO setting for CVSD S3 */ #define BTA_HF_CLIENT_ESCO_PARAM_IDX_MSBC_T2 2 /* eSCO setting for mSBC T2 */ #define BTA_HF_CLIENT_ESCO_PARAM_IDX_CVSD_S4 3 /* eSCO setting for CVSD S4 */ + +static BOOLEAN bta_hf_client_is_transparent_codec(tBTM_SCO_CODEC_TYPE codec) +{ + return (codec == BTM_SCO_CODEC_MSBC || codec == BTM_SCO_CODEC_LC3); +} + +static UINT32 bta_hf_client_sco_get_pcm_rate(tBTM_SCO_CODEC_TYPE codec) +{ + if (codec == BTM_SCO_CODEC_LC3) { + return BTA_HFP_SCO_SAMP_RATE_32K; + } + if (codec == BTM_SCO_CODEC_MSBC) { + return BTA_HFP_SCO_SAMP_RATE_16K; + } + return BTA_HFP_SCO_SAMP_RATE_8K; +} #define BTA_HF_CLIENT_ESCO_PARAM_IDX_CVSD_S1 4 /* eSCO setting for CVSD S1 */ static const tBTM_ESCO_PARAMS bta_hf_client_esco_params[] = { @@ -197,7 +213,11 @@ static UINT16 bta_hf_client_sco_get_frame_size(void) frame_size = bta_hf_client_cb.scb.out_pkt_len; break; case BTM_SCO_AIR_MODE_TRANSPNT: - frame_size = BTA_HF_CLIENT_MSBC_FRAME_SIZE; + if (bta_hf_client_cb.scb.negotiated_codec == BTM_SCO_CODEC_LC3) { + frame_size = BTA_HF_CLIENT_LC3_FRAME_SIZE; + } else { + frame_size = BTA_HF_CLIENT_MSBC_FRAME_SIZE; + } break; default: break; @@ -291,7 +311,7 @@ static void bta_hf_client_sco_conn_rsp(tBTM_ESCO_CONN_REQ_EVT_DATA *p_data) index = BTA_HF_CLIENT_ESCO_PARAM_IDX_CVSD_S1; } #endif /* BT_HF_CLIENT_BQB_INCLUDED */ - } else if (bta_hf_client_cb.scb.negotiated_codec == BTM_SCO_CODEC_MSBC) { + } else if (bta_hf_client_is_transparent_codec(bta_hf_client_cb.scb.negotiated_codec)) { index = BTA_HF_CLIENT_ESCO_PARAM_IDX_MSBC_T2; } } @@ -302,7 +322,7 @@ static void bta_hf_client_sco_conn_rsp(tBTM_ESCO_CONN_REQ_EVT_DATA *p_data) #if (BTM_SCO_HCI_INCLUDED == TRUE ) bta_hf_client_co_audio_state(bta_hf_client_cb.scb.sco_idx, SCO_STATE_SETUP, 0); - pcm_sample_rate = BTA_HFP_SCO_SAMP_RATE_8K; + pcm_sample_rate = bta_hf_client_sco_get_pcm_rate(bta_hf_client_cb.scb.negotiated_codec); /* initialize SCO setup, no voice setting for AG, data rate <==> sample rate */ BTM_ConfigScoPath(bta_hf_client_sco_co_init(pcm_sample_rate, pcm_sample_rate, &codec_info, 0), @@ -460,25 +480,34 @@ static void bta_hf_client_sco_data_send_cvsd(BT_HDR *p_buf, UINT16 out_pkt_len) } } +static UINT8 bta_hf_client_sco_transparent_air_pad(UINT16 frame_payload_size, UINT16 out_pkt_len) +{ + UINT16 air_len = frame_payload_size + BTA_HF_CLIENT_H2_HEADER_LEN; + if (out_pkt_len > air_len) { + return (UINT8)(out_pkt_len - air_len); + } + return 0; +} + /******************************************************************************* ** -** Function bta_hf_client_sco_data_send_msbc +** Function bta_hf_client_sco_data_send_transparent ** -** Description Process SCO data of mSBC air mode +** Description Process SCO data of transparent air mode (mSBC or LC3-SWB) ** ** ** Returns void ** *******************************************************************************/ -static void bta_hf_client_sco_data_send_msbc(BT_HDR *p_buf, UINT16 out_pkt_len) +static void bta_hf_client_sco_data_send_transparent(BT_HDR *p_buf, UINT16 out_pkt_len, UINT16 frame_payload_size) { - if (p_buf->len == BTA_HF_CLIENT_MSBC_FRAME_SIZE && p_buf->offset >= BTA_HF_CLIENT_BUFF_OFFSET_MIN + BTA_HF_CLIENT_H2_HEADER_LEN) { - /* add H2 header */ + UINT8 air_pad = bta_hf_client_sco_transparent_air_pad(frame_payload_size, out_pkt_len); + + if (p_buf->len == frame_payload_size && p_buf->offset >= BTA_HF_CLIENT_BUFF_OFFSET_MIN + BTA_HF_CLIENT_H2_HEADER_LEN) { p_buf->offset -= BTA_HF_CLIENT_H2_HEADER_LEN; UINT8 *p_data = (UINT8 *)(p_buf + 1) + p_buf->offset; bta_hf_client_h2_header((UINT16 *)p_data); - /* add header len, add addition one bytes, the len is BTA_HF_CLIENT_SCO_OUT_PKT_LEN_2EV3 now */ - p_buf->len += BTA_HF_CLIENT_H2_HEADER_LEN + 1; + p_buf->len += BTA_HF_CLIENT_H2_HEADER_LEN + air_pad; if (out_pkt_len == BTA_HF_CLIENT_SCO_OUT_PKT_LEN_2EV3) { /* mSBC frame can be send directly */ @@ -506,8 +535,7 @@ static void bta_hf_client_sco_data_send_msbc(BT_HDR *p_buf, UINT16 out_pkt_len) APPL_TRACE_WARNING("%s, invalid out pkt len: %d", __FUNCTION__, out_pkt_len); } } - else if (p_buf->len != 0 && p_buf->len % BTA_HF_CLIENT_MSBC_FRAME_SIZE == 0) { - /* multiple mSBC frame in the buffer, or just one but offset is too small */ + else if (p_buf->len != 0 && p_buf->len % frame_payload_size == 0) { UINT8 *p_data = (UINT8 *)(p_buf + 1) + p_buf->offset; UINT16 total_len = p_buf->len; if (out_pkt_len == BTA_HF_CLIENT_SCO_OUT_PKT_LEN_2EV3) { @@ -522,9 +550,9 @@ static void bta_hf_client_sco_data_send_msbc(BT_HDR *p_buf, UINT16 out_pkt_len) UINT8 *p_data2 = (UINT8 *)(p_buf2 + 1) + p_buf2->offset; bta_hf_client_h2_header((UINT16 *)p_data2); p_data2 += BTA_HF_CLIENT_H2_HEADER_LEN; - memcpy(p_data2, p_data, BTA_HF_CLIENT_MSBC_FRAME_SIZE); - p_data += BTA_HF_CLIENT_MSBC_FRAME_SIZE; - total_len -= BTA_HF_CLIENT_MSBC_FRAME_SIZE; + memcpy(p_data2, p_data, frame_payload_size); + p_data += frame_payload_size; + total_len -= frame_payload_size; bta_hf_client_write_sco_data(p_buf2, NULL); } } @@ -557,7 +585,7 @@ static void bta_hf_client_sco_data_send_msbc(BT_HDR *p_buf, UINT16 out_pkt_len) p_buf3->offset = BTA_HF_CLIENT_BUFF_OFFSET_MIN; p_buf3->len = BTA_HF_CLIENT_SCO_OUT_PKT_LEN_EV3; UINT8 *p_data3 = (UINT8 *)(p_buf3 + 1) + p_buf3->offset; - UINT16 rem_payload = BTA_HF_CLIENT_MSBC_FRAME_SIZE - (BTA_HF_CLIENT_SCO_OUT_PKT_LEN_EV3 - BTA_HF_CLIENT_H2_HEADER_LEN); + UINT16 rem_payload = frame_payload_size - (BTA_HF_CLIENT_SCO_OUT_PKT_LEN_EV3 - BTA_HF_CLIENT_H2_HEADER_LEN); memcpy(p_data3, p_data, rem_payload); p_data += rem_payload; total_len -= rem_payload; @@ -570,7 +598,7 @@ static void bta_hf_client_sco_data_send_msbc(BT_HDR *p_buf, UINT16 out_pkt_len) osi_free(p_buf); } else { - APPL_TRACE_WARNING("%s, unaccepted data len: %d", __FUNCTION__, p_buf->len); + APPL_TRACE_WARNING("%s, unaccepted data len: %d (frame %d)", __FUNCTION__, p_buf->len, frame_payload_size); osi_free(p_buf); } } @@ -604,7 +632,11 @@ void bta_hf_client_sco_data_send(tBTA_HF_CLIENT_DATA *p_data) bta_hf_client_sco_data_send_cvsd(p_buf, out_pkt_len); break; case BTM_SCO_AIR_MODE_TRANSPNT: - bta_hf_client_sco_data_send_msbc(p_buf, out_pkt_len); + if (bta_hf_client_cb.scb.negotiated_codec == BTM_SCO_CODEC_LC3) { + bta_hf_client_sco_data_send_transparent(p_buf, out_pkt_len, BTA_HF_CLIENT_LC3_FRAME_SIZE); + } else { + bta_hf_client_sco_data_send_transparent(p_buf, out_pkt_len, BTA_HF_CLIENT_MSBC_FRAME_SIZE); + } break; default: osi_free(p_buf); @@ -778,7 +810,7 @@ static void bta_hf_client_sco_create(BOOLEAN is_orig) (bta_hf_client_cb.scb.peer_features & BTA_HF_CLIENT_PEER_ESCO_S4)) { index = BTA_HF_CLIENT_ESCO_PARAM_IDX_CVSD_S4; } - } else if (bta_hf_client_cb.scb.negotiated_codec == BTM_SCO_CODEC_MSBC) { + } else if (bta_hf_client_is_transparent_codec(bta_hf_client_cb.scb.negotiated_codec)) { index = BTA_HF_CLIENT_ESCO_PARAM_IDX_MSBC_T2; } params = bta_hf_client_esco_params[index]; @@ -810,7 +842,7 @@ static void bta_hf_client_sco_create(BOOLEAN is_orig) /* Allow any platform specific pre-SCO set up to take place */ bta_hf_client_co_audio_state(bta_hf_client_cb.scb.sco_idx, SCO_STATE_SETUP, 0); - pcm_sample_rate = BTA_HFP_SCO_SAMP_RATE_8K; + pcm_sample_rate = bta_hf_client_sco_get_pcm_rate(bta_hf_client_cb.scb.negotiated_codec); sco_route = bta_hf_client_sco_co_init(pcm_sample_rate, pcm_sample_rate, &codec_info, 0); /* initialize SCO setup, no voice setting for AG, data rate <==> sample rate */ @@ -1165,7 +1197,9 @@ void bta_hf_client_sco_conn_open(tBTA_HF_CLIENT_DATA *p_data) bta_hf_client_cb.scb.out_pkt_len, BTA_HF_CLIENT_CI_SCO_DATA_EVT); #endif - if (bta_hf_client_cb.scb.negotiated_codec == BTM_SCO_CODEC_MSBC) { + if (bta_hf_client_cb.scb.negotiated_codec == BTM_SCO_CODEC_LC3) { + bta_hf_client_cback_sco(BTA_HF_CLIENT_AUDIO_LC3_OPEN_EVT); + } else if (bta_hf_client_cb.scb.negotiated_codec == BTM_SCO_CODEC_MSBC) { bta_hf_client_cback_sco(BTA_HF_CLIENT_AUDIO_MSBC_OPEN_EVT); } else { bta_hf_client_cback_sco(BTA_HF_CLIENT_AUDIO_OPEN_EVT); diff --git a/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_sdp.c b/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_sdp.c index 7faebb674ed..835eabccba6 100644 --- a/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_sdp.c +++ b/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_sdp.c @@ -26,6 +26,7 @@ #include #include "bta/bta_api.h" +#include "common/bt_target.h" #include "bta/bta_sys.h" #include "common/bt_defs.h" #include "bta/bta_hf_client_api.h" @@ -114,7 +115,11 @@ BOOLEAN bta_hf_client_add_record(char *p_service_name, UINT8 scn, /* add profile descriptor list */ profile_uuid = UUID_SERVCLASS_HF_HANDSFREE; +#if UC_BT_HFP_LC3_ENABLE + version = HFP_VERSION_1_9; +#else version = HFP_VERSION_1_8; +#endif result &= SDP_AddProfileDescriptorList(sdp_handle, profile_uuid, version); @@ -150,6 +155,12 @@ BOOLEAN bta_hf_client_add_record(char *p_service_name, UINT8 scn, sdp_features |= 0x0020; } +#if UC_BT_HFP_LC3_ENABLE + if (features & BTA_HF_CLIENT_FEAT_SWB) { + sdp_features |= 0x0100; /* SWB supported in SDP */ + } +#endif + UINT16_TO_BE_FIELD(buf, sdp_features); result &= SDP_AddAttribute(sdp_handle, ATTR_ID_SUPPORTED_FEATURES, UINT_DESC_TYPE, 2, buf); diff --git a/components/bt/host/bluedroid/bta/hf_client/include/bta_hf_client_int.h b/components/bt/host/bluedroid/bta/hf_client/include/bta_hf_client_int.h index d2b34518f1a..22d2b6a52b7 100644 --- a/components/bt/host/bluedroid/bta/hf_client/include/bta_hf_client_int.h +++ b/components/bt/host/bluedroid/bta/hf_client/include/bta_hf_client_int.h @@ -31,6 +31,7 @@ #define HFP_VERSION_1_6 0x0106 #define HFP_VERSION_1_7 0x0107 #define HFP_VERSION_1_8 0x0108 +#define HFP_VERSION_1_9 0x0109 /* RFCOMM MTU SIZE */ #define BTA_HF_CLIENT_MTU 256 @@ -39,6 +40,13 @@ #define BTA_HF_CLIENT_ACP 0 /* accepted connection */ #define BTA_HF_CLIENT_INT 1 /* initiating connection */ +/* feature mask that matches spec */ +#define BTA_HF_CLIENT_BRSF_FEAT_SPEC (BTA_HF_CLIENT_FEAT_ECNR | BTA_HF_CLIENT_FEAT_3WAY | \ + BTA_HF_CLIENT_FEAT_CLI | BTA_HF_CLIENT_FEAT_VREC | \ + BTA_HF_CLIENT_FEAT_VOL | BTA_HF_CLIENT_FEAT_ECS | \ + BTA_HF_CLIENT_FEAT_ECC | BTA_HF_CLIENT_FEAT_CODEC | \ + BTA_HF_CLIENT_FEAT_HF_IND | BTA_HF_CLIENT_FEAT_ESCO_S4) + /* Timer to wait for retry in case of collision */ #ifndef BTA_HF_CLIENT_COLLISION_TIMER #define BTA_HF_CLIENT_COLLISION_TIMER 2411 @@ -51,6 +59,8 @@ #define BTA_HF_CLIENT_H2_HEADER_LEN 2 /* mSBC frame size not include H1/H2 header */ #define BTA_HF_CLIENT_MSBC_FRAME_SIZE 57 +#define BTA_HF_CLIENT_LC3_FRAME_SIZE 58 +#define BTA_HF_CLIENT_LC3_VOHCI_FRAME_SIZE 60 /* max user data len of sco packet type EV3 */ #define BTA_HF_CLIENT_SCO_OUT_PKT_LEN_EV3 30 /* max user data len of sco packet type 2-EV3 */ @@ -209,6 +219,7 @@ typedef struct { UINT8 scn; tBTA_HF_CLIENT_CBACK *p_cback; /* application callback */ BOOLEAN msbc_enabled; + BOOLEAN lc3_enabled; } tBTA_HF_CLIENT_CB; /***************************************************************************** diff --git a/components/bt/host/bluedroid/bta/include/bta/bta_ag_api.h b/components/bt/host/bluedroid/bta/include/bta/bta_ag_api.h index ac714d85951..fd2bc7a0802 100644 --- a/components/bt/host/bluedroid/bta/include/bta/bta_ag_api.h +++ b/components/bt/host/bluedroid/bta/include/bta/bta_ag_api.h @@ -92,6 +92,7 @@ typedef UINT16 tBTA_AG_CHLD_FEAT; #define BTA_AG_CODEC_NONE BTM_SCO_CODEC_NONE #define BTA_AG_CODEC_CVSD BTM_SCO_CODEC_CVSD /* CVSD */ #define BTA_AG_CODEC_MSBC BTM_SCO_CODEC_MSBC /* mSBC */ +#define BTA_AG_CODEC_LC3 BTM_SCO_CODEC_LC3 /* LC3-SWB */ typedef UINT16 tBTA_AG_PEER_CODEC; /* AG parse mode */ @@ -159,6 +160,7 @@ typedef UINT8 tBTA_AG_RES; #define BTA_AG_AUDIO_MSBC_OPEN_EVT 32 /* Audio connection with mSBC codec open */ #define BTA_AG_PKT_NUMS_GET_EVT 33 /* AG packet status nums */ +#define BTA_AG_AUDIO_LC3_OPEN_EVT 34 /* Audio connection with LC3-SWB codec open */ /* Values below are for HFP only */ #define BTA_AG_AT_A_EVT 10 /* Answer a incoming call */ @@ -363,7 +365,8 @@ typedef struct tBTA_AG_CHLD_FEAT chld_feat; } tBTA_AG_CONN; -/* data associated with BTA_AG_AUDIO_OPEN_EVT, BTA_AG_AUDIO_CLOSE_EVT or BTA_AG_AUDIO_MSBC_OPEN_EVT */ +/* data associated with BTA_AG_AUDIO_OPEN_EVT, BTA_AG_AUDIO_CLOSE_EVT, */ +/* BTA_AG_AUDIO_MSBC_OPEN_EVT or BTA_AG_AUDIO_LC3_OPEN_EVT */ typedef struct { tBTA_AG_HDR hdr; diff --git a/components/bt/host/bluedroid/bta/include/bta/bta_hf_client_api.h b/components/bt/host/bluedroid/bta/include/bta/bta_hf_client_api.h index d2731d28770..af0b87d7432 100644 --- a/components/bt/host/bluedroid/bta/include/bta/bta_hf_client_api.h +++ b/components/bt/host/bluedroid/bta/include/bta/bta_hf_client_api.h @@ -36,6 +36,7 @@ /* Hands-Free unit(HF) version */ #define HFP_HF_VERSION_1_6 0x0106 /* v1.6 */ #define HFP_HF_VERSION_1_7 0x0107 /* v1.7 */ +#define HFP_HF_VERSION_1_9 0x0109 /* v1.9 */ /* HFP peer (AG) features*/ #define BTA_HF_CLIENT_PEER_FEAT_3WAY 0x00000001 /* Three-way calling */ @@ -66,6 +67,9 @@ typedef UINT16 tBTA_HF_CLIENT_PEER_FEAT; #define BTA_HF_CLIENT_FEAT_HF_IND 0x00000100 /* HF indicators */ #define BTA_HF_CLIENT_FEAT_ESCO_S4 0x00000200 /* eSCO S4 Setting Supported */ +/* Proprietary features: using 31 ~ 16 bits, AT+BRSF bit 12 ~ 31 are reserved by spec */ +#define BTA_HF_CLIENT_FEAT_SWB 0x00010000 /* Super Wideband Speech */ + /* HFP HF extended call handling - masks not related to any spec */ #define BTA_HF_CLIENT_CHLD_REL 0x00000001 /* 0 Release waiting call or held calls */ #define BTA_HF_CLIENT_CHLD_REL_ACC 0x00000002 /* 1 Release active calls and accept other (waiting or held) cal */ @@ -114,6 +118,7 @@ typedef UINT8 tBTA_HF_CLIENT_AT_RESULT_TYPE; #define BTA_HF_CLIENT_RING_INDICATION 21 /* HF Client ring indication */ #define BTA_HF_CLIENT_DISABLE_EVT 22 /* HF Client disabled */ #define BTA_HF_CLIENT_PKT_STAT_NUMS_GET_EVT 23 /* HF Client packet status nums */ +#define BTA_HF_CLIENT_AUDIO_LC3_OPEN_EVT 24 /* Audio connection with LC3-SWB codec open */ typedef UINT8 tBTA_HF_CLIENT_EVT; @@ -188,7 +193,7 @@ typedef struct { tBTA_HF_CLIENT_CHLD_FEAT chld_feat; } tBTA_HF_CLIENT_CONN; -/* data associated with BTA_HF_CLIENT_AUDIO_XXX_EVT */ +/* data associated with BTA_HF_CLIENT_AUDIO_XXX_EVT (OPEN/MSBC/LC3/CLOSE) */ typedef struct { tBTA_HF_CLIENT_HDR hdr; UINT16 preferred_frame_size; diff --git a/components/bt/host/bluedroid/bta/include/bta/bta_hfp_defs.h b/components/bt/host/bluedroid/bta/include/bta/bta_hfp_defs.h index 752b2be38fd..0c65eba91a1 100644 --- a/components/bt/host/bluedroid/bta/include/bta/bta_hfp_defs.h +++ b/components/bt/host/bluedroid/bta/include/bta/bta_hfp_defs.h @@ -12,6 +12,7 @@ #define BTA_HFP_CODEC_NONE BTM_SCO_CODEC_NONE #define BTA_HFP_CODEC_CVSD BTM_SCO_CODEC_CVSD /* CVSD */ #define BTA_HFP_CODEC_MSBC BTM_SCO_CODEC_MSBC /* mSBC */ +#define BTA_HFP_CODEC_LC3 BTM_SCO_CODEC_LC3 /* LC3-SWB */ typedef UINT16 tBTA_HFP_PEER_CODEC; @@ -25,6 +26,7 @@ typedef UINT8 tBTA_HFP_SCO_CODEC_TYPE; #define BTA_HFP_SCO_SAMP_RATE_8K 8000 #define BTA_HFP_SCO_SAMP_RATE_16K 16000 +#define BTA_HFP_SCO_SAMP_RATE_32K 32000 /* SCO codec information */ typedef struct { diff --git a/components/bt/host/bluedroid/btc/profile/std/hf_ag/bta_ag_co.c b/components/bt/host/bluedroid/btc/profile/std/hf_ag/bta_ag_co.c index a127b2ff7d9..19ff3f618c1 100644 --- a/components/bt/host/bluedroid/btc/profile/std/hf_ag/bta_ag_co.c +++ b/components/bt/host/bluedroid/btc/profile/std/hf_ag/bta_ag_co.c @@ -307,7 +307,7 @@ static void bta_ag_decode_msbc_frame(UINT8 **data, UINT8 *length, BOOLEAN is_bad *length = (UINT8)frame_len; } -// PLC_INCLUDED will be set to TRUE when enabling Wide Band Speech +// PLC_INCLUDED will be set to TRUE when enabling Wideband Speech #if (PLC_INCLUDED == TRUE) switch(status) { case OI_OK: diff --git a/components/bt/host/bluedroid/btc/profile/std/hf_ag/btc_hf_ag.c b/components/bt/host/bluedroid/btc/profile/std/hf_ag/btc_hf_ag.c index 17c13ce7853..d334ebb7bef 100644 --- a/components/bt/host/bluedroid/btc/profile/std/hf_ag/btc_hf_ag.c +++ b/components/bt/host/bluedroid/btc/profile/std/hf_ag/btc_hf_ag.c @@ -106,7 +106,7 @@ hf_local_param_t *hf_local_param_ptr = NULL; #endif #endif -/* wide band synchronous */ +/* Wideband synchronous */ #ifndef BTC_HF_WBS_PREFERRED #define BTC_HF_WBS_PREFERRED TRUE #endif @@ -203,6 +203,38 @@ static BOOLEAN is_connected(int idx, bt_bdaddr_t *bd_addr) return FALSE; } +static esp_hf_codec_mode_t btc_hf_bta_codec_to_esp(tBTA_AG_PEER_CODEC codec) +{ + if (codec == BTA_AG_CODEC_NONE) { + return ESP_HF_CODEC_NONE; + } + if (codec & BTA_AG_CODEC_LC3) { + return ESP_HF_CODEC_LC3; + } + if (codec & BTA_AG_CODEC_MSBC) { + return ESP_HF_CODEC_MSBC; + } + if (codec & BTA_AG_CODEC_CVSD) { + return ESP_HF_CODEC_CVSD; + } + return ESP_HF_CODEC_NONE; +} + +static tBTA_AG_PEER_CODEC btc_hf_esp_codec_to_bta(esp_hf_codec_mode_t mode) +{ + switch (mode) { + case ESP_HF_CODEC_CVSD: + return BTA_AG_CODEC_CVSD; + case ESP_HF_CODEC_MSBC: + return BTA_AG_CODEC_MSBC; + case ESP_HF_CODEC_LC3: + return BTA_AG_CODEC_LC3; + case ESP_HF_CODEC_NONE: + default: + return BTA_AG_CODEC_NONE; + } +} + static int btc_hf_latest_connected_idx(void) { struct timespec now, conn_time_delta; @@ -265,7 +297,8 @@ static void bte_hf_evt(tBTA_AG_EVT event, tBTA_AG *param) else if (BTA_AG_CONN_EVT == event) { param_len = sizeof(tBTA_AG_CONN); } - else if ((BTA_AG_AUDIO_OPEN_EVT == event) || (BTA_AG_AUDIO_CLOSE_EVT == event) || (BTA_AG_AUDIO_MSBC_OPEN_EVT == event)) { + else if ((BTA_AG_AUDIO_OPEN_EVT == event) || (BTA_AG_AUDIO_CLOSE_EVT == event) || + (BTA_AG_AUDIO_MSBC_OPEN_EVT == event) || (BTA_AG_AUDIO_LC3_OPEN_EVT == event)) { param_len = sizeof(tBTA_AG_AUDIO_STAT); } else if (param) { @@ -465,6 +498,22 @@ bt_status_t btc_hf_disconnect(bt_bdaddr_t *bd_addr) return BT_STATUS_FAIL; } +bt_status_t btc_hf_set_codec(bt_bdaddr_t *bd_addr, esp_hf_codec_mode_t mode) +{ + int idx = btc_hf_idx_by_bdaddr(bd_addr); + if ((idx < 0) || (idx >= BTC_HF_NUM_CB) || !hf_local_param.initialized) { + return BT_STATUS_FAIL; + } + /* AT+BAC may arrive before SLC_CONNECTED; allow CONNECTED or SLC_CONNECTED */ + if (!is_connected(idx, bd_addr)) { + BTIF_TRACE_WARNING("%s: HF AG not connected", __FUNCTION__); + return BT_STATUS_NOT_READY; + } + + BTA_AgSetCodec(hf_local_param.btc_hf_cb[idx].handle, btc_hf_esp_codec_to_bta(mode)); + return BT_STATUS_SUCCESS; +} + bt_status_t btc_hf_connect_audio(bt_bdaddr_t *bd_addr) { int idx = btc_hf_idx_by_bdaddr(bd_addr); @@ -1299,6 +1348,12 @@ void btc_hf_call_handler(btc_msg_t *msg) break; } + case BTC_HF_SET_CODEC_EVT: + { + btc_hf_set_codec(&arg->set_codec.remote_addr, arg->set_codec.mode); + break; + } + default: BTC_TRACE_WARNING("%s : unhandled event: %d\n", __FUNCTION__, msg->act); } @@ -1454,6 +1509,20 @@ void btc_hf_cb_handler(btc_msg_t *msg) } while (0); break; } + case BTA_AG_AUDIO_LC3_OPEN_EVT: + { + idx = p_data->hdr.handle - 1; + CHECK_HF_IDX(idx); + do { + param.audio_stat.state = ESP_HF_AUDIO_STATE_CONNECTED_LC3; + memcpy(param.audio_stat.remote_addr, &hf_local_param.btc_hf_cb[idx].connected_bda,sizeof(esp_bd_addr_t)); + hf_local_param.btc_hf_cb[idx].sync_conn_hdl = p_data->hdr.sync_conn_handle; + param.audio_stat.sync_conn_handle = p_data->hdr.sync_conn_handle; + param.audio_stat.preferred_frame_size = p_data->audio_stat.preferred_frame_size; + btc_hf_cb_to_app(ESP_HF_AUDIO_STATE_EVT, ¶m); + } while (0); + break; + } case BTA_AG_AUDIO_CLOSE_EVT: { idx = p_data->hdr.handle - 1; @@ -1641,18 +1710,24 @@ void btc_hf_cb_handler(btc_msg_t *msg) CHECK_HF_IDX(idx); BTC_TRACE_DEBUG("AG Bitmap of peer-codecs %d", p_data->val.num); #if (BTM_WBS_INCLUDED == TRUE) - /* If the peer supports mSBC and the BTC preferred codec is also mSBC, then - ** we should set the BTA AG Codec to mSBC. This would trigger a +BCS to mSBC at the time - ** of SCO connection establishment */ + /* Default stack preference; application may override in ESP_HF_BAC_RESPONSE_EVT */ +#if UC_BT_HFP_LC3_ENABLE + if (p_data->val.num & BTA_AG_CODEC_LC3) { + BTC_TRACE_DEBUG("%s btc_hf override-Preferred Codec to LC3", __FUNCTION__); + BTA_AgSetCodec(hf_local_param.btc_hf_cb[idx].handle, BTA_AG_CODEC_LC3); + } else +#endif if ((btc_conf_hf_force_wbs == TRUE) && (p_data->val.num & BTA_AG_CODEC_MSBC)) { BTC_TRACE_DEBUG("%s btc_hf override-Preferred Codec to MSBC", __FUNCTION__); - BTA_AgSetCodec(hf_local_param.btc_hf_cb[idx].handle,BTA_AG_CODEC_MSBC); - } - else { + BTA_AgSetCodec(hf_local_param.btc_hf_cb[idx].handle, BTA_AG_CODEC_MSBC); + } else { BTC_TRACE_DEBUG("%s btc_hf override-Preferred Codec to CVSD", __FUNCTION__); - BTA_AgSetCodec(hf_local_param.btc_hf_cb[idx].handle,BTA_AG_CODEC_CVSD); + BTA_AgSetCodec(hf_local_param.btc_hf_cb[idx].handle, BTA_AG_CODEC_CVSD); } #endif + memcpy(param.bac_rep.remote_addr, &hf_local_param.btc_hf_cb[idx].connected_bda, sizeof(esp_bd_addr_t)); + param.bac_rep.peer_codecs = p_data->val.num; + btc_hf_cb_to_app(ESP_HF_BAC_RESPONSE_EVT, ¶m); break; } #if (BTM_WBS_INCLUDED == TRUE) @@ -1661,9 +1736,11 @@ void btc_hf_cb_handler(btc_msg_t *msg) idx = p_data->hdr.handle - 1; CHECK_HF_IDX(idx); do { - BTC_TRACE_DEBUG("Set codec status %d codec %d 1=CVSD 2=MSBC", p_data->val.hdr.status, p_data->val.num); - memcpy(param.wbs_rep.remote_addr, &hf_local_param.btc_hf_cb[idx].connected_bda,sizeof(esp_bd_addr_t)); - param.wbs_rep.codec = p_data->val.num; + BTC_TRACE_DEBUG("Set codec status %d codec %d", p_data->val.hdr.status, p_data->val.num); + memcpy(param.wbs_rep.remote_addr, &hf_local_param.btc_hf_cb[idx].connected_bda, sizeof(esp_bd_addr_t)); + param.wbs_rep.status = (p_data->val.hdr.status == BTA_AG_SUCCESS) ? + ESP_BT_STATUS_SUCCESS : ESP_BT_STATUS_FAIL; + param.wbs_rep.codec = btc_hf_bta_codec_to_esp(p_data->val.num); btc_hf_cb_to_app(ESP_HF_WBS_RESPONSE_EVT, ¶m); } while (0); break; @@ -1674,10 +1751,9 @@ void btc_hf_cb_handler(btc_msg_t *msg) idx = p_data->hdr.handle - 1; CHECK_HF_IDX(idx); do { - BTC_TRACE_DEBUG("AG final seleded codec is %d 1=CVSD 2=MSBC", p_data->val.num); - memcpy(param.bcs_rep.remote_addr, &hf_local_param.btc_hf_cb[idx].connected_bda,sizeof(esp_bd_addr_t)); - param.bcs_rep.mode = p_data->val.num; - /* No ESP_HF_WBS_NONE case, because HFP 1.6 supported device can send BCS */ + BTC_TRACE_DEBUG("AG final selected codec is %d", p_data->val.num); + memcpy(param.bcs_rep.remote_addr, &hf_local_param.btc_hf_cb[idx].connected_bda, sizeof(esp_bd_addr_t)); + param.bcs_rep.mode = btc_hf_bta_codec_to_esp(p_data->val.num); btc_hf_cb_to_app(ESP_HF_BCS_RESPONSE_EVT, ¶m); } while (0); break; diff --git a/components/bt/host/bluedroid/btc/profile/std/hf_client/bta_hf_client_co.c b/components/bt/host/bluedroid/btc/profile/std/hf_client/bta_hf_client_co.c index 81fc431d090..b781759a85b 100644 --- a/components/bt/host/bluedroid/btc/profile/std/hf_client/bta_hf_client_co.c +++ b/components/bt/host/bluedroid/btc/profile/std/hf_client/bta_hf_client_co.c @@ -433,7 +433,7 @@ static void bta_hf_client_decode_msbc_frame(UINT8 **data, UINT8 *length, BOOLEAN *length = (UINT8)frame_len; } -// PLC_INCLUDED will be set to TRUE when enabling Wide Band Speech +// PLC_INCLUDED will be set to TRUE when enabling Wideband Speech #if (PLC_INCLUDED == TRUE) switch(status){ case OI_OK: @@ -534,18 +534,21 @@ void bta_hf_client_sco_co_in_data(BT_HDR *p_buf, tBTM_SCO_DATA_FLAG status) } p_new_buf->offset = 0; UINT8 *p_data = (UINT8 *)(p_new_buf + 1) + p_new_buf->offset; + UINT16 data_len = BTM_MSBC_FRAME_SIZE; memcpy(p_data, bta_hf_client_co_cb.rx_half_msbc_data, BTM_MSBC_FRAME_SIZE / 2); memcpy(p_data + BTM_MSBC_FRAME_SIZE / 2, p, pkt_size); osi_free(p_buf); if (BTA_HF_H2_HEADER_SYNC_WORD_CHECK(p_data)) { /* H2 header sync word found, skip */ p_data += 2; + data_len -= 2; } else if (!bta_hf_client_co_cb.is_bad_frame){ /* not a bad frame, assume as H1 header */ p_data += 1; + data_len -= 1; } - btc_hf_client_audio_data_cb_to_app((uint8_t *)p_new_buf, (uint8_t *)p_data, BTM_MSBC_FRAME_SIZE, bta_hf_client_co_cb.is_bad_frame); + btc_hf_client_audio_data_cb_to_app((uint8_t *)p_new_buf, (uint8_t *)p_data, data_len, bta_hf_client_co_cb.is_bad_frame); bta_hf_client_co_cb.is_bad_frame = false; memset(bta_hf_client_co_cb.rx_half_msbc_data, 0, BTM_MSBC_FRAME_SIZE / 2); } @@ -555,15 +558,18 @@ void bta_hf_client_sco_co_in_data(BT_HDR *p_buf, tBTM_SCO_DATA_FLAG status) if (pkt_size > BTM_MSBC_FRAME_SIZE) { pkt_size = BTM_MSBC_FRAME_SIZE; } + UINT16 data_len = pkt_size; if (BTA_HF_H2_HEADER_SYNC_WORD_CHECK(p)) { /* H2 header sync word found, skip */ p += 2; + data_len -= 2; } else if (!bta_hf_client_co_cb.is_bad_frame){ /* not a bad frame, assume as H1 header */ p += 1; + data_len -= 1; } - btc_hf_client_audio_data_cb_to_app((uint8_t *)p_buf, (uint8_t *)p, pkt_size, bta_hf_client_co_cb.is_bad_frame); + btc_hf_client_audio_data_cb_to_app((uint8_t *)p_buf, (uint8_t *)p, data_len, bta_hf_client_co_cb.is_bad_frame); bta_hf_client_co_cb.is_bad_frame = false; } else { diff --git a/components/bt/host/bluedroid/btc/profile/std/hf_client/btc_hf_client.c b/components/bt/host/bluedroid/btc/profile/std/hf_client/btc_hf_client.c index 81aa96a8ec7..36bece5b168 100644 --- a/components/bt/host/bluedroid/btc/profile/std/hf_client/btc_hf_client.c +++ b/components/bt/host/bluedroid/btc/profile/std/hf_client/btc_hf_client.c @@ -78,7 +78,12 @@ void bta_hf_client_bqb_esco_s4_ctrl(BOOLEAN enable) /************************************************************************************ ** Static variables ************************************************************************************/ -const int btc_hf_client_version = HFP_HF_VERSION_1_7; +const int btc_hf_client_version = +#if UC_BT_HFP_LC3_ENABLE + HFP_HF_VERSION_1_9; +#else + HFP_HF_VERSION_1_7; +#endif #if HFP_DYNAMIC_MEMORY == FALSE static hf_client_local_param_t hf_client_local_param; @@ -796,6 +801,9 @@ bt_status_t btc_hf_client_execute_service(BOOLEAN b_enable) /* Enable and register with BTA-HFClient */ BTA_HfClientEnable(bte_hf_client_evt); hf_client_local_param.btc_hf_client_features = BTC_HF_CLIENT_FEATURES; +#if UC_BT_HFP_LC3_ENABLE + hf_client_local_param.btc_hf_client_features |= BTA_HF_CLIENT_FEAT_SWB; +#endif if (btc_hf_client_version >= HFP_HF_VERSION_1_7) { hf_client_local_param.btc_hf_client_features |= BTA_HF_CLIENT_FEAT_ESCO_S4; @@ -1114,6 +1122,17 @@ void btc_hf_client_cb_handler(btc_msg_t *msg) btc_hf_client_cb_to_app(ESP_HF_CLIENT_AUDIO_STATE_EVT, ¶m); } while (0); break; + case BTA_HF_CLIENT_AUDIO_LC3_OPEN_EVT: + do { + param.audio_stat.state = ESP_HF_CLIENT_AUDIO_STATE_CONNECTED_LC3; + memcpy(param.audio_stat.remote_bda, &hf_client_local_param.btc_hf_client_cb.connected_bda, + sizeof(esp_bd_addr_t)); + hf_client_local_param.btc_hf_client_cb.sync_conn_hdl = p_data->hdr.sync_conn_handle; + param.audio_stat.sync_conn_handle = p_data->hdr.sync_conn_handle; + param.audio_stat.preferred_frame_size = p_data->audio_stat.preferred_frame_size; + btc_hf_client_cb_to_app(ESP_HF_CLIENT_AUDIO_STATE_EVT, ¶m); + } while (0); + break; case BTA_HF_CLIENT_AUDIO_CLOSE_EVT: do { param.audio_stat.state = ESP_HF_CLIENT_AUDIO_STATE_DISCONNECTED; diff --git a/components/bt/host/bluedroid/btc/profile/std/include/btc_hf_ag.h b/components/bt/host/bluedroid/btc/profile/std/include/btc_hf_ag.h index aae9356b87f..8ac94acb173 100644 --- a/components/bt/host/bluedroid/btc/profile/std/include/btc_hf_ag.h +++ b/components/bt/host/bluedroid/btc/profile/std/include/btc_hf_ag.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -57,7 +57,8 @@ typedef enum //REG BTC_HF_REGISTER_DATA_CALLBACK_EVT, BTC_HF_REGISTER_AUDIO_DATA_CALLBACK_EVT, - BTC_HF_REQUEST_PKT_STAT_EVT + BTC_HF_REQUEST_PKT_STAT_EVT, + BTC_HF_SET_CODEC_EVT } btc_hf_act_t; /* btc_hf_args_t */ @@ -190,6 +191,12 @@ typedef union UINT16 sync_conn_handle; } pkt_sync_hd; + // BTC_HF_SET_CODEC_EVT + struct ag_set_codec_param { + bt_bdaddr_t remote_addr; + esp_hf_codec_mode_t mode; + } set_codec; + } btc_hf_args_t; /************************************************************************************ 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 69cfe5d970b..2fd3bbb5958 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 @@ -805,13 +805,20 @@ #define UC_BT_HFP_AUDIO_DATA_PATH_HCI FALSE #endif -//Wide Band Speech +//Wideband Speech #ifdef CONFIG_BT_HFP_WBS_ENABLE #define UC_BT_HFP_WBS_ENABLE CONFIG_BT_HFP_WBS_ENABLE #else #define UC_BT_HFP_WBS_ENABLE FALSE #endif +//Super Wideband Speech (LC3-SWB) +#ifdef CONFIG_BT_HFP_LC3_ENABLE +#define UC_BT_HFP_LC3_ENABLE CONFIG_BT_HFP_LC3_ENABLE +#else +#define UC_BT_HFP_LC3_ENABLE FALSE +#endif + /********************************************************** * Memory reference **********************************************************/ 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 ae1a8cfd5b4..44568966146 100644 --- a/components/bt/host/bluedroid/common/include/common/bt_target.h +++ b/components/bt/host/bluedroid/common/include/common/bt_target.h @@ -1221,6 +1221,20 @@ #define BTM_MSBC_FRAME_DATA_SIZE 57 #endif +/* LC3-SWB eSCO air frame (H2 + payload); same 60-byte eSCO packet as mSBC */ +#ifndef BTM_LC3_FRAME_SIZE +#define BTM_LC3_FRAME_SIZE 60 +#endif + +#ifndef BTM_LC3_FRAME_DATA_SIZE +#define BTM_LC3_FRAME_DATA_SIZE 58 +#endif + +/* Transparent wideband air frame size (mSBC and LC3-SWB) */ +#ifndef BTM_HFP_TRANSPARENT_FRAME_SIZE +#define BTM_HFP_TRANSPARENT_FRAME_SIZE BTM_MSBC_FRAME_SIZE +#endif + /* The size in bytes of the BTM inquiry database. 5 As Default */ #ifndef BTM_INQ_DB_SIZE #define BTM_INQ_DB_SIZE 5 diff --git a/components/bt/host/bluedroid/stack/include/stack/btm_api.h b/components/bt/host/bluedroid/stack/include/stack/btm_api.h index 11163bc051f..07b4c3ab87f 100644 --- a/components/bt/host/bluedroid/stack/include/stack/btm_api.h +++ b/components/bt/host/bluedroid/stack/include/stack/btm_api.h @@ -1116,6 +1116,7 @@ typedef UINT8 tBTM_SCO_ROUTE_TYPE; #define BTM_SCO_CODEC_NONE 0x0000 #define BTM_SCO_CODEC_CVSD 0x0001 #define BTM_SCO_CODEC_MSBC 0x0002 +#define BTM_SCO_CODEC_LC3 0x0004 typedef UINT16 tBTM_SCO_CODEC_TYPE; diff --git a/components/bt/host/bluedroid/stack/include/stack/sdpdefs.h b/components/bt/host/bluedroid/stack/include/stack/sdpdefs.h index d7eab356208..4a1b969a655 100644 --- a/components/bt/host/bluedroid/stack/include/stack/sdpdefs.h +++ b/components/bt/host/bluedroid/stack/include/stack/sdpdefs.h @@ -279,6 +279,7 @@ #if (BTM_WBS_INCLUDED == TRUE) #define UUID_CODEC_CVSD 0x0001 /* CVSD */ #define UUID_CODEC_MSBC 0x0002 /* mSBC */ +#define UUID_CODEC_LC3 0x0003 /* LC3-SWB */ #endif /* Define all the 'Descriptor Type' values. @@ -308,7 +309,7 @@ #define LANG_ID_CODE_ENGLISH ((UINT16) 0x656e) /* "en" */ #define LANG_ID_CHAR_ENCODE_UTF8 ((UINT16) 0x006a) /* UTF-8 */ -/* Constants used for display purposes only. These define ovelapping attribute values */ +/* Constants used for display purposes only. These define overlapping attribute values */ #define ATTR_ID_VERS_OR_GRP_OR_DRELNUM_OR_IPSUB_OR_SPECID 0x0200 #define ATTR_ID_VEND_ID_OR_SERVICE_DB_STATE_OR_PARSE_VER 0x0201 #define ATTR_ID_PROD_ID_OR_HID_DEV_SUBCLASS 0x0202 diff --git a/docs/conf_common.py b/docs/conf_common.py index 57588afe0dc..3d4e209b3d6 100644 --- a/docs/conf_common.py +++ b/docs/conf_common.py @@ -103,6 +103,7 @@ CLASSIC_BT_DOCS = [ 'migration-guides/release-5.x/5.3/bluetooth-classic.rst', 'migration-guides/release-5.x/5.4/bluetooth-classic.rst', 'migration-guides/release-6.x/6.0/bluetooth-classic.rst', + 'migration-guides/release-6.x/6.2/bluetooth-classic.rst', ] BLUFI_DOCS = ['api-guides/ble/blufi.rst', 'api-reference/bluetooth/esp_blufi.rst'] diff --git a/docs/en/migration-guides/release-6.x/6.2/bluetooth-classic.rst b/docs/en/migration-guides/release-6.x/6.2/bluetooth-classic.rst new file mode 100644 index 00000000000..8074179cdf1 --- /dev/null +++ b/docs/en/migration-guides/release-6.x/6.2/bluetooth-classic.rst @@ -0,0 +1,65 @@ +Bluetooth Classic +================= + +:link_to_translation:`zh_CN:[中文]` + +Bluedroid +--------- + + The following Bluedroid APIs have been changed: + + - :component_file:`/bt/host/bluedroid/api/include/api/esp_hf_defs.h` + + - Type ``esp_hf_wbs_config_t`` is replaced by ``esp_hf_codec_mode_t``. The new type identifies the negotiated codec (CVSD, mSBC, or LC3-SWB), not a Wideband Speech on/off flag. ``esp_hf_wbs_config_t`` is kept as a deprecated alias of ``esp_hf_codec_mode_t``. + - Enumerators are renamed. Numeric values 0 / 1 / 2 are unchanged. The old ``ESP_HF_WBS_*`` names remain as macros that alias the new enumerators: + + .. list-table:: + :header-rows: 1 + :widths: 40 40 20 + + * - Old enumerator + - New enumerator + - Value + * - ``ESP_HF_WBS_NONE`` + - ``ESP_HF_CODEC_NONE`` + - 0 + * - ``ESP_HF_WBS_NO`` + - ``ESP_HF_CODEC_CVSD`` + - 1 + * - ``ESP_HF_WBS_YES`` + - ``ESP_HF_CODEC_MSBC`` + - 2 + * - — + - ``ESP_HF_CODEC_LC3`` + - 3 + + - Existing ``switch`` statements and comparisons that use ``ESP_HF_WBS_NO`` or ``ESP_HF_WBS_YES`` still compile. Prefer the new names in new code. + + - ``ESP_HF_CODEC_NONE`` means no codec has been negotiated, or the preference is reset to the stack default. It does not mean that CVSD is unavailable. ``ESP_HF_CODEC_CVSD`` is the selected narrowband codec, whether Wideband Speech is unsupported, disabled, or the link fell back from mSBC / LC3. + + - ``ESP_HF_CODEC_LC3`` is new. Code that indexes a three-entry table with the codec value must add an LC3 entry or a default branch. + + - Do not use ``esp_hf_codec_mode_t`` as a capability bitmap. Peer codec capabilities are reported separately as ``ESP_HF_CODEC_CAP_CVSD`` / ``ESP_HF_CODEC_CAP_MSBC`` / ``ESP_HF_CODEC_CAP_LC3`` in ``ESP_HF_BAC_RESPONSE_EVT``. + + - Audio connection state ``ESP_HF_AUDIO_STATE_CONNECTED_LC3`` is added for an established LC3-SWB link, alongside the existing ``ESP_HF_AUDIO_STATE_CONNECTED`` (CVSD) and ``ESP_HF_AUDIO_STATE_CONNECTED_MSBC`` values. + + - :component_file:`/bt/host/bluedroid/api/include/api/esp_hf_ag_api.h` + + - ``esp_hf_ag_set_codec()`` now takes ``esp_hf_codec_mode_t``. Pass ``ESP_HF_CODEC_CVSD``, ``ESP_HF_CODEC_MSBC``, ``ESP_HF_CODEC_LC3``, or ``ESP_HF_CODEC_NONE`` to restore the stack default. + + - ``ESP_HF_WBS_RESPONSE_EVT`` (``wbs_rep``): field ``codec`` is now ``esp_hf_codec_mode_t``. The field name ``codec`` is unchanged. A ``status`` field (``esp_bt_status_t``) is added to report whether the request succeeded. + + - ``ESP_HF_BCS_RESPONSE_EVT`` (``bcs_rep``): field ``mode`` is now ``esp_hf_codec_mode_t``. The field name ``mode`` is unchanged. + + Suggested replacements: + + .. code-block:: c + + // Before + esp_hf_ag_set_codec(bda, ESP_HF_WBS_YES); + if (param->bcs_rep.mode == ESP_HF_WBS_NO) { /* CVSD */ } + + // After + esp_hf_ag_set_codec(bda, ESP_HF_CODEC_MSBC); + if (param->bcs_rep.mode == ESP_HF_CODEC_CVSD) { /* CVSD */ } + else if (param->bcs_rep.mode == ESP_HF_CODEC_LC3) { /* LC3-SWB */ } diff --git a/docs/en/migration-guides/release-6.x/6.2/index.rst b/docs/en/migration-guides/release-6.x/6.2/index.rst index c19f7650634..9045e81aebd 100644 --- a/docs/en/migration-guides/release-6.x/6.2/index.rst +++ b/docs/en/migration-guides/release-6.x/6.2/index.rst @@ -6,4 +6,5 @@ Migration from 6.1 to 6.2 .. toctree:: :maxdepth: 1 + :SOC_BT_CLASSIC_SUPPORTED: bluetooth-classic security diff --git a/docs/zh_CN/migration-guides/release-6.x/6.2/bluetooth-classic.rst b/docs/zh_CN/migration-guides/release-6.x/6.2/bluetooth-classic.rst new file mode 100644 index 00000000000..3af29f5e7e6 --- /dev/null +++ b/docs/zh_CN/migration-guides/release-6.x/6.2/bluetooth-classic.rst @@ -0,0 +1,66 @@ +经典蓝牙 +======== + +:link_to_translation:`en:[English]` + +Bluedroid +--------- + + 以下 Bluedroid API 已变更: + + - :component_file:`/bt/host/bluedroid/api/include/api/esp_hf_defs.h` + + - 类型 ``esp_hf_wbs_config_t`` 已替换为 ``esp_hf_codec_mode_t``,新的公开类型表示协商得到的编解码(CVSD, mSBC 或 LC3-SWB),而不再是“是否开启宽带语音”的开关。``esp_hf_wbs_config_t`` 保留为 ``esp_hf_codec_mode_t`` 的废弃别名。 + + - 枚举名已重命名,数值 0 / 1 / 2 保持不变。旧的 ``ESP_HF_WBS_*`` 名称仍作为宏别名指向新枚举: + + .. list-table:: + :header-rows: 1 + :widths: 40 40 20 + + * - 旧枚举 + - 新枚举 + - 取值 + * - ``ESP_HF_WBS_NONE`` + - ``ESP_HF_CODEC_NONE`` + - 0 + * - ``ESP_HF_WBS_NO`` + - ``ESP_HF_CODEC_CVSD`` + - 1 + * - ``ESP_HF_WBS_YES`` + - ``ESP_HF_CODEC_MSBC`` + - 2 + * - — + - ``ESP_HF_CODEC_LC3`` + - 3 + + - 现有对 ``ESP_HF_WBS_NO`` / ``ESP_HF_WBS_YES`` 的 ``switch`` 或比较仍可编译。新代码请使用新名称。 + + - ``ESP_HF_CODEC_NONE`` 表示尚未协商编解码,或将偏好重置为协议栈默认值,并不表示不能使用 CVSD。``ESP_HF_CODEC_CVSD`` 表示当前选中的是窄带编解码,可能是对端不支持宽带、未开启宽带,或从 mSBC / LC3 回退。 + + - ``ESP_HF_CODEC_LC3`` 为新增取值。若代码用编解码值去下标访问只有 3 项的表,需补充 LC3 项或增加 default 分支。 + + - 不要把 ``esp_hf_codec_mode_t`` 当作能力位图。对端编解码能力通过 ``ESP_HF_BAC_RESPONSE_EVT`` 中的 ``ESP_HF_CODEC_CAP_CVSD`` / ``ESP_HF_CODEC_CAP_MSBC`` / ``ESP_HF_CODEC_CAP_LC3`` 单独上报。 + + - 音频连接状态新增 ``ESP_HF_AUDIO_STATE_CONNECTED_LC3``,用于表示已建立的 LC3-SWB 链路,与现有的 ``ESP_HF_AUDIO_STATE_CONNECTED`` (CVSD)和 ``ESP_HF_AUDIO_STATE_CONNECTED_MSBC`` 并列。 + + - :component_file:`/bt/host/bluedroid/api/include/api/esp_hf_ag_api.h` + + - ``esp_hf_ag_set_codec()`` 的参数类型改为 ``esp_hf_codec_mode_t``。传入 ``ESP_HF_CODEC_CVSD``、``ESP_HF_CODEC_MSBC``、``ESP_HF_CODEC_LC3``,或 ``ESP_HF_CODEC_NONE`` 以恢复协议栈默认偏好。 + + - ``ESP_HF_WBS_RESPONSE_EVT`` (``wbs_rep``):字段 ``codec`` 的类型改为 ``esp_hf_codec_mode_t``。字段名 ``codec`` 未改。新增 ``status`` 字段(``esp_bt_status_t``),用于报告请求是否成功。 + + - ``ESP_HF_BCS_RESPONSE_EVT`` (``bcs_rep``):字段 ``mode`` 的类型改为 ``esp_hf_codec_mode_t``。字段名 ``mode`` 未改。 + + 建议替换: + + .. code-block:: c + + // 修改前 + esp_hf_ag_set_codec(bda, ESP_HF_WBS_YES); + if (param->bcs_rep.mode == ESP_HF_WBS_NO) { /* CVSD */ } + + // 修改后 + esp_hf_ag_set_codec(bda, ESP_HF_CODEC_MSBC); + if (param->bcs_rep.mode == ESP_HF_CODEC_CVSD) { /* CVSD */ } + else if (param->bcs_rep.mode == ESP_HF_CODEC_LC3) { /* LC3-SWB */ } diff --git a/docs/zh_CN/migration-guides/release-6.x/6.2/index.rst b/docs/zh_CN/migration-guides/release-6.x/6.2/index.rst index c4f35f62498..a1b0740e9a5 100644 --- a/docs/zh_CN/migration-guides/release-6.x/6.2/index.rst +++ b/docs/zh_CN/migration-guides/release-6.x/6.2/index.rst @@ -6,4 +6,5 @@ .. toctree:: :maxdepth: 1 + :SOC_BT_CLASSIC_SUPPORTED: bluetooth-classic security diff --git a/examples/bluetooth/bluedroid/classic_bt/hfp_ag/README.md b/examples/bluetooth/bluedroid/classic_bt/hfp_ag/README.md index 09887875b69..7683beb051d 100644 --- a/examples/bluetooth/bluedroid/classic_bt/hfp_ag/README.md +++ b/examples/bluetooth/bluedroid/classic_bt/hfp_ag/README.md @@ -64,19 +64,85 @@ PCM Signal supports three configurations in menuconfig: PCM Role, PCM Polar and ### Codec Choice -Supported targets provide two types of codec for HFP audio data: `CVSD` and `mSBC`. +Supported targets provide the following codecs for HFP audio data: `CVSD`, `mSBC` (WBS), and optionally `LC3-SWB` (HFP 1.9). -`CVSD` is the default setting and is also the widely used codec for voice audio. But, `mSBC` is designed to have a better voice quality through `HFP`. To select which one is in use, we provide `Wide Band Speech` item in the `menuconfig`: +`CVSD` is the default setting and is also the widely used codec for voice audio. `mSBC` is designed for better voice quality through HFP Wideband Speech. `LC3-SWB` further extends sample rate to 32 kHz and must be encoded/decoded in the application layer. -`Component config --> Bluetooth --> Bluedroid Options --> Wide Band Speech.` +To select Wideband / Super Wideband negotiation options, use: -Switching on the `Wide Band Speech` means that the preferred codec is `mSBC`, but which one is actually being used also depends on the `Data Path` configuration. +`Component config --> Bluetooth --> Bluedroid Options --> Hands Free/Handset Profile --> Wideband Speech` -- If you choose `PCM` for datapath, you can only use `CVSD` and hardware is responsible for the codec job. In the meanwhile, you cannot use `mSBC` by switching `Wide Band Speech` on, because the `mSBC` is implemented in the Bluedroid (Bluetooth Host Stack) by software. +and (for LC3-SWB): -- If you choose `vHCI` for datapath with `Wide Band Speech` on, codec job is done in the Bluedroid and mSBC is being used. +`Component config --> Bluetooth --> Bluedroid Options --> Hands Free/Handset Profile --> Super Wideband Speech (LC3-SWB)` -- If you choose `vHCI` for datapath with `Wide Band Speech` off, hardware is responsible for the codec job and `CVSD` is in use. +Which codec is actually used also depends on the `Data Path` configuration and peer capability: + +- If you choose `PCM` for datapath, you can only use `CVSD` and hardware is responsible for the codec job. You cannot use `mSBC`/`LC3` on the PCM path, because those codecs are handled in software (stack or application) over HCI. +- If you choose `vHCI` for datapath with `Wideband Speech` on and LC3 off, codec job for mSBC is done in Bluedroid (unless External Codec is enabled, see below). +- If you choose `vHCI` for datapath with `Wideband Speech` off, hardware is responsible for the codec job and `CVSD` is in use. +- If you enable `Super Wideband Speech (LC3-SWB)`, LC3 negotiation is enabled. Bluedroid has **no internal LC3 codec**; this example implements LC3 encode/decode in the application using [espressif/esp_audio_codec](https://components.espressif.com/components/espressif/esp_audio_codec/). + +#### External Codec (mSBC and LC3-SWB) + +`Use External Codec for HFP` (`BT_HFP_USE_EXTERNAL_CODEC`) means the application owns encode/decode and uses encoded-frame APIs (`esp_hf_ag_register_audio_data_callback` / `esp_hf_ag_audio_data_send`). When enabled, Bluedroid's built-in mSBC software codec is removed. + +This example reuses **one** push TX / decode worker path for both codecs; only the `esp_audio_codec` open/process/close calls differ: + +| Negotiated codec | Application encoder/decoder | Frame period / PCM size | +| ---------------- | --------------------------- | ----------------------- | +| mSBC | `esp_sbc_enc_*` / `esp_sbc_dec_*` (`ESP_SBC_MODE_MSBC`) | 7.5 ms / 240 bytes PCM | +| LC3-SWB | `esp_lc3_enc_*` / `esp_lc3_dec_*` | 7.5 ms / 480 bytes PCM | + +H2 sync headers are **not** filled by the application: send the codec payload only (mSBC 57 bytes / LC3 58 bytes). The stack adds H2 on TX and strips it on RX. + +| Mode | Typical menuconfig | Who encodes/decodes | +| ---- | ------------------ | ------------------- | +| Internal mSBC (legacy) | HCI + WBS, External Codec **off** | Bluedroid + PCM callbacks / ringbuffer in this example | +| External mSBC | HCI + WBS, External Codec **on**, LC3 optional | Application via `esp_audio_codec` SBC (mSBC mode) | +| External LC3-SWB | HCI + WBS + LC3 **on** + External Codec **on** | Application via `esp_audio_codec` LC3 | + +Recommended settings with peer [hfp_hf](../hfp_hf): + +1. Controller and Bluedroid SCO data path: **HCI** +2. Enable `Wideband Speech` +3. Enable `Use External Codec for HFP` (required for both external mSBC and LC3-SWB) +4. Optionally enable `Super Wideband Speech (LC3-SWB)` if you want LC3 negotiation +5. Keep AG/HF options consistent on both boards +6. Build so Component Manager can fetch `esp_audio_codec` (see below) +7. `con` then `cona`. Expect `connected_msbc` or `connected_lc3`, and a log such as `ext codec ready: type=mSBC` / `type=LC3-SWB` + +`CVSD` + External Codec is not the main path of this AG sine demo (prefer mSBC/LC3). + +#### Dependency: `esp_audio_codec` + +External mSBC/LC3 encode/decode link against [`espressif/esp_audio_codec`](https://components.espressif.com/components/espressif/esp_audio_codec/). The dependency is already declared in `main/idf_component.yml`: + +```yaml +dependencies: + espressif/esp_audio_codec: "^2.6.1" +``` + +On the first configure/build, ESP-IDF Component Manager downloads it into `managed_components/`. You normally do **not** need a manual step. + +If the dependency is missing, add it with: + +```bash +idf.py add-dependency "espressif/esp_audio_codec^2.6.1" +``` + +Enable the codecs you need under: + +`Component config --> ESP Audio Codec --> Audio Encoder / Audio Decoder` + +- mSBC external path: enable **SBC** encoder/decoder +- LC3-SWB: also enable **LC3** encoder/decoder + +Then rebuild: + +```bash +idf.py reconfigure build +``` ### Build and Flash @@ -156,13 +222,15 @@ You can type `cona` to establish the audio connection between HF Unit and AG dev #### Choice of Codec -Supported targets support both CVSD and mSBC codec. HF Unit and AG device determine which codec to use by exchanging features during service level connection. The choice of codec also depends on the your configuration in `menuconfig`. +Supported targets can negotiate CVSD, mSBC, and (when enabled) LC3-SWB. HF Unit and AG determine the codec by exchanging features during service level connection. The result also depends on your `menuconfig`. -Since CVSD is the default codec in HFP, we just show the scenarios using mSBC: +CVSD is the default. For higher quality: -- If you enable `BT_HFP_WBS_ENABLE` in `menuconfig`, mSBC will be available. -- If both HF Unit and AG support mSBC and `BT_HFP_WBS_ENABLE` is enabled, the local device chooses mSBC. -- If you use PCM data path, mSBC is not available. +- If you enable `BT_HFP_WBS_ENABLE`, mSBC can be negotiated. +- With `BT_HFP_USE_EXTERNAL_CODEC`, this example encodes/decodes mSBC in the application (`esp_sbc_*`). Without it, Bluedroid's internal mSBC path is used (PCM callbacks). +- If you also enable `BT_HFP_LC3_ENABLE` **and** External Codec, negotiation may select LC3-SWB and this example runs the shared external path with `esp_lc3_*`. +- LC3-SWB always requires External Codec (`esp_hf_ag_audio_data_send` is only active in that mode). +- If you use the PCM data path, mSBC and LC3 are not available over the Bluedroid HCI audio APIs used in this demo. ### Answer or Reject an Incoming Call @@ -293,6 +361,8 @@ If you encounter any problems, please check if the following rules are followed: - Not all commands in the table are supported by the HF Unit. - If you want to `hf con;` to establish a service level connection with a specific HF Unit, you should add the MAC address of the HF Unit in `app_hf_msg_set.c` for example: `esp_bd_addr_t peer_addr = {0xb4, 0xe6, 0x2d, 0xeb, 0x09, 0x93};` - Use `esp_hf_client_register_callback()` and `esp_hf_client_init();` before establishing a service level connection. +- For external mSBC/LC3: enable HCI + WBS + `BT_HFP_USE_EXTERNAL_CODEC` on **both** AG and HF; for LC3 also enable `BT_HFP_LC3_ENABLE`. Confirm `managed_components/espressif__esp_audio_codec` exists after build. Missing `esp_sbc_enc.h` / `esp_lc3_enc.h` usually means the Component Manager dependency was not fetched—run `idf.py reconfigure` or `idf.py add-dependency "espressif/esp_audio_codec^2.6.1"`. +- Enabling `BT_HFP_USE_EXTERNAL_CODEC` without using the example's external encoded-frame path (or with an outdated AG tree) can cause `rb send fail` / `BTA_AG_SCO_OPEN_ST: Ignoring event 9`, because the stack no longer pulls PCM for internal mSBC encode. ## Example Breakdown diff --git a/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/CMakeLists.txt b/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/CMakeLists.txt index 728e74c3529..00a1e203a89 100644 --- a/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/CMakeLists.txt +++ b/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/CMakeLists.txt @@ -3,5 +3,5 @@ idf_component_register(SRCS "app_hf_msg_set.c" "bt_app_hf.c" "gpio_pcm_config.c" "main.c" - PRIV_REQUIRES bt nvs_flash esp_driver_gpio console esp_ringbuf + PRIV_REQUIRES bt nvs_flash esp_driver_gpio console esp_ringbuf espressif__esp_audio_codec INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/app_hf_msg_set.c b/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/app_hf_msg_set.c index 6e1ad2d9a36..a64974b17ac 100644 --- a/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/app_hf_msg_set.c +++ b/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/app_hf_msg_set.c @@ -43,6 +43,9 @@ HF_CMD_HANDLER(conn_audio) HF_CMD_HANDLER(disc_audio) { printf("Disconnect Audio\n"); +#if CONFIG_BT_HFP_AUDIO_DATA_PATH_HCI + bt_app_hf_prepare_audio_disconnect(); +#endif esp_hf_ag_audio_disconnect(hf_peer_addr); return 0; } diff --git a/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/bt_app_hf.c b/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/bt_app_hf.c index b70af4cbcd0..aa346819b27 100644 --- a/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/bt_app_hf.c +++ b/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/bt_app_hf.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -25,6 +25,15 @@ #include "sdkconfig.h" #include "bt_app_core.h" #include "bt_app_hf.h" +#include "esp_hf_defs.h" +#if CONFIG_BT_HFP_USE_EXTERNAL_CODEC +#include "encoder/impl/esp_sbc_enc.h" +#include "decoder/impl/esp_sbc_dec.h" +#if CONFIG_BT_HFP_LC3_ENABLE +#include "encoder/impl/esp_lc3_enc.h" +#include "decoder/impl/esp_lc3_dec.h" +#endif +#endif /* CONFIG_BT_HFP_USE_EXTERNAL_CODEC */ const char *c_hf_evt_str[] = { "CONNECTION_STATE_EVT", /*!< SERVICE LEVEL CONNECTION STATE CONTROL */ @@ -46,6 +55,7 @@ const char *c_hf_evt_str[] = { "BCS_EVT", /*!< CODEC NEGO EVT */ "PKT_STAT_EVT", /*!< REQUEST PACKET STATUS EVT */ "PROF_STATE_EVT", /*!< Indicate HF init or deinit complete */ + "BAC_RESPONSE_EVT", /*!< Peer codec capabilities from AT+BAC */ }; //esp_hf_connection_state_t @@ -59,10 +69,11 @@ const char *c_connection_state_str[] = { // esp_hf_audio_state_t const char *c_audio_state_str[] = { - "disconnected", - "connecting", - "connected", - "connected_msbc", + [ESP_HF_AUDIO_STATE_DISCONNECTED] = "disconnected", + [ESP_HF_AUDIO_STATE_CONNECTING] = "connecting", + [ESP_HF_AUDIO_STATE_CONNECTED] = "connected", + [ESP_HF_AUDIO_STATE_CONNECTED_MSBC] = "connected_msbc", + [ESP_HF_AUDIO_STATE_CONNECTED_LC3] = "connected_lc3", }; /// esp_hf_vr_state_t @@ -97,11 +108,12 @@ char *c_subscriber_service_type_str[] = { "FAX", }; -// esp_hf_nego_codec_status_t +// esp_hf_codec_mode_t const char *c_codec_mode_str[] = { - "CVSD Only", - "Use CVSD", - "Use MSBC", + [ESP_HF_CODEC_NONE] = "None", + [ESP_HF_CODEC_CVSD] = "CVSD", + [ESP_HF_CODEC_MSBC] = "MSBC", + [ESP_HF_CODEC_LC3] = "LC3-SWB", }; #if CONFIG_BT_HFP_AUDIO_DATA_PATH_HCI @@ -127,16 +139,22 @@ static const int16_t sine_int16[TABLE_SIZE] = { #define PCM_BLOCK_DURATION_US (7500) #define WBS_PCM_SAMPLING_RATE_KHZ (16) +#define SWB_PCM_SAMPLING_RATE_KHZ (32) #define PCM_SAMPLING_RATE_KHZ (8) #define BYTES_PER_SAMPLE (2) // input can refer to Enhanced Setup Synchronous Connection Command in core spec4.2 Vol2, Part E #define WBS_PCM_INPUT_DATA_SIZE (WBS_PCM_SAMPLING_RATE_KHZ * PCM_BLOCK_DURATION_US / 1000 * BYTES_PER_SAMPLE) //240 +#define SWB_PCM_INPUT_DATA_SIZE (SWB_PCM_SAMPLING_RATE_KHZ * PCM_BLOCK_DURATION_US / 1000 * BYTES_PER_SAMPLE) //480 #define PCM_INPUT_DATA_SIZE (PCM_SAMPLING_RATE_KHZ * PCM_BLOCK_DURATION_US / 1000 * BYTES_PER_SAMPLE) //120 #define PCM_GENERATOR_TICK_US (4000) +#define TASK_STOP_TIMEOUT_MS (1000) + +static void bt_app_send_data_shut_down(void); + static long s_data_num = 0; static RingbufHandle_t s_m_rb = NULL; static uint64_t s_time_new, s_time_old; @@ -144,8 +162,446 @@ static esp_timer_handle_t s_periodic_timer; static uint64_t s_last_enter_time, s_now_enter_time; static uint64_t s_us_duration; static SemaphoreHandle_t s_send_data_Semaphore = NULL; +static SemaphoreHandle_t s_send_data_done_sem = NULL; static TaskHandle_t s_bt_app_send_data_task_handler = NULL; +static volatile bool s_audio_tx_active = false; +static bool s_periodic_timer_running = false; static esp_hf_audio_state_t s_audio_code; +static esp_hf_sync_conn_hdl_t s_sync_conn_hdl = ESP_INVALID_CONN_HANDLE; + +#if CONFIG_BT_HFP_USE_EXTERNAL_CODEC +static void bt_app_hf_incoming_cb(const uint8_t *buf, uint32_t sz); + +#define EXT_CODEC_TASK_STACK (8 * 1024) +#define EXT_CODEC_TASK_PRIO (configMAX_PRIORITIES - 5) +#define EXT_CODEC_RX_QUEUE_LEN 8 +#define EXT_CODEC_TX_QUEUE_LEN 4 +#define EXT_CODEC_ENC_TASK_CORE 1 +#define EXT_CODEC_DEC_TASK_CORE 0 + +#define EXT_CODEC_ENC_MAX_SIZE \ + ((ESP_HF_LC3_ENCODED_FRAME_SIZE > ESP_HF_MSBC_ENCODED_FRAME_SIZE) ? \ + ESP_HF_LC3_ENCODED_FRAME_SIZE : ESP_HF_MSBC_ENCODED_FRAME_SIZE) +#define EXT_CODEC_PCM_MAX_SIZE SWB_PCM_INPUT_DATA_SIZE + +typedef enum { + BT_APP_EXT_CODEC_NONE = 0, + BT_APP_EXT_CODEC_MSBC, +#if CONFIG_BT_HFP_LC3_ENABLE + BT_APP_EXT_CODEC_LC3, +#endif +} bt_app_ext_codec_type_t; + +typedef struct { + uint8_t data[EXT_CODEC_ENC_MAX_SIZE]; + uint16_t len; + bool is_bad; +} bt_app_ext_rx_item_t; + +typedef struct { + uint8_t pcm[EXT_CODEC_PCM_MAX_SIZE]; + uint16_t pcm_len; +} bt_app_ext_tx_item_t; + +static bt_app_ext_codec_type_t s_ext_codec_type = BT_APP_EXT_CODEC_NONE; +static uint16_t s_ext_pcm_frame_bytes = 0; +static uint16_t s_ext_enc_frame_bytes = 0; +static void *s_ext_enc_handle = NULL; +static void *s_ext_dec_handle = NULL; +static QueueHandle_t s_ext_rx_queue = NULL; +static QueueHandle_t s_ext_tx_queue = NULL; +static TaskHandle_t s_ext_enc_task = NULL; +static TaskHandle_t s_ext_dec_task = NULL; +static SemaphoreHandle_t s_ext_codec_done_sem = NULL; +static volatile bool s_ext_codec_active = false; + +/* Shared push TX path: one 7.5 ms PCM frame per timer tick, no ringbuffer. */ +static uint8_t s_ext_tx_pcm_frame[EXT_CODEC_PCM_MAX_SIZE]; + +static void bt_app_hf_ext_codec_deinit(void); + +static void bt_app_hf_ext_enc_send_pcm(const uint8_t *pcm, uint32_t pcm_len) +{ + if (!s_audio_tx_active || !s_ext_codec_active || s_ext_enc_handle == NULL || + s_sync_conn_hdl == ESP_INVALID_CONN_HANDLE || pcm_len != s_ext_pcm_frame_bytes) { + return; + } + + esp_audio_enc_in_frame_t in_frame = { + .buffer = (uint8_t *)pcm, + .len = (int)pcm_len, + }; + uint8_t enc_buf[EXT_CODEC_ENC_MAX_SIZE]; + esp_audio_enc_out_frame_t out_frame = { + .buffer = enc_buf, + .len = sizeof(enc_buf), + }; + esp_audio_err_t enc_ret = ESP_AUDIO_ERR_FAIL; + + if (s_ext_codec_type == BT_APP_EXT_CODEC_MSBC) { + enc_ret = esp_sbc_enc_process(s_ext_enc_handle, &in_frame, &out_frame); +#if CONFIG_BT_HFP_LC3_ENABLE + } else if (s_ext_codec_type == BT_APP_EXT_CODEC_LC3) { + enc_ret = esp_lc3_enc_process(s_ext_enc_handle, &in_frame, &out_frame); +#endif + } + if (enc_ret != ESP_AUDIO_ERR_OK || out_frame.encoded_bytes == 0 || + out_frame.encoded_bytes > s_ext_enc_frame_bytes) { + return; + } + + esp_hf_audio_buff_t *audio_buf = esp_hf_ag_audio_buff_alloc(s_ext_enc_frame_bytes); + if (audio_buf == NULL) { + return; + } + memcpy(audio_buf->data, enc_buf, out_frame.encoded_bytes); + audio_buf->data_len = out_frame.encoded_bytes; + if (esp_hf_ag_audio_data_send(s_sync_conn_hdl, audio_buf) != ESP_OK) { + esp_hf_ag_audio_buff_free(audio_buf); + } +} + +static void bt_app_hf_ext_dec_frame(const bt_app_ext_rx_item_t *rx_item) +{ + if (!s_ext_codec_active || s_ext_dec_handle == NULL || rx_item == NULL) { + return; + } + + esp_audio_dec_in_raw_t in_frame = { + .buffer = (uint8_t *)rx_item->data, + .len = rx_item->len, + .frame_recover = rx_item->is_bad ? ESP_AUDIO_DEC_RECOVERY_PLC : ESP_AUDIO_DEC_RECOVERY_NONE, + }; + int16_t pcm_buf[EXT_CODEC_PCM_MAX_SIZE / sizeof(int16_t)]; + esp_audio_dec_out_frame_t out_frame = { + .buffer = (uint8_t *)pcm_buf, + .len = sizeof(pcm_buf), + .decoded_size = 0, + }; + esp_audio_dec_info_t dec_info = {0}; + esp_audio_err_t dec_ret = ESP_AUDIO_ERR_FAIL; + + if (s_ext_codec_type == BT_APP_EXT_CODEC_MSBC) { + dec_ret = esp_sbc_dec_decode(s_ext_dec_handle, &in_frame, &out_frame, &dec_info); +#if CONFIG_BT_HFP_LC3_ENABLE + } else if (s_ext_codec_type == BT_APP_EXT_CODEC_LC3) { + dec_ret = esp_lc3_dec_decode(s_ext_dec_handle, &in_frame, &out_frame, &dec_info); +#endif + } + + if (dec_ret == ESP_AUDIO_ERR_OK) { + bt_app_hf_incoming_cb((const uint8_t *)pcm_buf, out_frame.decoded_size); + } else if (dec_ret != ESP_AUDIO_ERR_BUFF_NOT_ENOUGH) { + ESP_LOGD(BT_HF_TAG, "ext codec decode skipped, ret: %d", dec_ret); + } +} + +static void bt_app_hf_ext_enc_task(void *arg) +{ + bt_app_ext_tx_item_t tx_item; + + (void)arg; + while (s_ext_codec_active || uxQueueMessagesWaiting(s_ext_tx_queue) > 0) { + if (xQueueReceive(s_ext_tx_queue, &tx_item, pdMS_TO_TICKS(10)) == pdTRUE) { + /* Keep only the latest pending PCM frame if we fell behind. */ + bt_app_ext_tx_item_t newer; + while (xQueueReceive(s_ext_tx_queue, &newer, 0) == pdTRUE) { + tx_item = newer; + } + bt_app_hf_ext_enc_send_pcm(tx_item.pcm, tx_item.pcm_len); + } else if (!s_ext_codec_active) { + break; + } + } + if (s_ext_codec_done_sem) { + xSemaphoreGive(s_ext_codec_done_sem); + } + vTaskDelete(NULL); +} + +static void bt_app_hf_ext_dec_task(void *arg) +{ + bt_app_ext_rx_item_t rx_item; + + (void)arg; + while (s_ext_codec_active || uxQueueMessagesWaiting(s_ext_rx_queue) > 0) { + if (xQueueReceive(s_ext_rx_queue, &rx_item, pdMS_TO_TICKS(10)) == pdTRUE) { + bt_app_ext_rx_item_t newer; + while (xQueueReceive(s_ext_rx_queue, &newer, 0) == pdTRUE) { + rx_item = newer; + } + bt_app_hf_ext_dec_frame(&rx_item); + } else if (!s_ext_codec_active) { + break; + } + } + if (s_ext_codec_done_sem) { + xSemaphoreGive(s_ext_codec_done_sem); + } + vTaskDelete(NULL); +} + +/* Single-owner deletion: worker deletes itself and signals via sem. */ +static void bt_app_hf_ext_wait_task_done(TaskHandle_t *task_handle) +{ + if (*task_handle == NULL) { + return; + } + if (s_ext_codec_done_sem == NULL || + xSemaphoreTake(s_ext_codec_done_sem, pdMS_TO_TICKS(TASK_STOP_TIMEOUT_MS)) != pdTRUE) { + ESP_LOGE(BT_HF_TAG, "ext codec task stop timeout"); + } + *task_handle = NULL; +} + +static bool bt_app_hf_ext_codec_worker_start(void) +{ + if (s_ext_rx_queue == NULL) { + s_ext_rx_queue = xQueueCreate(EXT_CODEC_RX_QUEUE_LEN, sizeof(bt_app_ext_rx_item_t)); + } + if (s_ext_tx_queue == NULL) { + s_ext_tx_queue = xQueueCreate(EXT_CODEC_TX_QUEUE_LEN, sizeof(bt_app_ext_tx_item_t)); + } + if (s_ext_rx_queue == NULL || s_ext_tx_queue == NULL) { + return false; + } + if (s_ext_codec_done_sem == NULL) { + s_ext_codec_done_sem = xSemaphoreCreateCounting(2, 0); + } + if (s_ext_codec_done_sem == NULL) { + return false; + } + s_ext_codec_active = true; + if (s_ext_enc_task == NULL) { + BaseType_t ret = xTaskCreatePinnedToCore(bt_app_hf_ext_enc_task, "HfExtEnc", + EXT_CODEC_TASK_STACK, NULL, EXT_CODEC_TASK_PRIO, + &s_ext_enc_task, EXT_CODEC_ENC_TASK_CORE); + if (ret != pdPASS) { + s_ext_codec_active = false; + return false; + } + } + if (s_ext_dec_task == NULL) { + BaseType_t ret = xTaskCreatePinnedToCore(bt_app_hf_ext_dec_task, "HfExtDec", + EXT_CODEC_TASK_STACK, NULL, EXT_CODEC_TASK_PRIO, + &s_ext_dec_task, EXT_CODEC_DEC_TASK_CORE); + if (ret != pdPASS) { + s_ext_codec_active = false; + bt_app_hf_ext_wait_task_done(&s_ext_enc_task); + return false; + } + } + return true; +} + +static void bt_app_hf_ext_codec_worker_stop(void) +{ + s_ext_codec_active = false; + bt_app_hf_ext_wait_task_done(&s_ext_enc_task); + bt_app_hf_ext_wait_task_done(&s_ext_dec_task); + if (s_ext_rx_queue) { + xQueueReset(s_ext_rx_queue); + } + if (s_ext_tx_queue) { + xQueueReset(s_ext_tx_queue); + } +} + +static bool bt_app_hf_ext_codec_open_msbc(void) +{ + esp_sbc_enc_config_t enc_cfg = ESP_SBC_MSBC_ENC_CONFIG_DEFAULT(); + esp_sbc_dec_cfg_t dec_cfg = { + .sbc_mode = ESP_SBC_MODE_MSBC, + .ch_num = 1, + .enable_plc = true, + }; + int in_size = 0; + int out_size = 0; + + if (esp_sbc_enc_open(&enc_cfg, sizeof(enc_cfg), &s_ext_enc_handle) != ESP_AUDIO_ERR_OK) { + return false; + } + if (esp_sbc_dec_open(&dec_cfg, sizeof(dec_cfg), &s_ext_dec_handle) != ESP_AUDIO_ERR_OK) { + esp_sbc_enc_close(s_ext_enc_handle); + s_ext_enc_handle = NULL; + return false; + } + if (esp_sbc_enc_get_frame_size(s_ext_enc_handle, &in_size, &out_size) != ESP_AUDIO_ERR_OK || + in_size <= 0 || out_size <= 0 || out_size > EXT_CODEC_ENC_MAX_SIZE || + in_size > EXT_CODEC_PCM_MAX_SIZE) { + esp_sbc_enc_close(s_ext_enc_handle); + esp_sbc_dec_close(s_ext_dec_handle); + s_ext_enc_handle = NULL; + s_ext_dec_handle = NULL; + return false; + } + s_ext_codec_type = BT_APP_EXT_CODEC_MSBC; + s_ext_pcm_frame_bytes = (uint16_t)in_size; + s_ext_enc_frame_bytes = (uint16_t)out_size; + return true; +} + +#if CONFIG_BT_HFP_LC3_ENABLE +static bool bt_app_hf_ext_codec_open_lc3(void) +{ + esp_lc3_enc_config_t enc_cfg = { + .sample_rate = ESP_HF_LC3_SAMPLING_RATE_HZ, + .bits_per_sample = 16, + .channel = 1, + .frame_dms = ESP_HF_LC3_FRAME_DURATION_US / 100, + .nbyte = ESP_HF_LC3_ENCODED_FRAME_SIZE, + .len_prefixed = false, + }; + esp_lc3_dec_cfg_t dec_cfg = { + .sample_rate = ESP_HF_LC3_SAMPLING_RATE_HZ, + .channel = 1, + .bits_per_sample = 16, + .frame_dms = ESP_HF_LC3_FRAME_DURATION_US / 100, + .nbyte = ESP_HF_LC3_ENCODED_FRAME_SIZE, + .is_cbr = true, + .len_prefixed = false, + .enable_plc = true, + }; + + if (esp_lc3_enc_open(&enc_cfg, sizeof(enc_cfg), &s_ext_enc_handle) != ESP_AUDIO_ERR_OK) { + return false; + } + if (esp_lc3_dec_open(&dec_cfg, sizeof(dec_cfg), &s_ext_dec_handle) != ESP_AUDIO_ERR_OK) { + esp_lc3_enc_close(s_ext_enc_handle); + s_ext_enc_handle = NULL; + return false; + } + s_ext_codec_type = BT_APP_EXT_CODEC_LC3; + s_ext_pcm_frame_bytes = SWB_PCM_INPUT_DATA_SIZE; + s_ext_enc_frame_bytes = ESP_HF_LC3_ENCODED_FRAME_SIZE; + return true; +} +#endif /* CONFIG_BT_HFP_LC3_ENABLE */ + +static bool bt_app_hf_ext_codec_init(esp_hf_audio_state_t audio_state) +{ + bool opened = false; + + if (audio_state == ESP_HF_AUDIO_STATE_CONNECTED_MSBC) { + opened = bt_app_hf_ext_codec_open_msbc(); +#if CONFIG_BT_HFP_LC3_ENABLE + } else if (audio_state == ESP_HF_AUDIO_STATE_CONNECTED_LC3) { + opened = bt_app_hf_ext_codec_open_lc3(); +#endif + } + if (!opened) { + return false; + } + if (!bt_app_hf_ext_codec_worker_start()) { + bt_app_hf_ext_codec_deinit(); + return false; + } + ESP_LOGI(BT_HF_TAG, "ext codec ready: type=%s pcm=%u enc=%u", + (s_ext_codec_type == BT_APP_EXT_CODEC_MSBC) ? "mSBC" : +#if CONFIG_BT_HFP_LC3_ENABLE + (s_ext_codec_type == BT_APP_EXT_CODEC_LC3) ? "LC3-SWB" : +#endif + "unknown", + s_ext_pcm_frame_bytes, s_ext_enc_frame_bytes); + return true; +} + +static void bt_app_hf_ext_codec_deinit(void) +{ + bt_app_hf_ext_codec_worker_stop(); + if (s_ext_enc_handle) { + if (s_ext_codec_type == BT_APP_EXT_CODEC_MSBC) { + esp_sbc_enc_close(s_ext_enc_handle); +#if CONFIG_BT_HFP_LC3_ENABLE + } else if (s_ext_codec_type == BT_APP_EXT_CODEC_LC3) { + esp_lc3_enc_close(s_ext_enc_handle); +#endif + } + s_ext_enc_handle = NULL; + } + if (s_ext_dec_handle) { + if (s_ext_codec_type == BT_APP_EXT_CODEC_MSBC) { + esp_sbc_dec_close(s_ext_dec_handle); +#if CONFIG_BT_HFP_LC3_ENABLE + } else if (s_ext_codec_type == BT_APP_EXT_CODEC_LC3) { + esp_lc3_dec_close(s_ext_dec_handle); +#endif + } + s_ext_dec_handle = NULL; + } + if (s_ext_rx_queue) { + vQueueDelete(s_ext_rx_queue); + s_ext_rx_queue = NULL; + } + if (s_ext_tx_queue) { + vQueueDelete(s_ext_tx_queue); + s_ext_tx_queue = NULL; + } + if (s_ext_codec_done_sem) { + vSemaphoreDelete(s_ext_codec_done_sem); + s_ext_codec_done_sem = NULL; + } + s_ext_codec_type = BT_APP_EXT_CODEC_NONE; + s_ext_pcm_frame_bytes = 0; + s_ext_enc_frame_bytes = 0; +} + +static bool bt_app_hf_ext_queue_tx_pcm(const uint8_t *pcm, uint32_t pcm_len) +{ + if (!s_ext_codec_active || s_ext_tx_queue == NULL || pcm_len == 0 || + pcm_len != s_ext_pcm_frame_bytes || pcm_len > EXT_CODEC_PCM_MAX_SIZE) { + return false; + } + bt_app_ext_tx_item_t item = {0}; + memcpy(item.pcm, pcm, pcm_len); + item.pcm_len = (uint16_t)pcm_len; + if (xQueueSend(s_ext_tx_queue, &item, 0) == pdTRUE) { + return true; + } + /* Drop oldest and keep newest. */ + bt_app_ext_tx_item_t drop; + if (xQueueReceive(s_ext_tx_queue, &drop, 0) == pdTRUE) { + return xQueueSend(s_ext_tx_queue, &item, 0) == pdTRUE; + } + return false; +} + +static void bt_app_hf_ext_audio_cb(esp_hf_sync_conn_hdl_t sync_conn_hdl, esp_hf_audio_buff_t *audio_buff, bool is_bad_frame) +{ + (void)sync_conn_hdl; + if (!s_ext_codec_active || s_ext_rx_queue == NULL) { + if (audio_buff) { + esp_hf_ag_audio_buff_free(audio_buff); + } + return; + } + + bt_app_ext_rx_item_t item = {0}; + item.is_bad = is_bad_frame; + if (!is_bad_frame) { + if (audio_buff == NULL || audio_buff->data_len == 0 || + audio_buff->data_len > EXT_CODEC_ENC_MAX_SIZE) { + if (audio_buff) { + esp_hf_ag_audio_buff_free(audio_buff); + } + return; + } + memcpy(item.data, audio_buff->data, audio_buff->data_len); + item.len = audio_buff->data_len; + } + if (audio_buff) { + esp_hf_ag_audio_buff_free(audio_buff); + } + if (xQueueSend(s_ext_rx_queue, &item, 0) != pdTRUE) { + ESP_LOGD(BT_HF_TAG, "ext codec RX queue full, drop frame"); + } +} + +static bool bt_app_hf_ext_codec_active(void) +{ + return s_ext_codec_active; +} +#endif /* CONFIG_BT_HFP_USE_EXTERNAL_CODEC */ static void print_speed(void); @@ -203,11 +659,13 @@ static void print_speed(void) static void bt_app_send_data_timer_cb(void *arg) { - if (!xSemaphoreGive(s_send_data_Semaphore)) { - ESP_LOGE(BT_HF_TAG, "%s xSemaphoreGive failed", __func__); + if (!s_audio_tx_active || s_send_data_Semaphore == NULL) { return; } - return; + /* Binary semaphore: give may fail if send task is still encoding (ext codec). */ + if (xSemaphoreGive(s_send_data_Semaphore) != pdTRUE) { + ESP_LOGD(BT_HF_TAG, "%s: send task busy, skip tick", __func__); + } } static void bt_app_send_data_task(void *arg) @@ -216,10 +674,27 @@ static void bt_app_send_data_task(void *arg) size_t item_size = 0; uint8_t *buf = NULL; for (;;) { - if (xSemaphoreTake(s_send_data_Semaphore, (TickType_t)portMAX_DELAY)) { + if (xSemaphoreTake(s_send_data_Semaphore, (TickType_t)portMAX_DELAY) != pdTRUE) { + continue; + } + if (!s_audio_tx_active) { + break; + } + +#if CONFIG_BT_HFP_USE_EXTERNAL_CODEC + if (bt_app_hf_ext_codec_active()) { + /* mSBC/LC3 external path bypasses the TX ringbuffer. Timer is one + * 7.5 ms tick per frame: generate PCM and queue encode directly. */ + s_last_enter_time = esp_timer_get_time(); + bt_app_hf_create_audio_data(s_ext_tx_pcm_frame, s_ext_pcm_frame_bytes); + bt_app_hf_ext_queue_tx_pcm(s_ext_tx_pcm_frame, s_ext_pcm_frame_bytes); + continue; + } +#endif + { s_now_enter_time = esp_timer_get_time(); s_us_duration = s_now_enter_time - s_last_enter_time; - if(s_audio_code == ESP_HF_AUDIO_STATE_CONNECTED_MSBC) { + if (s_audio_code == ESP_HF_AUDIO_STATE_CONNECTED_MSBC) { // time of a frame is 7.5ms, sample is 120, data is 2 (byte/sample), so a frame is 240 byte (HF_SBC_ENC_RAW_DATA_SIZE) frame_data_num = s_us_duration / PCM_BLOCK_DURATION_US * WBS_PCM_INPUT_DATA_SIZE; s_last_enter_time += frame_data_num / WBS_PCM_INPUT_DATA_SIZE * PCM_BLOCK_DURATION_US; @@ -254,37 +729,103 @@ static void bt_app_send_data_task(void *arg) } } } + if (s_send_data_done_sem) { + xSemaphoreGive(s_send_data_done_sem); + } + vTaskDelete(NULL); } + +static void bt_app_stop_periodic_timer(void) +{ + if (s_periodic_timer && s_periodic_timer_running) { + esp_err_t err = esp_timer_stop(s_periodic_timer); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + ESP_LOGW(BT_HF_TAG, "esp_timer_stop failed: %s", esp_err_to_name(err)); + } + s_periodic_timer_running = false; + } +} + +static void bt_app_audio_tx_stop(void) +{ + if (!s_audio_tx_active && s_bt_app_send_data_task_handler == NULL) { + return; + } + s_audio_tx_active = false; + s_sync_conn_hdl = ESP_INVALID_CONN_HANDLE; + + bt_app_stop_periodic_timer(); + if (s_send_data_Semaphore) { + xSemaphoreGive(s_send_data_Semaphore); + } + if (s_bt_app_send_data_task_handler) { + if (s_send_data_done_sem == NULL || + xSemaphoreTake(s_send_data_done_sem, pdMS_TO_TICKS(TASK_STOP_TIMEOUT_MS)) != pdTRUE) { + ESP_LOGE(BT_HF_TAG, "send data task stop timeout"); + } + s_bt_app_send_data_task_handler = NULL; + } +} + +void bt_app_hf_prepare_audio_disconnect(void) +{ + bt_app_audio_tx_stop(); +} + void bt_app_send_data(void) { + if (s_periodic_timer || s_bt_app_send_data_task_handler) { + bt_app_send_data_shut_down(); + } s_send_data_Semaphore = xSemaphoreCreateBinary(); + if (s_send_data_done_sem == NULL) { + s_send_data_done_sem = xSemaphoreCreateBinary(); + } xTaskCreate(bt_app_send_data_task, "BtAppSendDataTask", 4 * 1024, NULL, configMAX_PRIORITIES - 3, &s_bt_app_send_data_task_handler); - s_m_rb = xRingbufferCreate(ESP_HFP_RINGBUF_SIZE, RINGBUF_TYPE_BYTEBUF); +#if CONFIG_BT_HFP_USE_EXTERNAL_CODEC + if (!bt_app_hf_ext_codec_active()) +#endif + { + s_m_rb = xRingbufferCreate(ESP_HFP_RINGBUF_SIZE, RINGBUF_TYPE_BYTEBUF); + } const esp_timer_create_args_t c_periodic_timer_args = { .callback = &bt_app_send_data_timer_cb, .name = "periodic" }; ESP_ERROR_CHECK(esp_timer_create(&c_periodic_timer_args, &s_periodic_timer)); - ESP_ERROR_CHECK(esp_timer_start_periodic(s_periodic_timer, PCM_GENERATOR_TICK_US)); +#if CONFIG_BT_HFP_USE_EXTERNAL_CODEC + /* External mSBC/LC3 frame period is 7.5 ms. */ + uint64_t tick_us = bt_app_hf_ext_codec_active() ? + PCM_BLOCK_DURATION_US : PCM_GENERATOR_TICK_US; +#else + uint64_t tick_us = PCM_GENERATOR_TICK_US; +#endif + ESP_ERROR_CHECK(esp_timer_start_periodic(s_periodic_timer, tick_us)); + s_periodic_timer_running = true; s_last_enter_time = esp_timer_get_time(); + s_audio_tx_active = true; return; } void bt_app_send_data_shut_down(void) { - if (s_bt_app_send_data_task_handler) { - vTaskDelete(s_bt_app_send_data_task_handler); - s_bt_app_send_data_task_handler = NULL; - } - if(s_periodic_timer) { - ESP_ERROR_CHECK(esp_timer_stop(s_periodic_timer)); - ESP_ERROR_CHECK(esp_timer_delete(s_periodic_timer)); + bt_app_audio_tx_stop(); + bt_app_stop_periodic_timer(); + if (s_periodic_timer) { + esp_err_t err = esp_timer_delete(s_periodic_timer); + if (err != ESP_OK) { + ESP_LOGW(BT_HF_TAG, "esp_timer_delete failed: %s", esp_err_to_name(err)); + } s_periodic_timer = NULL; } if (s_send_data_Semaphore) { vSemaphoreDelete(s_send_data_Semaphore); s_send_data_Semaphore = NULL; } + if (s_send_data_done_sem) { + vSemaphoreDelete(s_send_data_done_sem); + s_send_data_done_sem = NULL; + } if (s_m_rb) { vRingbufferDelete(s_m_rb); s_m_rb = NULL; @@ -295,7 +836,7 @@ void bt_app_send_data_shut_down(void) void bt_app_hf_cb(esp_hf_cb_event_t event, esp_hf_cb_param_t *param) { - if (event <= ESP_HF_PROF_STATE_EVT) { + if (event <= ESP_HF_BAC_RESPONSE_EVT) { ESP_LOGI(BT_HF_TAG, "APP HFP event: %s", c_hf_evt_str[event]); } else { ESP_LOGE(BT_HF_TAG, "APP HFP invalid event %d", event); @@ -314,25 +855,57 @@ void bt_app_hf_cb(esp_hf_cb_event_t event, esp_hf_cb_param_t *param) case ESP_HF_AUDIO_STATE_EVT: { - ESP_LOGI(BT_HF_TAG, "--Audio State %s", c_audio_state_str[param->audio_stat.state]); + const char *audio_state = (param->audio_stat.state <= ESP_HF_AUDIO_STATE_CONNECTED_LC3 && + c_audio_state_str[param->audio_stat.state]) ? + c_audio_state_str[param->audio_stat.state] : "unknown"; + ESP_LOGI(BT_HF_TAG, "--Audio State %s", audio_state); #if CONFIG_BT_HFP_AUDIO_DATA_PATH_HCI if (param->audio_stat.state == ESP_HF_AUDIO_STATE_CONNECTED || - param->audio_stat.state == ESP_HF_AUDIO_STATE_CONNECTED_MSBC) + param->audio_stat.state == ESP_HF_AUDIO_STATE_CONNECTED_MSBC || + param->audio_stat.state == ESP_HF_AUDIO_STATE_CONNECTED_LC3) { - if(param->audio_stat.state == ESP_HF_AUDIO_STATE_CONNECTED) { - s_audio_code = ESP_HF_AUDIO_STATE_CONNECTED; - } else { - s_audio_code = ESP_HF_AUDIO_STATE_CONNECTED_MSBC; - } + s_sync_conn_hdl = param->audio_stat.sync_conn_handle; + s_audio_code = param->audio_stat.state; s_time_old = esp_timer_get_time(); +#if CONFIG_BT_HFP_USE_EXTERNAL_CODEC + if (param->audio_stat.state == ESP_HF_AUDIO_STATE_CONNECTED_MSBC +#if CONFIG_BT_HFP_LC3_ENABLE + || param->audio_stat.state == ESP_HF_AUDIO_STATE_CONNECTED_LC3 +#endif + ) { + if (!bt_app_hf_ext_codec_init(param->audio_stat.state)) { + ESP_LOGE(BT_HF_TAG, "external codec init failed"); + break; + } + esp_hf_ag_register_audio_data_callback(bt_app_hf_ext_audio_cb); + } else { + /* CVSD with EXTERNAL_CODEC: this demo TX path stays on legacy PCM APIs + * and is not the main supported case. Prefer mSBC/LC3 for external codec. */ + ESP_LOGW(BT_HF_TAG, "CVSD + EXTERNAL_CODEC: using legacy PCM callbacks"); + esp_hf_ag_register_data_callback(bt_app_hf_incoming_cb, bt_app_hf_outgoing_cb); + } +#else esp_hf_ag_register_data_callback(bt_app_hf_incoming_cb, bt_app_hf_outgoing_cb); /* Disable connectable and discoverable mode to save the over-the-air bandwidth and ensure audio quality */ esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE); /* Begin send esco data task */ +#if CONFIG_BT_HFP_LC3_ENABLE + if (param->audio_stat.state == ESP_HF_AUDIO_STATE_CONNECTED_LC3) { + ESP_LOGE(BT_HF_TAG, "LC3-SWB requires CONFIG_BT_HFP_USE_EXTERNAL_CODEC"); + break; + } +#endif + esp_hf_ag_register_data_callback(bt_app_hf_incoming_cb, bt_app_hf_outgoing_cb); +#endif /* CONFIG_BT_HFP_USE_EXTERNAL_CODEC */ bt_app_send_data(); } else if (param->audio_stat.state == ESP_HF_AUDIO_STATE_DISCONNECTED) { ESP_LOGI(BT_HF_TAG, "--ESP AG Audio Connection Disconnected."); bt_app_send_data_shut_down(); +#if CONFIG_BT_HFP_USE_EXTERNAL_CODEC + bt_app_hf_ext_codec_deinit(); +#endif + s_sync_conn_hdl = ESP_INVALID_CONN_HANDLE; + bt_app_send_data_shut_down(); /* Resume connectable and discoverable mode */ esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); } @@ -489,9 +1062,25 @@ void bt_app_hf_cb(esp_hf_cb_event_t event, esp_hf_cb_param_t *param) break; } #if (CONFIG_BT_HFP_WBS_ENABLE) + case ESP_HF_BAC_RESPONSE_EVT: + { + uint16_t caps = param->bac_rep.peer_codecs; + ESP_LOGI(BT_HF_TAG, "--Peer codec caps: 0x%04x (CVSD=%d mSBC=%d LC3=%d)", + caps, + !!(caps & ESP_HF_CODEC_CAP_CVSD), + !!(caps & ESP_HF_CODEC_CAP_MSBC), + !!(caps & ESP_HF_CODEC_CAP_LC3)); + /* Codec preference is set in btc_hf_ag on BAC; avoid duplicate esp_hf_ag_set_codec() + * here while SLC is not up yet (AT+BAC arrives before SLC_CONNECTED). */ + break; + } case ESP_HF_WBS_RESPONSE_EVT: { - ESP_LOGI(BT_HF_TAG, "--Current codec: %s",c_codec_mode_str[param->wbs_rep.codec]); + if (param->wbs_rep.status == ESP_BT_STATUS_SUCCESS) { + ESP_LOGI(BT_HF_TAG, "--Codec preference set: %s", c_codec_mode_str[param->wbs_rep.codec]); + } else { + ESP_LOGI(BT_HF_TAG, "--Codec preference rejected: %s", c_codec_mode_str[param->wbs_rep.codec]); + } break; } #endif diff --git a/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/bt_app_hf.h b/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/bt_app_hf.h index a58648aad5f..d4df473d623 100644 --- a/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/bt_app_hf.h +++ b/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/bt_app_hf.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -8,6 +8,7 @@ #define __BT_APP_HF_H__ #include +#include "sdkconfig.h" #include "esp_hf_ag_api.h" #include "esp_bt_defs.h" @@ -19,4 +20,12 @@ extern esp_bd_addr_t hf_peer_addr; // Declaration of peer device bdaddr * @brief callback function for HF client */ void bt_app_hf_cb(esp_hf_cb_event_t event, esp_hf_cb_param_t *param); + +#if CONFIG_BT_HFP_AUDIO_DATA_PATH_HCI +/** + * @brief Stop PCM/LC3 TX before esp_hf_ag_audio_disconnect() to avoid use-after-free. + */ +void bt_app_hf_prepare_audio_disconnect(void); +#endif + #endif /* __BT_APP_HF_H__*/ diff --git a/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/idf_component.yml b/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/idf_component.yml new file mode 100644 index 00000000000..70c889f5fe1 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/idf_component.yml @@ -0,0 +1,17 @@ +## IDF Component Manager Manifest File +dependencies: + ## Required IDF version + idf: + version: '>=4.1.0' + # # Put list of dependencies here + # # For components maintained by Espressif: + # component: "~1.0.0" + # # For 3rd party components: + # username/component: ">=1.0.0,<2.0.0" + # username2/component2: + # version: "~1.0.0" + # # For transient dependencies `public` flag can be set. + # # `public` flag doesn't have an effect dependencies of the `main` component. + # # All dependencies of `main` are public by default. + # public: true + espressif/esp_audio_codec: ^2.6.1 diff --git a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/README.md b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/README.md index 4bc8303bc02..3a31a424160 100644 --- a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/README.md +++ b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/README.md @@ -80,17 +80,17 @@ Step to initialize PBA Client connection: Supported targets provide two types of codec for HFP audio data: `CVSD` and `mSBC`. -`CVSD` is the default setting and is also the widely used codec for voice audio. But, `mSBC` is designed to have a better voice quality through `HFP`. To select which one is in use, we provide `Wide Band Speech` item in the `menuconfig` path: +`CVSD` is the default setting and is also the widely used codec for voice audio. But, `mSBC` is designed to have a better voice quality through `HFP`. To select which one is in use, we provide `Wideband Speech` item in the `menuconfig` path: -`Component config --> Bluetooth --> Bluedroid Options --> Wide Band Speech`. +`Component config --> Bluetooth --> Bluedroid Options --> Wideband Speech`. -Switching on the `Wide Band Speech` means that the preferred codec is `mSBC`, but which one is actually being used also depends on the `Data Path` configuration. +Switching on the `Wideband Speech` means that the preferred codec is `mSBC`, but which one is actually being used also depends on the `Data Path` configuration. -- If you choose `PCM` for datapath, you can only use `CVSD` and hardware is responsible for the codec job. In the meanwhile, you cannot use `mSBC` by switching `Wide Band Speech` on, because the `mSBC` is implemented in the Bluedroid (Bluetooth Host Stack) by software. +- If you choose `PCM` for datapath, you can only use `CVSD` and hardware is responsible for the codec job. In the meanwhile, you cannot use `mSBC` by switching `Wideband Speech` on, because the `mSBC` is implemented in the Bluedroid (Bluetooth Host Stack) by software. -- If you choose `vHCI` for datapath with `Wide Band Speech` on, codec job is done in the Bluedroid and mSBC is being used. +- If you choose `vHCI` for datapath with `Wideband Speech` on, codec job is done in the Bluedroid and mSBC is being used. -- If you choose `vHCI` for datapath with `Wide Band Speech` off, hardware is responsible for the codec job and `CVSD` is in use. +- If you choose `vHCI` for datapath with `Wideband Speech` off, hardware is responsible for the codec job and `CVSD` is in use. ### Build and Flash diff --git a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_hf.c b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_hf.c index 570c51d45cb..23f009ec8c5 100644 --- a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_hf.c +++ b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_hf.c @@ -65,10 +65,11 @@ const char *c_connection_state_str[] = { // esp_hf_client_audio_state_t const char *c_audio_state_str[] = { - "disconnected", - "connecting", - "connected", - "connected_msbc", + [ESP_HF_CLIENT_AUDIO_STATE_DISCONNECTED] = "disconnected", + [ESP_HF_CLIENT_AUDIO_STATE_CONNECTING] = "connecting", + [ESP_HF_CLIENT_AUDIO_STATE_CONNECTED] = "connected", + [ESP_HF_CLIENT_AUDIO_STATE_CONNECTED_MSBC] = "connected_msbc", + [ESP_HF_CLIENT_AUDIO_STATE_CONNECTED_LC3] = "connected_lc3", }; /// esp_hf_vr_state_t @@ -178,10 +179,16 @@ extern bool hf_client_connected; #if CONFIG_BT_HFP_AUDIO_DATA_PATH_HCI +typedef enum { + HF_AIR_CODEC_CVSD = 0, + HF_AIR_CODEC_MSBC, + HF_AIR_CODEC_LC3, +} hf_air_codec_t; + #if CONFIG_BT_HFP_USE_EXTERNAL_CODEC static esp_hf_sync_conn_hdl_t s_sync_conn_hdl; -static bool s_msbc_air_mode = false; +static hf_air_codec_t s_air_codec = HF_AIR_CODEC_CVSD; QueueHandle_t s_audio_buff_queue = NULL; static int s_audio_buff_cnt = 0; @@ -210,13 +217,17 @@ static void bt_app_hf_client_audio_data_cb(esp_hf_sync_conn_hdl_t sync_conn_hdl, } s_audio_buff_cnt--; - if (s_msbc_air_mode && audio_data_to_send->data_len > ESP_HF_MSBC_ENCODED_FRAME_SIZE) { + if (s_air_codec == HF_AIR_CODEC_MSBC && + audio_data_to_send->data_len > ESP_HF_MSBC_ENCODED_FRAME_SIZE) { /* * in mSBC air mode, we may receive a mSBC frame with some padding bytes at the end, * but esp_hf_client_audio_data_send API do not allow adding padding bytes at the end, * so we need to remove those padding bytes before send back to peer device. */ audio_data_to_send->data_len = ESP_HF_MSBC_ENCODED_FRAME_SIZE; + } else if (s_air_codec == HF_AIR_CODEC_LC3 && + audio_data_to_send->data_len > ESP_HF_LC3_ENCODED_FRAME_SIZE) { + audio_data_to_send->data_len = ESP_HF_LC3_ENCODED_FRAME_SIZE; } /* send audio data back to AG */ @@ -226,7 +237,7 @@ static void bt_app_hf_client_audio_data_cb(esp_hf_sync_conn_hdl_t sync_conn_hdl, } } -#else +#else /* !CONFIG_BT_HFP_USE_EXTERNAL_CODEC */ #define ESP_HFP_RINGBUF_SIZE 3600 static RingbufHandle_t m_rb = NULL; @@ -280,7 +291,7 @@ static void bt_app_hf_client_incoming_cb(const uint8_t *buf, uint32_t sz) esp_hf_client_outgoing_data_ready(); } -#endif /* #if CONFIG_BT_HFP_USE_EXTERNAL_CODEC */ +#endif /* CONFIG_BT_HFP_USE_EXTERNAL_CODEC */ #endif /* #if CONFIG_BT_HFP_AUDIO_DATA_PATH_HCI */ @@ -313,30 +324,38 @@ void bt_app_hf_client_cb(esp_hf_client_cb_event_t event, esp_hf_client_cb_param_ case ESP_HF_CLIENT_AUDIO_STATE_EVT: { - ESP_LOGI(BT_HF_TAG, "--audio state %s", - c_audio_state_str[param->audio_stat.state]); + const char *audio_state = (param->audio_stat.state <= ESP_HF_CLIENT_AUDIO_STATE_CONNECTED_LC3 && + c_audio_state_str[param->audio_stat.state]) ? + c_audio_state_str[param->audio_stat.state] : "unknown"; + ESP_LOGI(BT_HF_TAG, "--audio state %s", audio_state); #if CONFIG_BT_HFP_AUDIO_DATA_PATH_HCI #if CONFIG_BT_HFP_USE_EXTERNAL_CODEC if (param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_CONNECTED_MSBC) { - s_msbc_air_mode = true; - ESP_LOGI(BT_HF_TAG, "--audio air mode: mSBC , preferred_frame_size: %d", param->audio_stat.preferred_frame_size); - } - else if (param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_CONNECTED) { - s_msbc_air_mode = false; - ESP_LOGI(BT_HF_TAG, "--audio air mode: CVSD , preferred_frame_size: %d", param->audio_stat.preferred_frame_size); + s_air_codec = HF_AIR_CODEC_MSBC; + ESP_LOGI(BT_HF_TAG, "--audio air mode: mSBC, preferred_frame_size: %d", + param->audio_stat.preferred_frame_size); + } else if (param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_CONNECTED_LC3) { + s_air_codec = HF_AIR_CODEC_LC3; + ESP_LOGI(BT_HF_TAG, "--audio air mode: LC3-SWB, preferred_frame_size: %d", + param->audio_stat.preferred_frame_size); + } else if (param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_CONNECTED) { + s_air_codec = HF_AIR_CODEC_CVSD; + ESP_LOGI(BT_HF_TAG, "--audio air mode: CVSD, preferred_frame_size: %d", + param->audio_stat.preferred_frame_size); } if (param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_CONNECTED || - param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_CONNECTED_MSBC) { + param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_CONNECTED_MSBC || + param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_CONNECTED_LC3) { s_sync_conn_hdl = param->audio_stat.sync_conn_handle; s_audio_buff_queue = xQueueCreate(50, sizeof(esp_hf_audio_buff_t*)); esp_hf_client_register_audio_data_callback(bt_app_hf_client_audio_data_cb); /* Disable connectable and discoverable mode to save the over-the-air bandwidth and ensure audio quality */ esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE); } else if (param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_DISCONNECTED) { - s_sync_conn_hdl = 0; - s_msbc_air_mode = false; + s_sync_conn_hdl = ESP_INVALID_CONN_HANDLE; + s_air_codec = HF_AIR_CODEC_CVSD; if (s_audio_buff_queue) { esp_hf_audio_buff_t *buff_to_free = NULL; while (xQueueReceive(s_audio_buff_queue, &buff_to_free, 0)) { @@ -350,7 +369,10 @@ void bt_app_hf_client_cb(esp_hf_client_cb_event_t event, esp_hf_client_cb_param_ esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); } #else - if (param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_CONNECTED || + if (param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_CONNECTED_LC3) { + ESP_LOGE(BT_HF_TAG, "LC3-SWB requires CONFIG_BT_HFP_USE_EXTERNAL_CODEC " + "(Bluedroid has no internal LC3 codec; use encoded-frame loopback path)"); + } else if (param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_CONNECTED || param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_CONNECTED_MSBC) { esp_hf_client_register_data_callback(bt_app_hf_client_incoming_cb, bt_app_hf_client_outgoing_cb); From 25ca3296506845a78fe1396c570af15baffb63c6 Mon Sep 17 00:00:00 2001 From: hejiaxin Date: Wed, 2 Sep 2026 17:43:12 +0800 Subject: [PATCH 2/2] fix(bt_hfp): Fix the solution of unknown AT command --- components/bt/host/bluedroid/api/esp_hf_ag_api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/bt/host/bluedroid/api/esp_hf_ag_api.c b/components/bt/host/bluedroid/api/esp_hf_ag_api.c index 2bdd139294f..e25c8c6c94d 100644 --- a/components/bt/host/bluedroid/api/esp_hf_ag_api.c +++ b/components/bt/host/bluedroid/api/esp_hf_ag_api.c @@ -241,7 +241,7 @@ esp_err_t esp_hf_ag_unknown_at_send(esp_bd_addr_t remote_addr, char *unat) if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } - if (remote_addr == NULL || unat == NULL) { + if (remote_addr == NULL) { return ESP_ERR_INVALID_ARG; } btc_msg_t msg;