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
This commit is contained in:
hejiaxin
2026-06-17 14:45:35 +08:00
parent fbb0518b1a
commit 9f6cf1ac3c
43 changed files with 1490 additions and 185 deletions

View File

@@ -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

View File

@@ -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 ".")

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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 <stdint.h>
#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__*/

View File

@@ -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

View File

@@ -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

View File

@@ -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);