mirror of
https://github.com/espressif/esp-idf.git
synced 2025-08-04 13:14:32 +02:00
feat(bt/bluedroid): Support AVRCP Cover Art feature in a2dp_sink demo
This commit is contained in:
@@ -36,7 +36,7 @@ idf.py menuconfig
|
|||||||
|
|
||||||
* Choose external I2S codec or internal DAC for audio output, and configure the output PINs under A2DP Example Configuration
|
* Choose external I2S codec or internal DAC for audio output, and configure the output PINs under A2DP Example Configuration
|
||||||
|
|
||||||
* Enable Classic Bluetooth and A2DP under **Component config --> Bluetooth --> Bluedroid Enable**
|
* 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`.
|
||||||
|
|
||||||
### Build and Flash
|
### Build and Flash
|
||||||
|
|
||||||
@@ -66,7 +66,13 @@ I (122697) BT_AV: Audio packet count 100
|
|||||||
I (124697) BT_AV: Audio packet count 200
|
I (124697) BT_AV: Audio packet count 200
|
||||||
I (126697) BT_AV: Audio packet count 300
|
I (126697) BT_AV: Audio packet count 300
|
||||||
I (128697) BT_AV: Audio packet count 400
|
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.
|
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.
|
||||||
|
@@ -53,4 +53,12 @@ menu "A2DP Example Configuration"
|
|||||||
default "ESP_SPEAKER"
|
default "ESP_SPEAKER"
|
||||||
help
|
help
|
||||||
Use this option to set local device name.
|
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.
|
||||||
|
|
||||||
endmenu
|
endmenu
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||||
*/
|
*/
|
||||||
@@ -93,6 +93,13 @@ i2s_chan_handle_t tx_chan = NULL;
|
|||||||
dac_continuous_handle_t tx_chan;
|
dac_continuous_handle_t tx_chan;
|
||||||
#endif
|
#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 FUNCTION DEFINITIONS
|
||||||
*******************************/
|
*******************************/
|
||||||
@@ -107,6 +114,18 @@ static void bt_app_alloc_meta_buffer(esp_avrc_ct_cb_param_t *param)
|
|||||||
rc->meta_rsp.attr_text = attr_text;
|
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)
|
static void bt_av_new_track(void)
|
||||||
{
|
{
|
||||||
/* request metadata */
|
/* request metadata */
|
||||||
@@ -114,6 +133,11 @@ static void bt_av_new_track(void)
|
|||||||
ESP_AVRC_MD_ATTR_ARTIST |
|
ESP_AVRC_MD_ATTR_ARTIST |
|
||||||
ESP_AVRC_MD_ATTR_ALBUM |
|
ESP_AVRC_MD_ATTR_ALBUM |
|
||||||
ESP_AVRC_MD_ATTR_GENRE;
|
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);
|
esp_avrc_ct_send_metadata_cmd(APP_RC_CT_TL_GET_META_DATA, attr_mask);
|
||||||
|
|
||||||
/* register notification if peer support the event_id */
|
/* register notification if peer support the event_id */
|
||||||
@@ -364,7 +388,7 @@ static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param)
|
|||||||
if (a2d->a2d_psc_cfg_stat.psc_mask & ESP_A2D_PSC_DELAY_RPT) {
|
if (a2d->a2d_psc_cfg_stat.psc_mask & ESP_A2D_PSC_DELAY_RPT) {
|
||||||
ESP_LOGI(BT_AV_TAG, "Peer device support delay reporting");
|
ESP_LOGI(BT_AV_TAG, "Peer device support delay reporting");
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGI(BT_AV_TAG, "Peer device unsupport delay reporting");
|
ESP_LOGI(BT_AV_TAG, "Peer device unsupported delay reporting");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -415,15 +439,24 @@ static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
/* when passthrough responsed, this event comes */
|
/* when passthrough response, this event comes */
|
||||||
case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: {
|
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,
|
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);
|
rc->psth_rsp.key_state, rc->psth_rsp.rsp_code);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
/* when metadata responsed, this event comes */
|
/* when metadata response, this event comes */
|
||||||
case ESP_AVRC_CT_METADATA_RSP_EVT: {
|
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);
|
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);
|
free(rc->meta_rsp.attr_text);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -436,6 +469,13 @@ static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
|
|||||||
/* when feature of remote device indicated, this event comes */
|
/* when feature of remote device indicated, this event comes */
|
||||||
case ESP_AVRC_CT_REMOTE_FEATURES_EVT: {
|
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);
|
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;
|
break;
|
||||||
}
|
}
|
||||||
/* when notification capability of peer device got, this event comes */
|
/* when notification capability of peer device got, this event comes */
|
||||||
@@ -448,6 +488,36 @@ static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
|
|||||||
bt_av_play_pos_changed();
|
bt_av_play_pos_changed();
|
||||||
break;
|
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;
|
||||||
|
}
|
||||||
/* others */
|
/* others */
|
||||||
default:
|
default:
|
||||||
ESP_LOGE(BT_RC_CT_TAG, "%s unhandled event: %d", __func__, event);
|
ESP_LOGE(BT_RC_CT_TAG, "%s unhandled event: %d", __func__, event);
|
||||||
@@ -545,6 +615,14 @@ void bt_app_a2d_data_cb(const uint8_t *data, uint32_t len)
|
|||||||
|
|
||||||
void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param)
|
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) {
|
switch (event) {
|
||||||
case ESP_AVRC_CT_METADATA_RSP_EVT:
|
case ESP_AVRC_CT_METADATA_RSP_EVT:
|
||||||
bt_app_alloc_meta_buffer(param);
|
bt_app_alloc_meta_buffer(param);
|
||||||
@@ -553,7 +631,9 @@ void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param
|
|||||||
case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT:
|
case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT:
|
||||||
case ESP_AVRC_CT_CHANGE_NOTIFY_EVT:
|
case ESP_AVRC_CT_CHANGE_NOTIFY_EVT:
|
||||||
case ESP_AVRC_CT_REMOTE_FEATURES_EVT:
|
case ESP_AVRC_CT_REMOTE_FEATURES_EVT:
|
||||||
case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_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: {
|
||||||
bt_app_work_dispatch(bt_av_hdl_avrc_ct_evt, event, param, sizeof(esp_avrc_ct_cb_param_t), NULL);
|
bt_app_work_dispatch(bt_av_hdl_avrc_ct_evt, event, param, sizeof(esp_avrc_ct_cb_param_t), NULL);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@@ -7,4 +7,5 @@ CONFIG_BTDM_CTRL_MODE_BTDM=n
|
|||||||
CONFIG_BT_BLUEDROID_ENABLED=y
|
CONFIG_BT_BLUEDROID_ENABLED=y
|
||||||
CONFIG_BT_CLASSIC_ENABLED=y
|
CONFIG_BT_CLASSIC_ENABLED=y
|
||||||
CONFIG_BT_A2DP_ENABLE=y
|
CONFIG_BT_A2DP_ENABLE=y
|
||||||
|
CONFIG_BT_AVRCP_CT_COVER_ART_ENABLED=y
|
||||||
CONFIG_DAC_DMA_AUTO_16BIT_ALIGN=n
|
CONFIG_DAC_DMA_AUTO_16BIT_ALIGN=n
|
||||||
|
Reference in New Issue
Block a user