From bb46db736cd5d6239ef7158e4e3d5fb8bde601a4 Mon Sep 17 00:00:00 2001 From: yangfeng Date: Thu, 14 Aug 2025 14:55:39 +0800 Subject: [PATCH] refactor(examples/classic_bt): Refactor A2DP sink and AVRCP examples - Add avrcp_ct_metadata example - Add avrcp_absolute_volume example - Add the example components about common code for a2dp sink and avrcp --- .../classic_bt/a2dp_sink/main/CMakeLists.txt | 5 - .../classic_bt/a2dp_sink/main/bt_app_av.c | 717 ------------------ .../classic_bt/a2dp_sink/main/bt_app_av.h | 59 -- .../classic_bt/a2dp_sink/main/bt_app_core.c | 266 ------- .../classic_bt/a2dp_sink/main/main.c | 265 ------- .../CMakeLists.txt | 2 +- .../{a2dp_sink => a2dp_sink_stream}/README.md | 37 +- .../a2dp_sink_stream/main/CMakeLists.txt | 10 + .../a2dp_sink_stream/main/Kconfig.projbuild | 15 + .../a2dp_sink_stream/main/idf_component.yml | 11 + .../classic_bt/a2dp_sink_stream/main/main.c | 177 +++++ .../sdkconfig.ci.ca_dis | 3 - .../sdkconfig.ci.test | 0 .../sdkconfig.defaults | 3 - .../tutorial/Example_A2DP_Sink.md | 92 +-- .../avrcp_absolute_volume/CMakeLists.txt | 8 + .../avrcp_absolute_volume/README.md | 70 ++ .../avrcp_absolute_volume/main/CMakeLists.txt | 12 + .../main/Kconfig.projbuild | 23 + .../main/idf_component.yml | 15 + .../avrcp_absolute_volume/main/main.c | 249 ++++++ .../avrcp_absolute_volume/sdkconfig.ci.test | 1 + .../avrcp_absolute_volume/sdkconfig.defaults | 8 + .../avrcp_ct_metadata/CMakeLists.txt | 8 + .../classic_bt/avrcp_ct_metadata/README.md | 67 ++ .../avrcp_ct_metadata/main/CMakeLists.txt | 12 + .../avrcp_ct_metadata/main/Kconfig.projbuild | 23 + .../avrcp_ct_metadata/main/idf_component.yml | 15 + .../classic_bt/avrcp_ct_metadata/main/main.c | 295 +++++++ .../pytest_classic_bt_metadata_test.py | 12 + .../avrcp_ct_metadata/sdkconfig.defaults | 8 + .../bluedroid/classic_bt/common/README.md | 44 ++ .../a2dp_sink_common_utils/CMakeLists.txt | 3 + .../a2dp_sink_common_utils.c | 118 +++ .../a2dp_sink_common_utils.h | 20 + .../a2dp_sink_ext_codec_utils/CMakeLists.txt | 3 + .../a2dp_sink_ext_codec_utils.c | 116 +++ .../a2dp_sink_ext_codec_utils.h | 29 + .../a2dp_sink_int_codec_utils/CMakeLists.txt | 6 + .../Kconfig.projbuild | 37 +- .../a2dp_sink_int_codec_utils.c | 86 +++ .../a2dp_sink_int_codec_utils.h | 28 + .../audio_sink_service.h | 51 ++ .../audio_sink_service_dac.c | 222 ++++++ .../audio_sink_service_i2s.c | 229 ++++++ .../audio_sink_service_idle.c | 49 ++ .../a2dp_utils/include/a2dp_utils_tags.h | 13 + .../avrcp_abs_vol_utils/CMakeLists.txt | 4 + .../avrcp_abs_vol_service.c | 88 +++ .../avrcp_abs_vol_service.h | 48 ++ .../avrcp_abs_vol_utils/avrcp_abs_vol_utils.c | 103 +++ .../avrcp_abs_vol_utils/avrcp_abs_vol_utils.h | 20 + .../avrcp_common_utils/CMakeLists.txt | 3 + .../avrcp_common_utils/avrcp_common_utils.c | 228 ++++++ .../avrcp_common_utils/avrcp_common_utils.h | 82 ++ .../avrcp_metadata_utils/CMakeLists.txt | 4 + .../avrcp_metadata_service.c | 155 ++++ .../avrcp_metadata_service.h | 48 ++ .../avrcp_metadata_utils.c | 77 ++ .../avrcp_metadata_utils.h | 20 + .../avrcp_metadata_utils/idf_component.yml | 3 + .../avrcp_utils/include/avrcp_utils_tags.h | 15 + .../bredr_app_common_utils/CMakeLists.txt | 3 + .../bredr_app_common_utils/Kconfig.projbuild | 10 + .../bredr_app_common_utils.c | 185 +++++ .../bredr_app_common_utils.h | 37 + .../common/bt_app_core_utils/CMakeLists.txt | 2 + .../bt_app_core_utils/bt_app_core_utils.c | 138 ++++ .../bt_app_core_utils/bt_app_core_utils.h} | 37 +- 69 files changed, 3380 insertions(+), 1472 deletions(-) delete mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/CMakeLists.txt delete mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_av.c delete mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_av.h delete mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_core.c delete mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/main.c rename examples/bluetooth/bluedroid/classic_bt/{a2dp_sink => a2dp_sink_stream}/CMakeLists.txt (93%) rename examples/bluetooth/bluedroid/classic_bt/{a2dp_sink => a2dp_sink_stream}/README.md (73%) create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/main/CMakeLists.txt create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/main/Kconfig.projbuild create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/main/idf_component.yml create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/main/main.c rename examples/bluetooth/bluedroid/classic_bt/{a2dp_sink => a2dp_sink_stream}/sdkconfig.ci.ca_dis (75%) rename examples/bluetooth/bluedroid/classic_bt/{a2dp_sink => a2dp_sink_stream}/sdkconfig.ci.test (100%) rename examples/bluetooth/bluedroid/classic_bt/{a2dp_sink => a2dp_sink_stream}/sdkconfig.defaults (73%) rename examples/bluetooth/bluedroid/classic_bt/{a2dp_sink => a2dp_sink_stream}/tutorial/Example_A2DP_Sink.md (51%) create mode 100644 examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/CMakeLists.txt create mode 100644 examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/README.md create mode 100644 examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/main/CMakeLists.txt create mode 100644 examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/main/Kconfig.projbuild create mode 100644 examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/main/idf_component.yml create mode 100644 examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/main/main.c create mode 100644 examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/sdkconfig.ci.test create mode 100644 examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/sdkconfig.defaults create mode 100644 examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/CMakeLists.txt create mode 100644 examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/README.md create mode 100644 examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/main/CMakeLists.txt create mode 100644 examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/main/Kconfig.projbuild create mode 100644 examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/main/idf_component.yml create mode 100644 examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/main/main.c create mode 100644 examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/pytest_classic_bt_metadata_test.py create mode 100644 examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/sdkconfig.defaults create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/README.md create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_common_utils/CMakeLists.txt create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_common_utils/a2dp_sink_common_utils.c create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_common_utils/a2dp_sink_common_utils.h create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_ext_codec_utils/CMakeLists.txt create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_ext_codec_utils/a2dp_sink_ext_codec_utils.c create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_ext_codec_utils/a2dp_sink_ext_codec_utils.h create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/CMakeLists.txt rename examples/bluetooth/bluedroid/classic_bt/{a2dp_sink/main => common/a2dp_utils/a2dp_sink_int_codec_utils}/Kconfig.projbuild (54%) create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/a2dp_sink_int_codec_utils.c create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/a2dp_sink_int_codec_utils.h create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service.h create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_dac.c create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_i2s.c create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_idle.c create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/include/a2dp_utils_tags.h create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_abs_vol_utils/CMakeLists.txt create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_abs_vol_utils/avrcp_abs_vol_service.c create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_abs_vol_utils/avrcp_abs_vol_service.h create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_abs_vol_utils/avrcp_abs_vol_utils.c create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_abs_vol_utils/avrcp_abs_vol_utils.h create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_common_utils/CMakeLists.txt create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_common_utils/avrcp_common_utils.c create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_common_utils/avrcp_common_utils.h create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_metadata_utils/CMakeLists.txt create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_metadata_utils/avrcp_metadata_service.c create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_metadata_utils/avrcp_metadata_service.h create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_metadata_utils/avrcp_metadata_utils.c create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_metadata_utils/avrcp_metadata_utils.h create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_metadata_utils/idf_component.yml create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/include/avrcp_utils_tags.h create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/bredr_app_common_utils/CMakeLists.txt create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/bredr_app_common_utils/Kconfig.projbuild create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/bredr_app_common_utils/bredr_app_common_utils.c create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/bredr_app_common_utils/bredr_app_common_utils.h create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/bt_app_core_utils/CMakeLists.txt create mode 100644 examples/bluetooth/bluedroid/classic_bt/common/bt_app_core_utils/bt_app_core_utils.c rename examples/bluetooth/bluedroid/classic_bt/{a2dp_sink/main/bt_app_core.h => common/bt_app_core_utils/bt_app_core_utils.h} (64%) diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/CMakeLists.txt b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/CMakeLists.txt deleted file mode 100644 index e68a065139..0000000000 --- a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -idf_component_register(SRCS "bt_app_av.c" - "bt_app_core.c" - "main.c" - PRIV_REQUIRES esp_driver_i2s bt nvs_flash esp_ringbuf esp_driver_dac - INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_av.c b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_av.c deleted file mode 100644 index 9fd92b2d2b..0000000000 --- a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_av.c +++ /dev/null @@ -1,717 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Unlicense OR CC0-1.0 - */ - -#include -#include -#include -#include -#include -#include "esp_log.h" - -#include "bt_app_core.h" -#include "bt_app_av.h" -#include "esp_bt_main.h" -#include "esp_bt_device.h" -#include "esp_gap_bt_api.h" -#include "esp_a2dp_api.h" -#include "esp_avrc_api.h" - -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC -#include "driver/dac_continuous.h" -#else -#include "driver/i2s_std.h" -#endif - -#include "sys/lock.h" - -/* AVRCP used transaction labels */ -#define APP_RC_CT_TL_GET_CAPS (0) -#define APP_RC_CT_TL_GET_META_DATA (1) -#define APP_RC_CT_TL_RN_TRACK_CHANGE (2) -#define APP_RC_CT_TL_RN_PLAYBACK_CHANGE (3) -#define APP_RC_CT_TL_RN_PLAY_POS_CHANGE (4) - -/* Application layer causes delay value */ -#define APP_DELAY_VALUE 50 // 5ms - -/******************************* - * STATIC FUNCTION DECLARATIONS - ******************************/ - -/* allocate new meta buffer */ -static void bt_app_alloc_meta_buffer(esp_avrc_ct_cb_param_t *param); -/* handler for new track is loaded */ -static void bt_av_new_track(void); -/* handler for track status change */ -static void bt_av_playback_changed(void); -/* handler for track playing position change */ -static void bt_av_play_pos_changed(void); -/* notification event handler */ -static void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_parameter); -/* installation for i2s */ -static void bt_i2s_driver_install(void); -/* uninstallation for i2s */ -static void bt_i2s_driver_uninstall(void); -/* set volume by remote controller */ -static void volume_set_by_controller(uint8_t volume); -/* set volume by local host */ -static void volume_set_by_local_host(uint8_t volume); -/* simulation volume change */ -static void volume_change_simulation(void *arg); -/* a2dp event handler */ -static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param); -/* avrc controller event handler */ -static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param); -/* avrc target event handler */ -static void bt_av_hdl_avrc_tg_evt(uint16_t event, void *p_param); - -/******************************* - * STATIC VARIABLE DEFINITIONS - ******************************/ - -static uint32_t s_pkt_cnt = 0; /* count for audio packet */ -static esp_a2d_audio_state_t s_audio_state = ESP_A2D_AUDIO_STATE_SUSPEND; - /* audio stream datapath state */ -static const char *s_a2d_conn_state_str[] = {"Disconnected", "Connecting", "Connected", "Disconnecting"}; - /* connection state in string */ -static const char *s_a2d_audio_state_str[] = {"Suspended", "Started"}; - /* audio stream datapath state in string */ -static esp_avrc_rn_evt_cap_mask_t s_avrc_peer_rn_cap; - /* AVRC target notification capability bit mask */ -static _lock_t s_volume_lock; -static TaskHandle_t s_vcs_task_hdl = NULL; /* handle for volume change simulation task */ -static uint8_t s_volume = 0; /* local volume value */ -static bool s_volume_notify; /* notify volume change or not */ -#ifndef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC -i2s_chan_handle_t tx_chan = NULL; -#else -dac_continuous_handle_t tx_chan; -#endif - -#if CONFIG_EXAMPLE_AVRCP_CT_COVER_ART_ENABLE -static bool cover_art_connected = false; -static bool cover_art_getting = false; -static uint32_t cover_art_image_size = 0; -static uint8_t image_handle_old[7]; -#endif - -/******************************** - * STATIC FUNCTION DEFINITIONS - *******************************/ - -static void bt_app_alloc_meta_buffer(esp_avrc_ct_cb_param_t *param) -{ - esp_avrc_ct_cb_param_t *rc = (esp_avrc_ct_cb_param_t *)(param); - uint8_t *attr_text = (uint8_t *) malloc (rc->meta_rsp.attr_length + 1); - - memcpy(attr_text, rc->meta_rsp.attr_text, rc->meta_rsp.attr_length); - attr_text[rc->meta_rsp.attr_length] = 0; - rc->meta_rsp.attr_text = attr_text; -} - -#if CONFIG_EXAMPLE_AVRCP_CT_COVER_ART_ENABLE -static bool image_handle_check(uint8_t *image_handle, int len) -{ - /* Image handle length must be 7 */ - if (len == 7 && memcmp(image_handle_old, image_handle, 7) != 0) { - memcpy(image_handle_old, image_handle, 7); - return true; - } - return false; -} -#endif - -static void bt_av_new_track(void) -{ - /* request metadata */ - uint8_t attr_mask = ESP_AVRC_MD_ATTR_TITLE | - ESP_AVRC_MD_ATTR_ARTIST | - ESP_AVRC_MD_ATTR_ALBUM | - ESP_AVRC_MD_ATTR_GENRE; -#if CONFIG_EXAMPLE_AVRCP_CT_COVER_ART_ENABLE - if (cover_art_connected) { - attr_mask |= ESP_AVRC_MD_ATTR_COVER_ART; - } -#endif - esp_avrc_ct_send_metadata_cmd(APP_RC_CT_TL_GET_META_DATA, attr_mask); - - /* register notification if peer support the event_id */ - if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_peer_rn_cap, - ESP_AVRC_RN_TRACK_CHANGE)) { - esp_avrc_ct_send_register_notification_cmd(APP_RC_CT_TL_RN_TRACK_CHANGE, - ESP_AVRC_RN_TRACK_CHANGE, 0); - } -} - -static void bt_av_playback_changed(void) -{ - /* register notification if peer support the event_id */ - if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_peer_rn_cap, - ESP_AVRC_RN_PLAY_STATUS_CHANGE)) { - esp_avrc_ct_send_register_notification_cmd(APP_RC_CT_TL_RN_PLAYBACK_CHANGE, - ESP_AVRC_RN_PLAY_STATUS_CHANGE, 0); - } -} - -static void bt_av_play_pos_changed(void) -{ - /* register notification if peer support the event_id */ - if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_peer_rn_cap, - ESP_AVRC_RN_PLAY_POS_CHANGED)) { - esp_avrc_ct_send_register_notification_cmd(APP_RC_CT_TL_RN_PLAY_POS_CHANGE, - ESP_AVRC_RN_PLAY_POS_CHANGED, 10); - } -} - -static void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_parameter) -{ - switch (event_id) { - /* when new track is loaded, this event comes */ - case ESP_AVRC_RN_TRACK_CHANGE: - bt_av_new_track(); - break; - /* when track status changed, this event comes */ - case ESP_AVRC_RN_PLAY_STATUS_CHANGE: - ESP_LOGI(BT_AV_TAG, "Playback status changed: 0x%x", event_parameter->playback); - bt_av_playback_changed(); - break; - /* when track playing position changed, this event comes */ - case ESP_AVRC_RN_PLAY_POS_CHANGED: - ESP_LOGI(BT_AV_TAG, "Play position changed: %"PRIu32"-ms", event_parameter->play_pos); - bt_av_play_pos_changed(); - break; - /* others */ - default: - ESP_LOGI(BT_AV_TAG, "unhandled event: %d", event_id); - break; - } -} - -void bt_i2s_driver_install(void) -{ -#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC - dac_continuous_config_t cont_cfg = { - .chan_mask = DAC_CHANNEL_MASK_ALL, - .desc_num = 8, - .buf_size = 2048, - .freq_hz = 44100, - .offset = 127, - .clk_src = DAC_DIGI_CLK_SRC_DEFAULT, // Using APLL as clock source to get a wider frequency range - .chan_mode = DAC_CHANNEL_MODE_ALTER, - }; - /* Allocate continuous channels */ - ESP_ERROR_CHECK(dac_continuous_new_channels(&cont_cfg, &tx_chan)); - /* Enable the continuous channels */ - ESP_ERROR_CHECK(dac_continuous_enable(tx_chan)); -#else - i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); - chan_cfg.auto_clear = true; - i2s_std_config_t std_cfg = { - .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(44100), - .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), - .gpio_cfg = { - .mclk = I2S_GPIO_UNUSED, - .bclk = CONFIG_EXAMPLE_I2S_BCK_PIN, - .ws = CONFIG_EXAMPLE_I2S_LRCK_PIN, - .dout = CONFIG_EXAMPLE_I2S_DATA_PIN, - .din = I2S_GPIO_UNUSED, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false, - }, - }, - }; - /* enable I2S */ - ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_chan, NULL)); - ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_chan, &std_cfg)); - ESP_ERROR_CHECK(i2s_channel_enable(tx_chan)); -#endif -} - -void bt_i2s_driver_uninstall(void) -{ -#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC - ESP_ERROR_CHECK(dac_continuous_disable(tx_chan)); - ESP_ERROR_CHECK(dac_continuous_del_channels(tx_chan)); -#else - ESP_ERROR_CHECK(i2s_channel_disable(tx_chan)); - ESP_ERROR_CHECK(i2s_del_channel(tx_chan)); -#endif -} - -static void volume_set_by_controller(uint8_t volume) -{ - ESP_LOGI(BT_RC_TG_TAG, "Volume is set by remote controller to: %"PRIu32"%%", (uint32_t)volume * 100 / 0x7f); - /* set the volume in protection of lock */ - _lock_acquire(&s_volume_lock); - s_volume = volume; - _lock_release(&s_volume_lock); -} - -static void volume_set_by_local_host(uint8_t volume) -{ - ESP_LOGI(BT_RC_TG_TAG, "Volume is set locally to: %"PRIu32"%%", (uint32_t)volume * 100 / 0x7f); - /* set the volume in protection of lock */ - _lock_acquire(&s_volume_lock); - s_volume = volume; - _lock_release(&s_volume_lock); - - /* send notification response to remote AVRCP controller */ - if (s_volume_notify) { - esp_avrc_rn_param_t rn_param; - rn_param.volume = s_volume; - esp_avrc_tg_send_rn_rsp(ESP_AVRC_RN_VOLUME_CHANGE, ESP_AVRC_RN_RSP_CHANGED, &rn_param); - s_volume_notify = false; - } -} - -static void volume_change_simulation(void *arg) -{ - ESP_LOGI(BT_RC_TG_TAG, "start volume change simulation"); - - for (;;) { - /* volume up locally every 10 seconds */ - vTaskDelay(10000 / portTICK_PERIOD_MS); - uint8_t volume = (s_volume + 5) & 0x7f; - volume_set_by_local_host(volume); - } -} - -static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param) -{ - ESP_LOGD(BT_AV_TAG, "%s event: %d", __func__, event); - - esp_a2d_cb_param_t *a2d = NULL; - - switch (event) { - /* when connection state changed, this event comes */ - case ESP_A2D_CONNECTION_STATE_EVT: { - a2d = (esp_a2d_cb_param_t *)(p_param); - uint8_t *bda = a2d->conn_stat.remote_bda; - ESP_LOGI(BT_AV_TAG, "A2DP connection state: %s, [%02x:%02x:%02x:%02x:%02x:%02x]", - s_a2d_conn_state_str[a2d->conn_stat.state], bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); - if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { - esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); - bt_i2s_driver_uninstall(); - bt_i2s_task_shut_down(); - } else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED){ - esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE); - bt_i2s_task_start_up(); - } else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTING) { - bt_i2s_driver_install(); - } - break; - } - /* when audio stream transmission state changed, this event comes */ - case ESP_A2D_AUDIO_STATE_EVT: { - a2d = (esp_a2d_cb_param_t *)(p_param); - ESP_LOGI(BT_AV_TAG, "A2DP audio state: %s", s_a2d_audio_state_str[a2d->audio_stat.state]); - s_audio_state = a2d->audio_stat.state; - if (ESP_A2D_AUDIO_STATE_STARTED == a2d->audio_stat.state) { - s_pkt_cnt = 0; - } - break; - } - /* when audio codec is configured, this event comes */ - case ESP_A2D_AUDIO_CFG_EVT: { - a2d = (esp_a2d_cb_param_t *)(p_param); - esp_a2d_mcc_t *p_mcc = &a2d->audio_cfg.mcc; - ESP_LOGI(BT_AV_TAG, "A2DP audio stream configuration, codec type: %d", p_mcc->type); - /* for now only SBC stream is supported */ - if (p_mcc->type == ESP_A2D_MCT_SBC) { - int sample_rate = 16000; - int ch_count = 2; - if (p_mcc->cie.sbc_info.samp_freq & ESP_A2D_SBC_CIE_SF_32K) { - sample_rate = 32000; - } else if (p_mcc->cie.sbc_info.samp_freq & ESP_A2D_SBC_CIE_SF_44K) { - sample_rate = 44100; - } else if (p_mcc->cie.sbc_info.samp_freq & ESP_A2D_SBC_CIE_SF_48K) { - sample_rate = 48000; - } - - if (p_mcc->cie.sbc_info.ch_mode & ESP_A2D_SBC_CIE_CH_MODE_MONO) { - ch_count = 1; - } - #ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC - dac_continuous_disable(tx_chan); - dac_continuous_del_channels(tx_chan); - dac_continuous_config_t cont_cfg = { - .chan_mask = DAC_CHANNEL_MASK_ALL, - .desc_num = 8, - .buf_size = 2048, - .freq_hz = sample_rate, - .offset = 127, - .clk_src = DAC_DIGI_CLK_SRC_DEFAULT, // Using APLL as clock source to get a wider frequency range - .chan_mode = (ch_count == 1) ? DAC_CHANNEL_MODE_SIMUL : DAC_CHANNEL_MODE_ALTER, - }; - /* Allocate continuous channels */ - dac_continuous_new_channels(&cont_cfg, &tx_chan); - /* Enable the continuous channels */ - dac_continuous_enable(tx_chan); - #else - i2s_channel_disable(tx_chan); - i2s_std_clk_config_t clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(sample_rate); - i2s_std_slot_config_t slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, ch_count); - i2s_channel_reconfig_std_clock(tx_chan, &clk_cfg); - i2s_channel_reconfig_std_slot(tx_chan, &slot_cfg); - i2s_channel_enable(tx_chan); - #endif - ESP_LOGI(BT_AV_TAG, "Configure audio player: 0x%x-0x%x-0x%x-0x%x-0x%x-%d-%d", - p_mcc->cie.sbc_info.samp_freq, - p_mcc->cie.sbc_info.ch_mode, - p_mcc->cie.sbc_info.block_len, - p_mcc->cie.sbc_info.num_subbands, - p_mcc->cie.sbc_info.alloc_mthd, - p_mcc->cie.sbc_info.min_bitpool, - p_mcc->cie.sbc_info.max_bitpool); - ESP_LOGI(BT_AV_TAG, "Audio player configured, sample rate: %d", sample_rate); - } - break; - } - /* when a2dp init or deinit completed, this event comes */ - case ESP_A2D_PROF_STATE_EVT: { - a2d = (esp_a2d_cb_param_t *)(p_param); - if (ESP_A2D_INIT_SUCCESS == a2d->a2d_prof_stat.init_state) { - ESP_LOGI(BT_AV_TAG, "A2DP PROF STATE: Init Complete"); - } else { - ESP_LOGI(BT_AV_TAG, "A2DP PROF STATE: Deinit Complete"); - } - break; - } - /* when using external codec, after sep registration done, this event comes */ - case ESP_A2D_SEP_REG_STATE_EVT: { - a2d = (esp_a2d_cb_param_t *)(p_param); - if (a2d->a2d_sep_reg_stat.reg_state == ESP_A2D_SEP_REG_SUCCESS) { - ESP_LOGI(BT_AV_TAG, "A2DP register SEP success, seid: %d", a2d->a2d_sep_reg_stat.seid); - } - else { - ESP_LOGI(BT_AV_TAG, "A2DP register SEP fail, seid: %d, state: %d", a2d->a2d_sep_reg_stat.seid, a2d->a2d_sep_reg_stat.reg_state); - } - break; - } - /* When protocol service capabilities configured, this event comes */ - case ESP_A2D_SNK_PSC_CFG_EVT: { - a2d = (esp_a2d_cb_param_t *)(p_param); - ESP_LOGI(BT_AV_TAG, "protocol service capabilities configured: 0x%x ", a2d->a2d_psc_cfg_stat.psc_mask); - if (a2d->a2d_psc_cfg_stat.psc_mask & ESP_A2D_PSC_DELAY_RPT) { - ESP_LOGI(BT_AV_TAG, "Peer device support delay reporting"); - } else { - ESP_LOGI(BT_AV_TAG, "Peer device unsupported delay reporting"); - } - break; - } - /* when set delay value completed, this event comes */ - case ESP_A2D_SNK_SET_DELAY_VALUE_EVT: { - a2d = (esp_a2d_cb_param_t *)(p_param); - if (ESP_A2D_SET_INVALID_PARAMS == a2d->a2d_set_delay_value_stat.set_state) { - ESP_LOGI(BT_AV_TAG, "Set delay report value: fail"); - } else { - ESP_LOGI(BT_AV_TAG, "Set delay report value: success, delay_value: %u * 1/10 ms", a2d->a2d_set_delay_value_stat.delay_value); - } - break; - } - /* when get delay value completed, this event comes */ - case ESP_A2D_SNK_GET_DELAY_VALUE_EVT: { - a2d = (esp_a2d_cb_param_t *)(p_param); - ESP_LOGI(BT_AV_TAG, "Get delay report value: delay_value: %u * 1/10 ms", a2d->a2d_get_delay_value_stat.delay_value); - /* Default delay value plus delay caused by application layer */ - esp_a2d_sink_set_delay_value(a2d->a2d_get_delay_value_stat.delay_value + APP_DELAY_VALUE); - break; - } - /* others */ - default: - ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event); - break; - } -} - -static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param) -{ - ESP_LOGD(BT_RC_CT_TAG, "%s event: %d", __func__, event); - - esp_avrc_ct_cb_param_t *rc = (esp_avrc_ct_cb_param_t *)(p_param); - - switch (event) { - /* when connection state changed, this event comes */ - case ESP_AVRC_CT_CONNECTION_STATE_EVT: { - uint8_t *bda = rc->conn_stat.remote_bda; - ESP_LOGI(BT_RC_CT_TAG, "AVRC conn_state event: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]", - rc->conn_stat.connected, bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); - - if (rc->conn_stat.connected) { - /* get remote supported event_ids of peer AVRCP Target */ - esp_avrc_ct_send_get_rn_capabilities_cmd(APP_RC_CT_TL_GET_CAPS); - } else { - /* clear peer notification capability record */ - s_avrc_peer_rn_cap.bits = 0; - } - break; - } - /* when passthrough response, this event comes */ - case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: { - ESP_LOGI(BT_RC_CT_TAG, "AVRC passthrough rsp: key_code 0x%x, key_state %d, rsp_code %d", rc->psth_rsp.key_code, - rc->psth_rsp.key_state, rc->psth_rsp.rsp_code); - break; - } - /* when metadata response, this event comes */ - case ESP_AVRC_CT_METADATA_RSP_EVT: { - ESP_LOGI(BT_RC_CT_TAG, "AVRC metadata rsp: attribute id 0x%x, %s", rc->meta_rsp.attr_id, rc->meta_rsp.attr_text); -#if CONFIG_EXAMPLE_AVRCP_CT_COVER_ART_ENABLE - if(rc->meta_rsp.attr_id == 0x80 && cover_art_connected && cover_art_getting == false) { - /* check image handle is valid and different with last one, wo dont want to get an image repeatedly */ - if(image_handle_check(rc->meta_rsp.attr_text, rc->meta_rsp.attr_length)) { - esp_avrc_ct_cover_art_get_linked_thumbnail(rc->meta_rsp.attr_text); - cover_art_getting = true; - } - } -#endif - free(rc->meta_rsp.attr_text); - break; - } - /* when notified, this event comes */ - case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: { - ESP_LOGI(BT_RC_CT_TAG, "AVRC event notification: %d", rc->change_ntf.event_id); - bt_av_notify_evt_handler(rc->change_ntf.event_id, &rc->change_ntf.event_parameter); - break; - } - /* when feature of remote device indicated, this event comes */ - case ESP_AVRC_CT_REMOTE_FEATURES_EVT: { - ESP_LOGI(BT_RC_CT_TAG, "AVRC remote features %"PRIx32", TG features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.tg_feat_flag); -#if CONFIG_EXAMPLE_AVRCP_CT_COVER_ART_ENABLE - if ((rc->rmt_feats.tg_feat_flag & ESP_AVRC_FEAT_FLAG_TG_COVER_ART) && !cover_art_connected) { - ESP_LOGW(BT_RC_CT_TAG, "Peer support Cover Art feature, start connection..."); - /* set mtu to zero to use a default value */ - esp_avrc_ct_cover_art_connect(0); - } -#endif - break; - } - /* when notification capability of peer device got, this event comes */ - case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: { - ESP_LOGI(BT_RC_CT_TAG, "remote rn_cap: count %d, bitmask 0x%x", rc->get_rn_caps_rsp.cap_count, - rc->get_rn_caps_rsp.evt_set.bits); - s_avrc_peer_rn_cap.bits = rc->get_rn_caps_rsp.evt_set.bits; - bt_av_new_track(); - bt_av_playback_changed(); - bt_av_play_pos_changed(); - break; - } - case ESP_AVRC_CT_COVER_ART_STATE_EVT: { -#if CONFIG_EXAMPLE_AVRCP_CT_COVER_ART_ENABLE - if (rc->cover_art_state.state == ESP_AVRC_COVER_ART_CONNECTED) { - cover_art_connected = true; - ESP_LOGW(BT_RC_CT_TAG, "Cover Art Client connected"); - } - else { - cover_art_connected = false; - ESP_LOGW(BT_RC_CT_TAG, "Cover Art Client disconnected, reason:%d", rc->cover_art_state.reason); - } -#endif - break; - } - case ESP_AVRC_CT_COVER_ART_DATA_EVT: { -#if CONFIG_EXAMPLE_AVRCP_CT_COVER_ART_ENABLE - /* when rc->cover_art_data.final is true, it means we have received the entire image or get operation failed */ - if (rc->cover_art_data.final) { - if(rc->cover_art_data.status == ESP_BT_STATUS_SUCCESS) { - ESP_LOGI(BT_RC_CT_TAG, "Cover Art Client final data event, image size: %lu bytes", cover_art_image_size); - } - else { - ESP_LOGE(BT_RC_CT_TAG, "Cover Art Client get operation failed"); - } - cover_art_image_size = 0; - /* set the getting state to false, we can get next image now */ - cover_art_getting = false; - } -#endif - break; - } - /* when avrcp controller init or deinit completed, this event comes */ - case ESP_AVRC_CT_PROF_STATE_EVT: { - if (ESP_AVRC_INIT_SUCCESS == rc->avrc_ct_init_stat.state) { - ESP_LOGI(BT_RC_CT_TAG, "AVRCP CT STATE: Init Complete"); - } else if (ESP_AVRC_DEINIT_SUCCESS == rc->avrc_ct_init_stat.state) { - ESP_LOGI(BT_RC_CT_TAG, "AVRCP CT STATE: Deinit Complete"); - } else { - ESP_LOGE(BT_RC_CT_TAG, "AVRCP CT STATE error: %d", rc->avrc_ct_init_stat.state); - } - break; - } - /* others */ - default: - ESP_LOGE(BT_RC_CT_TAG, "%s unhandled event: %d", __func__, event); - break; - } -} - -static void bt_av_hdl_avrc_tg_evt(uint16_t event, void *p_param) -{ - ESP_LOGD(BT_RC_TG_TAG, "%s event: %d", __func__, event); - - esp_avrc_tg_cb_param_t *rc = (esp_avrc_tg_cb_param_t *)(p_param); - - switch (event) { - /* when connection state changed, this event comes */ - case ESP_AVRC_TG_CONNECTION_STATE_EVT: { - uint8_t *bda = rc->conn_stat.remote_bda; - ESP_LOGI(BT_RC_TG_TAG, "AVRC conn_state evt: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]", - rc->conn_stat.connected, bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); - if (rc->conn_stat.connected) { - /* create task to simulate volume change */ - xTaskCreate(volume_change_simulation, "vcsTask", 2048, NULL, 5, &s_vcs_task_hdl); - } else { - vTaskDelete(s_vcs_task_hdl); - ESP_LOGI(BT_RC_TG_TAG, "Stop volume change simulation"); - } - break; - } - /* when passthrough commanded, this event comes */ - case ESP_AVRC_TG_PASSTHROUGH_CMD_EVT: { - ESP_LOGI(BT_RC_TG_TAG, "AVRC passthrough cmd: key_code 0x%x, key_state %d", rc->psth_cmd.key_code, rc->psth_cmd.key_state); - break; - } - /* when absolute volume command from remote device set, this event comes */ - case ESP_AVRC_TG_SET_ABSOLUTE_VOLUME_CMD_EVT: { - ESP_LOGI(BT_RC_TG_TAG, "AVRC set absolute volume: %d%%", (int)rc->set_abs_vol.volume * 100 / 0x7f); - volume_set_by_controller(rc->set_abs_vol.volume); - break; - } - /* when notification registered, this event comes */ - case ESP_AVRC_TG_REGISTER_NOTIFICATION_EVT: { - ESP_LOGI(BT_RC_TG_TAG, "AVRC register event notification: %d, param: 0x%"PRIx32, rc->reg_ntf.event_id, rc->reg_ntf.event_parameter); - if (rc->reg_ntf.event_id == ESP_AVRC_RN_VOLUME_CHANGE) { - s_volume_notify = true; - esp_avrc_rn_param_t rn_param; - rn_param.volume = s_volume; - esp_avrc_tg_send_rn_rsp(ESP_AVRC_RN_VOLUME_CHANGE, ESP_AVRC_RN_RSP_INTERIM, &rn_param); - } - break; - } - /* when feature of remote device indicated, this event comes */ - case ESP_AVRC_TG_REMOTE_FEATURES_EVT: { - ESP_LOGI(BT_RC_TG_TAG, "AVRC remote features: %"PRIx32", CT features: %x", rc->rmt_feats.feat_mask, rc->rmt_feats.ct_feat_flag); - break; - } - /* when avrcp target init or deinit completed, this event comes */ - case ESP_AVRC_TG_PROF_STATE_EVT: { - if (ESP_AVRC_INIT_SUCCESS == rc->avrc_tg_init_stat.state) { - ESP_LOGI(BT_RC_CT_TAG, "AVRCP TG STATE: Init Complete"); - } else if (ESP_AVRC_DEINIT_SUCCESS == rc->avrc_tg_init_stat.state) { - ESP_LOGI(BT_RC_CT_TAG, "AVRCP TG STATE: Deinit Complete"); - } else { - ESP_LOGE(BT_RC_CT_TAG, "AVRCP TG STATE error: %d", rc->avrc_tg_init_stat.state); - } - break; - } - /* others */ - default: - ESP_LOGE(BT_RC_TG_TAG, "%s unhandled event: %d", __func__, event); - break; - } -} - -/******************************** - * EXTERNAL FUNCTION DEFINITIONS - *******************************/ - -void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param) -{ - switch (event) { - case ESP_A2D_CONNECTION_STATE_EVT: - case ESP_A2D_AUDIO_STATE_EVT: - case ESP_A2D_AUDIO_CFG_EVT: - case ESP_A2D_PROF_STATE_EVT: - case ESP_A2D_SEP_REG_STATE_EVT: - case ESP_A2D_SNK_PSC_CFG_EVT: - case ESP_A2D_SNK_SET_DELAY_VALUE_EVT: - case ESP_A2D_SNK_GET_DELAY_VALUE_EVT: { - bt_app_work_dispatch(bt_av_hdl_a2d_evt, event, param, sizeof(esp_a2d_cb_param_t), NULL); - break; - } - default: - ESP_LOGE(BT_AV_TAG, "Invalid A2DP event: %d", event); - break; - } -} - -#if CONFIG_EXAMPLE_A2DP_SINK_USE_EXTERNAL_CODEC == FALSE - -void bt_app_a2d_data_cb(const uint8_t *data, uint32_t len) -{ - write_ringbuf(data, len); - - /* log the number every 100 packets */ - if (++s_pkt_cnt % 100 == 0) { - ESP_LOGI(BT_AV_TAG, "Audio packet count: %"PRIu32, s_pkt_cnt); - } -} - -#else - -void bt_app_a2d_audio_data_cb(esp_a2d_conn_hdl_t conn_hdl, esp_a2d_audio_buff_t *audio_buf) -{ - ESP_LOGI(BT_AV_TAG, "data_len: %d, number_frame: %d, ts: %lu", audio_buf->data_len, audio_buf->number_frame, audio_buf->timestamp); - - /* - * Normally, user should send the audio_buf to other task, decode and free audio buff, - * But the codec component is not merge into IDF now, so we just free audio data here - */ - esp_a2d_audio_buff_free(audio_buf); -} - -#endif - -void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param) -{ -#if CONFIG_EXAMPLE_AVRCP_CT_COVER_ART_ENABLE - /* we must handle ESP_AVRC_CT_COVER_ART_DATA_EVT in this callback, copy image data to other buff before return if need */ - if (event == ESP_AVRC_CT_COVER_ART_DATA_EVT && param->cover_art_data.status == ESP_BT_STATUS_SUCCESS) { - cover_art_image_size += param->cover_art_data.data_len; - /* copy image data to other place */ - /* memcpy(p_buf, param->cover_art_data.p_data, param->cover_art_data.data_len); */ - } -#endif - switch (event) { - case ESP_AVRC_CT_METADATA_RSP_EVT: - bt_app_alloc_meta_buffer(param); - /* fall through */ - case ESP_AVRC_CT_CONNECTION_STATE_EVT: - case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: - case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: - case ESP_AVRC_CT_REMOTE_FEATURES_EVT: - case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: - case ESP_AVRC_CT_COVER_ART_STATE_EVT: - case ESP_AVRC_CT_COVER_ART_DATA_EVT: - case ESP_AVRC_CT_PROF_STATE_EVT: { - bt_app_work_dispatch(bt_av_hdl_avrc_ct_evt, event, param, sizeof(esp_avrc_ct_cb_param_t), NULL); - break; - } - default: - ESP_LOGE(BT_RC_CT_TAG, "Invalid AVRC event: %d", event); - break; - } -} - -void bt_app_rc_tg_cb(esp_avrc_tg_cb_event_t event, esp_avrc_tg_cb_param_t *param) -{ - switch (event) { - case ESP_AVRC_TG_CONNECTION_STATE_EVT: - case ESP_AVRC_TG_REMOTE_FEATURES_EVT: - case ESP_AVRC_TG_PASSTHROUGH_CMD_EVT: - case ESP_AVRC_TG_SET_ABSOLUTE_VOLUME_CMD_EVT: - case ESP_AVRC_TG_REGISTER_NOTIFICATION_EVT: - case ESP_AVRC_TG_SET_PLAYER_APP_VALUE_EVT: - case ESP_AVRC_TG_PROF_STATE_EVT: - bt_app_work_dispatch(bt_av_hdl_avrc_tg_evt, event, param, sizeof(esp_avrc_tg_cb_param_t), NULL); - break; - default: - ESP_LOGE(BT_RC_TG_TAG, "Invalid AVRC event: %d", event); - break; - } -} diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_av.h b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_av.h deleted file mode 100644 index 50a2116627..0000000000 --- a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_av.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Unlicense OR CC0-1.0 - */ - -#ifndef __BT_APP_AV_H__ -#define __BT_APP_AV_H__ - -#include -#include "esp_a2dp_api.h" -#include "esp_avrc_api.h" - -/* log tags */ -#define BT_AV_TAG "BT_AV" -#define BT_RC_TG_TAG "RC_TG" -#define BT_RC_CT_TAG "RC_CT" - -/** - * @brief callback function for A2DP sink - * - * @param [in] event event id - * @param [in] param callback parameter - */ -void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param); - -/** - * @brief callback function for A2DP sink audio data stream - * - * @param [out] data data stream writteen by application task - * @param [in] len length of data stream in byte - */ -void bt_app_a2d_data_cb(const uint8_t *data, uint32_t len); - -/** - * @brief callback function for A2DP sink undecoded audio data - * - * @param [in] conn_hdl connection handle - * @param [in] audio_buf pointer to audio buff - */ -void bt_app_a2d_audio_data_cb(esp_a2d_conn_hdl_t conn_hdl, esp_a2d_audio_buff_t *audio_buf); - -/** - * @brief callback function for AVRCP controller - * - * @param [in] event event id - * @param [in] param callback parameter - */ -void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param); - -/** - * @brief callback function for AVRCP target - * - * @param [in] event event id - * @param [in] param callback parameter - */ -void bt_app_rc_tg_cb(esp_avrc_tg_cb_event_t event, esp_avrc_tg_cb_param_t *param); - -#endif /* __BT_APP_AV_H__*/ diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_core.c b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_core.c deleted file mode 100644 index 29c915e893..0000000000 --- a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_core.c +++ /dev/null @@ -1,266 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Unlicense OR CC0-1.0 - */ - -#include -#include -#include -#include "freertos/FreeRTOSConfig.h" -#include "freertos/FreeRTOS.h" -#include "freertos/queue.h" -#include "freertos/semphr.h" -#include "freertos/task.h" -#include "esp_log.h" -#include "bt_app_core.h" -#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC -#include "driver/dac_continuous.h" -#else -#include "driver/i2s_std.h" -#endif -#include "freertos/ringbuf.h" - - -#define RINGBUF_HIGHEST_WATER_LEVEL (32 * 1024) -#define RINGBUF_PREFETCH_WATER_LEVEL (20 * 1024) - -enum { - RINGBUFFER_MODE_PROCESSING, /* ringbuffer is buffering incoming audio data, I2S is working */ - RINGBUFFER_MODE_PREFETCHING, /* ringbuffer is buffering incoming audio data, I2S is waiting */ - RINGBUFFER_MODE_DROPPING /* ringbuffer is not buffering (dropping) incoming audio data, I2S is working */ -}; - -/******************************* - * STATIC FUNCTION DECLARATIONS - ******************************/ - -/* handler for application task */ -static void bt_app_task_handler(void *arg); -/* handler for I2S task */ -static void bt_i2s_task_handler(void *arg); -/* message sender */ -static bool bt_app_send_msg(bt_app_msg_t *msg); -/* handle dispatched messages */ -static void bt_app_work_dispatched(bt_app_msg_t *msg); - -/******************************* - * STATIC VARIABLE DEFINITIONS - ******************************/ - -static QueueHandle_t s_bt_app_task_queue = NULL; /* handle of work queue */ -static TaskHandle_t s_bt_app_task_handle = NULL; /* handle of application task */ -static TaskHandle_t s_bt_i2s_task_handle = NULL; /* handle of I2S task */ -static RingbufHandle_t s_ringbuf_i2s = NULL; /* handle of ringbuffer for I2S */ -static SemaphoreHandle_t s_i2s_write_semaphore = NULL; -static uint16_t ringbuffer_mode = RINGBUFFER_MODE_PROCESSING; - -/********************************* - * EXTERNAL FUNCTION DECLARATIONS - ********************************/ -#ifndef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC -extern i2s_chan_handle_t tx_chan; -#else -extern dac_continuous_handle_t tx_chan; -#endif - -/******************************* - * STATIC FUNCTION DEFINITIONS - ******************************/ - -static bool bt_app_send_msg(bt_app_msg_t *msg) -{ - if (msg == NULL) { - return false; - } - - /* send the message to work queue */ - if (xQueueSend(s_bt_app_task_queue, msg, 10 / portTICK_PERIOD_MS) != pdTRUE) { - ESP_LOGE(BT_APP_CORE_TAG, "%s xQueue send failed", __func__); - return false; - } - return true; -} - -static void bt_app_work_dispatched(bt_app_msg_t *msg) -{ - if (msg->cb) { - msg->cb(msg->event, msg->param); - } -} - -static void bt_app_task_handler(void *arg) -{ - bt_app_msg_t msg; - - for (;;) { - /* receive message from work queue and handle it */ - if (pdTRUE == xQueueReceive(s_bt_app_task_queue, &msg, (TickType_t)portMAX_DELAY)) { - ESP_LOGD(BT_APP_CORE_TAG, "%s, signal: 0x%x, event: 0x%x", __func__, msg.sig, msg.event); - - switch (msg.sig) { - case BT_APP_SIG_WORK_DISPATCH: - bt_app_work_dispatched(&msg); - break; - default: - ESP_LOGW(BT_APP_CORE_TAG, "%s, unhandled signal: %d", __func__, msg.sig); - break; - } /* switch (msg.sig) */ - - if (msg.param) { - free(msg.param); - } - } - } -} - -static void bt_i2s_task_handler(void *arg) -{ - uint8_t *data = NULL; - size_t item_size = 0; - /** - * The total length of DMA buffer of I2S is: - * `dma_frame_num * dma_desc_num * i2s_channel_num * i2s_data_bit_width / 8`. - * Transmit `dma_frame_num * dma_desc_num` bytes to DMA is trade-off. - */ - const size_t item_size_upto = 240 * 6; - size_t bytes_written = 0; - - for (;;) { - if (pdTRUE == xSemaphoreTake(s_i2s_write_semaphore, portMAX_DELAY)) { - for (;;) { - item_size = 0; - /* receive data from ringbuffer and write it to I2S DMA transmit buffer */ - data = (uint8_t *)xRingbufferReceiveUpTo(s_ringbuf_i2s, &item_size, (TickType_t)pdMS_TO_TICKS(20), item_size_upto); - if (item_size == 0) { - ESP_LOGI(BT_APP_CORE_TAG, "ringbuffer underflowed! mode changed: RINGBUFFER_MODE_PREFETCHING"); - ringbuffer_mode = RINGBUFFER_MODE_PREFETCHING; - break; - } - - #ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC - dac_continuous_write(tx_chan, data, item_size, &bytes_written, -1); - #else - i2s_channel_write(tx_chan, data, item_size, &bytes_written, portMAX_DELAY); - #endif - vRingbufferReturnItem(s_ringbuf_i2s, (void *)data); - } - } - } -} - -/******************************** - * EXTERNAL FUNCTION DEFINITIONS - *******************************/ - -bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback) -{ - ESP_LOGD(BT_APP_CORE_TAG, "%s event: 0x%x, param len: %d", __func__, event, param_len); - - bt_app_msg_t msg; - memset(&msg, 0, sizeof(bt_app_msg_t)); - - msg.sig = BT_APP_SIG_WORK_DISPATCH; - msg.event = event; - msg.cb = p_cback; - - if (param_len == 0) { - return bt_app_send_msg(&msg); - } else if (p_params && param_len > 0) { - if ((msg.param = malloc(param_len)) != NULL) { - memcpy(msg.param, p_params, param_len); - /* check if caller has provided a copy callback to do the deep copy */ - if (p_copy_cback) { - p_copy_cback(msg.param, p_params, param_len); - } - return bt_app_send_msg(&msg); - } - } - - return false; -} - -void bt_app_task_start_up(void) -{ - s_bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t)); - xTaskCreate(bt_app_task_handler, "BtAppTask", 3072, NULL, 10, &s_bt_app_task_handle); -} - -void bt_app_task_shut_down(void) -{ - if (s_bt_app_task_handle) { - vTaskDelete(s_bt_app_task_handle); - s_bt_app_task_handle = NULL; - } - if (s_bt_app_task_queue) { - vQueueDelete(s_bt_app_task_queue); - s_bt_app_task_queue = NULL; - } -} - -void bt_i2s_task_start_up(void) -{ - ESP_LOGI(BT_APP_CORE_TAG, "ringbuffer data empty! mode changed: RINGBUFFER_MODE_PREFETCHING"); - ringbuffer_mode = RINGBUFFER_MODE_PREFETCHING; - if ((s_i2s_write_semaphore = xSemaphoreCreateBinary()) == NULL) { - ESP_LOGE(BT_APP_CORE_TAG, "%s, Semaphore create failed", __func__); - return; - } - if ((s_ringbuf_i2s = xRingbufferCreate(RINGBUF_HIGHEST_WATER_LEVEL, RINGBUF_TYPE_BYTEBUF)) == NULL) { - ESP_LOGE(BT_APP_CORE_TAG, "%s, ringbuffer create failed", __func__); - return; - } - xTaskCreate(bt_i2s_task_handler, "BtI2STask", 2048, NULL, configMAX_PRIORITIES - 3, &s_bt_i2s_task_handle); -} - -void bt_i2s_task_shut_down(void) -{ - if (s_bt_i2s_task_handle) { - vTaskDelete(s_bt_i2s_task_handle); - s_bt_i2s_task_handle = NULL; - } - if (s_ringbuf_i2s) { - vRingbufferDelete(s_ringbuf_i2s); - s_ringbuf_i2s = NULL; - } - if (s_i2s_write_semaphore) { - vSemaphoreDelete(s_i2s_write_semaphore); - s_i2s_write_semaphore = NULL; - } -} - -size_t write_ringbuf(const uint8_t *data, size_t size) -{ - size_t item_size = 0; - BaseType_t done = pdFALSE; - - if (ringbuffer_mode == RINGBUFFER_MODE_DROPPING) { - ESP_LOGW(BT_APP_CORE_TAG, "ringbuffer is full, drop this packet!"); - vRingbufferGetInfo(s_ringbuf_i2s, NULL, NULL, NULL, NULL, &item_size); - if (item_size <= RINGBUF_PREFETCH_WATER_LEVEL) { - ESP_LOGI(BT_APP_CORE_TAG, "ringbuffer data decreased! mode changed: RINGBUFFER_MODE_PROCESSING"); - ringbuffer_mode = RINGBUFFER_MODE_PROCESSING; - } - return 0; - } - - done = xRingbufferSend(s_ringbuf_i2s, (void *)data, size, (TickType_t)0); - - if (!done) { - ESP_LOGW(BT_APP_CORE_TAG, "ringbuffer overflowed, ready to decrease data! mode changed: RINGBUFFER_MODE_DROPPING"); - ringbuffer_mode = RINGBUFFER_MODE_DROPPING; - } - - if (ringbuffer_mode == RINGBUFFER_MODE_PREFETCHING) { - vRingbufferGetInfo(s_ringbuf_i2s, NULL, NULL, NULL, NULL, &item_size); - if (item_size >= RINGBUF_PREFETCH_WATER_LEVEL) { - ESP_LOGI(BT_APP_CORE_TAG, "ringbuffer data increased! mode changed: RINGBUFFER_MODE_PROCESSING"); - ringbuffer_mode = RINGBUFFER_MODE_PROCESSING; - if (pdFALSE == xSemaphoreGive(s_i2s_write_semaphore)) { - ESP_LOGE(BT_APP_CORE_TAG, "semphore give failed"); - } - } - } - - return done ? size : 0; -} diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/main.c b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/main.c deleted file mode 100644 index 8393b3e40f..0000000000 --- a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/main.c +++ /dev/null @@ -1,265 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Unlicense OR CC0-1.0 - */ - -#include -#include -#include -#include -#include -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "nvs.h" -#include "nvs_flash.h" -#include "esp_system.h" -#include "esp_log.h" - -#include "esp_bt.h" -#include "bt_app_core.h" -#include "bt_app_av.h" -#include "esp_bt_main.h" -#include "esp_bt_device.h" -#include "esp_gap_bt_api.h" -#include "esp_a2dp_api.h" -#include "esp_avrc_api.h" - -/* device name */ -static const char local_device_name[] = CONFIG_EXAMPLE_LOCAL_DEVICE_NAME; - -/* event for stack up */ -enum { - BT_APP_EVT_STACK_UP = 0, -}; - -/******************************** - * STATIC FUNCTION DECLARATIONS - *******************************/ - -/* Device callback function */ -static void bt_app_dev_cb(esp_bt_dev_cb_event_t event, esp_bt_dev_cb_param_t *param); -/* GAP callback function */ -static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param); -/* handler for bluetooth stack enabled events */ -static void bt_av_hdl_stack_evt(uint16_t event, void *p_param); - -/******************************* - * STATIC FUNCTION DEFINITIONS - ******************************/ -static char *bda2str(uint8_t * bda, char *str, size_t size) -{ - if (bda == NULL || str == NULL || size < 18) { - return NULL; - } - - 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 bt_app_dev_cb(esp_bt_dev_cb_event_t event, esp_bt_dev_cb_param_t *param) -{ - switch (event) { - case ESP_BT_DEV_NAME_RES_EVT: { - if (param->name_res.status == ESP_BT_STATUS_SUCCESS) { - ESP_LOGI(BT_AV_TAG, "Get local device name success: %s", param->name_res.name); - } else { - ESP_LOGE(BT_AV_TAG, "Get local device name failed, status: %d", param->name_res.status); - } - break; - } - default: { - ESP_LOGI(BT_AV_TAG, "event: %d", event); - break; - } - } -} - -static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) -{ - uint8_t *bda = NULL; - - switch (event) { - /* when authentication completed, this event comes */ - case ESP_BT_GAP_AUTH_CMPL_EVT: { - if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) { - ESP_LOGI(BT_AV_TAG, "authentication success: %s", param->auth_cmpl.device_name); - ESP_LOG_BUFFER_HEX(BT_AV_TAG, param->auth_cmpl.bda, ESP_BD_ADDR_LEN); - } else { - ESP_LOGE(BT_AV_TAG, "authentication failed, status: %d", param->auth_cmpl.stat); - } - ESP_LOGI(BT_AV_TAG, "link key type of current link is: %d", param->auth_cmpl.lk_type); - break; - } - case ESP_BT_GAP_ENC_CHG_EVT: { - char *str_enc[3] = {"OFF", "E0", "AES"}; - bda = (uint8_t *)param->enc_chg.bda; - ESP_LOGI(BT_AV_TAG, "Encryption mode to [%02x:%02x:%02x:%02x:%02x:%02x] changed to %s", - bda[0], bda[1], bda[2], bda[3], bda[4], bda[5], str_enc[param->enc_chg.enc_mode]); - break; - } - -#if (CONFIG_EXAMPLE_A2DP_SINK_SSP_ENABLED == true) - /* when Security Simple Pairing user confirmation requested, this event comes */ - case ESP_BT_GAP_CFM_REQ_EVT: - ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %06"PRIu32, param->cfm_req.num_val); - esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true); - break; - /* when Security Simple Pairing passkey notified, this event comes */ - case ESP_BT_GAP_KEY_NOTIF_EVT: - ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey: %06"PRIu32, param->key_notif.passkey); - break; - /* when Security Simple Pairing passkey requested, this event comes */ - case ESP_BT_GAP_KEY_REQ_EVT: - ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!"); - break; -#endif - - /* when GAP mode changed, this event comes */ - case ESP_BT_GAP_MODE_CHG_EVT: - ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_MODE_CHG_EVT mode: %d, interval: %.2f ms", - param->mode_chg.mode, param->mode_chg.interval * 0.625); - break; - /* when ACL connection completed, this event comes */ - case ESP_BT_GAP_ACL_CONN_CMPL_STAT_EVT: - bda = (uint8_t *)param->acl_conn_cmpl_stat.bda; - ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_ACL_CONN_CMPL_STAT_EVT Connected to [%02x:%02x:%02x:%02x:%02x:%02x], status: 0x%x", - bda[0], bda[1], bda[2], bda[3], bda[4], bda[5], param->acl_conn_cmpl_stat.stat); - break; - /* when ACL disconnection completed, this event comes */ - case ESP_BT_GAP_ACL_DISCONN_CMPL_STAT_EVT: - bda = (uint8_t *)param->acl_disconn_cmpl_stat.bda; - ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_ACL_DISC_CMPL_STAT_EVT Disconnected from [%02x:%02x:%02x:%02x:%02x:%02x], reason: 0x%x", - bda[0], bda[1], bda[2], bda[3], bda[4], bda[5], param->acl_disconn_cmpl_stat.reason); - break; - /* others */ - default: { - ESP_LOGI(BT_AV_TAG, "event: %d", event); - break; - } - } -} - -static void bt_av_hdl_stack_evt(uint16_t event, void *p_param) -{ - ESP_LOGD(BT_AV_TAG, "%s event: %d", __func__, event); - - switch (event) { - /* when do the stack up, this event comes */ - case BT_APP_EVT_STACK_UP: { - esp_bt_gap_set_device_name(local_device_name); - esp_bt_dev_register_callback(bt_app_dev_cb); - esp_bt_gap_register_callback(bt_app_gap_cb); - - esp_avrc_ct_register_callback(bt_app_rc_ct_cb); - assert(esp_avrc_ct_init() == ESP_OK); - esp_avrc_tg_register_callback(bt_app_rc_tg_cb); - assert(esp_avrc_tg_init() == ESP_OK); - - esp_avrc_rn_evt_cap_mask_t evt_set = {0}; - esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_SET, &evt_set, ESP_AVRC_RN_VOLUME_CHANGE); - assert(esp_avrc_tg_set_rn_evt_cap(&evt_set) == ESP_OK); - - esp_a2d_register_callback(&bt_app_a2d_cb); - assert(esp_a2d_sink_init() == ESP_OK); - -#if CONFIG_EXAMPLE_A2DP_SINK_USE_EXTERNAL_CODEC == FALSE - esp_a2d_sink_register_data_callback(bt_app_a2d_data_cb); -#else - esp_a2d_mcc_t mcc = {0}; - mcc.type = ESP_A2D_MCT_SBC; - mcc.cie.sbc_info.samp_freq = 0xf; - mcc.cie.sbc_info.ch_mode = 0xf; - mcc.cie.sbc_info.block_len = 0xf; - mcc.cie.sbc_info.num_subbands = 0x3; - mcc.cie.sbc_info.alloc_mthd = 0x3; - mcc.cie.sbc_info.max_bitpool = 250; - mcc.cie.sbc_info.min_bitpool = 2; - /* register stream end point, only support mSBC currently */ - esp_a2d_sink_register_stream_endpoint(0, &mcc); - esp_a2d_sink_register_audio_data_callback(bt_app_a2d_audio_data_cb); -#endif - /* Get the default value of the delay value */ - esp_a2d_sink_get_delay_value(); - /* Get local device name */ - esp_bt_gap_get_device_name(); - - /* set discoverable and connectable mode, wait to be connected */ - esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); - break; - } - /* others */ - default: - ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event); - break; - } -} - -/******************************* - * MAIN ENTRY POINT - ******************************/ - -void app_main(void) -{ - char bda_str[18] = {0}; - /* initialize NVS — it is used to store PHY calibration data */ - esp_err_t err = nvs_flash_init(); - if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { - ESP_ERROR_CHECK(nvs_flash_erase()); - err = nvs_flash_init(); - } - ESP_ERROR_CHECK(err); - - /* - * This example only uses the functions of Classical Bluetooth. - * So release the controller memory for Bluetooth Low Energy. - */ - ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE)); - - esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); - if ((err = esp_bt_controller_init(&bt_cfg)) != ESP_OK) { - ESP_LOGE(BT_AV_TAG, "%s initialize controller failed: %s", __func__, esp_err_to_name(err)); - return; - } - if ((err = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) { - ESP_LOGE(BT_AV_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(err)); - return; - } - - esp_bluedroid_config_t bluedroid_cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT(); -#if (CONFIG_EXAMPLE_A2DP_SINK_SSP_ENABLED == false) - bluedroid_cfg.ssp_en = false; -#endif - if ((err = esp_bluedroid_init_with_cfg(&bluedroid_cfg)) != ESP_OK) { - ESP_LOGE(BT_AV_TAG, "%s initialize bluedroid failed: %s", __func__, esp_err_to_name(err)); - return; - } - - if ((err = esp_bluedroid_enable()) != ESP_OK) { - ESP_LOGE(BT_AV_TAG, "%s enable bluedroid failed: %s", __func__, esp_err_to_name(err)); - return; - } - -#if (CONFIG_EXAMPLE_A2DP_SINK_SSP_ENABLED == true) - /* set default parameters for Secure Simple Pairing */ - esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE; - esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO; - esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t)); -#endif - - /* set default parameters for Legacy Pairing (use fixed pin code 1234) */ - esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_FIXED; - esp_bt_pin_code_t pin_code; - pin_code[0] = '1'; - pin_code[1] = '2'; - pin_code[2] = '3'; - pin_code[3] = '4'; - esp_bt_gap_set_pin(pin_type, 4, pin_code); - - ESP_LOGI(BT_AV_TAG, "Own address:[%s]", bda2str((uint8_t *)esp_bt_dev_get_address(), bda_str, sizeof(bda_str))); - bt_app_task_start_up(); - /* bluetooth device name, connection mode and profile set up */ - bt_app_work_dispatch(bt_av_hdl_stack_evt, BT_APP_EVT_STACK_UP, NULL, 0, NULL); -} diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/CMakeLists.txt b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/CMakeLists.txt similarity index 93% rename from examples/bluetooth/bluedroid/classic_bt/a2dp_sink/CMakeLists.txt rename to examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/CMakeLists.txt index 4602462be0..ea6e7e25a7 100644 --- a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/CMakeLists.txt +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/CMakeLists.txt @@ -5,4 +5,4 @@ cmake_minimum_required(VERSION 3.22) include($ENV{IDF_PATH}/tools/cmake/project.cmake) # "Trim" the build. Include the minimal set of components, main, and anything it depends on. idf_build_set_property(MINIMAL_BUILD ON) -project(a2dp_sink) +project(a2dp_sink_stream) diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/README.md b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/README.md similarity index 73% rename from examples/bluetooth/bluedroid/classic_bt/a2dp_sink/README.md rename to examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/README.md index 072cacd12c..c21418d203 100644 --- a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/README.md +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/README.md @@ -1,16 +1,34 @@ | Supported Targets | ESP32 | | ----------------- | ----- | -A2DP-SINK EXAMPLE +A2DP-SINK-STREAM EXAMPLE ====================== Example of A2DP audio sink role This is the example of API implementing Advanced Audio Distribution Profile to receive an audio stream. -This example involves the use of Bluetooth legacy profile A2DP for audio stream reception, AVRCP for media information notifications, and I2S for audio stream output interface. +This example involves the use of Bluetooth legacy profile A2DP for audio stream reception and I2S for audio stream output interface. -Applications such as bluetooth speakers can take advantage of this example as a reference of basic functionalities. +## Required components + +- [bt_app_core_utils](../common/bt_app_core_utils) +- [bredr_app_common_utils](../common/bredr_app_common_utils) +- [a2dp_sink_common_utils](../common/a2dp_utils/a2dp_sink_common_utils) +- [a2dp_sink_int_codec_utils](../common/a2dp_utils/a2dp_sink_int_codec_utils) +- [a2dp_sink_ext_codec_utils](../common/a2dp_utils/a2dp_sink_ext_codec_utils) + +``` ++-------------------------+-------------------------+---------------------+ +|a2dp_sink_int_codec_utils|a2dp_sink_ext_codec_utils| | ++-------------------------+-------------------------+ | +| a2dp_sink_common_utils | bt_app_core_utils | ++---------------------------------------------------+ | +| bredr_app_common_utils | | ++---------------------------------------------------+---------------------+ +``` + +Detailed information can be viewed through the [../common/README.md](../common/README.md). ## How to use this example @@ -22,7 +40,7 @@ For the I2S codec, pick whatever chip or board works for you; this code was writ | ESP pin | I2S signal | | :-------- | :----------- | -| GPIO22 | LRCK | +| GPIO27 | LRCK | | GPIO25 | DATA | | GPIO26 | BCK | @@ -34,9 +52,7 @@ If the internal DAC is selected, analog audio will be available on GPIO25 and GP idf.py menuconfig ``` -* Choose external I2S codec or internal DAC for audio output, and configure the output PINs under A2DP Example Configuration - -* For AVRCP CT Cover Art feature, is enabled by default, we can disable it by unselecting menuconfig option `Component config --> Bluetooth --> Bluedroid Options --> Classic Bluetooth --> AVRCP Features --> AVRCP CT Cover Art`. This example will try to use AVRCP CT Cover Art feature, get cover art image and count the image size if peer device support, this can be disable in `A2DP Example Configuration --> Use AVRCP CT Cover Art Feature`. +* Choose external I2S codec or internal DAC for audio output, and configure the output PINs under A2DP Sink Internal Codec Example Configuration. ### Build and Flash @@ -68,13 +84,6 @@ I (126697) BT_AV: Audio packet count 300 I (128697) BT_AV: Audio packet count 400 ``` -The output when receiving a cover art image: - -``` -I (53349) RC_CT: AVRC metadata rsp: attribute id 0x80, 1000748 -I (53639) RC_CT: Cover Art Client final data event, image size: 14118 bytes -``` - Also, the sound will be heard if a loudspeaker is connected and possible external I2S codec is correctly configured. For ESP32 A2DP source example, the sound is noise as the audio source generates the samples with a random sequence. ## Troubleshooting diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/main/CMakeLists.txt b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/main/CMakeLists.txt new file mode 100644 index 0000000000..6b20ae1360 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/main/CMakeLists.txt @@ -0,0 +1,10 @@ +if(CONFIG_EXAMPLE_A2DP_SINK_USE_EXTERNAL_CODEC) + set(EXTRA_COMPONENTS a2dp_sink_ext_codec_utils) +else() + set(EXTRA_COMPONENTS a2dp_sink_int_codec_utils) +endif() + +idf_component_register(SRCS "main.c" + PRIV_REQUIRES bt bt_app_core_utils bredr_app_common_utils a2dp_sink_common_utils + PRIV_REQUIRES ${EXTRA_COMPONENTS} + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/main/Kconfig.projbuild b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/main/Kconfig.projbuild new file mode 100644 index 0000000000..605249c797 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/main/Kconfig.projbuild @@ -0,0 +1,15 @@ +menu "A2DP Example Configuration" + config EXAMPLE_LOCAL_DEVICE_NAME + string "Local Device Name" + default "ESP_SPEAKER" + help + Use this option to set local device name. + + config EXAMPLE_A2DP_SINK_USE_EXTERNAL_CODEC + bool "Use External Codec Instead of Internal" + default n + select BT_A2DP_USE_EXTERNAL_CODEC + help + If enable, Bluedroid stack will not decode A2DP audio data, user need to decode it in application layer. + +endmenu diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/main/idf_component.yml b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/main/idf_component.yml new file mode 100644 index 0000000000..17c8a5daae --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/main/idf_component.yml @@ -0,0 +1,11 @@ +dependencies: + bt_app_core_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/bt_app_core_utils + bredr_app_common_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/bredr_app_common_utils + a2dp_sink_common_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_common_utils + a2dp_sink_int_codec_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils + a2dp_sink_ext_codec_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_ext_codec_utils diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/main/main.c b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/main/main.c new file mode 100644 index 0000000000..863c0127d1 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/main/main.c @@ -0,0 +1,177 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include + +#include "esp_log.h" +#include "esp_bt_device.h" +#include "esp_gap_bt_api.h" +#include "esp_a2dp_api.h" + +#include "bt_app_core_utils.h" +#include "bredr_app_common_utils.h" +#include "a2dp_sink_common_utils.h" +#include "a2dp_utils_tags.h" +#if CONFIG_EXAMPLE_A2DP_SINK_USE_EXTERNAL_CODEC == FALSE +#include "a2dp_sink_int_codec_utils.h" +#else +#include "a2dp_sink_ext_codec_utils.h" +#endif + +/* device name */ +static const char local_device_name[] = CONFIG_EXAMPLE_LOCAL_DEVICE_NAME; + +/* event for stack up */ +enum { + BT_APP_EVT_STACK_UP = 0, +}; + +/******************************** + * STATIC FUNCTION DECLARATIONS + *******************************/ + +/* Device callback function */ +static void bt_app_dev_cb(esp_bt_dev_cb_event_t event, esp_bt_dev_cb_param_t *param); + +/* GAP callback function */ +static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param); + +/* callback function for A2DP sink */ +static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param); + +#if CONFIG_EXAMPLE_A2DP_SINK_USE_EXTERNAL_CODEC == FALSE +/* callback function for A2DP sink audio data stream */ +static void bt_app_a2d_data_cb(const uint8_t *data, uint32_t len); +#else +/* callback function for A2DP sink undecoded audio data */ +static void bt_app_a2d_audio_data_cb(esp_a2d_conn_hdl_t conn_hdl, esp_a2d_audio_buff_t *audio_buf); +#endif + +/* handler for bluetooth stack enabled events */ +static void bt_av_hdl_stack_evt(uint16_t event, void *p_param); + +/******************************* + * STATIC FUNCTION DEFINITIONS + ******************************/ + +static void bt_app_dev_cb(esp_bt_dev_cb_event_t event, esp_bt_dev_cb_param_t *param) +{ + bredr_app_dev_evt_def_hdl(event, param); +} + +static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) +{ + bredr_app_gap_evt_def_hdl(event, param); +} + +static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param) +{ + switch (event) { + case ESP_A2D_PROF_STATE_EVT: + case ESP_A2D_SNK_PSC_CFG_EVT: + case ESP_A2D_SNK_SET_DELAY_VALUE_EVT: + case ESP_A2D_SNK_GET_DELAY_VALUE_EVT: { + bt_app_work_dispatch(bt_a2d_evt_def_hdl, event, param, sizeof(esp_a2d_cb_param_t), NULL); + break; + } + case ESP_A2D_CONNECTION_STATE_EVT: + case ESP_A2D_AUDIO_STATE_EVT: + case ESP_A2D_AUDIO_CFG_EVT: + case ESP_A2D_SEP_REG_STATE_EVT: { +#if CONFIG_EXAMPLE_A2DP_SINK_USE_EXTERNAL_CODEC == FALSE + bt_app_work_dispatch(bt_a2d_evt_int_codec_hdl, event, param, sizeof(esp_a2d_cb_param_t), NULL); +#else + bt_app_work_dispatch(bt_a2d_evt_ext_codec_hdl, event, param, sizeof(esp_a2d_cb_param_t), NULL); +#endif + break; + } + default: + ESP_LOGE(BT_AV_TAG, "Invalid A2DP event: %d", event); + break; + } +} + +#if CONFIG_EXAMPLE_A2DP_SINK_USE_EXTERNAL_CODEC == FALSE +static void bt_app_a2d_data_cb(const uint8_t *data, uint32_t len) +{ + bt_a2d_data_hdl(data, len); +} +#else +static void bt_app_a2d_audio_data_cb(esp_a2d_conn_hdl_t conn_hdl, esp_a2d_audio_buff_t *audio_buf) +{ + bt_a2d_audio_data_hdl(conn_hdl, audio_buf); +} +#endif + +static void bt_av_hdl_stack_evt(uint16_t event, void *p_param) +{ + ESP_LOGD(BT_AV_TAG, "%s event: %d", __func__, event); + + switch (event) { + /* when do the stack up, this event comes */ + case BT_APP_EVT_STACK_UP: { + esp_bt_gap_set_device_name(local_device_name); + esp_bt_dev_register_callback(bt_app_dev_cb); + esp_bt_gap_register_callback(bt_app_gap_cb); + + esp_a2d_register_callback(&bt_app_a2d_cb); + assert(esp_a2d_sink_init() == ESP_OK); + +#if CONFIG_EXAMPLE_A2DP_SINK_USE_EXTERNAL_CODEC == FALSE + esp_a2d_sink_register_data_callback(bt_app_a2d_data_cb); +#else + esp_a2d_mcc_t mcc = {0}; + mcc.type = ESP_A2D_MCT_SBC; + mcc.cie.sbc_info.samp_freq = ESP_A2D_SBC_CIE_SF_16K | + ESP_A2D_SBC_CIE_SF_32K | + ESP_A2D_SBC_CIE_SF_44K | + ESP_A2D_SBC_CIE_SF_48K; + mcc.cie.sbc_info.ch_mode = ESP_A2D_SBC_CIE_CH_MODE_MONO | + ESP_A2D_SBC_CIE_CH_MODE_DUAL_CHANNEL | + ESP_A2D_SBC_CIE_CH_MODE_STEREO | + ESP_A2D_SBC_CIE_CH_MODE_JOINT_STEREO; + mcc.cie.sbc_info.block_len = ESP_A2D_SBC_CIE_BLOCK_LEN_4 | + ESP_A2D_SBC_CIE_BLOCK_LEN_8 | + ESP_A2D_SBC_CIE_BLOCK_LEN_12 | + ESP_A2D_SBC_CIE_BLOCK_LEN_16; + mcc.cie.sbc_info.num_subbands = ESP_A2D_SBC_CIE_NUM_SUBBANDS_4 | ESP_A2D_SBC_CIE_NUM_SUBBANDS_8; + mcc.cie.sbc_info.alloc_mthd = ESP_A2D_SBC_CIE_ALLOC_MTHD_SRN | ESP_A2D_SBC_CIE_ALLOC_MTHD_LOUDNESS; + mcc.cie.sbc_info.max_bitpool = 250; + mcc.cie.sbc_info.min_bitpool = 2; + /* register stream end point, only support SBC currently */ + esp_a2d_sink_register_stream_endpoint(0, &mcc); + esp_a2d_sink_register_audio_data_callback(bt_app_a2d_audio_data_cb); +#endif + + /* Get the default value of the delay value */ + esp_a2d_sink_get_delay_value(); + /* Get local device name */ + esp_bt_gap_get_device_name(); + + /* set discoverable and connectable mode, wait to be connected */ + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + break; + } + /* others */ + default: + ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event); + break; + } +} + +/******************************* + * MAIN ENTRY POINT + ******************************/ + +void app_main(void) +{ + ESP_ERROR_CHECK(bredr_app_common_init()); + + bt_app_task_start_up(); + /* bluetooth device name, connection mode and profile set up */ + bt_app_work_dispatch(bt_av_hdl_stack_evt, BT_APP_EVT_STACK_UP, NULL, 0, NULL); +} diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/sdkconfig.ci.ca_dis b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/sdkconfig.ci.ca_dis similarity index 75% rename from examples/bluetooth/bluedroid/classic_bt/a2dp_sink/sdkconfig.ci.ca_dis rename to examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/sdkconfig.ci.ca_dis index 1c6372a1b3..9d5c2a060d 100644 --- a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/sdkconfig.ci.ca_dis +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/sdkconfig.ci.ca_dis @@ -1,12 +1,9 @@ # Override some defaults so BT stack is enabled and # Classic BT is enabled and BT_DRAM_RELEASE is disabled CONFIG_BT_ENABLED=y -CONFIG_BTDM_CTRL_MODE_BLE_ONLY=n CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y -CONFIG_BTDM_CTRL_MODE_BTDM=n CONFIG_BT_BLUEDROID_ENABLED=y CONFIG_BT_CLASSIC_ENABLED=y CONFIG_BT_A2DP_ENABLE=y CONFIG_BT_AVRCP_CT_COVER_ART_ENABLED=n -CONFIG_EXAMPLE_AVRCP_CT_COVER_ART_ENABLE=n CONFIG_DAC_DMA_AUTO_16BIT_ALIGN=n diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/sdkconfig.ci.test b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/sdkconfig.ci.test similarity index 100% rename from examples/bluetooth/bluedroid/classic_bt/a2dp_sink/sdkconfig.ci.test rename to examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/sdkconfig.ci.test diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/sdkconfig.defaults b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/sdkconfig.defaults similarity index 73% rename from examples/bluetooth/bluedroid/classic_bt/a2dp_sink/sdkconfig.defaults rename to examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/sdkconfig.defaults index 3d17667a4c..2b93a51a0e 100644 --- a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/sdkconfig.defaults @@ -1,11 +1,8 @@ # Override some defaults so BT stack is enabled and # Classic BT is enabled and BT_DRAM_RELEASE is disabled CONFIG_BT_ENABLED=y -CONFIG_BTDM_CTRL_MODE_BLE_ONLY=n CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y -CONFIG_BTDM_CTRL_MODE_BTDM=n CONFIG_BT_BLUEDROID_ENABLED=y CONFIG_BT_CLASSIC_ENABLED=y CONFIG_BT_A2DP_ENABLE=y -CONFIG_BT_AVRCP_CT_COVER_ART_ENABLED=y CONFIG_DAC_DMA_AUTO_16BIT_ALIGN=n diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/tutorial/Example_A2DP_Sink.md b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/tutorial/Example_A2DP_Sink.md similarity index 51% rename from examples/bluetooth/bluedroid/classic_bt/a2dp_sink/tutorial/Example_A2DP_Sink.md rename to examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/tutorial/Example_A2DP_Sink.md index 510077b9aa..72b06ad89e 100644 --- a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/tutorial/Example_A2DP_Sink.md +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream/tutorial/Example_A2DP_Sink.md @@ -4,36 +4,19 @@ In this tutorial, the A2DP sink example code is reviewed. The code implements an A2DP sink role, which receives audio stream from A2DP source devices. -## File Tree - -The file tree of this example is shown below. The [main](../main) folder contains the main files of this example including audio and video related files, program core files, main function file, etc. The [tutorial](../tutorial) folder contains this guidance document. - -```c -└── a2dp_sink -    ├── CMakeLists.txt -    ├── main -    │   ├── bt_app_av.c -    │   ├── bt_app_av.h -    │   ├── bt_app_core.c -    │   ├── bt_app_core.h -    │   ├── CMakeLists.txt -    │   ├── Kconfig.projbuild -    │   └── main.c -    ├── README.md -    ├── sdkconfig.defaults -    └── tutorial -    └── Example_A2DP_Sink.md - -2 directories, 11 files -``` - ## Main Entry Point -This example is located in the examples folder of the ESP-IDF under the [bluetooth/bluedroid/classic_bt/a2dp_sink](../). The entry point of this program is `app_main()` which contained in [main/main.c](../main/main.c). +This example is located in the examples folder of the ESP-IDF under the [bluetooth/bluedroid/classic_bt/a2dp_sink_stream](../). The entry point of this program is `app_main()` which contained in [main/main.c](../main/main.c). -### NVS Initialization +The main function first initializes through `bredr_app_common_init()`, and then calls `bt_app_task_start_up()` and `bt_app_work_dispatch(bt_av_hdl_stack_evt, BT_APP_EVT_STACK_UP, NULL, 0, NULL)` to create and execute the task. -The main function starts by initializing the non-volatile storage library. This library allows to save key-value pairs in flash memory and is used by some components such as the Wi-Fi library to save the SSID and password: +### Classic Bluetooth Initialization + +Initialize the Classic Bluetooth by calling `bredr_app_common_init()`. + +#### NVS Initialization + +The `bredr_app_common_init()` starts by initializing the non-volatile storage library. This library allows to save key-value pairs in flash memory and is used by some components such as the Wi-Fi library to save the SSID and password: ```c /* initialize NVS — it is used to store PHY calibration */ @@ -45,9 +28,9 @@ if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ERR_NVS_NEW_VERSION_FOUND) { ESP_ERROR_CHECK(ret); ``` -### Bluetooth Controller and Stack Initialization +#### Bluetooth Controller and Stack Initialization -The main function also initializes the Bluetooth Controller with default settings. The Bluetooth Controller is invisible to the user applications and deals with the lower layers of the Bluetooth Stack. Next, the controller is enabled in Classic Bluetooth Mode. +The `bredr_app_common_init()` also initializes the Bluetooth Controller with default settings. The Bluetooth Controller is invisible to the user applications and deals with the lower layers of the Bluetooth Stack. Next, the controller is enabled in Classic Bluetooth Mode. ```c /* initialize Bluetooth Controller with default configuration */ @@ -86,58 +69,9 @@ The Classic Bluetooth uses an asynchronous programming paradigm. The entire Blue For example, after executing `esp_bt_gap_start_discovery()`, an event of `ESP_BT_GAP_DISC_STATE_CHANGED_EVT` occurs to inform that the discovery state has been changed. Application can do some processing at this time. -### I2S Installation +#### Paring Parameter Settings -The main function installs I2S to play the audio. A loudspeaker, additional ADC or hardware requirements and possibly an external I2S codec may be needed. - -```c - #ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC - /* I2S configuration parameters */ - i2s_config_t i2s_config = { - .mode = I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN, - .sample_rate = 44100, - .bits_per_sample = 16, - .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, /* 2-channels */ - .communication_format = I2S_COMM_FORMAT_STAND_MSB, - .dma_buf_count = 6, - .dma_buf_len = 60, - .intr_alloc_flags = 0, /* default interrupt priority */ - .tx_desc_auto_clear = true /* auto clear tx descriptor on underflow */ - }; - - /* enable I2S */ - ESP_ERROR_CHECK(i2s_driver_install(0, &i2s_config, 0, NULL)); - ESP_ERROR_CHECK(i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN)); - ESP_ERROR_CHECK(i2s_set_pin(0, NULL)); - #else - i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); - chan_cfg.auto_clear = true; - i2s_std_config_t std_cfg = { - .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(44100), - .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), - .gpio_cfg = { - .mclk = I2S_GPIO_UNUSED, - .bclk = CONFIG_EXAMPLE_I2S_BCK_PIN, - .ws = CONFIG_EXAMPLE_I2S_LRCK_PIN, - .dout = CONFIG_EXAMPLE_I2S_DATA_PIN, - .din = I2S_GPIO_UNUSED, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false, - }, - }, - }; - /* enable I2S */ - ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_chan, NULL)); - ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_chan, &std_cfg)); - ESP_ERROR_CHECK(i2s_channel_enable(tx_chan)); - #endif -``` - -### Paring Parameter Settings - -The main function continues to set up paring parameters including Secure Simple Pairing and Legacy Pairing. +The `bredr_app_common_init()` continues to set up paring parameters including Secure Simple Pairing and Legacy Pairing. ```c #if (CONFIG_EXAMPLE_A2DP_SINK_SSP_ENABLED == true) diff --git a/examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/CMakeLists.txt b/examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/CMakeLists.txt new file mode 100644 index 0000000000..6058baebf0 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +idf_build_set_property(MINIMAL_BUILD ON) +project(avrcp_absolute_volume) diff --git a/examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/README.md b/examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/README.md new file mode 100644 index 0000000000..c8080e5eff --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/README.md @@ -0,0 +1,70 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +AVRCP-ABSOLUTE-VOLUME EXAMPLE +====================== + +This is the example of API implementing Audio/Video Remote Control Profile to control absolute volume. + +## Required components + +- [bt_app_core_utils](../common/bt_app_core_utils) +- [bredr_app_common_utils](../common/bredr_app_common_utils) +- [a2dp_sink_common_utils](../common/a2dp_utils/a2dp_sink_common_utils) +- [a2dp_sink_int_codec_utils](../common/a2dp_utils/a2dp_sink_int_codec_utils) +- [a2dp_sink_ext_codec_utils](../common/a2dp_utils/a2dp_sink_ext_codec_utils) +- [avrcp_common_utils](../common/avrcp_utils/avrcp_common_utils) +- [avrcp_abs_vol_utils](../common/avrcp_utils/avrcp_abs_vol_utils) + +``` ++---------------------------------------------------+---------------------+ +| avrcp_abs_vol_utils | | ++---------------------------------------------------+ | +| avrcp_common_utils | | ++-------------------------+-------------------------+ | +|a2dp_sink_int_codec_utils|a2dp_sink_ext_codec_utils| bt_app_core_utils | ++-------------------------+-------------------------+ | +| a2dp_sink_common_utils | | ++---------------------------------------------------+ | +| bredr_app_common_utils | | ++---------------------------------------------------+---------------------+ +``` + +Detailed information can be viewed through the [../common/README.md](../common/README.md). + +## How to use this example + +### Configure the project + +``` +idf.py menuconfig +``` + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output. + +``` +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +## Example Output + +The output when the connection is established: + +``` +I (56320) RC_VC_SRV: Volume is set locally to: 3% +I (57160) RC_TG: AVRC register event notification: 13, param: 0x0 +I (66320) RC_VC_SRV: Volume is set locally to: 7% +I (67160) RC_TG: AVRC register event notification: 13, param: 0x0 +I (76320) RC_VC_SRV: Volume is set locally to: 11% +I (77160) RC_TG: AVRC register event notification: 13, param: 0x0 +I (86320) RC_VC_SRV: Volume is set locally to: 15% +I (87160) RC_TG: AVRC register event notification: 13, param: 0x0 +``` + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/main/CMakeLists.txt b/examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/main/CMakeLists.txt new file mode 100644 index 0000000000..d6ae771f4b --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/main/CMakeLists.txt @@ -0,0 +1,12 @@ +if(CONFIG_EXAMPLE_A2DP_SINK_STREAM_ENABLE) + if(CONFIG_EXAMPLE_A2DP_SINK_USE_EXTERNAL_CODEC) + set(EXTRA_COMPONENTS a2dp_sink_ext_codec_utils) + else() + set(EXTRA_COMPONENTS a2dp_sink_int_codec_utils) + endif() +endif() + +idf_component_register(SRCS "main.c" + PRIV_REQUIRES bt bt_app_core_utils bredr_app_common_utils a2dp_sink_common_utils + PRIV_REQUIRES ${EXTRA_COMPONENTS} avrcp_common_utils avrcp_abs_vol_utils + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/main/Kconfig.projbuild b/examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/main/Kconfig.projbuild new file mode 100644 index 0000000000..2a4dfc0176 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/main/Kconfig.projbuild @@ -0,0 +1,23 @@ +menu "AVRCP Example Configuration" + config EXAMPLE_LOCAL_DEVICE_NAME + string "Local Device Name" + default "ESP_SPEAKER" + help + Use this option to set local device name. + + config EXAMPLE_A2DP_SINK_STREAM_ENABLE + bool "Enable A2DP Sink Stream" + default y + help + This enables the A2DP sink stream. If disable this option, + audio data will not be transmitted + + config EXAMPLE_A2DP_SINK_USE_EXTERNAL_CODEC + bool "Use External Codec Instead of Internal" + depends on EXAMPLE_A2DP_SINK_STREAM_ENABLE + default n + select BT_A2DP_USE_EXTERNAL_CODEC + help + If enable, Bluedroid stack will not decode A2DP audio data, user need to decode it in application layer. + +endmenu diff --git a/examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/main/idf_component.yml b/examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/main/idf_component.yml new file mode 100644 index 0000000000..5e5b34e4b4 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/main/idf_component.yml @@ -0,0 +1,15 @@ +dependencies: + bt_app_core_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/bt_app_core_utils + bredr_app_common_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/bredr_app_common_utils + a2dp_sink_common_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_common_utils + a2dp_sink_int_codec_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils + a2dp_sink_ext_codec_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_ext_codec_utils + avrcp_common_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_common_utils + avrcp_abs_vol_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_abs_vol_utils diff --git a/examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/main/main.c b/examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/main/main.c new file mode 100644 index 0000000000..3e8411cbdf --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/main/main.c @@ -0,0 +1,249 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include + +#include "esp_log.h" +#include "esp_bt_device.h" +#include "esp_gap_bt_api.h" +#include "esp_a2dp_api.h" +#include "esp_avrc_api.h" + +#include "bt_app_core_utils.h" +#include "bredr_app_common_utils.h" +#include "a2dp_utils_tags.h" +#include "a2dp_sink_common_utils.h" +#include "avrcp_utils_tags.h" +#include "avrcp_common_utils.h" +#include "avrcp_abs_vol_utils.h" +#if CONFIG_EXAMPLE_A2DP_SINK_STREAM_ENABLE +#if CONFIG_EXAMPLE_A2DP_SINK_USE_EXTERNAL_CODEC == FALSE +#include "a2dp_sink_int_codec_utils.h" +#else +#include "a2dp_sink_ext_codec_utils.h" +#endif +#endif + +/* device name */ +static const char local_device_name[] = CONFIG_EXAMPLE_LOCAL_DEVICE_NAME; + +/* event for stack up */ +enum { + BT_APP_EVT_STACK_UP = 0, +}; + +/******************************** + * STATIC FUNCTION DECLARATIONS + *******************************/ + +/* Device callback function */ +static void bt_app_dev_cb(esp_bt_dev_cb_event_t event, esp_bt_dev_cb_param_t *param); + +/* GAP callback function */ +static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param); + +/* callback function for A2DP sink */ +static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param); + +#if CONFIG_EXAMPLE_A2DP_SINK_STREAM_ENABLE +#if CONFIG_EXAMPLE_A2DP_SINK_USE_EXTERNAL_CODEC == FALSE +/* callback function for A2DP sink audio data stream */ +static void bt_app_a2d_data_cb(const uint8_t *data, uint32_t len); +#else +/* callback function for A2DP sink undecoded audio data */ +static void bt_app_a2d_audio_data_cb(esp_a2d_conn_hdl_t conn_hdl, esp_a2d_audio_buff_t *audio_buf); +#endif +#endif + +/* callback function for AVRCP controller */ +static void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param); + +/* callback function for AVRCP target */ +static void bt_app_rc_tg_cb(esp_avrc_tg_cb_event_t event, esp_avrc_tg_cb_param_t *param); + +/* handler for bluetooth stack enabled events */ +static void bt_av_hdl_stack_evt(uint16_t event, void *p_param); + +/******************************* + * STATIC FUNCTION DEFINITIONS + ******************************/ + +static void bt_app_dev_cb(esp_bt_dev_cb_event_t event, esp_bt_dev_cb_param_t *param) +{ + bredr_app_dev_evt_def_hdl(event, param); +} + +static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) +{ + bredr_app_gap_evt_def_hdl(event, param); +} + +static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param) +{ + switch (event) { + case ESP_A2D_PROF_STATE_EVT: + case ESP_A2D_SNK_PSC_CFG_EVT: + case ESP_A2D_SNK_SET_DELAY_VALUE_EVT: + case ESP_A2D_SNK_GET_DELAY_VALUE_EVT: { + bt_app_work_dispatch(bt_a2d_evt_def_hdl, event, param, sizeof(esp_a2d_cb_param_t), NULL); + break; + } + case ESP_A2D_CONNECTION_STATE_EVT: + case ESP_A2D_AUDIO_STATE_EVT: + case ESP_A2D_AUDIO_CFG_EVT: + case ESP_A2D_SEP_REG_STATE_EVT: { +#if CONFIG_EXAMPLE_A2DP_SINK_STREAM_ENABLE +#if CONFIG_EXAMPLE_A2DP_SINK_USE_EXTERNAL_CODEC == FALSE + bt_app_work_dispatch(bt_a2d_evt_int_codec_hdl, event, param, sizeof(esp_a2d_cb_param_t), NULL); +#else + bt_app_work_dispatch(bt_a2d_evt_ext_codec_hdl, event, param, sizeof(esp_a2d_cb_param_t), NULL); +#endif +#else + bt_app_work_dispatch(bt_a2d_evt_def_hdl, event, param, sizeof(esp_a2d_cb_param_t), NULL); +#endif + break; + } + default: + ESP_LOGE(BT_AV_TAG, "Invalid A2DP event: %d", event); + break; + } +} + +#if CONFIG_EXAMPLE_A2DP_SINK_STREAM_ENABLE +#if CONFIG_EXAMPLE_A2DP_SINK_USE_EXTERNAL_CODEC == FALSE +static void bt_app_a2d_data_cb(const uint8_t *data, uint32_t len) +{ + bt_a2d_data_hdl(data, len); +} +#else +static void bt_app_a2d_audio_data_cb(esp_a2d_conn_hdl_t conn_hdl, esp_a2d_audio_buff_t *audio_buf) +{ + bt_a2d_audio_data_hdl(conn_hdl, audio_buf); +} +#endif +#endif + +static void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param) +{ + switch (event) { + case ESP_AVRC_CT_METADATA_RSP_EVT: + case ESP_AVRC_CT_CONNECTION_STATE_EVT: + case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: + case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: + case ESP_AVRC_CT_REMOTE_FEATURES_EVT: + case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: + case ESP_AVRC_CT_COVER_ART_STATE_EVT: + case ESP_AVRC_CT_COVER_ART_DATA_EVT: + case ESP_AVRC_CT_PROF_STATE_EVT: { + bt_app_work_dispatch(bt_avrc_common_ct_evt_def_hdl, event, param, sizeof(esp_avrc_ct_cb_param_t), NULL); + break; + } + default: + ESP_LOGE(BT_RC_CT_TAG, "Invalid AVRC event: %d", event); + break; + } +} + +static void bt_app_rc_tg_cb(esp_avrc_tg_cb_event_t event, esp_avrc_tg_cb_param_t *param) +{ + switch (event) { + case ESP_AVRC_TG_REMOTE_FEATURES_EVT: + case ESP_AVRC_TG_PASSTHROUGH_CMD_EVT: + case ESP_AVRC_TG_SET_PLAYER_APP_VALUE_EVT: + case ESP_AVRC_TG_PROF_STATE_EVT: { + bt_app_work_dispatch(bt_avrc_common_tg_evt_def_hdl, event, param, sizeof(esp_avrc_tg_cb_param_t), NULL); + break; + } + case ESP_AVRC_TG_CONNECTION_STATE_EVT: + case ESP_AVRC_TG_SET_ABSOLUTE_VOLUME_CMD_EVT: + case ESP_AVRC_TG_REGISTER_NOTIFICATION_EVT: { + bt_app_work_dispatch(bt_avrc_avc_tg_evt_hdl, event, param, sizeof(esp_avrc_tg_cb_param_t), NULL); + break; + } + default: + ESP_LOGE(BT_RC_TG_TAG, "Invalid AVRC event: %d", event); + break; + } +} + +static void bt_av_hdl_stack_evt(uint16_t event, void *p_param) +{ + ESP_LOGD(BT_AV_TAG, "%s event: %d", __func__, event); + + switch (event) { + /* when do the stack up, this event comes */ + case BT_APP_EVT_STACK_UP: { + esp_bt_gap_set_device_name(local_device_name); + esp_bt_dev_register_callback(bt_app_dev_cb); + esp_bt_gap_register_callback(bt_app_gap_cb); + + esp_avrc_ct_register_callback(bt_app_rc_ct_cb); + assert(esp_avrc_ct_init() == ESP_OK); + esp_avrc_tg_register_callback(bt_app_rc_tg_cb); + assert(esp_avrc_tg_init() == ESP_OK); + + esp_avrc_rn_evt_cap_mask_t evt_set = {0}; + esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_SET, &evt_set, ESP_AVRC_RN_VOLUME_CHANGE); + assert(esp_avrc_tg_set_rn_evt_cap(&evt_set) == ESP_OK); + + esp_a2d_register_callback(&bt_app_a2d_cb); + assert(esp_a2d_sink_init() == ESP_OK); + +#if CONFIG_EXAMPLE_A2DP_SINK_STREAM_ENABLE +#if CONFIG_EXAMPLE_A2DP_SINK_USE_EXTERNAL_CODEC == FALSE + esp_a2d_sink_register_data_callback(bt_app_a2d_data_cb); +#else + esp_a2d_mcc_t mcc = {0}; + mcc.type = ESP_A2D_MCT_SBC; + mcc.cie.sbc_info.samp_freq = ESP_A2D_SBC_CIE_SF_16K | + ESP_A2D_SBC_CIE_SF_32K | + ESP_A2D_SBC_CIE_SF_44K | + ESP_A2D_SBC_CIE_SF_48K; + mcc.cie.sbc_info.ch_mode = ESP_A2D_SBC_CIE_CH_MODE_MONO | + ESP_A2D_SBC_CIE_CH_MODE_DUAL_CHANNEL | + ESP_A2D_SBC_CIE_CH_MODE_STEREO | + ESP_A2D_SBC_CIE_CH_MODE_JOINT_STEREO; + mcc.cie.sbc_info.block_len = ESP_A2D_SBC_CIE_BLOCK_LEN_4 | + ESP_A2D_SBC_CIE_BLOCK_LEN_8 | + ESP_A2D_SBC_CIE_BLOCK_LEN_12 | + ESP_A2D_SBC_CIE_BLOCK_LEN_16; + mcc.cie.sbc_info.num_subbands = ESP_A2D_SBC_CIE_NUM_SUBBANDS_4 | ESP_A2D_SBC_CIE_NUM_SUBBANDS_8; + mcc.cie.sbc_info.alloc_mthd = ESP_A2D_SBC_CIE_ALLOC_MTHD_SRN | ESP_A2D_SBC_CIE_ALLOC_MTHD_LOUDNESS; + mcc.cie.sbc_info.max_bitpool = 250; + mcc.cie.sbc_info.min_bitpool = 2; + /* register stream end point, only support SBC currently */ + esp_a2d_sink_register_stream_endpoint(0, &mcc); + esp_a2d_sink_register_audio_data_callback(bt_app_a2d_audio_data_cb); +#endif +#endif + + /* Get local device name */ + esp_bt_gap_get_device_name(); + + /* set discoverable and connectable mode, wait to be connected */ + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + break; + } + /* others */ + default: + ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event); + break; + } +} + +/******************************* + * MAIN ENTRY POINT + ******************************/ + +void app_main(void) +{ + ESP_ERROR_CHECK(bredr_app_common_init()); + + bt_app_task_start_up(); + /* bluetooth device name, connection mode and profile set up */ + bt_app_work_dispatch(bt_av_hdl_stack_evt, BT_APP_EVT_STACK_UP, NULL, 0, NULL); +} diff --git a/examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/sdkconfig.ci.test b/examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/sdkconfig.ci.test new file mode 100644 index 0000000000..79e4f555d6 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/sdkconfig.ci.test @@ -0,0 +1 @@ +CONFIG_EXAMPLE_LOCAL_DEVICE_NAME="${CI_PIPELINE_ID}_A2DP_SINK" diff --git a/examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/sdkconfig.defaults b/examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/sdkconfig.defaults new file mode 100644 index 0000000000..2b93a51a0e --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/avrcp_absolute_volume/sdkconfig.defaults @@ -0,0 +1,8 @@ +# Override some defaults so BT stack is enabled and +# Classic BT is enabled and BT_DRAM_RELEASE is disabled +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y +CONFIG_BT_BLUEDROID_ENABLED=y +CONFIG_BT_CLASSIC_ENABLED=y +CONFIG_BT_A2DP_ENABLE=y +CONFIG_DAC_DMA_AUTO_16BIT_ALIGN=n diff --git a/examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/CMakeLists.txt b/examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/CMakeLists.txt new file mode 100644 index 0000000000..1a024579a8 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +idf_build_set_property(MINIMAL_BUILD ON) +project(avrcp_ct_metadata) diff --git a/examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/README.md b/examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/README.md new file mode 100644 index 0000000000..5a02564951 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/README.md @@ -0,0 +1,67 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +AVRCP-CT-METADATA EXAMPLE +====================== + +This is the example of API implementing Audio/Video Remote Control Profile to get metadata. + +## Required components + +- [bt_app_core_utils](../common/bt_app_core_utils) +- [bredr_app_common_utils](../common/bredr_app_common_utils) +- [a2dp_sink_common_utils](../common/a2dp_utils/a2dp_sink_common_utils) +- [a2dp_sink_int_codec_utils](../common/a2dp_utils/a2dp_sink_int_codec_utils) +- [a2dp_sink_ext_codec_utils](../common/a2dp_utils/a2dp_sink_ext_codec_utils) +- [avrcp_common_utils](../common/avrcp_utils/avrcp_common_utils) +- [avrcp_metadata_utils](../common/avrcp_utils/avrcp_metadata_utils) + +``` ++---------------------------------------------------+---------------------+ +| avrcp_metadata_utils | | ++---------------------------------------------------+ | +| avrcp_common_utils | | ++-------------------------+-------------------------+ | +|a2dp_sink_int_codec_utils|a2dp_sink_ext_codec_utils| bt_app_core_utils | ++-------------------------+-------------------------+ | +| a2dp_sink_common_utils | | ++---------------------------------------------------+ | +| bredr_app_common_utils | | ++---------------------------------------------------+---------------------+ +``` + +Detailed information can be viewed through the [../common/README.md](../common/README.md). + +## How to use this example + +### Configure the project + +``` +idf.py menuconfig +``` + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output. + +``` +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +## Example Output + +The output when receiving metadata: + +``` +I (81700) RC_CT: AVRC event notification: 2 +I (81740) RC_CT: AVRC metadata rsp: attribute id 0x1, +I (81740) RC_CT: AVRC metadata rsp: attribute id 0x2, +I (81740) RC_CT: AVRC metadata rsp: attribute id 0x4, +I (81750) RC_CT: AVRC metadata rsp: attribute id 0x20, +``` + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/main/CMakeLists.txt b/examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/main/CMakeLists.txt new file mode 100644 index 0000000000..9807f92c7c --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/main/CMakeLists.txt @@ -0,0 +1,12 @@ +if(CONFIG_EXAMPLE_A2DP_SINK_STREAM_ENABLE) + if(CONFIG_EXAMPLE_A2DP_SINK_USE_EXTERNAL_CODEC) + set(EXTRA_COMPONENTS a2dp_sink_ext_codec_utils) + else() + set(EXTRA_COMPONENTS a2dp_sink_int_codec_utils) + endif() +endif() + +idf_component_register(SRCS "main.c" + PRIV_REQUIRES bt bt_app_core_utils bredr_app_common_utils a2dp_sink_common_utils + PRIV_REQUIRES ${EXTRA_COMPONENTS} avrcp_common_utils avrcp_metadata_utils + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/main/Kconfig.projbuild b/examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/main/Kconfig.projbuild new file mode 100644 index 0000000000..2a4dfc0176 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/main/Kconfig.projbuild @@ -0,0 +1,23 @@ +menu "AVRCP Example Configuration" + config EXAMPLE_LOCAL_DEVICE_NAME + string "Local Device Name" + default "ESP_SPEAKER" + help + Use this option to set local device name. + + config EXAMPLE_A2DP_SINK_STREAM_ENABLE + bool "Enable A2DP Sink Stream" + default y + help + This enables the A2DP sink stream. If disable this option, + audio data will not be transmitted + + config EXAMPLE_A2DP_SINK_USE_EXTERNAL_CODEC + bool "Use External Codec Instead of Internal" + depends on EXAMPLE_A2DP_SINK_STREAM_ENABLE + default n + select BT_A2DP_USE_EXTERNAL_CODEC + help + If enable, Bluedroid stack will not decode A2DP audio data, user need to decode it in application layer. + +endmenu diff --git a/examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/main/idf_component.yml b/examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/main/idf_component.yml new file mode 100644 index 0000000000..bedf3538c1 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/main/idf_component.yml @@ -0,0 +1,15 @@ +dependencies: + bt_app_core_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/bt_app_core_utils + bredr_app_common_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/bredr_app_common_utils + a2dp_sink_common_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_common_utils + a2dp_sink_int_codec_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils + a2dp_sink_ext_codec_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_ext_codec_utils + avrcp_common_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_common_utils + avrcp_metadata_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_metadata_utils diff --git a/examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/main/main.c b/examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/main/main.c new file mode 100644 index 0000000000..3267570d8c --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/main/main.c @@ -0,0 +1,295 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include + +#include "esp_log.h" +#include "esp_bt_device.h" +#include "esp_gap_bt_api.h" +#include "esp_a2dp_api.h" +#include "esp_avrc_api.h" + +#include "bt_app_core_utils.h" +#include "bredr_app_common_utils.h" +#include "a2dp_utils_tags.h" +#include "a2dp_sink_common_utils.h" +#include "avrcp_utils_tags.h" +#include "avrcp_common_utils.h" +#include "avrcp_metadata_utils.h" +#if CONFIG_EXAMPLE_A2DP_SINK_STREAM_ENABLE +#if CONFIG_EXAMPLE_A2DP_SINK_USE_EXTERNAL_CODEC == FALSE +#include "a2dp_sink_int_codec_utils.h" +#else +#include "a2dp_sink_ext_codec_utils.h" +#endif +#endif + +/* device name */ +static const char local_device_name[] = CONFIG_EXAMPLE_LOCAL_DEVICE_NAME; + +/* event for stack up */ +enum { + BT_APP_EVT_STACK_UP = 0, +}; + +/******************************** + * STATIC FUNCTION DECLARATIONS + *******************************/ + +/* Device callback function */ +static void bt_app_dev_cb(esp_bt_dev_cb_event_t event, esp_bt_dev_cb_param_t *param); + +/* GAP callback function */ +static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param); + +/* callback function for A2DP sink */ +static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param); + +#if CONFIG_EXAMPLE_A2DP_SINK_STREAM_ENABLE +#if CONFIG_EXAMPLE_A2DP_SINK_USE_EXTERNAL_CODEC == FALSE +/* callback function for A2DP sink audio data stream */ +static void bt_app_a2d_data_cb(const uint8_t *data, uint32_t len); +#else +/* callback function for A2DP sink undecoded audio data */ +static void bt_app_a2d_audio_data_cb(esp_a2d_conn_hdl_t conn_hdl, esp_a2d_audio_buff_t *audio_buf); +#endif +#endif + +/* handler for AVRCP controller events */ +static void bt_app_avrc_ct_evt_hdl(uint16_t event, void *param); + +/* callback function for AVRCP controller */ +static void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param); + +/* callback function for AVRCP target */ +static void bt_app_rc_tg_cb(esp_avrc_tg_cb_event_t event, esp_avrc_tg_cb_param_t *param); + +/* handler for bluetooth stack enabled events */ +static void bt_av_hdl_stack_evt(uint16_t event, void *p_param); + +/******************************* + * STATIC FUNCTION DEFINITIONS + ******************************/ + +static void bt_app_dev_cb(esp_bt_dev_cb_event_t event, esp_bt_dev_cb_param_t *param) +{ + bredr_app_dev_evt_def_hdl(event, param); +} + +static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) +{ + bredr_app_gap_evt_def_hdl(event, param); +} + +static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param) +{ + switch (event) { + case ESP_A2D_PROF_STATE_EVT: + case ESP_A2D_SNK_PSC_CFG_EVT: + case ESP_A2D_SNK_SET_DELAY_VALUE_EVT: + case ESP_A2D_SNK_GET_DELAY_VALUE_EVT: { + bt_app_work_dispatch(bt_a2d_evt_def_hdl, event, param, sizeof(esp_a2d_cb_param_t), NULL); + break; + } + case ESP_A2D_CONNECTION_STATE_EVT: + case ESP_A2D_AUDIO_STATE_EVT: + case ESP_A2D_AUDIO_CFG_EVT: + case ESP_A2D_SEP_REG_STATE_EVT: { +#if CONFIG_EXAMPLE_A2DP_SINK_STREAM_ENABLE +#if CONFIG_EXAMPLE_A2DP_SINK_USE_EXTERNAL_CODEC == FALSE + bt_app_work_dispatch(bt_a2d_evt_int_codec_hdl, event, param, sizeof(esp_a2d_cb_param_t), NULL); +#else + bt_app_work_dispatch(bt_a2d_evt_ext_codec_hdl, event, param, sizeof(esp_a2d_cb_param_t), NULL); +#endif +#else + bt_app_work_dispatch(bt_a2d_evt_def_hdl, event, param, sizeof(esp_a2d_cb_param_t), NULL); +#endif + break; + } + default: + ESP_LOGE(BT_AV_TAG, "Invalid A2DP event: %d", event); + break; + } +} + +#if CONFIG_EXAMPLE_A2DP_SINK_STREAM_ENABLE +#if CONFIG_EXAMPLE_A2DP_SINK_USE_EXTERNAL_CODEC == FALSE +static void bt_app_a2d_data_cb(const uint8_t *data, uint32_t len) +{ + bt_a2d_data_hdl(data, len); +} +#else +static void bt_app_a2d_audio_data_cb(esp_a2d_conn_hdl_t conn_hdl, esp_a2d_audio_buff_t *audio_buf) +{ + bt_a2d_audio_data_hdl(conn_hdl, audio_buf); +} +#endif +#endif + +static void bt_app_avrc_ct_evt_hdl(uint16_t event, void *param) +{ + ESP_LOGD(BT_RC_CT_TAG, "%s event: %d", __func__, event); + + esp_avrc_ct_cb_param_t *rc = (esp_avrc_ct_cb_param_t *)(param); + + switch (event) { + case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: + case ESP_AVRC_CT_REMOTE_FEATURES_EVT: + case ESP_AVRC_CT_PROF_STATE_EVT: { + bt_avrc_common_ct_evt_def_hdl(event, param); + break; + } + case ESP_AVRC_CT_CONNECTION_STATE_EVT: { + bt_avrc_md_ct_evt_hdl(event, param); + if (rc->conn_stat.connected) { + /* get remote supported event_ids of peer AVRCP Target */ + bt_avrc_common_ct_get_peer_rn_cap(); + } else { + /* clear peer notification capability record */ + bt_avrc_common_ct_set_peer_rn_cap(0); + } + break; + } + case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: { + bt_avrc_md_ct_evt_hdl(event, param); + bt_avrc_common_ct_notify_evt_handler(rc->change_ntf.event_id, &rc->change_ntf.event_parameter); + break; + } + case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: { + /* set peer notification capability record */ + bt_avrc_common_ct_set_peer_rn_cap(rc->get_rn_caps_rsp.evt_set.bits); + bt_avrc_common_ct_rn_track_changed(); + bt_avrc_common_ct_rn_play_status_changed(); + bt_avrc_common_ct_rn_play_pos_changed(); + + bt_avrc_md_ct_evt_hdl(event, param); + break; + } + case ESP_AVRC_CT_METADATA_RSP_EVT: { + bt_avrc_md_ct_evt_hdl(event, param); + break; + } + default: + ESP_LOGE(BT_RC_CT_TAG, "Invalid AVRC event: %d", event); + break; + } +} + +static void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param) +{ + switch (event) { + case ESP_AVRC_CT_METADATA_RSP_EVT: { + bt_app_work_dispatch(bt_app_avrc_ct_evt_hdl, event, param, sizeof(esp_avrc_ct_cb_param_t), bt_avrc_common_copy_metadata); + break; + } + case ESP_AVRC_CT_CONNECTION_STATE_EVT: + case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: + case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: + case ESP_AVRC_CT_REMOTE_FEATURES_EVT: + case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: + case ESP_AVRC_CT_PROF_STATE_EVT: + bt_app_work_dispatch(bt_app_avrc_ct_evt_hdl, event, param, sizeof(esp_avrc_ct_cb_param_t), NULL); + break; + default: + ESP_LOGE(BT_RC_CT_TAG, "Invalid AVRC event: %d", event); + break; + } +} + +static void bt_app_rc_tg_cb(esp_avrc_tg_cb_event_t event, esp_avrc_tg_cb_param_t *param) +{ + switch (event) { + case ESP_AVRC_TG_CONNECTION_STATE_EVT: + case ESP_AVRC_TG_REMOTE_FEATURES_EVT: + case ESP_AVRC_TG_PASSTHROUGH_CMD_EVT: + case ESP_AVRC_TG_SET_ABSOLUTE_VOLUME_CMD_EVT: + case ESP_AVRC_TG_REGISTER_NOTIFICATION_EVT: + case ESP_AVRC_TG_SET_PLAYER_APP_VALUE_EVT: + case ESP_AVRC_TG_PROF_STATE_EVT: + bt_app_work_dispatch(bt_avrc_common_tg_evt_def_hdl, event, param, sizeof(esp_avrc_tg_cb_param_t), NULL); + break; + default: + ESP_LOGE(BT_RC_TG_TAG, "Invalid AVRC event: %d", event); + break; + } +} + +static void bt_av_hdl_stack_evt(uint16_t event, void *p_param) +{ + ESP_LOGD(BT_AV_TAG, "%s event: %d", __func__, event); + + switch (event) { + /* when do the stack up, this event comes */ + case BT_APP_EVT_STACK_UP: { + esp_bt_gap_set_device_name(local_device_name); + esp_bt_dev_register_callback(bt_app_dev_cb); + esp_bt_gap_register_callback(bt_app_gap_cb); + + esp_avrc_ct_register_callback(bt_app_rc_ct_cb); + assert(esp_avrc_ct_init() == ESP_OK); + esp_avrc_tg_register_callback(bt_app_rc_tg_cb); + assert(esp_avrc_tg_init() == ESP_OK); + + esp_a2d_register_callback(&bt_app_a2d_cb); + assert(esp_a2d_sink_init() == ESP_OK); + +#if CONFIG_EXAMPLE_A2DP_SINK_STREAM_ENABLE +#if CONFIG_EXAMPLE_A2DP_SINK_USE_EXTERNAL_CODEC == FALSE + esp_a2d_sink_register_data_callback(bt_app_a2d_data_cb); +#else + esp_a2d_mcc_t mcc = {0}; + mcc.type = ESP_A2D_MCT_SBC; + mcc.cie.sbc_info.samp_freq = ESP_A2D_SBC_CIE_SF_16K | + ESP_A2D_SBC_CIE_SF_32K | + ESP_A2D_SBC_CIE_SF_44K | + ESP_A2D_SBC_CIE_SF_48K; + mcc.cie.sbc_info.ch_mode = ESP_A2D_SBC_CIE_CH_MODE_MONO | + ESP_A2D_SBC_CIE_CH_MODE_DUAL_CHANNEL | + ESP_A2D_SBC_CIE_CH_MODE_STEREO | + ESP_A2D_SBC_CIE_CH_MODE_JOINT_STEREO; + mcc.cie.sbc_info.block_len = ESP_A2D_SBC_CIE_BLOCK_LEN_4 | + ESP_A2D_SBC_CIE_BLOCK_LEN_8 | + ESP_A2D_SBC_CIE_BLOCK_LEN_12 | + ESP_A2D_SBC_CIE_BLOCK_LEN_16; + mcc.cie.sbc_info.num_subbands = ESP_A2D_SBC_CIE_NUM_SUBBANDS_4 | ESP_A2D_SBC_CIE_NUM_SUBBANDS_8; + mcc.cie.sbc_info.alloc_mthd = ESP_A2D_SBC_CIE_ALLOC_MTHD_SRN | ESP_A2D_SBC_CIE_ALLOC_MTHD_LOUDNESS; + mcc.cie.sbc_info.max_bitpool = 250; + mcc.cie.sbc_info.min_bitpool = 2; + /* register stream end point, only support SBC currently */ + esp_a2d_sink_register_stream_endpoint(0, &mcc); + esp_a2d_sink_register_audio_data_callback(bt_app_a2d_audio_data_cb); +#endif +#endif + + /* Get the default value of the delay value */ + esp_a2d_sink_get_delay_value(); + /* Get local device name */ + esp_bt_gap_get_device_name(); + + /* set discoverable and connectable mode, wait to be connected */ + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + break; + } + /* others */ + default: + ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event); + break; + } +} + +/******************************* + * MAIN ENTRY POINT + ******************************/ + +void app_main(void) +{ + ESP_ERROR_CHECK(bredr_app_common_init()); + + bt_app_task_start_up(); + /* bluetooth device name, connection mode and profile set up */ + bt_app_work_dispatch(bt_av_hdl_stack_evt, BT_APP_EVT_STACK_UP, NULL, 0, NULL); +} diff --git a/examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/pytest_classic_bt_metadata_test.py b/examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/pytest_classic_bt_metadata_test.py new file mode 100644 index 0000000000..1c01e7fff8 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/pytest_classic_bt_metadata_test.py @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize + + +@pytest.mark.generic +@idf_parametrize('target', ['esp32'], indirect=['target']) +def test_bt_avrcp_metadata(dut: Dut) -> None: + dut.expect(r'AVRCP (CT|TG) STATE: Init Complete', timeout=30) + dut.expect(r'AVRCP (CT|TG) STATE: Init Complete', timeout=30) diff --git a/examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/sdkconfig.defaults b/examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/sdkconfig.defaults new file mode 100644 index 0000000000..2b93a51a0e --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/avrcp_ct_metadata/sdkconfig.defaults @@ -0,0 +1,8 @@ +# Override some defaults so BT stack is enabled and +# Classic BT is enabled and BT_DRAM_RELEASE is disabled +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y +CONFIG_BT_BLUEDROID_ENABLED=y +CONFIG_BT_CLASSIC_ENABLED=y +CONFIG_BT_A2DP_ENABLE=y +CONFIG_DAC_DMA_AUTO_16BIT_ALIGN=n diff --git a/examples/bluetooth/bluedroid/classic_bt/common/README.md b/examples/bluetooth/bluedroid/classic_bt/common/README.md new file mode 100644 index 0000000000..e830d23d75 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/README.md @@ -0,0 +1,44 @@ +COMMON CODE +====================== + +This folder provides the universal code for classic Bluetooth applications, and corresponding functions can be achieved by combining different parts. + +## File Tree + +The file tree of this folder is shown below. +- The [a2dp_utils](./a2dp_utils) folder contains the files of A2DP including `a2dp_sink_common_utils`, `a2dp_sink_ext_codec_utils` and `a2dp_sink_int_codec_utils` related files. The [a2dp_sink_common_utils](./a2dp_utils/a2dp_sink_common_utils) folder is about common handle function for A2DP sink event. The [a2dp_sink_ext_codec_utils](./a2dp_utils/a2dp_sink_ext_codec_utils) folder is about external codec of audio data. The [a2dp_sink_int_codec_utils](./a2dp_utils/a2dp_sink_int_codec_utils) folder is about internal codec of audio data. +- The [avrcp_utils](./avrcp_utils) folder contains the files of AVRCP including `avrcp_abs_vol_utils`, `avrcp_common_utils`, `avrcp_cover_art_utils` and `avrcp_metadata_utils` related files. The [avrcp_abs_vol_utils](./avrcp_utils/avrcp_abs_vol_utils) folder is about AVRCP absolute volume control event. The [avrcp_common_utils](./avrcp_utils/avrcp_common_utils) folder is about common handle function for AVRCP event. The [avrcp_cover_art_utils](./avrcp_utils/avrcp_cover_art_utils) folder is about AVRCP cover art event. The [avrcp_metadata_utils](./avrcp_utils/avrcp_metadata_utils) folder is about AVRCP metadata event. +- The [bredr_app_common_utils](./bredr_app_common_utils) folder contains the files of Classic Bluetooth common code including Classic Bluetooth common initialization and handle function about GAP and device. +- The [bt_app_core_utils](./bt_app_core_utils) folder contains the program core files. + +``` +└── common +    ├── a2dp_utils +    │   ├── a2dp_sink_common_utils +    │   ├── a2dp_sink_ext_codec_utils +    │   └── a2dp_sink_int_codec_utils +    ├── avrcp_utils +   │   ├── avrcp_abs_vol_utils +    │   ├── avrcp_common_utils +    │   ├── avrcp_cover_art_utils +    │   └── avrcp_metadata_utils +    ├── bredr_app_common_utils +    ├── bt_app_core_utils +    └── README.md +``` + +## How to use + +The functional framework diagram is shown below. The `bt_app_core_utils` is responsible for all event scheduling. Specific functions can be implemented by general code combined with unique code. For instance, the internal decoding audio output of A2DP sink can be achieved through components such as `bredr_app_common_utils`, `a2dp_sink_common_utils`, and `a2dp_sink_int_codec_utils`. On this basis, adding the code `avrcp_common_utils` and `avrcp_abs_vol_utils` can enable volume adjustment, or incorporating `avrcp_common_utils` and `avrcp_metadata_utils` can facilitate the transmission of metadata. + +``` ++---------------------------------------------------+---------------------+ +| AVRCP feature(avrcp_utils/unique code) | | ++-------------------------+-------------------------+ | +|a2dp_sink_int_codec_utils|a2dp_sink_ext_codec_utils| | ++-------------------------+-------------------------+ bt_app_core_utils | +| a2dp_sink_common_utils | | ++---------------------------------------------------+ | +| bredr_app_common_utils | | ++---------------------------------------------------+---------------------+ +``` diff --git a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_common_utils/CMakeLists.txt b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_common_utils/CMakeLists.txt new file mode 100644 index 0000000000..f45a66c150 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_common_utils/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "a2dp_sink_common_utils.c" + PRIV_REQUIRES bt + INCLUDE_DIRS "." "../include") diff --git a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_common_utils/a2dp_sink_common_utils.c b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_common_utils/a2dp_sink_common_utils.c new file mode 100644 index 0000000000..e5c886dd14 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_common_utils/a2dp_sink_common_utils.c @@ -0,0 +1,118 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include +#include + +#include "esp_log.h" +#include "esp_gap_bt_api.h" +#include "esp_a2dp_api.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "a2dp_sink_common_utils.h" +#include "a2dp_utils_tags.h" + +/* Application layer causes delay value */ +#define APP_DELAY_VALUE 50 // 5ms + +/******************************* + * STATIC VARIABLE DEFINITIONS + ******************************/ + +/* connection state in string */ +static const char *s_a2d_conn_state_str[] = {"Disconnected", "Connecting", "Connected", "Disconnecting"}; +/* audio stream datapath state in string */ +static const char *s_a2d_audio_state_str[] = {"Suspended", "Started"}; + +/******************************** + * EXTERNAL FUNCTION DEFINITIONS + *******************************/ + +void bt_a2d_evt_def_hdl(uint16_t event, void *param) +{ + ESP_LOGD(BT_AV_TAG, "%s event: %d", __func__, event); + + esp_a2d_cb_param_t *a2d = (esp_a2d_cb_param_t *)(param); + + switch (event) { + /* when connection state changed, this event comes */ + case ESP_A2D_CONNECTION_STATE_EVT: { + uint8_t *bda = a2d->conn_stat.remote_bda; + ESP_LOGI(BT_AV_TAG, "A2DP connection state: %s, [%02x:%02x:%02x:%02x:%02x:%02x]", + s_a2d_conn_state_str[a2d->conn_stat.state], bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); + if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + } else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED) { + esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE); + } + break; + } + /* when audio stream transmission state changed, this event comes */ + case ESP_A2D_AUDIO_STATE_EVT: { + ESP_LOGI(BT_AV_TAG, "A2DP audio state: %s", s_a2d_audio_state_str[a2d->audio_stat.state]); + break; + } + /* when audio codec is configured, this event comes */ + case ESP_A2D_AUDIO_CFG_EVT: { + esp_a2d_mcc_t *p_mcc = &a2d->audio_cfg.mcc; + ESP_LOGI(BT_AV_TAG, "A2DP audio stream configuration, codec type: %d", p_mcc->type); + break; + } + /* when a2dp init or deinit completed, this event comes */ + case ESP_A2D_PROF_STATE_EVT: { + if (ESP_A2D_INIT_SUCCESS == a2d->a2d_prof_stat.init_state) { + ESP_LOGI(BT_AV_TAG, "A2DP PROF STATE: Init Complete"); + } else { + ESP_LOGI(BT_AV_TAG, "A2DP PROF STATE: Deinit Complete"); + } + break; + } + /* when using external codec, after sep registration done, this event comes */ + case ESP_A2D_SEP_REG_STATE_EVT: { + if (a2d->a2d_sep_reg_stat.reg_state == ESP_A2D_SEP_REG_SUCCESS) { + ESP_LOGI(BT_AV_TAG, "A2DP register SEP success, seid: %d", a2d->a2d_sep_reg_stat.seid); + } else { + ESP_LOGI(BT_AV_TAG, "A2DP register SEP fail, seid: %d, state: %d", a2d->a2d_sep_reg_stat.seid, a2d->a2d_sep_reg_stat.reg_state); + } + break; + } + /* When protocol service capabilities configured, this event comes */ + case ESP_A2D_SNK_PSC_CFG_EVT: { + ESP_LOGI(BT_AV_TAG, "protocol service capabilities configured: 0x%x ", a2d->a2d_psc_cfg_stat.psc_mask); + if (a2d->a2d_psc_cfg_stat.psc_mask & ESP_A2D_PSC_DELAY_RPT) { + ESP_LOGI(BT_AV_TAG, "Peer device support delay reporting"); + } else { + ESP_LOGI(BT_AV_TAG, "Peer device unsupported delay reporting"); + } + break; + } + /* when set delay value completed, this event comes */ + case ESP_A2D_SNK_SET_DELAY_VALUE_EVT: { + if (ESP_A2D_SET_INVALID_PARAMS == a2d->a2d_set_delay_value_stat.set_state) { + ESP_LOGI(BT_AV_TAG, "Set delay report value: fail"); + } else { + ESP_LOGI(BT_AV_TAG, "Set delay report value: success, delay_value: %u * 1/10 ms", a2d->a2d_set_delay_value_stat.delay_value); + } + break; + } + /* when get delay value completed, this event comes */ + case ESP_A2D_SNK_GET_DELAY_VALUE_EVT: { + ESP_LOGI(BT_AV_TAG, "Get delay report value: delay_value: %u * 1/10 ms", a2d->a2d_get_delay_value_stat.delay_value); + /* Default delay value plus delay caused by application layer */ + esp_a2d_sink_set_delay_value(a2d->a2d_get_delay_value_stat.delay_value + APP_DELAY_VALUE); + break; + } + /* others */ + default: + ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event); + break; + } +} diff --git a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_common_utils/a2dp_sink_common_utils.h b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_common_utils/a2dp_sink_common_utils.h new file mode 100644 index 0000000000..8a09368a94 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_common_utils/a2dp_sink_common_utils.h @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#ifndef __A2DP_SINK_COMMON_UTILS_H__ +#define __A2DP_SINK_COMMON_UTILS_H__ + +#include + +/** + * @brief default handle function for A2DP sink event + * + * @param [in] event event id + * @param [in] param callback parameter + */ +void bt_a2d_evt_def_hdl(uint16_t event, void *param); + +#endif /* __A2DP_SINK_COMMON_UTILS_H__*/ diff --git a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_ext_codec_utils/CMakeLists.txt b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_ext_codec_utils/CMakeLists.txt new file mode 100644 index 0000000000..229a18af0e --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_ext_codec_utils/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "a2dp_sink_ext_codec_utils.c" + PRIV_REQUIRES bt + INCLUDE_DIRS "." "../include") diff --git a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_ext_codec_utils/a2dp_sink_ext_codec_utils.c b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_ext_codec_utils/a2dp_sink_ext_codec_utils.c new file mode 100644 index 0000000000..e19ca2d95d --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_ext_codec_utils/a2dp_sink_ext_codec_utils.c @@ -0,0 +1,116 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include +#include + +#include "esp_log.h" +#include "esp_gap_bt_api.h" +#include "esp_a2dp_api.h" + +#include "a2dp_sink_ext_codec_utils.h" +#include "a2dp_utils_tags.h" + +/******************************* + * STATIC VARIABLE DEFINITIONS + ******************************/ + +/* count for audio packet */ +static uint32_t s_pkt_cnt = 0; +/* connection state in string */ +static const char *s_a2d_conn_state_str[] = {"Disconnected", "Connecting", "Connected", "Disconnecting"}; +/* audio stream datapath state in string */ +static const char *s_a2d_audio_state_str[] = {"Suspended", "Started"}; + +/******************************** + * EXTERNAL FUNCTION DEFINITIONS + *******************************/ + +void bt_a2d_evt_ext_codec_hdl(uint16_t event, void *param) +{ + ESP_LOGD(BT_AV_TAG, "%s event: %d", __func__, event); + + esp_a2d_cb_param_t *a2d = (esp_a2d_cb_param_t *)(param); + + switch (event) { + /* when connection state changed, this event comes */ + case ESP_A2D_CONNECTION_STATE_EVT: { + uint8_t *bda = a2d->conn_stat.remote_bda; + ESP_LOGI(BT_AV_TAG, "A2DP connection state: %s, [%02x:%02x:%02x:%02x:%02x:%02x]", + s_a2d_conn_state_str[a2d->conn_stat.state], bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); + if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + } else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED) { + esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE); + } + break; + } + /* when audio stream transmission state changed, this event comes */ + case ESP_A2D_AUDIO_STATE_EVT: { + ESP_LOGI(BT_AV_TAG, "A2DP audio state: %s", s_a2d_audio_state_str[a2d->audio_stat.state]); + if (ESP_A2D_AUDIO_STATE_STARTED == a2d->audio_stat.state) { + s_pkt_cnt = 0; + } + break; + } + /* when audio codec is configured, this event comes */ + case ESP_A2D_AUDIO_CFG_EVT: { + esp_a2d_mcc_t *p_mcc = &a2d->audio_cfg.mcc; + ESP_LOGI(BT_AV_TAG, "A2DP audio stream configuration, codec type: %d", p_mcc->type); + /* for now only SBC stream is supported */ + if (p_mcc->type == ESP_A2D_MCT_SBC) { + int sample_rate = 16000; + if (p_mcc->cie.sbc_info.samp_freq & ESP_A2D_SBC_CIE_SF_32K) { + sample_rate = 32000; + } else if (p_mcc->cie.sbc_info.samp_freq & ESP_A2D_SBC_CIE_SF_44K) { + sample_rate = 44100; + } else if (p_mcc->cie.sbc_info.samp_freq & ESP_A2D_SBC_CIE_SF_48K) { + sample_rate = 48000; + } + ESP_LOGI(BT_AV_TAG, "Configure audio player: 0x%x-0x%x-0x%x-0x%x-0x%x-%d-%d", + p_mcc->cie.sbc_info.samp_freq, + p_mcc->cie.sbc_info.ch_mode, + p_mcc->cie.sbc_info.block_len, + p_mcc->cie.sbc_info.num_subbands, + p_mcc->cie.sbc_info.alloc_mthd, + p_mcc->cie.sbc_info.min_bitpool, + p_mcc->cie.sbc_info.max_bitpool); + ESP_LOGI(BT_AV_TAG, "Audio player configured, sample rate: %d", sample_rate); + } + break; + } + /* when using external codec, after sep registration done, this event comes */ + case ESP_A2D_SEP_REG_STATE_EVT: { + if (a2d->a2d_sep_reg_stat.reg_state == ESP_A2D_SEP_REG_SUCCESS) { + ESP_LOGI(BT_AV_TAG, "A2DP register SEP success, seid: %d", a2d->a2d_sep_reg_stat.seid); + } else { + ESP_LOGI(BT_AV_TAG, "A2DP register SEP fail, seid: %d, state: %d", a2d->a2d_sep_reg_stat.seid, a2d->a2d_sep_reg_stat.reg_state); + } + break; + } + /* others */ + default: + ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event); + break; + } +} + +void bt_a2d_audio_data_hdl(esp_a2d_conn_hdl_t conn_hdl, esp_a2d_audio_buff_t *audio_buf) +{ + /* log the number every 100 packets */ + if (++s_pkt_cnt % 100 == 0) { + ESP_LOGI(BT_AV_TAG, "Undecoded audio package count: %"PRIu32, s_pkt_cnt); + } + + /* + * Normally, user should send the audio_buf to other task, decode and free audio buff, + * But the codec component is not merge into IDF now, so we just free audio data here + */ + esp_a2d_audio_buff_free(audio_buf); +} diff --git a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_ext_codec_utils/a2dp_sink_ext_codec_utils.h b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_ext_codec_utils/a2dp_sink_ext_codec_utils.h new file mode 100644 index 0000000000..400b7ab376 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_ext_codec_utils/a2dp_sink_ext_codec_utils.h @@ -0,0 +1,29 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#ifndef __A2DP_SINK_EXT_CODEC_UTILS_H__ +#define __A2DP_SINK_EXT_CODEC_UTILS_H__ + +#include +#include "esp_a2dp_api.h" + +/** + * @brief handle function for A2DP sink external codec + * + * @param [in] event event id + * @param [in] param callback parameter + */ +void bt_a2d_evt_ext_codec_hdl(uint16_t event, void *param); + +/** + * @brief handle function for A2DP sink undecoded audio data + * + * @param [in] conn_hdl connection handle + * @param [in] audio_buf pointer to audio buff + */ +void bt_a2d_audio_data_hdl(esp_a2d_conn_hdl_t conn_hdl, esp_a2d_audio_buff_t *audio_buf); + +#endif /* __A2DP_SINK_EXT_CODEC_UTILS_H__*/ diff --git a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/CMakeLists.txt b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/CMakeLists.txt new file mode 100644 index 0000000000..4e39651fff --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/CMakeLists.txt @@ -0,0 +1,6 @@ +idf_component_register(SRCS "a2dp_sink_int_codec_utils.c" + "audio_sink_service_idle.c" + "audio_sink_service_i2s.c" + "audio_sink_service_dac.c" + PRIV_REQUIRES esp_driver_i2s bt esp_ringbuf esp_driver_dac + INCLUDE_DIRS "." "../include") diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/Kconfig.projbuild b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/Kconfig.projbuild similarity index 54% rename from examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/Kconfig.projbuild rename to examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/Kconfig.projbuild index e627e8b5d1..cd16bd351a 100644 --- a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/Kconfig.projbuild +++ b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/Kconfig.projbuild @@ -1,12 +1,4 @@ -menu "A2DP Example Configuration" - config EXAMPLE_A2DP_SINK_SSP_ENABLED - bool "Secure Simple Pairing" - depends on BT_CLASSIC_ENABLED - default y - help - This enables the Secure Simple Pairing. If disable this option, - Bluedroid will only support Legacy Pairing - +menu "A2DP Sink Internal Codec Example Configuration" choice EXAMPLE_A2DP_SINK_OUTPUT prompt "A2DP Sink Output" default EXAMPLE_A2DP_SINK_OUTPUT_EXTERNAL_I2S @@ -25,11 +17,16 @@ menu "A2DP Example Configuration" help Select this to use External I2S sink output + config EXAMPLE_A2DP_SINK_OUTPUT_IDLE + bool "Idle Output" + help + Select this to discard a2dp sink data + endchoice config EXAMPLE_I2S_LRCK_PIN int "I2S LRCK (WS) GPIO" - default 22 + default 27 depends on EXAMPLE_A2DP_SINK_OUTPUT_EXTERNAL_I2S help GPIO number to use for I2S LRCK(WS) Driver. @@ -48,24 +45,4 @@ menu "A2DP Example Configuration" help GPIO number to use for I2S Data Driver. - config EXAMPLE_LOCAL_DEVICE_NAME - string "Local Device Name" - default "ESP_SPEAKER" - help - Use this option to set local device name. - - config EXAMPLE_AVRCP_CT_COVER_ART_ENABLE - bool "Use AVRCP CT Cover Art Feature" - depends on BT_CLASSIC_ENABLED - default y - help - This enables the AVRCP Cover Art feature in example and try to get cover art image from peer device. - - config EXAMPLE_A2DP_SINK_USE_EXTERNAL_CODEC - bool "Use External Codec Instead of Internal" - default n - select BT_A2DP_USE_EXTERNAL_CODEC - help - If enable, Bluedroid stack will not decode A2DP audio data, user need to decode it in application layer. - endmenu diff --git a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/a2dp_sink_int_codec_utils.c b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/a2dp_sink_int_codec_utils.c new file mode 100644 index 0000000000..105b0070ae --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/a2dp_sink_int_codec_utils.c @@ -0,0 +1,86 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include +#include + +#include "esp_log.h" +#include "esp_gap_bt_api.h" +#include "esp_a2dp_api.h" + +#include "audio_sink_service.h" +#include "a2dp_sink_int_codec_utils.h" +#include "a2dp_utils_tags.h" + +/******************************* + * STATIC VARIABLE DEFINITIONS + ******************************/ + +/* count for audio packet */ +static uint32_t s_pkt_cnt = 0; +/* connection state in string */ +static const char *s_a2d_conn_state_str[] = {"Disconnected", "Connecting", "Connected", "Disconnecting"}; +/* audio stream datapath state in string */ +static const char *s_a2d_audio_state_str[] = {"Suspended", "Started"}; + +/******************************** + * EXTERNAL FUNCTION DEFINITIONS + *******************************/ + +void bt_a2d_evt_int_codec_hdl(uint16_t event, void *param) +{ + esp_a2d_cb_param_t *a2d = (esp_a2d_cb_param_t *)(param); + + switch (event) { + /* when connection state changed, this event comes */ + case ESP_A2D_CONNECTION_STATE_EVT: { + uint8_t *bda = a2d->conn_stat.remote_bda; + ESP_LOGI(BT_AV_TAG, "A2DP connection state: %s, [%02x:%02x:%02x:%02x:%02x:%02x]", + s_a2d_conn_state_str[a2d->conn_stat.state], bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); + if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + audio_sink_srv_stop(); + audio_sink_srv_close(); + } else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED) { + esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE); + audio_sink_srv_start(); + } else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTING) { + audio_sink_srv_open(); + } + break; + } + /* when audio stream transmission state changed, this event comes */ + case ESP_A2D_AUDIO_STATE_EVT: { + ESP_LOGI(BT_AV_TAG, "A2DP audio state: %s", s_a2d_audio_state_str[a2d->audio_stat.state]); + if (ESP_A2D_AUDIO_STATE_STARTED == a2d->audio_stat.state) { + s_pkt_cnt = 0; + } + break; + } + /* when audio codec is configured, this event comes */ + case ESP_A2D_AUDIO_CFG_EVT: { + audio_sink_srv_codec_info_update(&a2d->audio_cfg.mcc); + break; + } + /* others */ + default: + ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event); + break; + } +} + +void bt_a2d_data_hdl(const uint8_t *data, uint32_t len) +{ + audio_sink_srv_data_output(data, len); + + /* log the number every 100 packets */ + if (++s_pkt_cnt % 100 == 0) { + ESP_LOGI(BT_AV_TAG, "Audio packet count: %"PRIu32, s_pkt_cnt); + } +} diff --git a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/a2dp_sink_int_codec_utils.h b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/a2dp_sink_int_codec_utils.h new file mode 100644 index 0000000000..7a3f44e28f --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/a2dp_sink_int_codec_utils.h @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#ifndef __A2DP_SINK_INT_CODEC_UTILS_H__ +#define __A2DP_SINK_INT_CODEC_UTILS_H__ + +#include + +/** + * @brief handle function for A2DP sink internal codec + * + * @param [in] event event id + * @param [in] param callback parameter + */ +void bt_a2d_evt_int_codec_hdl(uint16_t event, void *param); + +/** + * @brief handle function for A2DP sink audio data stream + * + * @param [out] data data stream written by application task + * @param [in] len length of data stream in byte + */ +void bt_a2d_data_hdl(const uint8_t *data, uint32_t len); + +#endif /* __A2DP_SINK_INT_CODEC_UTILS_H__*/ diff --git a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service.h b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service.h new file mode 100644 index 0000000000..6fbe7382e2 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service.h @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#ifndef __AUDIO_SINK_SERVICE_H__ +#define __AUDIO_SINK_SERVICE_H__ + +#include +#include +#include "esp_a2dp_api.h" + +/** + * @brief open audio sink service + */ +void audio_sink_srv_open(void); + +/** + * @brief close audio sink service + */ +void audio_sink_srv_close(void); + +/** + * @brief start audio sink service + */ +void audio_sink_srv_start(void); + +/** + * @brief stop audio sink service + */ +void audio_sink_srv_stop(void); + +/** + * @brief update codec information + * + * @param [in] mcc codec information + */ +void audio_sink_srv_codec_info_update(esp_a2d_mcc_t *mcc); + +/** + * @brief write data to ringbuffer + * + * @param [in] data pointer to data stream + * @param [in] size data length in byte + * + * @return size if written ringbuffer successfully, 0 others + */ +size_t audio_sink_srv_data_output(const uint8_t *data, size_t size); + +#endif /* __AUDIO_SINK_SERVICE_H__ */ diff --git a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_dac.c b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_dac.c new file mode 100644 index 0000000000..a87873fe98 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_dac.c @@ -0,0 +1,222 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include "esp_log.h" +#include "esp_a2dp_api.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" +#include "freertos/ringbuf.h" +#include "driver/dac_continuous.h" +#include "audio_sink_service.h" + +#if defined(CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC) + +/* log tag */ +#define AUDIO_SNK_SRV_DAC_TAG "SNK_SRV_DAC" + +#define RINGBUF_HIGHEST_WATER_LEVEL (32 * 1024) +#define RINGBUF_PREFETCH_WATER_LEVEL (20 * 1024) + +enum { + RINGBUFFER_MODE_PROCESSING, /* ringbuffer is buffering incoming audio data, I2S is working */ + RINGBUFFER_MODE_PREFETCHING, /* ringbuffer is buffering incoming audio data, I2S is waiting */ + RINGBUFFER_MODE_DROPPING /* ringbuffer is not buffering (dropping) incoming audio data, I2S is working */ +}; + +typedef struct { + dac_continuous_handle_t tx_chan; /* handle of dac continuous channel */ + TaskHandle_t write_task_handle; /* handle of writing task */ + RingbufHandle_t ringbuf; /* handle of ringbuffer */ + SemaphoreHandle_t write_semaphore; /* handle of write semaphore */ + uint16_t ringbuffer_mode; /* ringbuffer mode */ +} audio_sink_srv_dac_cb_t; + +/******************************* + * STATIC FUNCTION DECLARATIONS + ******************************/ + +/* task handler for writing data to dac */ +static void audio_sink_srv_dac_task_handler(void *arg); + +/******************************* + * STATIC VARIABLE DEFINITIONS + ******************************/ + +/* audio sink service for dac control block */ +static audio_sink_srv_dac_cb_t s_dac_cb; + +/******************************* + * STATIC FUNCTION DEFINITIONS + ******************************/ + +static void audio_sink_srv_dac_task_handler(void *arg) +{ + uint8_t *data = NULL; + size_t item_size = 0; + const size_t item_size_upto = 240 * 6; + size_t bytes_written = 0; + + for (;;) { + if (pdTRUE == xSemaphoreTake(s_dac_cb.write_semaphore, portMAX_DELAY)) { + for (;;) { + item_size = 0; + /* receive data from ringbuffer and write it to DAC DMA transmit buffer */ + data = (uint8_t *)xRingbufferReceiveUpTo(s_dac_cb.ringbuf, &item_size, (TickType_t)pdMS_TO_TICKS(20), item_size_upto); + if (item_size == 0) { + ESP_LOGI(AUDIO_SNK_SRV_DAC_TAG, "ringbuffer underflowed! mode changed: RINGBUFFER_MODE_PREFETCHING"); + s_dac_cb.ringbuffer_mode = RINGBUFFER_MODE_PREFETCHING; + break; + } + + dac_continuous_write(s_dac_cb.tx_chan, data, item_size, &bytes_written, -1); + vRingbufferReturnItem(s_dac_cb.ringbuf, (void *)data); + } + } + } +} + +/******************************* + * EXTERNAL FUNCTION DEFINITIONS + ******************************/ + +void audio_sink_srv_open(void) +{ + memset(&s_dac_cb, 0, sizeof(audio_sink_srv_dac_cb_t)); + dac_continuous_config_t cont_cfg = { + .chan_mask = DAC_CHANNEL_MASK_ALL, + .desc_num = 8, + .buf_size = 2048, + .freq_hz = 44100, + .offset = 127, + .clk_src = DAC_DIGI_CLK_SRC_DEFAULT, // Using APLL as clock source to get a wider frequency range + .chan_mode = DAC_CHANNEL_MODE_ALTER, + }; + /* Allocate continuous channels */ + ESP_ERROR_CHECK(dac_continuous_new_channels(&cont_cfg, &s_dac_cb.tx_chan)); +} + +void audio_sink_srv_close(void) +{ + ESP_ERROR_CHECK(dac_continuous_del_channels(s_dac_cb.tx_chan)); + if (s_dac_cb.write_task_handle) { + vTaskDelete(s_dac_cb.write_task_handle); + s_dac_cb.write_task_handle = NULL; + } + if (s_dac_cb.ringbuf) { + vRingbufferDelete(s_dac_cb.ringbuf); + s_dac_cb.ringbuf = NULL; + } + if (s_dac_cb.write_semaphore) { + vSemaphoreDelete(s_dac_cb.write_semaphore); + s_dac_cb.write_semaphore = NULL; + } + memset(&s_dac_cb, 0, sizeof(audio_sink_srv_dac_cb_t)); +} + +void audio_sink_srv_start(void) +{ + dac_continuous_enable(s_dac_cb.tx_chan); + ESP_LOGI(AUDIO_SNK_SRV_DAC_TAG, "ringbuffer data empty! mode changed: RINGBUFFER_MODE_PREFETCHING"); + s_dac_cb.ringbuffer_mode = RINGBUFFER_MODE_PREFETCHING; + if ((s_dac_cb.write_semaphore = xSemaphoreCreateBinary()) == NULL) { + ESP_LOGE(AUDIO_SNK_SRV_DAC_TAG, "%s, Semaphore create failed", __func__); + return; + } + if ((s_dac_cb.ringbuf = xRingbufferCreate(RINGBUF_HIGHEST_WATER_LEVEL, RINGBUF_TYPE_BYTEBUF)) == NULL) { + ESP_LOGE(AUDIO_SNK_SRV_DAC_TAG, "%s, ringbuffer create failed", __func__); + return; + } + xTaskCreate(audio_sink_srv_dac_task_handler, "BtDACTask", 4 * 1024, NULL, configMAX_PRIORITIES - 3, &s_dac_cb.write_task_handle); +} + +void audio_sink_srv_stop(void) +{ + ESP_ERROR_CHECK(dac_continuous_disable(s_dac_cb.tx_chan)); +} + +void audio_sink_srv_codec_info_update(esp_a2d_mcc_t *mcc) +{ + ESP_LOGI(AUDIO_SNK_SRV_DAC_TAG, "A2DP audio stream configuration, codec type: %d", mcc->type); + /* for now only SBC stream is supported */ + if (mcc->type == ESP_A2D_MCT_SBC) { + int sample_rate = 16000; + int ch_count = 2; + if (mcc->cie.sbc_info.samp_freq & ESP_A2D_SBC_CIE_SF_32K) { + sample_rate = 32000; + } else if (mcc->cie.sbc_info.samp_freq & ESP_A2D_SBC_CIE_SF_44K) { + sample_rate = 44100; + } else if (mcc->cie.sbc_info.samp_freq & ESP_A2D_SBC_CIE_SF_48K) { + sample_rate = 48000; + } + + if (mcc->cie.sbc_info.ch_mode & ESP_A2D_SBC_CIE_CH_MODE_MONO) { + ch_count = 1; + } + dac_continuous_del_channels(s_dac_cb.tx_chan); + dac_continuous_config_t cont_cfg = { + .chan_mask = DAC_CHANNEL_MASK_ALL, + .desc_num = 8, + .buf_size = 2048, + .freq_hz = sample_rate, + .offset = 127, + .clk_src = DAC_DIGI_CLK_SRC_DEFAULT, // Using APLL as clock source to get a wider frequency range + .chan_mode = (ch_count == 1) ? DAC_CHANNEL_MODE_SIMUL : DAC_CHANNEL_MODE_ALTER, + }; + /* Allocate continuous channels */ + dac_continuous_new_channels(&cont_cfg, &s_dac_cb.tx_chan); + ESP_LOGI(AUDIO_SNK_SRV_DAC_TAG, "Configure audio player: 0x%x-0x%x-0x%x-0x%x-0x%x-%d-%d", + mcc->cie.sbc_info.samp_freq, + mcc->cie.sbc_info.ch_mode, + mcc->cie.sbc_info.block_len, + mcc->cie.sbc_info.num_subbands, + mcc->cie.sbc_info.alloc_mthd, + mcc->cie.sbc_info.min_bitpool, + mcc->cie.sbc_info.max_bitpool); + ESP_LOGI(AUDIO_SNK_SRV_DAC_TAG, "Audio player configured, sample rate: %d", sample_rate); + } +} + +size_t audio_sink_srv_data_output(const uint8_t *data, size_t size) +{ + size_t item_size = 0; + BaseType_t done = pdFALSE; + + if (s_dac_cb.ringbuffer_mode == RINGBUFFER_MODE_DROPPING) { + ESP_LOGW(AUDIO_SNK_SRV_DAC_TAG, "ringbuffer is full, drop this packet!"); + vRingbufferGetInfo(s_dac_cb.ringbuf, NULL, NULL, NULL, NULL, &item_size); + if (item_size <= RINGBUF_PREFETCH_WATER_LEVEL) { + ESP_LOGI(AUDIO_SNK_SRV_DAC_TAG, "ringbuffer data decreased! mode changed: RINGBUFFER_MODE_PROCESSING"); + s_dac_cb.ringbuffer_mode = RINGBUFFER_MODE_PROCESSING; + } + return 0; + } + + done = xRingbufferSend(s_dac_cb.ringbuf, (void *)data, size, (TickType_t)0); + + if (!done) { + ESP_LOGW(AUDIO_SNK_SRV_DAC_TAG, "ringbuffer overflowed, ready to decrease data! mode changed: RINGBUFFER_MODE_DROPPING"); + s_dac_cb.ringbuffer_mode = RINGBUFFER_MODE_DROPPING; + } + + if (s_dac_cb.ringbuffer_mode == RINGBUFFER_MODE_PREFETCHING) { + vRingbufferGetInfo(s_dac_cb.ringbuf, NULL, NULL, NULL, NULL, &item_size); + if (item_size >= RINGBUF_PREFETCH_WATER_LEVEL) { + ESP_LOGI(AUDIO_SNK_SRV_DAC_TAG, "ringbuffer data increased! mode changed: RINGBUFFER_MODE_PROCESSING"); + s_dac_cb.ringbuffer_mode = RINGBUFFER_MODE_PROCESSING; + if (pdFALSE == xSemaphoreGive(s_dac_cb.write_semaphore)) { + ESP_LOGE(AUDIO_SNK_SRV_DAC_TAG, "semaphore give failed"); + } + } + } + + return done ? size : 0; +} + +#endif /* defined(CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC) */ diff --git a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_i2s.c b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_i2s.c new file mode 100644 index 0000000000..bed32c1546 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_i2s.c @@ -0,0 +1,229 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include "esp_log.h" +#include "esp_a2dp_api.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" +#include "freertos/ringbuf.h" +#include "driver/i2s_std.h" +#include "audio_sink_service.h" + +#if defined(CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_EXTERNAL_I2S) + +/* log tag */ +#define AUDIO_SNK_SRV_I2S_TAG "SNK_SRV_I2S" + +#define RINGBUF_HIGHEST_WATER_LEVEL (32 * 1024) +#define RINGBUF_PREFETCH_WATER_LEVEL (20 * 1024) + +enum { + RINGBUFFER_MODE_PROCESSING, /* ringbuffer is buffering incoming audio data, I2S is working */ + RINGBUFFER_MODE_PREFETCHING, /* ringbuffer is buffering incoming audio data, I2S is waiting */ + RINGBUFFER_MODE_DROPPING /* ringbuffer is not buffering (dropping) incoming audio data, I2S is working */ +}; + +typedef struct { + i2s_chan_handle_t tx_chan; /* handle of i2s channel */ + TaskHandle_t write_task_handle; /* handle of writing task */ + RingbufHandle_t ringbuf; /* handle of ringbuffer */ + SemaphoreHandle_t write_semaphore;/* handle of write semaphore */ + uint16_t ringbuffer_mode; /* ringbuffer mode */ +} audio_sink_srv_i2s_cb_t; + +/******************************* + * STATIC FUNCTION DECLARATIONS + ******************************/ + +/* task handler for writing data to i2s */ +static void audio_sink_srv_i2s_task_handler(void *arg); + +/******************************* + * STATIC VARIABLE DEFINITIONS + ******************************/ + +/* audio sink service for i2s control block */ +static audio_sink_srv_i2s_cb_t s_i2s_cb; + +/******************************* + * STATIC FUNCTION DEFINITIONS + ******************************/ + +static void audio_sink_srv_i2s_task_handler(void *arg) +{ + uint8_t *data = NULL; + size_t item_size = 0; + /** + * The total length of DMA buffer of I2S is: + * `dma_frame_num * dma_desc_num * i2s_channel_num * i2s_data_bit_width / 8`. + * Transmit `dma_frame_num * dma_desc_num` bytes to DMA is trade-off. + */ + const size_t item_size_upto = 240 * 6; + size_t bytes_written = 0; + + for (;;) { + if (pdTRUE == xSemaphoreTake(s_i2s_cb.write_semaphore, portMAX_DELAY)) { + for (;;) { + item_size = 0; + /* receive data from ringbuffer and write it to I2S DMA transmit buffer */ + data = (uint8_t *)xRingbufferReceiveUpTo(s_i2s_cb.ringbuf, &item_size, (TickType_t)pdMS_TO_TICKS(20), item_size_upto); + if (item_size == 0) { + ESP_LOGI(AUDIO_SNK_SRV_I2S_TAG, "ringbuffer underflowed! mode changed: RINGBUFFER_MODE_PREFETCHING"); + s_i2s_cb.ringbuffer_mode = RINGBUFFER_MODE_PREFETCHING; + break; + } + + i2s_channel_write(s_i2s_cb.tx_chan, data, item_size, &bytes_written, portMAX_DELAY); + vRingbufferReturnItem(s_i2s_cb.ringbuf, (void *)data); + } + } + } +} + +/******************************* + * EXTERNAL FUNCTION DEFINITIONS + ******************************/ + +void audio_sink_srv_open(void) +{ + memset(&s_i2s_cb, 0, sizeof(audio_sink_srv_i2s_cb_t)); + i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); + chan_cfg.auto_clear = true; + i2s_std_config_t std_cfg = { + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(44100), + .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, + .bclk = CONFIG_EXAMPLE_I2S_BCK_PIN, + .ws = CONFIG_EXAMPLE_I2S_LRCK_PIN, + .dout = CONFIG_EXAMPLE_I2S_DATA_PIN, + .din = I2S_GPIO_UNUSED, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + }, + }, + }; + /* initialize I2S channel */ + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &s_i2s_cb.tx_chan, NULL)); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(s_i2s_cb.tx_chan, &std_cfg)); +} + +void audio_sink_srv_close(void) +{ + ESP_ERROR_CHECK(i2s_del_channel(s_i2s_cb.tx_chan)); + if (s_i2s_cb.write_task_handle) { + vTaskDelete(s_i2s_cb.write_task_handle); + s_i2s_cb.write_task_handle = NULL; + } + if (s_i2s_cb.ringbuf) { + vRingbufferDelete(s_i2s_cb.ringbuf); + s_i2s_cb.ringbuf = NULL; + } + if (s_i2s_cb.write_semaphore) { + vSemaphoreDelete(s_i2s_cb.write_semaphore); + s_i2s_cb.write_semaphore = NULL; + } + memset(&s_i2s_cb, 0, sizeof(audio_sink_srv_i2s_cb_t)); +} + +void audio_sink_srv_start(void) +{ + i2s_channel_enable(s_i2s_cb.tx_chan); + ESP_LOGI(AUDIO_SNK_SRV_I2S_TAG, "ringbuffer data empty! mode changed: RINGBUFFER_MODE_PREFETCHING"); + s_i2s_cb.ringbuffer_mode = RINGBUFFER_MODE_PREFETCHING; + if ((s_i2s_cb.write_semaphore = xSemaphoreCreateBinary()) == NULL) { + ESP_LOGE(AUDIO_SNK_SRV_I2S_TAG, "%s, Semaphore create failed", __func__); + return; + } + if ((s_i2s_cb.ringbuf = xRingbufferCreate(RINGBUF_HIGHEST_WATER_LEVEL, RINGBUF_TYPE_BYTEBUF)) == NULL) { + ESP_LOGE(AUDIO_SNK_SRV_I2S_TAG, "%s, ringbuffer create failed", __func__); + return; + } + xTaskCreate(audio_sink_srv_i2s_task_handler, "BtI2STask", 4 * 1024, NULL, configMAX_PRIORITIES - 3, &s_i2s_cb.write_task_handle); +} + +void audio_sink_srv_stop(void) +{ + ESP_ERROR_CHECK(i2s_channel_disable(s_i2s_cb.tx_chan)); +} + +void audio_sink_srv_codec_info_update(esp_a2d_mcc_t *mcc) +{ + ESP_LOGI(AUDIO_SNK_SRV_I2S_TAG, "A2DP audio stream configuration, codec type: %d", mcc->type); + /* for now only SBC stream is supported */ + if (mcc->type == ESP_A2D_MCT_SBC) { + int sample_rate = 16000; + int ch_count = 2; + if (mcc->cie.sbc_info.samp_freq & ESP_A2D_SBC_CIE_SF_32K) { + sample_rate = 32000; + } else if (mcc->cie.sbc_info.samp_freq & ESP_A2D_SBC_CIE_SF_44K) { + sample_rate = 44100; + } else if (mcc->cie.sbc_info.samp_freq & ESP_A2D_SBC_CIE_SF_48K) { + sample_rate = 48000; + } + + if (mcc->cie.sbc_info.ch_mode & ESP_A2D_SBC_CIE_CH_MODE_MONO) { + ch_count = 1; + } + i2s_std_clk_config_t clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(sample_rate); + i2s_std_slot_config_t slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, ch_count); + i2s_channel_reconfig_std_clock(s_i2s_cb.tx_chan, &clk_cfg); + i2s_channel_reconfig_std_slot(s_i2s_cb.tx_chan, &slot_cfg); + ESP_LOGI(AUDIO_SNK_SRV_I2S_TAG, "Configure audio player: 0x%x-0x%x-0x%x-0x%x-0x%x-%d-%d", + mcc->cie.sbc_info.samp_freq, + mcc->cie.sbc_info.ch_mode, + mcc->cie.sbc_info.block_len, + mcc->cie.sbc_info.num_subbands, + mcc->cie.sbc_info.alloc_mthd, + mcc->cie.sbc_info.min_bitpool, + mcc->cie.sbc_info.max_bitpool); + ESP_LOGI(AUDIO_SNK_SRV_I2S_TAG, "Audio player configured, sample rate: %d", sample_rate); + } +} + +size_t audio_sink_srv_data_output(const uint8_t *data, size_t size) +{ + size_t item_size = 0; + BaseType_t done = pdFALSE; + + if (s_i2s_cb.ringbuffer_mode == RINGBUFFER_MODE_DROPPING) { + ESP_LOGW(AUDIO_SNK_SRV_I2S_TAG, "ringbuffer is full, drop this packet!"); + vRingbufferGetInfo(s_i2s_cb.ringbuf, NULL, NULL, NULL, NULL, &item_size); + if (item_size <= RINGBUF_PREFETCH_WATER_LEVEL) { + ESP_LOGI(AUDIO_SNK_SRV_I2S_TAG, "ringbuffer data decreased! mode changed: RINGBUFFER_MODE_PROCESSING"); + s_i2s_cb.ringbuffer_mode = RINGBUFFER_MODE_PROCESSING; + } + return 0; + } + + done = xRingbufferSend(s_i2s_cb.ringbuf, (void *)data, size, (TickType_t)0); + + if (!done) { + ESP_LOGW(AUDIO_SNK_SRV_I2S_TAG, "ringbuffer overflowed, ready to decrease data! mode changed: RINGBUFFER_MODE_DROPPING"); + s_i2s_cb.ringbuffer_mode = RINGBUFFER_MODE_DROPPING; + } + + if (s_i2s_cb.ringbuffer_mode == RINGBUFFER_MODE_PREFETCHING) { + vRingbufferGetInfo(s_i2s_cb.ringbuf, NULL, NULL, NULL, NULL, &item_size); + if (item_size >= RINGBUF_PREFETCH_WATER_LEVEL) { + ESP_LOGI(AUDIO_SNK_SRV_I2S_TAG, "ringbuffer data increased! mode changed: RINGBUFFER_MODE_PROCESSING"); + s_i2s_cb.ringbuffer_mode = RINGBUFFER_MODE_PROCESSING; + if (pdFALSE == xSemaphoreGive(s_i2s_cb.write_semaphore)) { + ESP_LOGE(AUDIO_SNK_SRV_I2S_TAG, "semaphore give failed"); + } + } + } + + return done ? size : 0; +} + +#endif /* defined(CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_EXTERNAL_I2S) */ diff --git a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_idle.c b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_idle.c new file mode 100644 index 0000000000..85a1526c28 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_idle.c @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include "audio_sink_service.h" + +#if defined(CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_IDLE) + +/******************************* + * EXTERNAL FUNCTION DEFINITIONS + ******************************/ + +void audio_sink_srv_open(void) +{ + // do nothing +} + +void audio_sink_srv_close(void) +{ + // do nothing +} + +void audio_sink_srv_start(void) +{ + // do nothing +} + +void audio_sink_srv_stop(void) +{ + // do nothing +} + +void audio_sink_srv_codec_info_update(esp_a2d_mcc_t *mcc) +{ + // do nothing + (void)mcc; +} + +size_t audio_sink_srv_data_output(const uint8_t *data, size_t size) +{ + // do nothing + (void)data; + (void)size; + return 0; +} + +#endif /* defined(CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_IDLE) */ diff --git a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/include/a2dp_utils_tags.h b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/include/a2dp_utils_tags.h new file mode 100644 index 0000000000..6ab3d8e6c4 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/include/a2dp_utils_tags.h @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#ifndef __A2DP_UTILS_TAGS_H__ +#define __A2DP_UTILS_TAGS_H__ + +/* log tags */ +#define BT_AV_TAG "BT_AV" + +#endif /* __A2DP_UTILS_TAGS_H__ */ diff --git a/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_abs_vol_utils/CMakeLists.txt b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_abs_vol_utils/CMakeLists.txt new file mode 100644 index 0000000000..8c16db1f68 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_abs_vol_utils/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "avrcp_abs_vol_utils.c" + "avrcp_abs_vol_service.c" + PRIV_REQUIRES bt + INCLUDE_DIRS "." "../include") diff --git a/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_abs_vol_utils/avrcp_abs_vol_service.c b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_abs_vol_utils/avrcp_abs_vol_service.c new file mode 100644 index 0000000000..889b265111 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_abs_vol_utils/avrcp_abs_vol_service.c @@ -0,0 +1,88 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include "sys/lock.h" +#include "esp_log.h" +#include "esp_avrc_api.h" +#include "avrcp_abs_vol_service.h" + +/* tags */ +#define RC_VC_SRV_TAG "RC_VC_SRV" + +typedef struct { + _lock_t volume_lock; /* lock local volume value */ + uint8_t volume; /* local volume value */ + bool volume_notify; /* notify volume change or not */ +} avrc_abs_vol_srv_cb_t; + +/******************************* + * STATIC VARIABLE DEFINITIONS + ******************************/ + +/* AVRCP absolute volume service control block */ +static avrc_abs_vol_srv_cb_t s_avrc_abs_vol_srv_cb; + +/******************************** + * EXTERNAL FUNCTION DEFINITIONS + *******************************/ + +void avrc_abs_vol_srv_open(void) +{ + memset(&s_avrc_abs_vol_srv_cb, 0, sizeof(avrc_abs_vol_srv_cb_t)); +} + +void avrc_abs_vol_srv_close(void) +{ + memset(&s_avrc_abs_vol_srv_cb, 0, sizeof(avrc_abs_vol_srv_cb_t)); +} + +uint8_t avrc_abs_vol_srv_get_volume(void) +{ + return s_avrc_abs_vol_srv_cb.volume; +} + +void avrc_abs_vol_srv_set_volume(avrc_volume_set_t vol_set_t, uint8_t volume) +{ + switch (vol_set_t) { + case VOLUME_SET_BY_CONTROLLER: { + ESP_LOGI(RC_VC_SRV_TAG, "Volume is set by remote controller to: %"PRIu32"%%", (uint32_t)volume * 100 / 0x7f); + /* set the volume in protection of lock */ + _lock_acquire(&s_avrc_abs_vol_srv_cb.volume_lock); + s_avrc_abs_vol_srv_cb.volume = volume; + _lock_release(&s_avrc_abs_vol_srv_cb.volume_lock); + break; + } + case VOLUME_SET_BY_LOCAL_HOST: { + ESP_LOGI(RC_VC_SRV_TAG, "Volume is set locally to: %"PRIu32"%%", (uint32_t)volume * 100 / 0x7f); + /* set the volume in protection of lock */ + _lock_acquire(&s_avrc_abs_vol_srv_cb.volume_lock); + s_avrc_abs_vol_srv_cb.volume = volume; + _lock_release(&s_avrc_abs_vol_srv_cb.volume_lock); + + /* send notification response to remote AVRCP controller */ + if (s_avrc_abs_vol_srv_cb.volume_notify) { + esp_avrc_rn_param_t rn_param; + rn_param.volume = s_avrc_abs_vol_srv_cb.volume; + esp_avrc_tg_send_rn_rsp(ESP_AVRC_RN_VOLUME_CHANGE, ESP_AVRC_RN_RSP_CHANGED, &rn_param); + s_avrc_abs_vol_srv_cb.volume_notify = false; + } + break; + } + default: + ESP_LOGE(RC_VC_SRV_TAG, "Invalid volume setting type"); + break; + } +} + +void avrc_abs_vol_srv_rn_volume_change(void) +{ + s_avrc_abs_vol_srv_cb.volume_notify = true; + esp_avrc_rn_param_t rn_param; + rn_param.volume = s_avrc_abs_vol_srv_cb.volume; + esp_avrc_tg_send_rn_rsp(ESP_AVRC_RN_VOLUME_CHANGE, ESP_AVRC_RN_RSP_INTERIM, &rn_param); +} diff --git a/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_abs_vol_utils/avrcp_abs_vol_service.h b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_abs_vol_utils/avrcp_abs_vol_service.h new file mode 100644 index 0000000000..d21c21ec58 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_abs_vol_utils/avrcp_abs_vol_service.h @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#ifndef __AVRCP_ABS_VOL_SERVICE_H__ +#define __AVRCP_ABS_VOL_SERVICE_H__ + +#include +#include "esp_avrc_api.h" + +typedef enum { + VOLUME_SET_BY_CONTROLLER = 0, /*!< volume is set by controller */ + VOLUME_SET_BY_LOCAL_HOST, /*!< volume is set by local host */ +} avrc_volume_set_t; + +/** + * @brief open AVRCP absolute volume service + */ +void avrc_abs_vol_srv_open(void); + +/** + * @brief close AVRCP absolute volume service + */ +void avrc_abs_vol_srv_close(void); + +/** + * @brief get local volume value + * + * @return local volume value + */ +uint8_t avrc_abs_vol_srv_get_volume(void); + +/** + * @brief set volume value + * + * @param [in] vol_set_t volume setting type + * @param [in] volume volume value to be set + */ +void avrc_abs_vol_srv_set_volume(avrc_volume_set_t vol_set_t, uint8_t volume); + +/** + * @brief register notification of volume change + */ +void avrc_abs_vol_srv_rn_volume_change(void); + +#endif /* __AVRCP_ABS_VOL_SERVICE_H__ */ diff --git a/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_abs_vol_utils/avrcp_abs_vol_utils.c b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_abs_vol_utils/avrcp_abs_vol_utils.c new file mode 100644 index 0000000000..0a41ae063a --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_abs_vol_utils/avrcp_abs_vol_utils.c @@ -0,0 +1,103 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include +#include + +#include "sys/lock.h" + +#include "esp_log.h" +#include "esp_avrc_api.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "avrcp_abs_vol_utils.h" +#include "avrcp_utils_tags.h" +#include "avrcp_abs_vol_service.h" + +/******************************* + * STATIC FUNCTION DECLARATIONS + ******************************/ + +/* simulation volume change */ +static void volume_change_simulation(void *arg); + +/******************************* + * STATIC VARIABLE DEFINITIONS + ******************************/ + +static TaskHandle_t s_vcs_task_hdl = NULL; /* handle for volume change simulation task */ + +/******************************** + * STATIC FUNCTION DEFINITIONS + *******************************/ + +static void volume_change_simulation(void *arg) +{ + ESP_LOGI(BT_RC_TG_TAG, "start volume change simulation"); + + for (;;) { + /* volume up locally every 10 seconds */ + vTaskDelay(10000 / portTICK_PERIOD_MS); + uint8_t volume = (avrc_abs_vol_srv_get_volume() + 5) & 0x7f; + avrc_abs_vol_srv_set_volume(VOLUME_SET_BY_LOCAL_HOST, volume); + } +} + +/******************************** + * EXTERNAL FUNCTION DEFINITIONS + *******************************/ + +void bt_avrc_avc_tg_evt_hdl(uint16_t event, void *param) +{ + ESP_LOGD(BT_RC_TG_TAG, "%s event: %d", __func__, event); + + esp_avrc_tg_cb_param_t *rc = (esp_avrc_tg_cb_param_t *)(param); + + switch (event) { + /* when connection state changed, this event comes */ + case ESP_AVRC_TG_CONNECTION_STATE_EVT: { + uint8_t *bda = rc->conn_stat.remote_bda; + ESP_LOGI(BT_RC_TG_TAG, "AVRC conn_state evt: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]", + rc->conn_stat.connected, bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); + if (rc->conn_stat.connected) { + /* create task to simulate volume change */ + avrc_abs_vol_srv_open(); + xTaskCreate(volume_change_simulation, "vcsTask", 4 * 1024, NULL, 5, &s_vcs_task_hdl); + } else { + avrc_abs_vol_srv_close(); + if (s_vcs_task_hdl) { + vTaskDelete(s_vcs_task_hdl); + s_vcs_task_hdl = NULL; + } + ESP_LOGI(BT_RC_TG_TAG, "Stop volume change simulation"); + } + break; + } + /* when absolute volume command from remote device set, this event comes */ + case ESP_AVRC_TG_SET_ABSOLUTE_VOLUME_CMD_EVT: { + ESP_LOGI(BT_RC_TG_TAG, "AVRC set absolute volume: %d%%", (int)rc->set_abs_vol.volume * 100 / 0x7f); + avrc_abs_vol_srv_set_volume(VOLUME_SET_BY_CONTROLLER, rc->set_abs_vol.volume); + break; + } + /* when notification registered, this event comes */ + case ESP_AVRC_TG_REGISTER_NOTIFICATION_EVT: { + ESP_LOGI(BT_RC_TG_TAG, "AVRC register event notification: %d, param: 0x%"PRIx32, rc->reg_ntf.event_id, rc->reg_ntf.event_parameter); + if (rc->reg_ntf.event_id == ESP_AVRC_RN_VOLUME_CHANGE) { + avrc_abs_vol_srv_rn_volume_change(); + } + break; + } + /* others */ + default: + ESP_LOGE(BT_RC_TG_TAG, "%s unhandled event: %d", __func__, event); + break; + } +} diff --git a/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_abs_vol_utils/avrcp_abs_vol_utils.h b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_abs_vol_utils/avrcp_abs_vol_utils.h new file mode 100644 index 0000000000..c10594049c --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_abs_vol_utils/avrcp_abs_vol_utils.h @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#ifndef __AVRCP_ABS_VOL_UTILS_H__ +#define __AVRCP_ABS_VOL_UTILS_H__ + +#include + +/** + * @brief handle function for AVRCP target absolute volume control event + * + * @param [in] event event id + * @param [in] param callback parameter + */ +void bt_avrc_avc_tg_evt_hdl(uint16_t event, void *param); + +#endif /* __AVRCP_ABS_VOL_UTILS_H__ */ diff --git a/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_common_utils/CMakeLists.txt b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_common_utils/CMakeLists.txt new file mode 100644 index 0000000000..6d95f6fac3 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_common_utils/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "avrcp_common_utils.c" + PRIV_REQUIRES bt + INCLUDE_DIRS "." "../include") diff --git a/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_common_utils/avrcp_common_utils.c b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_common_utils/avrcp_common_utils.c new file mode 100644 index 0000000000..6f3ae6801e --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_common_utils/avrcp_common_utils.c @@ -0,0 +1,228 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include +#include + +#include "esp_log.h" +#include "esp_bt_main.h" +#include "esp_avrc_api.h" + +#include "avrcp_common_utils.h" +#include "avrcp_utils_tags.h" + +typedef struct { + uint8_t tl; /* AVRCP transaction labels */ + esp_avrc_rn_evt_cap_mask_t peer_rn_cap; /* AVRCP target notification capability bit mask */ +} avrc_common_cb_t; + +/******************************* + * STATIC VARIABLE DEFINITIONS + ******************************/ + +static avrc_common_cb_t s_avrc_common_cb; + +/******************************** + * EXTERNAL FUNCTION DEFINITIONS + *******************************/ + +void bt_avrc_common_ct_evt_def_hdl(uint16_t event, void *param) +{ + ESP_LOGD(BT_RC_CT_TAG, "%s event: %d", __func__, event); + + esp_avrc_ct_cb_param_t *rc = (esp_avrc_ct_cb_param_t *)(param); + + switch (event) { + /* when connection state changed, this event comes */ + case ESP_AVRC_CT_CONNECTION_STATE_EVT: { + uint8_t *bda = rc->conn_stat.remote_bda; + ESP_LOGI(BT_RC_CT_TAG, "AVRC conn_state event: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]", + rc->conn_stat.connected, bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); + break; + } + /* when passthrough response, this event comes */ + case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC passthrough rsp: key_code 0x%x, key_state %d, rsp_code %d", rc->psth_rsp.key_code, + rc->psth_rsp.key_state, rc->psth_rsp.rsp_code); + break; + } + /* when metadata response, this event comes */ + case ESP_AVRC_CT_METADATA_RSP_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC metadata rsp: attribute id 0x%x, %s", rc->meta_rsp.attr_id, rc->meta_rsp.attr_text); + break; + } + /* when notified, this event comes */ + case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC event notification: %d", rc->change_ntf.event_id); + break; + } + /* when feature of remote device indicated, this event comes */ + case ESP_AVRC_CT_REMOTE_FEATURES_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC remote features %"PRIx32", TG features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.tg_feat_flag); + break; + } + /* when notification capability of peer device got, this event comes */ + case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "remote rn_cap: count %d, bitmask 0x%x", rc->get_rn_caps_rsp.cap_count, + rc->get_rn_caps_rsp.evt_set.bits); + break; + } + /* when avrcp controller init or deinit completed, this event comes */ + case ESP_AVRC_CT_PROF_STATE_EVT: { + if (ESP_AVRC_INIT_SUCCESS == rc->avrc_ct_init_stat.state) { + ESP_LOGI(BT_RC_CT_TAG, "AVRCP CT STATE: Init Complete"); + } else if (ESP_AVRC_DEINIT_SUCCESS == rc->avrc_ct_init_stat.state) { + ESP_LOGI(BT_RC_CT_TAG, "AVRCP CT STATE: Deinit Complete"); + } else { + ESP_LOGE(BT_RC_CT_TAG, "AVRCP CT STATE error: %d", rc->avrc_ct_init_stat.state); + } + break; + } + /* others */ + default: + ESP_LOGE(BT_RC_CT_TAG, "%s unhandled event: %d", __func__, event); + break; + } +} + +void bt_avrc_common_tg_evt_def_hdl(uint16_t event, void *param) +{ + ESP_LOGD(BT_RC_TG_TAG, "%s event: %d", __func__, event); + + esp_avrc_tg_cb_param_t *rc = (esp_avrc_tg_cb_param_t *)(param); + + switch (event) { + /* when connection state changed, this event comes */ + case ESP_AVRC_TG_CONNECTION_STATE_EVT: { + uint8_t *bda = rc->conn_stat.remote_bda; + ESP_LOGI(BT_RC_TG_TAG, "AVRC conn_state evt: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]", + rc->conn_stat.connected, bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); + break; + } + /* when passthrough commanded, this event comes */ + case ESP_AVRC_TG_PASSTHROUGH_CMD_EVT: { + ESP_LOGI(BT_RC_TG_TAG, "AVRC passthrough cmd: key_code 0x%x, key_state %d", rc->psth_cmd.key_code, rc->psth_cmd.key_state); + break; + } + /* when absolute volume command from remote device set, this event comes */ + case ESP_AVRC_TG_SET_ABSOLUTE_VOLUME_CMD_EVT: { + ESP_LOGI(BT_RC_TG_TAG, "AVRC set absolute volume: %d%%", (int)rc->set_abs_vol.volume * 100 / 0x7f); + break; + } + /* when notification registered, this event comes */ + case ESP_AVRC_TG_REGISTER_NOTIFICATION_EVT: { + ESP_LOGI(BT_RC_TG_TAG, "AVRC register event notification: %d, param: 0x%"PRIx32, rc->reg_ntf.event_id, rc->reg_ntf.event_parameter); + break; + } + /* when feature of remote device indicated, this event comes */ + case ESP_AVRC_TG_REMOTE_FEATURES_EVT: { + ESP_LOGI(BT_RC_TG_TAG, "AVRC remote features: %"PRIx32", CT features: %x", rc->rmt_feats.feat_mask, rc->rmt_feats.ct_feat_flag); + break; + } + /* when avrcp target init or deinit completed, this event comes */ + case ESP_AVRC_TG_PROF_STATE_EVT: { + if (ESP_AVRC_INIT_SUCCESS == rc->avrc_tg_init_stat.state) { + ESP_LOGI(BT_RC_TG_TAG, "AVRCP TG STATE: Init Complete"); + } else if (ESP_AVRC_DEINIT_SUCCESS == rc->avrc_tg_init_stat.state) { + ESP_LOGI(BT_RC_TG_TAG, "AVRCP TG STATE: Deinit Complete"); + } else { + ESP_LOGE(BT_RC_TG_TAG, "AVRCP TG STATE error: %d", rc->avrc_tg_init_stat.state); + } + break; + } + /* others */ + default: + ESP_LOGE(BT_RC_TG_TAG, "%s unhandled event: %d", __func__, event); + break; + } +} + +uint8_t bt_avrc_common_alloc_tl(void) +{ + if (s_avrc_common_cb.tl > ESP_AVRC_TRANS_LABEL_MAX) { + s_avrc_common_cb.tl = 0; + } + + return s_avrc_common_cb.tl++; +} + +void bt_avrc_common_copy_metadata(void *p_dest, void *p_src, int len) +{ + esp_avrc_ct_cb_param_t *p_dest_rc = (esp_avrc_ct_cb_param_t *)(p_dest); + esp_avrc_ct_cb_param_t *p_src_rc = (esp_avrc_ct_cb_param_t *)(p_src); + + p_dest_rc->meta_rsp.attr_id = p_src_rc->meta_rsp.attr_id; + p_dest_rc->meta_rsp.attr_length = p_src_rc->meta_rsp.attr_length; + + p_dest_rc->meta_rsp.attr_text = (uint8_t *) malloc(p_dest_rc->meta_rsp.attr_length + 1); + memcpy(p_dest_rc->meta_rsp.attr_text, p_src_rc->meta_rsp.attr_text, p_dest_rc->meta_rsp.attr_length); + p_dest_rc->meta_rsp.attr_text[p_dest_rc->meta_rsp.attr_length] = 0; +} + +void bt_avrc_common_ct_get_peer_rn_cap(void) +{ + esp_avrc_ct_send_get_rn_capabilities_cmd(bt_avrc_common_alloc_tl()); +} + +void bt_avrc_common_ct_set_peer_rn_cap(uint16_t peer_rn_cap_t) +{ + s_avrc_common_cb.peer_rn_cap.bits = peer_rn_cap_t; +} + +void bt_avrc_common_ct_rn_track_changed(void) +{ + /* register notification if peer support the event_id */ + if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_common_cb.peer_rn_cap, + ESP_AVRC_RN_TRACK_CHANGE)) { + esp_avrc_ct_send_register_notification_cmd(bt_avrc_common_alloc_tl(), ESP_AVRC_RN_TRACK_CHANGE, 0); + } +} + +void bt_avrc_common_ct_rn_play_status_changed(void) +{ + /* register notification if peer support the event_id */ + if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_common_cb.peer_rn_cap, + ESP_AVRC_RN_PLAY_STATUS_CHANGE)) { + esp_avrc_ct_send_register_notification_cmd(bt_avrc_common_alloc_tl(), ESP_AVRC_RN_PLAY_STATUS_CHANGE, 0); + } +} + +void bt_avrc_common_ct_rn_play_pos_changed(void) +{ + /* register notification if peer support the event_id */ + if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_common_cb.peer_rn_cap, + ESP_AVRC_RN_PLAY_POS_CHANGED)) { + esp_avrc_ct_send_register_notification_cmd(bt_avrc_common_alloc_tl(), ESP_AVRC_RN_PLAY_POS_CHANGED, 10); + } +} + +void bt_avrc_common_ct_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_parameter) +{ + switch (event_id) { + /* when new track is loaded, this event comes */ + case ESP_AVRC_RN_TRACK_CHANGE: + ESP_LOGI(BT_RC_CT_TAG, "New track loaded"); + bt_avrc_common_ct_rn_track_changed(); + break; + /* when track status changed, this event comes */ + case ESP_AVRC_RN_PLAY_STATUS_CHANGE: + ESP_LOGI(BT_RC_CT_TAG, "Playback status changed: 0x%x", event_parameter->playback); + bt_avrc_common_ct_rn_play_status_changed(); + break; + /* when track playing position changed, this event comes */ + case ESP_AVRC_RN_PLAY_POS_CHANGED: + ESP_LOGI(BT_RC_CT_TAG, "Play position changed: %"PRIu32"-ms", event_parameter->play_pos); + bt_avrc_common_ct_rn_play_pos_changed(); + break; + /* others */ + default: + ESP_LOGI(BT_RC_CT_TAG, "unhandled event: %d", event_id); + break; + } +} diff --git a/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_common_utils/avrcp_common_utils.h b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_common_utils/avrcp_common_utils.h new file mode 100644 index 0000000000..e7d4c00fb8 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_common_utils/avrcp_common_utils.h @@ -0,0 +1,82 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#ifndef __AVRCP_COMMON_UTILS_H__ +#define __AVRCP_COMMON_UTILS_H__ + +#include +#include "esp_avrc_api.h" + +/** + * @brief default handle function for AVRCP controller event + * + * @param [in] event event id + * @param [in] param callback parameter + */ +void bt_avrc_common_ct_evt_def_hdl(uint16_t event, void *param); + +/** + * @brief default handle function for AVRCP target event + * + * @param [in] event event id + * @param [in] param callback parameter + */ +void bt_avrc_common_tg_evt_def_hdl(uint16_t event, void *param); + +/** + * @brief AVRCP transaction labels allocation + * + * @param [out] tl transaction label + * + * @return transaction label + */ +uint8_t bt_avrc_common_alloc_tl(void); + +/** + * @brief AVRCP deep copy function + * + * @param [in] p_dest pointer to destination data + * @param [in] p_src pointer to source data + * @param [in] len data length in byte + */ +void bt_avrc_common_copy_metadata(void *p_dest, void *p_src, int len); + +/** + * @brief AVRCP controller get notification capabilities command + */ +void bt_avrc_common_ct_get_peer_rn_cap(void); + +/** + * @brief AVRCP controller set notification capabilities command + * + * @param [in] peer_rn_cap_t register notification capabilities of peer in bits + */ +void bt_avrc_common_ct_set_peer_rn_cap(uint16_t peer_rn_cap_t); + +/** + * @brief AVRCP controller track changed notification command + */ +void bt_avrc_common_ct_rn_track_changed(void); + +/** + * @brief AVRCP controller play status changed notification command + */ +void bt_avrc_common_ct_rn_play_status_changed(void); + +/** + * @brief AVRCP controller play position changed notification command + */ +void bt_avrc_common_ct_rn_play_pos_changed(void); + +/** + * @brief AVRCP controller notification event handler + * + * @param [in] event_id event id + * @param [in] event_parameter event parameter + */ +void bt_avrc_common_ct_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_parameter); + +#endif /* __AVRCP_COMMON_UTILS_H__ */ diff --git a/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_metadata_utils/CMakeLists.txt b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_metadata_utils/CMakeLists.txt new file mode 100644 index 0000000000..896be16c5a --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_metadata_utils/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "avrcp_metadata_utils.c" + "avrcp_metadata_service.c" + PRIV_REQUIRES bt avrcp_common_utils + INCLUDE_DIRS "." "../include") diff --git a/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_metadata_utils/avrcp_metadata_service.c b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_metadata_utils/avrcp_metadata_service.c new file mode 100644 index 0000000000..271311afb1 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_metadata_utils/avrcp_metadata_service.c @@ -0,0 +1,155 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include "esp_log.h" +#include "esp_avrc_api.h" +#include "avrcp_metadata_service.h" +#include "avrcp_common_utils.h" + +/* tags*/ +#define RC_MD_SRV_TAG "RC_MD_SRV" + +typedef struct { + /* AVRCP metadata response data from ESP_AVRC_CT_METADATA_RSP_EVT */ + avrc_metadata_srv_param_t attr_title; + avrc_metadata_srv_param_t attr_artist; + avrc_metadata_srv_param_t attr_album; + avrc_metadata_srv_param_t attr_genre; +} avrc_metadata_srv_cb_t; + +/******************************* + * STATIC VARIABLE DEFINITIONS + ******************************/ + +/* AVRCP metadata service control block */ +static avrc_metadata_srv_cb_t s_avrc_metadata_srv_cb; + +/******************************** + * STATIC FUNCTION DEFINITIONS + *******************************/ + +static void avrc_metadata_srv_copy_metadata(avrc_metadata_srv_param_t *p_dest, avrc_metadata_srv_param_t *p_src) +{ + p_dest->attr_id = p_src->attr_id; + p_dest->attr_length = p_src->attr_length; + + if (p_dest->attr_text) { + free(p_dest->attr_text); + p_dest->attr_text = NULL; + } + + p_dest->attr_text = (uint8_t *)malloc(p_dest->attr_length + 1); + if (p_dest->attr_text == NULL) { + ESP_LOGE(RC_MD_SRV_TAG, "Memory allocation failed."); + return; + } + + memcpy(p_dest->attr_text, p_src->attr_text, p_dest->attr_length); + p_dest->attr_text[p_dest->attr_length] = 0; +} + +/******************************** + * EXTERNAL FUNCTION DEFINITIONS + *******************************/ + +void avrc_metadata_srv_open(void) +{ + avrc_metadata_srv_md_free(ESP_AVRC_MD_ATTR_TITLE); + avrc_metadata_srv_md_free(ESP_AVRC_MD_ATTR_ARTIST); + avrc_metadata_srv_md_free(ESP_AVRC_MD_ATTR_ALBUM); + avrc_metadata_srv_md_free(ESP_AVRC_MD_ATTR_GENRE); + + memset(&s_avrc_metadata_srv_cb, 0, sizeof(avrc_metadata_srv_cb_t)); +} + +void avrc_metadata_srv_close(void) +{ + avrc_metadata_srv_md_free(ESP_AVRC_MD_ATTR_TITLE); + avrc_metadata_srv_md_free(ESP_AVRC_MD_ATTR_ARTIST); + avrc_metadata_srv_md_free(ESP_AVRC_MD_ATTR_ALBUM); + avrc_metadata_srv_md_free(ESP_AVRC_MD_ATTR_GENRE); + + memset(&s_avrc_metadata_srv_cb, 0, sizeof(avrc_metadata_srv_cb_t)); +} + +void avrc_metadata_srv_md_req(void) +{ + /* request metadata */ + uint8_t attr_mask = ESP_AVRC_MD_ATTR_TITLE | + ESP_AVRC_MD_ATTR_ARTIST | + ESP_AVRC_MD_ATTR_ALBUM | + ESP_AVRC_MD_ATTR_GENRE; + + /* send metadata request */ + esp_avrc_ct_send_metadata_cmd(bt_avrc_common_alloc_tl(), attr_mask); +} + +void avrc_metadata_srv_md_save(avrc_metadata_srv_param_t *param) +{ + avrc_metadata_srv_param_t *rc = param; + + switch (rc->attr_id) { + case ESP_AVRC_MD_ATTR_TITLE: { + avrc_metadata_srv_copy_metadata(&s_avrc_metadata_srv_cb.attr_title, rc); + break; + } + case ESP_AVRC_MD_ATTR_ARTIST: { + avrc_metadata_srv_copy_metadata(&s_avrc_metadata_srv_cb.attr_artist, rc); + break; + } + case ESP_AVRC_MD_ATTR_ALBUM: { + avrc_metadata_srv_copy_metadata(&s_avrc_metadata_srv_cb.attr_album, rc); + break; + } + case ESP_AVRC_MD_ATTR_GENRE: { + avrc_metadata_srv_copy_metadata(&s_avrc_metadata_srv_cb.attr_genre, rc); + break; + } + + default: + ESP_LOGW(RC_MD_SRV_TAG, "%s unhandled attr_id: %d", __func__, rc->attr_id); + break; + } +} + +void avrc_metadata_srv_md_free(uint8_t attr_id) +{ + switch (attr_id) { + case ESP_AVRC_MD_ATTR_TITLE: { + if (s_avrc_metadata_srv_cb.attr_title.attr_text) { + free(s_avrc_metadata_srv_cb.attr_title.attr_text); + s_avrc_metadata_srv_cb.attr_title.attr_text = NULL; + } + break; + } + case ESP_AVRC_MD_ATTR_ARTIST: { + if (s_avrc_metadata_srv_cb.attr_artist.attr_text) { + free(s_avrc_metadata_srv_cb.attr_artist.attr_text); + s_avrc_metadata_srv_cb.attr_artist.attr_text = NULL; + } + break; + } + case ESP_AVRC_MD_ATTR_ALBUM: { + if (s_avrc_metadata_srv_cb.attr_album.attr_text) { + free(s_avrc_metadata_srv_cb.attr_album.attr_text); + s_avrc_metadata_srv_cb.attr_album.attr_text = NULL; + } + break; + } + case ESP_AVRC_MD_ATTR_GENRE: { + if (s_avrc_metadata_srv_cb.attr_genre.attr_text) { + free(s_avrc_metadata_srv_cb.attr_genre.attr_text); + s_avrc_metadata_srv_cb.attr_genre.attr_text = NULL; + } + break; + } + + default: + ESP_LOGW(RC_MD_SRV_TAG, "%s unhandled attr_id: %d", __func__, attr_id); + break; + } +} diff --git a/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_metadata_utils/avrcp_metadata_service.h b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_metadata_utils/avrcp_metadata_service.h new file mode 100644 index 0000000000..e90319bf31 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_metadata_utils/avrcp_metadata_service.h @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#ifndef __AVRCP_METADATA_SERVICE_H__ +#define __AVRCP_METADATA_SERVICE_H__ + +#include +#include "esp_avrc_api.h" + +typedef struct { + uint8_t attr_id; /*!< id of metadata attribute */ + uint8_t *attr_text; /*!< attribute itself */ + int attr_length; /*!< attribute character length */ +} avrc_metadata_srv_param_t; + +/** + * @brief open AVRCP metadata service + */ +void avrc_metadata_srv_open(void); + +/** + * @brief close AVRCP metadata service + */ +void avrc_metadata_srv_close(void); + +/** + * @brief request AVRCP metadata + */ +void avrc_metadata_srv_md_req(void); + +/** + * @brief save AVRCP metadata response data from ESP_AVRC_CT_METADATA_RSP_EVT + * + * @param [in] param AVRCP metadata response data + */ +void avrc_metadata_srv_md_save(avrc_metadata_srv_param_t *param); + +/** + * @brief free AVRCP metadata response data from ESP_AVRC_CT_METADATA_RSP_EVT + * + * @param [in] attr_id attribute id + */ +void avrc_metadata_srv_md_free(uint8_t attr_id); + +#endif /* __AVRCP_METADATA_SERVICE_H__ */ diff --git a/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_metadata_utils/avrcp_metadata_utils.c b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_metadata_utils/avrcp_metadata_utils.c new file mode 100644 index 0000000000..5516db7d4e --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_metadata_utils/avrcp_metadata_utils.c @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include +#include + +#include "esp_log.h" +#include "esp_bt_main.h" +#include "esp_avrc_api.h" + +#include "avrcp_metadata_utils.h" +#include "avrcp_utils_tags.h" +#include "avrcp_metadata_service.h" + +/******************************** + * EXTERNAL FUNCTION DEFINITIONS + *******************************/ + +void bt_avrc_md_ct_evt_hdl(uint16_t event, void *param) +{ + ESP_LOGD(BT_RC_CT_TAG, "%s event: %d", __func__, event); + + esp_avrc_ct_cb_param_t *rc = (esp_avrc_ct_cb_param_t *)(param); + + switch (event) { + /* when connection state changed, this event comes */ + case ESP_AVRC_CT_CONNECTION_STATE_EVT: { + uint8_t *bda = rc->conn_stat.remote_bda; + ESP_LOGI(BT_RC_CT_TAG, "AVRC conn_state event: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]", + rc->conn_stat.connected, bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); + + if (rc->conn_stat.connected) { + /* get remote supported event_ids of peer AVRCP Target */ + avrc_metadata_srv_open(); + } else { + /* clear peer notification capability record */ + avrc_metadata_srv_close(); + } + break; + } + /* when metadata response, this event comes */ + case ESP_AVRC_CT_METADATA_RSP_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC metadata rsp: attribute id 0x%x, %s", rc->meta_rsp.attr_id, rc->meta_rsp.attr_text); + /* save metadata to avrcp metadata service and then if there is a need to handle metadata, + functions can be added in the avrcp metadata service for processing */ + avrc_metadata_srv_md_save((avrc_metadata_srv_param_t *)rc); + break; + } + /* when notified, this event comes */ + case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC event notification: %d", rc->change_ntf.event_id); + if (rc->change_ntf.event_id == ESP_AVRC_RN_TRACK_CHANGE) { + /* request metadata */ + avrc_metadata_srv_md_req(); + } + break; + } + /* when notification capability of peer device got, this event comes */ + case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "remote rn_cap: count %d, bitmask 0x%x", rc->get_rn_caps_rsp.cap_count, + rc->get_rn_caps_rsp.evt_set.bits); + /* request metadata */ + avrc_metadata_srv_md_req(); + break; + } + /* others */ + default: + ESP_LOGE(BT_RC_CT_TAG, "%s unhandled event: %d", __func__, event); + break; + } +} diff --git a/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_metadata_utils/avrcp_metadata_utils.h b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_metadata_utils/avrcp_metadata_utils.h new file mode 100644 index 0000000000..0d979b8b18 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_metadata_utils/avrcp_metadata_utils.h @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#ifndef __AVRCP_METADATA_UTILS_H__ +#define __AVRCP_METADATA_UTILS_H__ + +#include + +/** + * @brief handle function for AVRCP controller metadata event + * + * @param [in] event event id + * @param [in] param callback parameter + */ +void bt_avrc_md_ct_evt_hdl(uint16_t event, void *param); + +#endif /* __AVRCP_METADATA_UTILS_H__*/ diff --git a/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_metadata_utils/idf_component.yml b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_metadata_utils/idf_component.yml new file mode 100644 index 0000000000..421ea1aa86 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_metadata_utils/idf_component.yml @@ -0,0 +1,3 @@ +dependencies: + avrcp_common_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/avrcp_common_utils diff --git a/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/include/avrcp_utils_tags.h b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/include/avrcp_utils_tags.h new file mode 100644 index 0000000000..a722fbb8b1 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/avrcp_utils/include/avrcp_utils_tags.h @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#ifndef __AVRCP_UTILS_TAGS_H__ +#define __AVRCP_UTILS_TAGS_H__ + +/* log tags */ +#define BT_RC_TAG "RC" +#define BT_RC_TG_TAG "RC_TG" +#define BT_RC_CT_TAG "RC_CT" + +#endif /* __AVRCP_UTILS_TAGS_H__ */ diff --git a/examples/bluetooth/bluedroid/classic_bt/common/bredr_app_common_utils/CMakeLists.txt b/examples/bluetooth/bluedroid/classic_bt/common/bredr_app_common_utils/CMakeLists.txt new file mode 100644 index 0000000000..3dd3d34ef1 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/bredr_app_common_utils/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "bredr_app_common_utils.c" + PRIV_REQUIRES bt nvs_flash + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/classic_bt/common/bredr_app_common_utils/Kconfig.projbuild b/examples/bluetooth/bluedroid/classic_bt/common/bredr_app_common_utils/Kconfig.projbuild new file mode 100644 index 0000000000..7f1d22b398 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/bredr_app_common_utils/Kconfig.projbuild @@ -0,0 +1,10 @@ +menu "BR/EDR App Common Example Configuration" + config EXAMPLE_SSP_ENABLED + bool "Secure Simple Pairing" + depends on BT_CLASSIC_ENABLED + default y + help + This enables the Secure Simple Pairing. If disable this option, + Bluedroid will only support Legacy Pairing + +endmenu diff --git a/examples/bluetooth/bluedroid/classic_bt/common/bredr_app_common_utils/bredr_app_common_utils.c b/examples/bluetooth/bluedroid/classic_bt/common/bredr_app_common_utils/bredr_app_common_utils.c new file mode 100644 index 0000000000..05d0cd7715 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/bredr_app_common_utils/bredr_app_common_utils.c @@ -0,0 +1,185 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include +#include +#include "nvs.h" +#include "nvs_flash.h" + +#include "esp_system.h" +#include "esp_log.h" +#include "esp_bt.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#include "esp_gap_bt_api.h" + +#include "bredr_app_common_utils.h" + +#define BREDR_APP_TAG "BREDR_APP" + +/******************************* + * STATIC FUNCTION DEFINITIONS + ******************************/ + +static char *bda2str(uint8_t * bda, char *str, size_t size) +{ + if (bda == NULL || str == NULL || size < 18) { + return NULL; + } + + 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; +} + +void bredr_app_dev_evt_def_hdl(esp_bt_dev_cb_event_t event, esp_bt_dev_cb_param_t *param) +{ + switch (event) { + case ESP_BT_DEV_NAME_RES_EVT: { + if (param->name_res.status == ESP_BT_STATUS_SUCCESS) { + ESP_LOGI(BREDR_APP_TAG, "Get local device name success: %s", param->name_res.name); + } else { + ESP_LOGE(BREDR_APP_TAG, "Get local device name failed, status: %d", param->name_res.status); + } + break; + } + default: { + ESP_LOGI(BREDR_APP_TAG, "event: %d", event); + break; + } + } +} + +void bredr_app_gap_evt_def_hdl(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) +{ + uint8_t *bda = NULL; + + switch (event) { + /* when authentication completed, this event comes */ + case ESP_BT_GAP_AUTH_CMPL_EVT: { + if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) { + ESP_LOGI(BREDR_APP_TAG, "authentication success: %s", param->auth_cmpl.device_name); + ESP_LOG_BUFFER_HEX(BREDR_APP_TAG, param->auth_cmpl.bda, ESP_BD_ADDR_LEN); + } else { + ESP_LOGE(BREDR_APP_TAG, "authentication failed, status: %d", param->auth_cmpl.stat); + } + ESP_LOGI(BREDR_APP_TAG, "link key type of current link is: %d", param->auth_cmpl.lk_type); + break; + } + case ESP_BT_GAP_ENC_CHG_EVT: { + char *str_enc[3] = {"OFF", "E0", "AES"}; + bda = (uint8_t *)param->enc_chg.bda; + ESP_LOGI(BREDR_APP_TAG, "Encryption mode to [%02x:%02x:%02x:%02x:%02x:%02x] changed to %s", + bda[0], bda[1], bda[2], bda[3], bda[4], bda[5], str_enc[param->enc_chg.enc_mode]); + break; + } + +#if (CONFIG_EXAMPLE_SSP_ENABLED == true) + /* when Security Simple Pairing user confirmation requested, this event comes */ + case ESP_BT_GAP_CFM_REQ_EVT: + ESP_LOGI(BREDR_APP_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %06"PRIu32, param->cfm_req.num_val); + esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true); + break; + /* when Security Simple Pairing passkey notified, this event comes */ + case ESP_BT_GAP_KEY_NOTIF_EVT: + ESP_LOGI(BREDR_APP_TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey: %06"PRIu32, param->key_notif.passkey); + break; + /* when Security Simple Pairing passkey requested, this event comes */ + case ESP_BT_GAP_KEY_REQ_EVT: + ESP_LOGI(BREDR_APP_TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!"); + break; +#endif + + /* when GAP mode changed, this event comes */ + case ESP_BT_GAP_MODE_CHG_EVT: + ESP_LOGI(BREDR_APP_TAG, "ESP_BT_GAP_MODE_CHG_EVT mode: %d, interval: %.2f ms", + param->mode_chg.mode, param->mode_chg.interval * 0.625); + break; + /* when ACL connection completed, this event comes */ + case ESP_BT_GAP_ACL_CONN_CMPL_STAT_EVT: + bda = (uint8_t *)param->acl_conn_cmpl_stat.bda; + ESP_LOGI(BREDR_APP_TAG, "ESP_BT_GAP_ACL_CONN_CMPL_STAT_EVT Connected to [%02x:%02x:%02x:%02x:%02x:%02x], status: 0x%x", + bda[0], bda[1], bda[2], bda[3], bda[4], bda[5], param->acl_conn_cmpl_stat.stat); + break; + /* when ACL disconnection completed, this event comes */ + case ESP_BT_GAP_ACL_DISCONN_CMPL_STAT_EVT: + bda = (uint8_t *)param->acl_disconn_cmpl_stat.bda; + ESP_LOGI(BREDR_APP_TAG, "ESP_BT_GAP_ACL_DISC_CMPL_STAT_EVT Disconnected from [%02x:%02x:%02x:%02x:%02x:%02x], reason: 0x%x", + bda[0], bda[1], bda[2], bda[3], bda[4], bda[5], param->acl_disconn_cmpl_stat.reason); + break; + /* others */ + default: { + ESP_LOGI(BREDR_APP_TAG, "event: %d", event); + break; + } + } +} + +esp_err_t bredr_app_common_init(void) +{ + char bda_str[18] = {0}; + /* initialize NVS — it is used to store PHY calibration data */ + esp_err_t err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + err = nvs_flash_init(); + } + ESP_ERROR_CHECK(err); + + /* + * This example only uses the functions of Classic Bluetooth. + * So release the controller memory for Bluetooth Low Energy. + */ + ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE)); + + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + if ((err = esp_bt_controller_init(&bt_cfg)) != ESP_OK) { + ESP_LOGE(BREDR_APP_TAG, "%s initialize controller failed: %s", __func__, esp_err_to_name(err)); + return err; + } + if ((err = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) { + ESP_LOGE(BREDR_APP_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(err)); + return err; + } + + esp_bluedroid_config_t bluedroid_cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT(); +#if (CONFIG_EXAMPLE_SSP_ENABLED == false) + bluedroid_cfg.ssp_en = false; +#endif + if ((err = esp_bluedroid_init_with_cfg(&bluedroid_cfg)) != ESP_OK) { + ESP_LOGE(BREDR_APP_TAG, "%s initialize bluedroid failed: %s", __func__, esp_err_to_name(err)); + return err; + } + + if ((err = esp_bluedroid_enable()) != ESP_OK) { + ESP_LOGE(BREDR_APP_TAG, "%s enable bluedroid failed: %s", __func__, esp_err_to_name(err)); + return err; + } + +#if (CONFIG_EXAMPLE_SSP_ENABLED == true) + /* set default parameters for Secure Simple Pairing */ + esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE; + esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO; + esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t)); +#endif + + /* set default parameters for Legacy Pairing (use fixed pin code 1234) */ + esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_FIXED; + esp_bt_pin_code_t pin_code; + pin_code[0] = '1'; + pin_code[1] = '2'; + pin_code[2] = '3'; + pin_code[3] = '4'; + esp_bt_gap_set_pin(pin_type, 4, pin_code); + + ESP_LOGI(BREDR_APP_TAG, "Own address:[%s]", bda2str((uint8_t *)esp_bt_dev_get_address(), bda_str, sizeof(bda_str))); + + return ESP_OK; +} diff --git a/examples/bluetooth/bluedroid/classic_bt/common/bredr_app_common_utils/bredr_app_common_utils.h b/examples/bluetooth/bluedroid/classic_bt/common/bredr_app_common_utils/bredr_app_common_utils.h new file mode 100644 index 0000000000..590144b095 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/bredr_app_common_utils/bredr_app_common_utils.h @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#ifndef __BREDR_APP_COMMON_UTILS_H__ +#define __BREDR_APP_COMMON_UTILS_H__ + +#include +#include "esp_bt_device.h" +#include "esp_gap_bt_api.h" + +/** + * @brief Device event default handle function + * + * @param [in] event event id + * @param [in] param handler parameter + */ +void bredr_app_dev_evt_def_hdl(esp_bt_dev_cb_event_t event, esp_bt_dev_cb_param_t *param); + +/** + * @brief GAP event default handle function + * + * @param [in] event event id + * @param [in] param handler parameter + */ +void bredr_app_gap_evt_def_hdl(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param); + +/** + * @brief Classic BT common initialization + * + * @return ESP_OK on successful init + */ +esp_err_t bredr_app_common_init(void); + +#endif /* __BREDR_APP_COMMON_UTILS_H__*/ diff --git a/examples/bluetooth/bluedroid/classic_bt/common/bt_app_core_utils/CMakeLists.txt b/examples/bluetooth/bluedroid/classic_bt/common/bt_app_core_utils/CMakeLists.txt new file mode 100644 index 0000000000..2f000acd2b --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/bt_app_core_utils/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "bt_app_core_utils.c" + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/classic_bt/common/bt_app_core_utils/bt_app_core_utils.c b/examples/bluetooth/bluedroid/classic_bt/common/bt_app_core_utils/bt_app_core_utils.c new file mode 100644 index 0000000000..514ce77f80 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/common/bt_app_core_utils/bt_app_core_utils.c @@ -0,0 +1,138 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include + +#include "esp_log.h" + +#include "freertos/FreeRTOSConfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" + +#include "bt_app_core_utils.h" + +/* log tag */ +#define BT_APP_CORE_TAG "BT_APP_CORE" + +/******************************* + * STATIC FUNCTION DECLARATIONS + ******************************/ + +/* handler for application task */ +static void bt_app_task_handler(void *arg); +/* message sender */ +static bool bt_app_send_msg(bt_app_msg_t *msg); +/* handle dispatched messages */ +static void bt_app_work_dispatched(bt_app_msg_t *msg); + +/******************************* + * STATIC VARIABLE DEFINITIONS + ******************************/ + +static QueueHandle_t s_bt_app_task_queue = NULL; /* handle of work queue */ +static TaskHandle_t s_bt_app_task_handle = NULL; /* handle of application task */ + +/******************************* + * STATIC FUNCTION DEFINITIONS + ******************************/ + +static bool bt_app_send_msg(bt_app_msg_t *msg) +{ + if (msg == NULL) { + return false; + } + + /* send the message to work queue */ + if (xQueueSend(s_bt_app_task_queue, msg, 10 / portTICK_PERIOD_MS) != pdTRUE) { + ESP_LOGE(BT_APP_CORE_TAG, "%s xQueue send failed", __func__); + return false; + } + return true; +} + +static void bt_app_work_dispatched(bt_app_msg_t *msg) +{ + if (msg->cb) { + msg->cb(msg->event, msg->param); + } +} + +static void bt_app_task_handler(void *arg) +{ + bt_app_msg_t msg; + + for (;;) { + /* receive message from work queue and handle it */ + if (pdTRUE == xQueueReceive(s_bt_app_task_queue, &msg, (TickType_t)portMAX_DELAY)) { + ESP_LOGD(BT_APP_CORE_TAG, "%s, signal: 0x%x, event: 0x%x", __func__, msg.sig, msg.event); + + switch (msg.sig) { + case BT_APP_SIG_WORK_DISPATCH: + bt_app_work_dispatched(&msg); + break; + default: + ESP_LOGW(BT_APP_CORE_TAG, "%s, unhandled signal: %d", __func__, msg.sig); + break; + } /* switch (msg.sig) */ + + if (msg.param) { + free(msg.param); + } + } + } +} + +/******************************** + * EXTERNAL FUNCTION DEFINITIONS + *******************************/ + +bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback) +{ + ESP_LOGD(BT_APP_CORE_TAG, "%s event: 0x%x, param len: %d", __func__, event, param_len); + + bt_app_msg_t msg; + memset(&msg, 0, sizeof(bt_app_msg_t)); + + msg.sig = BT_APP_SIG_WORK_DISPATCH; + msg.event = event; + msg.cb = p_cback; + + if (param_len == 0) { + return bt_app_send_msg(&msg); + } else if (p_params && param_len > 0) { + if ((msg.param = malloc(param_len)) != NULL) { + memcpy(msg.param, p_params, param_len); + /* check if caller has provided a copy callback to do the deep copy */ + if (p_copy_cback) { + p_copy_cback(msg.param, p_params, param_len); + } + return bt_app_send_msg(&msg); + } + } + + return false; +} + +void bt_app_task_start_up(void) +{ + s_bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t)); + xTaskCreate(bt_app_task_handler, "BtAppTask", 3072, NULL, 10, &s_bt_app_task_handle); +} + +void bt_app_task_shut_down(void) +{ + if (s_bt_app_task_handle) { + vTaskDelete(s_bt_app_task_handle); + s_bt_app_task_handle = NULL; + } + if (s_bt_app_task_queue) { + vQueueDelete(s_bt_app_task_queue); + s_bt_app_task_queue = NULL; + } +} diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_core.h b/examples/bluetooth/bluedroid/classic_bt/common/bt_app_core_utils/bt_app_core_utils.h similarity index 64% rename from examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_core.h rename to examples/bluetooth/bluedroid/classic_bt/common/bt_app_core_utils/bt_app_core_utils.h index 055f7609f1..9161f08155 100644 --- a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_core.h +++ b/examples/bluetooth/bluedroid/classic_bt/common/bt_app_core_utils/bt_app_core_utils.h @@ -1,19 +1,16 @@ /* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ -#ifndef __BT_APP_CORE_H__ -#define __BT_APP_CORE_H__ +#ifndef __BT_APP_CORE_UTILS_H__ +#define __BT_APP_CORE_UTILS_H__ #include #include #include -/* log tag */ -#define BT_APP_CORE_TAG "BT_APP_CORE" - /* signal for `bt_app_work_dispatch` */ #define BT_APP_SIG_WORK_DISPATCH (0x01) @@ -23,7 +20,7 @@ * @param [in] event event id * @param [in] param handler parameter */ -typedef void (* bt_app_cb_t) (uint16_t event, void *param); +typedef void (* bt_app_cb_t)(uint16_t event, void *param); /* message to be sent */ typedef struct { @@ -40,14 +37,14 @@ typedef struct { * @param [in] p_src pointer to source data * @param [in] len data length in byte */ -typedef void (* bt_app_copy_cb_t) (void *p_dest, void *p_src, int len); +typedef void (* bt_app_copy_cb_t)(void *p_dest, void *p_src, int len); /** * @brief work dispatcher for the application task * * @param [in] p_cback callback function * @param [in] event event id - * @param [in] p_params callback paramters + * @param [in] p_params callback parameters * @param [in] param_len parameter length in byte * @param [in] p_copy_cback parameter deep-copy function * @@ -65,24 +62,4 @@ void bt_app_task_start_up(void); */ void bt_app_task_shut_down(void); -/** - * @brief start up the is task - */ -void bt_i2s_task_start_up(void); - -/** - * @brief shut down the I2S task - */ -void bt_i2s_task_shut_down(void); - -/** - * @brief write data to ringbuffer - * - * @param [in] data pointer to data stream - * @param [in] size data length in byte - * - * @return size if writteen ringbuffer successfully, 0 others - */ -size_t write_ringbuf(const uint8_t *data, size_t size); - -#endif /* __BT_APP_CORE_H__ */ +#endif /* __BT_APP_CORE_UTILS_H__ */