-
Problem DescriptionHi Schatzmann, you wrote: The Hands-Free Profile (HFP), Headset Profile (HSP) and standalone AVRCP without A2DP are not supported! it could be understood both ways, in that HFP is supported IF a2dp is working at the same time and isnt disabled (because you wrote "without"). but it can also be understood that those profiles wont work with your a2dp library. please can you clarify? im working on hfp and a2dp "dual mode", and so far i have gotten sample rate switching to work (from 44.1k to 8k and back, and a2dp function during this time is fully fine. im also able to init the hfp profile and get callbacks at the same time a2dp is working. i do, however, hit a hard wall when trying to transport audio in hfp mode. basically, if my phone is connected to the esp during a call, the callbacks are working, but then the device seems to try sending data to the esp but falls back immediately to normal mode, so i wanted to understand ho much testing you did when it came to HFP and if you were able to get it working, and if not, why not? id like to see if there could be a potential solution to this based on your findings thanks again Device Descriptionesp32 wroom Sketch#include <Arduino.h>
#include "nvs_flash.h"
#include "esp_system.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_bt_device.h"
#include "esp_gap_bt_api.h"
#include "esp_hf_client_api.h"
#include "audio_controller.h"
#include "BluetoothA2DPSink.h"
// ============================================================
// CONFIG
// ============================================================
extern AudioController audio_controller;
static bool hfp_ready = false;
static bool remote_bda_valid = false;
static bool sco_connected = false;
bool incomingCall = false;
bool call_created = false;
bool deinit_called = false;
bool inited = false;
extern bool hfp_call;
static esp_bd_addr_t remote_bda = {0};
// ============================================================
// Helpers
// ============================================================
static char *bda2str(esp_bd_addr_t bda, char *str, size_t size)
{
if (!bda || !str || size < 18) return nullptr;
uint8_t *p = bda;
sprintf(str, "%02X:%02X:%02X:%02X:%02X:%02X",
p[0], p[1], p[2], p[3], p[4], p[5]);
return str;
}
static void hf_client_event_handler(esp_hf_client_cb_event_t event, esp_hf_client_cb_param_t *param);
static bool get_name_from_eir(uint8_t *eir, char *bdname, uint8_t *bdname_len)
{
uint8_t *rmt_bdname = nullptr;
uint8_t rmt_bdname_len = 0;
if (!eir) return false;
rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_CMPL_LOCAL_NAME, &rmt_bdname_len);
if (!rmt_bdname) {
rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_SHORT_LOCAL_NAME, &rmt_bdname_len);
}
if (rmt_bdname) {
if (rmt_bdname_len > ESP_BT_GAP_MAX_BDNAME_LEN) {
rmt_bdname_len = ESP_BT_GAP_MAX_BDNAME_LEN;
}
if (bdname) {
memcpy(bdname, rmt_bdname, rmt_bdname_len);
bdname[rmt_bdname_len] = '\0';
}
if (bdname_len) {
*bdname_len = rmt_bdname_len;
}
return true;
}
return false;
}
// ============================================================
// HFP AUDIO CALLBACKS (PCM/HCI SCO DATA)
// ============================================================
void safe_deinit_a2dp() {
if (!deinit_called) {
Serial.println("HFP: Deinit A2DP for SCO");
//esp_avrc_ct_deinit();
//esp_a2d_sink_deinit();
BluetoothA2DPOutputAudioTools().end();
deinit_called = true;
delay(50);
}
}
void safe_init_a2dp() {
if (deinit_called) {
Serial.println("HFP: Reinit A2DP after call");
//esp_avrc_ct_init();
//esp_a2d_sink_init();
BluetoothA2DPOutputAudioTools().begin();
deinit_called = false;
delay(50);
}
}
const char *esp_hf_client_audio_state_str(esp_hf_client_audio_state_t state) {
switch (state) {
case ESP_HF_CLIENT_AUDIO_STATE_DISCONNECTED: return "AUDIO_DISCONNECTED";
case ESP_HF_CLIENT_AUDIO_STATE_CONNECTING: return "AUDIO_CONNECTING";
case ESP_HF_CLIENT_AUDIO_STATE_CONNECTED: return "AUDIO_CONNECTED";
case ESP_HF_CLIENT_AUDIO_STATE_CONNECTED_MSBC: return "AUDIO_CONNECTED_MSBC"; // For Wideband Speech
default: return "UNKNOWN_AUDIO_STATE";
}
}
const char *esp_hf_client_conn_state_str(esp_hf_client_connection_state_t state) {
switch (state) {
case ESP_HF_CLIENT_CONNECTION_STATE_DISCONNECTED: return "DISCONNECTED";
case ESP_HF_CLIENT_CONNECTION_STATE_CONNECTING: return "CONNECTING";
case ESP_HF_CLIENT_CONNECTION_STATE_CONNECTED: return "CONNECTED";
case ESP_HF_CLIENT_CONNECTION_STATE_SLC_CONNECTED: return "SLC_CONNECTED";
case ESP_HF_CLIENT_CONNECTION_STATE_DISCONNECTING: return "DISCONNECTING";
default: return "UNKNOWN_CONN_STATE";
}
}
const char *esp_hf_client_call_setup_str(esp_hf_call_setup_status_t status) {
switch (status) {
case ESP_HF_CALL_SETUP_STATUS_IDLE: return "IDLE";
case ESP_HF_CALL_SETUP_STATUS_INCOMING: return "INCOMING";
case ESP_HF_CALL_SETUP_STATUS_OUTGOING_DIALING: return "OUTGOING_DIALING";
case ESP_HF_CALL_SETUP_STATUS_OUTGOING_ALERTING: return "OUTGOING_ALERTING";
default: return "UNKNOWN_CALL_SETUP_STATE";
}
}
const char *esp_hf_client_event_str(esp_hf_client_cb_event_t event) {
switch (event) {
case ESP_HF_CLIENT_CONNECTION_STATE_EVT: return "CONNECTION_STATE_EVT";
case ESP_HF_CLIENT_AUDIO_STATE_EVT: return "AUDIO_STATE_EVT";
case ESP_HF_CLIENT_BVRA_EVT: return "BVRA_EVT";
case ESP_HF_CLIENT_CIND_CALL_EVT: return "CIND_CALL_EVT";
case ESP_HF_CLIENT_CIND_CALL_SETUP_EVT: return "CIND_CALL_SETUP_EVT";
case ESP_HF_CLIENT_CIND_CALL_HELD_EVT: return "CIND_CALL_HELD_EVT";
case ESP_HF_CLIENT_CNUM_EVT: return "CNUM_EVT";
case ESP_HF_CLIENT_CLIP_EVT: return "CLIP_EVT";
case ESP_HF_CLIENT_RING_IND_EVT: return "RING_IND_EVT";
case ESP_HF_CLIENT_AT_RESPONSE_EVT: return "AT_RESPONSE_EVT";
// ... add other events
default: return "UNKNOWN_HF_CLIENT_EVENT";
}
}
static void hf_audio_in_cb(const uint8_t *data, uint32_t len)
{
// This is SCO audio coming from phone (8kHz CVSD / 16kHz mSBC)
// Here you route it into your DAC/I2S output.
// For now we just debug print.
if (!sco_connected) return;
Serial.printf("HFP: audio IN len=%lu\n", (unsigned long)len);
// Example:
audio_controller.aud_volume.write(data, len);
}
extern "C" uint32_t hf_audio_out_cb(uint8_t *buf, uint32_t len) {
// This is where ESP provides mic audio to phone
// Fill buf with mic PCM
if (!sco_connected) {
memset(buf, 0, len);
return len;
}
Serial.printf("HFP: audio OUT request len=%lu\n", (unsigned long)len);
// Example:
// int bytesRead = mic_in.readBytes(buf, len);
// if (bytesRead < (int)len) memset(buf + bytesRead, 0, len - bytesRead);
memset(buf, 0, len); // placeholder
return len;
}
// ============================================================
// HFP CLIENT CALLBACK
// ============================================================
static void hf_client_event_handler(esp_hf_client_cb_event_t event, esp_hf_client_cb_param_t *param)
{
Serial.printf("HFP: HFP Client Event: %s\n", esp_hf_client_event_str(event));
esp_hf_client_cb_param_t *hf_param = (esp_hf_client_cb_param_t *)param;
switch (event)
{
case ESP_HF_CLIENT_CONNECTION_STATE_EVT:
{
Serial.printf("HFP: CONNECTION_STATE_EVT state=%d (%s)\n",
hf_param->conn_stat.state,
esp_hf_client_conn_state_str(hf_param->conn_stat.state));
if (hf_param->conn_stat.state == ESP_HF_CLIENT_CONNECTION_STATE_CONNECTED ||
hf_param->conn_stat.state == ESP_HF_CLIENT_CONNECTION_STATE_SLC_CONNECTED) {
memcpy(remote_bda, hf_param->conn_stat.remote_bda, ESP_BD_ADDR_LEN);
remote_bda_valid = true;
char bda_str[18];
const char *addr = bda2str(remote_bda, bda_str, sizeof(bda_str));
Serial.printf("HFP: Connected to %s\n", addr ? addr : "INVALID_BDA");
}
break;
}
case ESP_HF_CLIENT_AUDIO_STATE_EVT:
{
Serial.printf("HFP: AUDIO_STATE_EVT state=%d (%s)\n",
hf_param->audio_stat.state,
esp_hf_client_audio_state_str(hf_param->audio_stat.state));
if (hf_param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_CONNECTED ||
hf_param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_CONNECTED_MSBC)
{
Serial.println("HFP: SCO CONNECTED");
sco_connected = true;
}
else if (hf_param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_DISCONNECTED)
{
Serial.println("HFP: SCO DISCONNECTED");
sco_connected = false;
}
break;
}
case ESP_HF_CLIENT_RING_IND_EVT:
{
Serial.println("HFP: RING_IND_EVT (incoming call)");
incomingCall = true;
break;
}
case ESP_HF_CLIENT_CIND_CALL_EVT:
{
Serial.printf("HFP: CALL_EVT status=%d\n", hf_param->call.status);
if (hf_param->call.status == 1) {
Serial.println("HFP: Call ACTIVE -> trying connect audio");
safe_deinit_a2dp();
hfp_call = true;
audio_out.end();
AudioController().end();
delay(200);
AudioController().begin();
audio_out.begin();
Serial.println("HFP: a2dp deinit done");
if (remote_bda_valid) {
if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) {
Serial.println("HFP: [ERR] Bluedroid not enabled");
} else {
delay(250);
esp_hf_client_connect_audio(remote_bda);
Serial.println("HFP: esp_hf_client_connect_audio called ...");
}
}
} else {
Serial.println("HFP: Call ended");
if (remote_bda_valid) {
esp_hf_client_disconnect_audio(remote_bda);
safe_deinit_a2dp();
delay(250);
hfp_call = false;
audio_out.end();
AudioController().end();
delay(250);
AudioController().begin();
Serial.println("HFP: a2dp init done");
audio_out.begin();
}
}
break;
}
case ESP_HF_CLIENT_CIND_CALL_SETUP_EVT:
{
Serial.printf("HFP: Call setup indicator: %s\n", esp_hf_client_call_setup_str(hf_param->call_setup.status));
break;
}
case ESP_HF_CLIENT_CLIP_EVT:
{
Serial.printf("HFP: Caller ID: %s\n", hf_param->clip.number ? hf_param->clip.number : "NULL");
break;
}
case ESP_HF_CLIENT_PROF_STATE_EVT:
{
Serial.printf("HFP: PROF_STATE_EVT state=%d\n", hf_param->prof_stat.state);
break;
}
case ESP_HF_CLIENT_AT_RESPONSE_EVT:
Serial.printf("HFP: AT response: code %d, cme_err %d\n", hf_param->at_response.code, hf_param->at_response.cme);
if (hf_param->at_response.code == ESP_HF_AT_RESPONSE_CODE_OK) {
Serial.printf("HFP: AT command OK\n");
} else if (hf_param->at_response.code == ESP_HF_AT_RESPONSE_CODE_ERR) {
Serial.printf("HFP: AT command ERROR: %d\n", hf_param->at_response.cme);
}
break;
default:
Serial.printf("HFP: Unhandled event=%d\n", event);
break;
}
}
// ============================================================
// GAP CALLBACK (DISCOVERY)
// ============================================================
static void gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
{
switch (event)
{
case ESP_BT_GAP_AUTH_CMPL_EVT:
{
if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) {
Serial.printf("HFP: Auth success: %s\n", param->auth_cmpl.device_name);
} else {
Serial.printf("HFP: Auth failed status=%d\n", param->auth_cmpl.stat);
}
break;
}
default:
break;
}
}
// ============================================================
// INIT FUNCTION (call once)
// ============================================================
void hfp_start() {
esp_log_level_set("*", ESP_LOG_VERBOSE);
esp_log_level_set("BT_BTM", ESP_LOG_VERBOSE);
esp_log_level_set("BT_HF", ESP_LOG_VERBOSE);
esp_log_level_set("BTC_HF", ESP_LOG_VERBOSE);
esp_log_level_set("BT_SDP", ESP_LOG_VERBOSE);
esp_log_level_set("BTM_SCO", ESP_LOG_VERBOSE);
esp_log_level_set("BTM_SCO", ESP_LOG_VERBOSE);
esp_log_level_set("BT_APPL", ESP_LOG_VERBOSE);
Serial.println("\n==============================");
Serial.println("HFP: INIT START");
Serial.println("==============================");
#if CONFIG_BT_HFP_AUDIO_DATA_PATH_HCI
Serial.println("HFP: HFP audio path = HCI/VHCI");
#elif CONFIG_BT_HFP_AUDIO_DATA_PATH_PCM
Serial.println("HFP: HFP audio path = PCM");
#else
Serial.println("HFP: HFP audio path = UNKNOWN/DEFAULT");
#endif
esp_bt_controller_mem_release(ESP_BT_MODE_BLE);
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
Serial.println("HFP: NVS erase required\n");
nvs_flash_erase();
nvs_flash_init();
}
// GAP callback
esp_bt_gap_register_callback(gap_cb);
// Register HFP callbacks
ret = esp_hf_client_register_callback(hf_client_event_handler);
if (ret != ESP_OK) {
Serial.printf("HFP: [ERR] esp_hf_client_register_callback failed: %s\n", esp_err_to_name(ret));
return;
}
// Register audio callbacks
ret = esp_hf_client_register_data_callback(hf_audio_in_cb, hf_audio_out_cb);
if (ret != ESP_OK) {
Serial.printf("HFP: [ERR] esp_hf_client_register_data_callback: %s\n", esp_err_to_name(ret));
return;
}
// Init profile
ret = esp_hf_client_init();
if (ret != ESP_OK) {
Serial.printf("HFP: [ERR] hf_client_init failed %d\n", ret);
return;
}
Serial.println("HFP: HFP client initialized");
hfp_ready = true;
Serial.println("HFP: INIT DONE");
}
// ============================================================
// LOOP FUNCTION (call repeatedly)
// ============================================================
void hfp_loop()
{
if (!hfp_ready) return;
// Nothing required here for core operation.
// HFP runs using callbacks from BT stack.
// But you can add logic like:
// - button to answer/hangup
// - reconnect if disconnected
// - call esp_hf_client_connect_audio() if needed
static unsigned long lastPrint = 0;
if (millis() - lastPrint > 2000)
{
lastPrint = millis();
Serial.printf("HFP: loop ok | remote=%d | sco=%d\n",
remote_bda_valid,
sco_connected);
}
}Other Steps to ReproduceNo response Provide your Version of the EP32 Arduino Core (or the IDF Version)pio v6.1.19 I have checked existing issues, discussions and online documentation (incl. the Wiki)
|
Beta Was this translation helpful? Give feedback.
Replies: 5 comments 2 replies
-
|
I never tried, so I can't really help. This sentence has been added because
To analyse what's happening in my library in detail, you can set the logger to info or debug mode! |
Beta Was this translation helpful? Give feedback.
-
|
I had such a same idea, but not so good at coding. so i asked chatgpt. #include <Arduino.h>
// -------- Bluetooth --------
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_bt_device.h"
// -------- Profiles --------
#include "esp_hf_client_api.h"
#include "esp_a2dp_api.h"
#include "esp_avrc_api.h"
#include "esp_spp_api.h"
// -------- I2S --------
#include "ESP_I2S.h"
// =====================================================
// CONFIG
// =====================================================
#define I2S_PORT I2S_NUM_0
#define I2S_BCLK 26
#define I2S_WS 25
#define I2S_DOUT 22
#define I2S_DIN 32
// =====================================================
// AUDIO MODE
// =====================================================
enum audio_mode_t {
AUDIO_NONE,
AUDIO_A2DP,
AUDIO_HFP
};
volatile audio_mode_t audio_mode = AUDIO_NONE;
static uint32_t spp_handle = 0;
// =====================================================
// I2S INIT
// =====================================================
void i2s_start(uint32_t sample_rate) {
i2s_driver_uninstall(I2S_PORT);
i2s_config_t cfg = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_RX),
.sample_rate = sample_rate,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = I2S_COMM_FORMAT_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 6,
.dma_buf_len = 120,
.use_apll = false,
.tx_desc_auto_clear = true
};
i2s_pin_config_t pins = {
.bck_io_num = I2S_BCLK,
.ws_io_num = I2S_WS,
.data_out_num = I2S_DOUT,
.data_in_num = I2S_DIN
};
i2s_driver_install(I2S_PORT, &cfg, 0, NULL);
i2s_set_pin(I2S_PORT, &pins);
i2s_zero_dma_buffer(I2S_PORT);
Serial.printf("I2S gestartet @ %lu Hz\n", sample_rate);
}
// =====================================================
// HFP CALLBACKS
// =====================================================
void hf_event_handler(esp_hf_client_cb_event_t event,
esp_hf_client_cb_param_t *param) {
if (event == ESP_HF_CLIENT_AUDIO_STATE_EVT) {
if (param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_CONNECTED) {
audio_mode = AUDIO_HFP;
i2s_start(8000);
Serial.println("HFP Audio aktiv");
} else {
audio_mode = AUDIO_A2DP;
i2s_start(44100);
Serial.println("HFP Audio aus → zurück zu A2DP");
}
}
}
void hf_audio_out(const uint8_t *data, uint32_t len) {
if (audio_mode != AUDIO_HFP) return;
size_t written;
i2s_write(I2S_PORT, data, len, &written, portMAX_DELAY);
}
uint32_t hf_audio_in(uint8_t *data, uint32_t len) {
if (audio_mode != AUDIO_HFP) return 0;
size_t read_bytes;
i2s_read(I2S_PORT, data, len, &read_bytes, portMAX_DELAY);
return read_bytes;
}
// =====================================================
// A2DP CALLBACKS
// =====================================================
void a2dp_audio_cb(const uint8_t *data, uint32_t len) {
if (audio_mode != AUDIO_A2DP) return;
size_t written;
i2s_write(I2S_PORT, data, len, &written, portMAX_DELAY);
}
void a2dp_event_cb(esp_a2d_cb_event_t event,
esp_a2d_cb_param_t *param) {
if (event == ESP_A2D_AUDIO_STATE_EVT) {
if (param->audio_stat.state == ESP_A2D_AUDIO_STATE_STARTED) {
audio_mode = AUDIO_A2DP;
i2s_start(44100);
Serial.println("A2DP Audio aktiv");
}
}
}
// =====================================================
// SPP CALLBACK
// =====================================================
void spp_cb(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) {
switch (event) {
case ESP_SPP_INIT_EVT:
esp_spp_start_srv(ESP_SPP_SEC_NONE,
ESP_SPP_ROLE_SLAVE,
0,
"ESP32_SPP");
break;
case ESP_SPP_SRV_OPEN_EVT:
spp_handle = param->srv_open.handle;
Serial.println("SPP verbunden");
break;
case ESP_SPP_CLOSE_EVT:
spp_handle = 0;
Serial.println("SPP getrennt");
break;
case ESP_SPP_DATA_IND_EVT:
{
String cmd;
for (int i = 0; i < param->data_ind.len; i++)
cmd += (char)param->data_ind.data[i];
cmd.trim();
Serial.println("CMD: " + cmd);
if (cmd == "ATA") esp_hf_client_answer_call();
else if (cmd == "ATH") esp_hf_client_reject_call();
else if (cmd == "PLAY") esp_avrc_ct_send_passthrough_cmd(0, ESP_AVRC_PT_CMD_PLAY, ESP_AVRC_PT_CMD_STATE_PRESSED);
else if (cmd == "PAUSE") esp_avrc_ct_send_passthrough_cmd(0, ESP_AVRC_PT_CMD_PAUSE, ESP_AVRC_PT_CMD_STATE_PRESSED);
break;
}
}
}
// =====================================================
// SETUP
// =====================================================
void setup() {
Serial.begin(115200);
delay(500);
Serial.println("ESP32 HFP + A2DP + SPP Headset");
esp_bt_controller_mem_release(ESP_BT_MODE_BLE);
esp_bt_controller_config_t bt_cfg =
BT_CONTROLLER_INIT_CONFIG_DEFAULT();
esp_bt_controller_init(&bt_cfg);
esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT);
esp_bluedroid_init();
esp_bluedroid_enable();
esp_bt_dev_set_device_name("ESP32BT");
// I2S default
i2s_start(44100);
audio_mode = AUDIO_A2DP;
// HFP
esp_hf_client_register_callback(hf_event_handler);
esp_hf_client_init();
esp_hf_client_register_data_callback(hf_audio_out, hf_audio_in);
// A2DP
esp_a2d_register_callback(a2dp_event_cb);
esp_a2d_sink_register_data_callback(a2dp_audio_cb);
esp_a2d_sink_init();
// AVRCP
esp_avrc_ct_init();
esp_avrc_ct_register_callback(NULL);
// SPP
esp_spp_register_callback(spp_cb);
esp_spp_init(ESP_SPP_MODE_CB);
Serial.println("Bluetooth bereit: HFP + A2DP + SPP");
}
// =====================================================
// LOOP
// =====================================================
void loop() {
delay(1000);
}
void spp_send(const char *msg) {
if (spp_handle != 0) {
esp_spp_write(spp_handle, strlen(msg), (uint8_t *)msg);
}
}
void send_audio_status() {
char buf[64];
snprintf(buf, sizeof(buf), "AUDIO_MODE=%d\n", audio_mode);
spp_send(buf);
} |
Beta Was this translation helpful? Give feedback.
-
|
Actually pretty bad code: don't use this obsolete I2S API ! |
Beta Was this translation helpful? Give feedback.
-
|
Please see reply for answer - hfp is working now but using both espidf and arduino set in platform, and a few code changes, making sure to modify the device in pio -t menuconfig |
Beta Was this translation helpful? Give feedback.
-
|
Hello @cjhudlin, I'll be interested in seeing how you made HFP work in HCI mode. |
Beta Was this translation helpful? Give feedback.
Hello Schatzmann, would like to report that HFP is working with you a2dp library if the project is built in the espidf and arduino platform as the arduino core does not support hfp via hci, which is needed. therefore the sdkconfig can be run in pio terminal and bluetooth should be set to HCI and PCM should be disabled. after, this example init / deinit code is used to hold a2dp (may need tailoring):
void safe_deinit_a2dp() {
if (!deinit_called) {
Serial.println("HFP: Deinit A2DP for SCO");
BluetoothA2DPOutputAudioTools().end();
BluetoothA2DPOutputAudioTools().set_output_active(false);
deinit_called = true;
delay(50);
}
}
void safe_init_a2dp() {
if (deinit_called) {
Serial.println("HFP: Re…